diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sw/qa/extras/tiledrendering | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
33 files changed, 4868 insertions, 0 deletions
diff --git a/sw/qa/extras/tiledrendering/data/2-pages.odt b/sw/qa/extras/tiledrendering/data/2-pages.odt Binary files differnew file mode 100644 index 0000000000..04953e5edf --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/2-pages.odt diff --git a/sw/qa/extras/tiledrendering/data/cond-coll-copy.odt b/sw/qa/extras/tiledrendering/data/cond-coll-copy.odt Binary files differnew file mode 100644 index 0000000000..8fa15f177e --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/cond-coll-copy.odt diff --git a/sw/qa/extras/tiledrendering/data/double-underline_and_strike-out.fodt b/sw/qa/extras/tiledrendering/data/double-underline_and_strike-out.fodt new file mode 100644 index 0000000000..881143bdae --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/double-underline_and_strike-out.fodt @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8"?> +<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:settings> + <config:config-item-set config:name="ooo:view-settings"> + <config:config-item config:name="ViewAreaTop" config:type="long">0</config:config-item> + <config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item> + <config:config-item config:name="ViewAreaWidth" config:type="long">35008</config:config-item> + <config:config-item config:name="ViewAreaHeight" config:type="long">27855</config:config-item> + <config:config-item config:name="ShowRedlineChanges" config:type="boolean">true</config:config-item> + <config:config-item config:name="InBrowseMode" config:type="boolean">false</config:config-item> + <config:config-item-map-indexed config:name="Views"> + <config:config-item-map-entry> + <config:config-item config:name="ViewId" config:type="string">view2</config:config-item> + <config:config-item config:name="ViewLeft" config:type="long">18905</config:config-item> + <config:config-item config:name="ViewTop" config:type="long">3475</config:config-item> + <config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item> + <config:config-item config:name="VisibleTop" config:type="long">0</config:config-item> + <config:config-item config:name="VisibleRight" config:type="long">35006</config:config-item> + <config:config-item config:name="VisibleBottom" config:type="long">27854</config:config-item> + <config:config-item config:name="ZoomType" config:type="short">0</config:config-item> + <config:config-item config:name="ViewLayoutColumns" config:type="short">0</config:config-item> + <config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item> + <config:config-item config:name="ZoomFactor" config:type="short">87</config:config-item> + <config:config-item config:name="IsSelectedFrame" config:type="boolean">false</config:config-item> + <config:config-item config:name="KeepRatio" config:type="boolean">false</config:config-item> + <config:config-item config:name="HideWhitespace" config:type="boolean">false</config:config-item> + <config:config-item config:name="AnchoredTextOverflowLegacy" config:type="boolean">false</config:config-item> + </config:config-item-map-entry> + </config:config-item-map-indexed> + </config:config-item-set> + </office:settings> + <office:styles> + <style:style style:name="Standard" style:family="paragraph" style:class="text"/> + <style:default-style style:family="paragraph"> + <style:text-properties fo:language="en" fo:country="US"/> + </style:default-style> + </office:styles> + <office:body> + <office:text> + <text:tracked-changes text:track-changes="false"> + <text:changed-region xml:id="ct94876403824720" text:id="ct94876403824720"> + <text:deletion> + <office:change-info> + <dc:creator>Unknown Author</dc:creator> + <dc:date>2022-11-28T10:18:08</dc:date> + </office:change-info> + </text:deletion> + </text:changed-region> + <text:changed-region xml:id="ct94876402741808" text:id="ct94876402741808"> + <text:insertion> + <office:change-info> + <dc:creator>Unknown Author</dc:creator> + <dc:date>2022-11-28T10:18:08</dc:date> + </office:change-info> + </text:insertion> + </text:changed-region> + </text:tracked-changes> + <text:sequence-decls> + <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/> + <text:sequence-decl text:display-outline-level="0" text:name="Table"/> + <text:sequence-decl text:display-outline-level="0" text:name="Text"/> + <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/> + <text:sequence-decl text:display-outline-level="0" text:name="Figure"/> + </text:sequence-decls> + <text:p text:style-name="Standard"><text:change-start text:change-id="ct94876403824720"/><text:tab/><text:tab/><text:tab/><text:tab/><text:tab/><text:tab/><text:span text:style-name="T1">Lorem ipsum…</text:span><text:change-end text:change-id="ct94876403824720"/></text:p> + <text:p text:style-name="P1"><text:tab/><text:tab/><text:tab/><text:tab/><text:tab/><text:tab/><text:span text:style-name="T1">Dolor…</text:span></text:p> + <text:p text:style-name="P1"><text:change-start text:change-id="ct94876402741808"/><text:tab/><text:tab/><text:tab/><text:tab/><text:tab/><text:tab/><text:span text:style-name="T1">Lorem ipsum…</text:span><text:change-end text:change-id="ct94876402741808"/></text:p> + </office:text> + </office:body> +</office:document> diff --git a/sw/qa/extras/tiledrendering/data/drop_down_form_field.odt b/sw/qa/extras/tiledrendering/data/drop_down_form_field.odt Binary files differnew file mode 100644 index 0000000000..7793aff4e9 --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/drop_down_form_field.odt diff --git a/sw/qa/extras/tiledrendering/data/drop_down_form_field2.odt b/sw/qa/extras/tiledrendering/data/drop_down_form_field2.odt Binary files differnew file mode 100644 index 0000000000..7793aff4e9 --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/drop_down_form_field2.odt diff --git a/sw/qa/extras/tiledrendering/data/drop_down_form_field_noitem.odt b/sw/qa/extras/tiledrendering/data/drop_down_form_field_noitem.odt Binary files differnew file mode 100644 index 0000000000..c0b703320b --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/drop_down_form_field_noitem.odt diff --git a/sw/qa/extras/tiledrendering/data/drop_down_form_field_noselection.odt b/sw/qa/extras/tiledrendering/data/drop_down_form_field_noselection.odt Binary files differnew file mode 100644 index 0000000000..0c433c6470 --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/drop_down_form_field_noselection.odt diff --git a/sw/qa/extras/tiledrendering/data/dummy.fodt b/sw/qa/extras/tiledrendering/data/dummy.fodt new file mode 100644 index 0000000000..79a34d5143 --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/dummy.fodt @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:body> + <office:text> + <text:p>Aaa bbb.</text:p> + </office:text> + </office:body> +</office:document> diff --git a/sw/qa/extras/tiledrendering/data/estonian.odt b/sw/qa/extras/tiledrendering/data/estonian.odt Binary files differnew file mode 100644 index 0000000000..0d1fedd6ba --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/estonian.odt diff --git a/sw/qa/extras/tiledrendering/data/fieldmark.docx b/sw/qa/extras/tiledrendering/data/fieldmark.docx Binary files differnew file mode 100644 index 0000000000..02748bb7e1 --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/fieldmark.docx diff --git a/sw/qa/extras/tiledrendering/data/frame.odt b/sw/qa/extras/tiledrendering/data/frame.odt Binary files differnew file mode 100644 index 0000000000..167e8f5b10 --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/frame.odt diff --git a/sw/qa/extras/tiledrendering/data/hiddenLoremIpsum.docx b/sw/qa/extras/tiledrendering/data/hiddenLoremIpsum.docx Binary files differnew file mode 100644 index 0000000000..0802f6e7d3 --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/hiddenLoremIpsum.docx diff --git a/sw/qa/extras/tiledrendering/data/hyperlink.odt b/sw/qa/extras/tiledrendering/data/hyperlink.odt Binary files differnew file mode 100644 index 0000000000..4a97bf76b6 --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/hyperlink.odt diff --git a/sw/qa/extras/tiledrendering/data/image-comment.odt b/sw/qa/extras/tiledrendering/data/image-comment.odt Binary files differnew file mode 100644 index 0000000000..0852bedabf --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/image-comment.odt diff --git a/sw/qa/extras/tiledrendering/data/multiline.odt b/sw/qa/extras/tiledrendering/data/multiline.odt Binary files differnew file mode 100644 index 0000000000..4c60b58dec --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/multiline.odt diff --git a/sw/qa/extras/tiledrendering/data/pagedown-invalidation.odt b/sw/qa/extras/tiledrendering/data/pagedown-invalidation.odt Binary files differnew file mode 100644 index 0000000000..0cad2d270a --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/pagedown-invalidation.odt diff --git a/sw/qa/extras/tiledrendering/data/pilcrow-redlining.fodt b/sw/qa/extras/tiledrendering/data/pilcrow-redlining.fodt new file mode 100644 index 0000000000..5f7bd03168 --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/pilcrow-redlining.fodt @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:body> + <office:text> + <text:tracked-changes text:track-changes="false"> + <text:changed-region xml:id="ct94231417949824" text:id="ct94231417949824"> + <text:insertion> + <office:change-info> + <dc:creator>NL</dc:creator> + <dc:date>2020-11-03T09:00:05</dc:date> + </office:change-info> + </text:insertion> + </text:changed-region> + <text:changed-region xml:id="ct94231385489296" text:id="ct94231385489296"> + <text:insertion> + <office:change-info> + <dc:creator>NL</dc:creator> + <dc:date>2020-11-03T08:56:53</dc:date> + </office:change-info> + </text:insertion> + </text:changed-region> + <text:changed-region xml:id="ct94231417752384" text:id="ct94231417752384"> + <text:deletion> + <office:change-info> + <dc:creator>NL</dc:creator> + <dc:date>2020-11-03T09:00:02</dc:date> + </office:change-info> + </text:deletion> + </text:changed-region> + <text:changed-region xml:id="ct94231417929984" text:id="ct94231417929984"> + <text:deletion> + <office:change-info> + <dc:creator>NL</dc:creator> + <dc:date>2020-11-03T08:57:37</dc:date> + </office:change-info> + </text:deletion> + </text:changed-region> + <text:changed-region xml:id="ct94231418119440" text:id="ct94231418119440"> + <text:insertion> + <office:change-info> + <dc:creator>NL</dc:creator> + <dc:date>2020-11-03T09:00:18</dc:date> + </office:change-info> + </text:insertion> + </text:changed-region> + <text:changed-region xml:id="ct94231417325888" text:id="ct94231417325888"> + <text:deletion> + <office:change-info> + <dc:creator>NL</dc:creator> + <dc:date>2020-11-03T09:00:21</dc:date> + </office:change-info> + </text:deletion> + </text:changed-region> + </text:tracked-changes> + <text:p text:style-name="P1"><text:span text:style-name="T1">insert paragraph breaks</text:span><text:change-start text:change-id="ct94231417949824"/></text:p> + <text:p text:style-name="P1"><text:change-end text:change-id="ct94231417949824"/><text:change-start text:change-id="ct94231385489296"/></text:p> + <text:p text:style-name="P1"><text:change-end text:change-id="ct94231385489296"/><text:span text:style-name="T1">delete paragraph breaks</text:span><text:change-start text:change-id="ct94231417752384"/></text:p> + <text:p text:style-name="P1"><text:change-end text:change-id="ct94231417752384"/><text:change-start text:change-id="ct94231417929984"/></text:p> + <text:p text:style-name="P2"><text:change-end text:change-id="ct94231417929984"/>insert line breaks<text:change-start text:change-id="ct94231418119440"/><text:line-break/><text:line-break/><text:change-end text:change-id="ct94231418119440"/>delete line breaks<text:change-start text:change-id="ct94231417325888"/><text:line-break/><text:line-break/><text:change-end text:change-id="ct94231417325888"/></text:p> + </office:text> + </office:body> +</office:document> diff --git a/sw/qa/extras/tiledrendering/data/redline-notification-during-save.odt b/sw/qa/extras/tiledrendering/data/redline-notification-during-save.odt Binary files differnew file mode 100644 index 0000000000..df4c30682a --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/redline-notification-during-save.odt diff --git a/sw/qa/extras/tiledrendering/data/removenode_redline_callback.fodt b/sw/qa/extras/tiledrendering/data/removenode_redline_callback.fodt new file mode 100644 index 0000000000..ed06348cc4 --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/removenode_redline_callback.fodt @@ -0,0 +1,306 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:meta><meta:creation-date>2016-05-17T06:56:18.472497792</meta:creation-date><meta:generator>LibreOfficeDev/6.1.0.0.alpha0$Linux_X86_64 LibreOffice_project/8b89db9127aaac53e19b884c3d546be4127ee38e</meta:generator><meta:editing-duration>PT1H57M33S</meta:editing-duration><meta:editing-cycles>32</meta:editing-cycles><dc:date>2018-03-05T02:20:39.602490321</dc:date><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="2" meta:word-count="2" meta:character-count="8" meta:non-whitespace-character-count="8"/></office:meta> + <office:settings> + <config:config-item-set config:name="ooo:view-settings"> + <config:config-item config:name="ViewAreaTop" config:type="long">0</config:config-item> + <config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item> + <config:config-item config:name="ViewAreaWidth" config:type="long">32228</config:config-item> + <config:config-item config:name="ViewAreaHeight" config:type="long">20348</config:config-item> + <config:config-item config:name="ShowRedlineChanges" config:type="boolean">true</config:config-item> + <config:config-item config:name="InBrowseMode" config:type="boolean">false</config:config-item> + <config:config-item-map-indexed config:name="Views"> + <config:config-item-map-entry> + <config:config-item config:name="ViewId" config:type="string">view2</config:config-item> + <config:config-item config:name="ViewLeft" config:type="long">7613</config:config-item> + <config:config-item config:name="ViewTop" config:type="long">5962</config:config-item> + <config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item> + <config:config-item config:name="VisibleTop" config:type="long">0</config:config-item> + <config:config-item config:name="VisibleRight" config:type="long">32226</config:config-item> + <config:config-item config:name="VisibleBottom" config:type="long">20346</config:config-item> + <config:config-item config:name="ZoomType" config:type="short">0</config:config-item> + <config:config-item config:name="ViewLayoutColumns" config:type="short">1</config:config-item> + <config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item> + <config:config-item config:name="ZoomFactor" config:type="short">100</config:config-item> + <config:config-item config:name="IsSelectedFrame" config:type="boolean">false</config:config-item> + <config:config-item config:name="AnchoredTextOverflowLegacy" config:type="boolean">false</config:config-item> + </config:config-item-map-entry> + </config:config-item-map-indexed> + </config:config-item-set> + <config:config-item-set config:name="ooo:configuration-settings"> + <config:config-item config:name="PrintProspect" config:type="boolean">false</config:config-item> + <config:config-item config:name="PrintLeftPages" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintGraphics" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintPageBackground" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintControls" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintAnnotationMode" config:type="short">0</config:config-item> + <config:config-item config:name="PrintRightPages" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintFaxName" config:type="string"/> + <config:config-item config:name="PrintPaperFromSetup" config:type="boolean">false</config:config-item> + <config:config-item config:name="PrintTextPlaceholder" config:type="boolean">false</config:config-item> + <config:config-item config:name="DisableOffPagePositioning" config:type="boolean">true</config:config-item> + <config:config-item config:name="ApplyParagraphMarkFormatToNumbering" config:type="boolean">false</config:config-item> + <config:config-item config:name="PrintReversed" config:type="boolean">false</config:config-item> + <config:config-item config:name="TabOverMargin" config:type="boolean">false</config:config-item> + <config:config-item config:name="EmbedFonts" config:type="boolean">false</config:config-item> + <config:config-item config:name="SurroundTextWrapSmall" config:type="boolean">false</config:config-item> + <config:config-item config:name="BackgroundParaOverDrawings" config:type="boolean">false</config:config-item> + <config:config-item config:name="ClippedPictures" config:type="boolean">false</config:config-item> + <config:config-item config:name="FloattableNomargins" config:type="boolean">false</config:config-item> + <config:config-item config:name="UnbreakableNumberings" config:type="boolean">false</config:config-item> + <config:config-item config:name="EmbedSystemFonts" config:type="boolean">false</config:config-item> + <config:config-item config:name="TabOverflow" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item> + <config:config-item config:name="SmallCapsPercentage66" config:type="boolean">false</config:config-item> + <config:config-item config:name="CollapseEmptyCellPara" config:type="boolean">true</config:config-item> + <config:config-item config:name="RsidRoot" config:type="int">24907</config:config-item> + <config:config-item config:name="IsLabelDocument" config:type="boolean">false</config:config-item> + <config:config-item config:name="PrinterName" config:type="string"/> + <config:config-item config:name="OutlineLevelYieldsNumbering" config:type="boolean">false</config:config-item> + <config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintBlackFonts" config:type="boolean">false</config:config-item> + <config:config-item config:name="TableRowKeep" config:type="boolean">false</config:config-item> + <config:config-item config:name="EmbeddedDatabaseName" config:type="string"/> + <config:config-item config:name="IgnoreTabsAndBlanksForLineCalculation" config:type="boolean">false</config:config-item> + <config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item> + <config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item> + <config:config-item config:name="SaveGlobalDocumentLinks" config:type="boolean">false</config:config-item> + <config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item> + <config:config-item config:name="Rsid" config:type="int">1961503</config:config-item> + <config:config-item config:name="PrintProspectRTL" config:type="boolean">false</config:config-item> + <config:config-item config:name="PrintEmptyPages" config:type="boolean">true</config:config-item> + <config:config-item config:name="ApplyUserData" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintHiddenText" config:type="boolean">false</config:config-item> + <config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item> + <config:config-item config:name="FieldAutoUpdate" config:type="boolean">true</config:config-item> + <config:config-item config:name="UseOldNumbering" config:type="boolean">false</config:config-item> + <config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item> + <config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item> + <config:config-item config:name="PrinterIndependentLayout" config:type="string">high-resolution</config:config-item> + <config:config-item config:name="IsKernAsianPunctuation" config:type="boolean">false</config:config-item> + <config:config-item config:name="ChartAutoUpdate" config:type="boolean">true</config:config-item> + <config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item> + <config:config-item config:name="IgnoreFirstLineIndentInNumbering" config:type="boolean">false</config:config-item> + <config:config-item config:name="PrinterPaperFromSetup" config:type="boolean">false</config:config-item> + <config:config-item config:name="AddParaTableSpacing" config:type="boolean">true</config:config-item> + <config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item> + <config:config-item config:name="TreatSingleColumnBreakAsPageBreak" config:type="boolean">false</config:config-item> + <config:config-item config:name="MathBaselineAlignment" config:type="boolean">true</config:config-item> + <config:config-item config:name="AddFrameOffsets" config:type="boolean">false</config:config-item> + <config:config-item config:name="AddVerticalFrameOffsets" config:type="boolean">false</config:config-item> + <config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">true</config:config-item> + <config:config-item config:name="AddParaSpacingToTableCells" config:type="boolean">true</config:config-item> + <config:config-item config:name="AddExternalLeading" config:type="boolean">true</config:config-item> + <config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/> + <config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item> + <config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item> + <config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item> + <config:config-item config:name="PrintDrawings" config:type="boolean">true</config:config-item> + <config:config-item config:name="UseFormerTextWrapping" config:type="boolean">false</config:config-item> + <config:config-item config:name="UnxForceZeroExtLeading" config:type="boolean">false</config:config-item> + <config:config-item config:name="TabAtLeftIndentForParagraphsInList" config:type="boolean">false</config:config-item> + <config:config-item config:name="RedlineProtectionKey" config:type="base64Binary"/> + <config:config-item config:name="PropLineSpacingShrinksFirstLine" config:type="boolean">false</config:config-item> + <config:config-item config:name="ConsiderTextWrapOnObjPos" config:type="boolean">false</config:config-item> + <config:config-item config:name="StylesNoDefault" config:type="boolean">false</config:config-item> + <config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item> + <config:config-item config:name="AlignTabStopPosition" config:type="boolean">true</config:config-item> + <config:config-item config:name="DoNotJustifyLinesWithManualBreak" config:type="boolean">false</config:config-item> + <config:config-item config:name="DoNotResetParaAttrsForNumFont" config:type="boolean">false</config:config-item> + <config:config-item config:name="CurrentDatabaseCommandType" config:type="int">0</config:config-item> + <config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item> + <config:config-item config:name="DoNotCaptureDrawObjsOnPage" config:type="boolean">false</config:config-item> + <config:config-item config:name="CurrentDatabaseCommand" config:type="string"/> + <config:config-item config:name="PrinterSetup" config:type="base64Binary"/> + <config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item> + </config:config-item-set> + </office:settings> + <office:scripts> + <office:script script:language="ooo:Basic"> + <ooo:libraries xmlns:ooo="http://openoffice.org/2004/office" xmlns:xlink="http://www.w3.org/1999/xlink"/> + </office:script> + </office:scripts> + <office:font-face-decls> + <style:font-face style:name="FreeSans1" svg:font-family="FreeSans" style:font-family-generic="swiss"/> + <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/> + <style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/> + <style:font-face style:name="Open Sans" svg:font-family="'Open Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/> + <style:font-face style:name="Droid Sans" svg:font-family="'Droid Sans'" style:font-family-generic="system" style:font-pitch="variable"/> + <style:font-face style:name="FreeSans" svg:font-family="FreeSans" style:font-family-generic="system" style:font-pitch="variable"/> + </office:font-face-decls> + <office:styles> + <style:default-style style:family="graphic"> + <style:graphic-properties svg:stroke-color="#808080" draw:fill-color="#cfe7f5" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" draw:start-line-spacing-horizontal="0.1114in" draw:start-line-spacing-vertical="0.1114in" draw:end-line-spacing-horizontal="0.1114in" draw:end-line-spacing-vertical="0.1114in" style:flow-with-text="false"/> + <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" style:font-independent-line-spacing="false"> + <style:tab-stops/> + </style:paragraph-properties> + <style:text-properties style:use-window-font-color="true" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Droid Sans" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="FreeSans" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/> + </style:default-style> + <style:default-style style:family="paragraph"> + <style:paragraph-properties fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="0.4925in" style:writing-mode="page"/> + <style:text-properties style:use-window-font-color="true" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Droid Sans" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="FreeSans" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2"/> + </style:default-style> + <style:default-style style:family="table"> + <style:table-properties table:border-model="collapsing"/> + </style:default-style> + <style:default-style style:family="table-row"> + <style:table-row-properties fo:keep-together="auto"/> + </style:default-style> + <style:style style:name="Standard" style:family="paragraph" style:class="text"/> + <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text"> + <style:paragraph-properties fo:margin-top="0.1665in" fo:margin-bottom="0.0835in" loext:contextual-spacing="false" fo:keep-with-next="always"/> + <style:text-properties style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt" style:font-name-asian="Droid Sans" style:font-family-asian="'Droid Sans'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="FreeSans" style:font-family-complex="FreeSans" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/> + </style:style> + <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text"> + <style:paragraph-properties fo:margin-top="0in" fo:margin-bottom="0.0835in" loext:contextual-spacing="false"/> + </style:style> + <style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list"> + <style:text-properties style:font-size-asian="12pt" style:font-name-complex="FreeSans1" style:font-family-complex="FreeSans" style:font-family-generic-complex="swiss"/> + </style:style> + <style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra"> + <style:paragraph-properties fo:margin-top="0.0835in" fo:margin-bottom="0.0835in" loext:contextual-spacing="false" text:number-lines="false" text:line-number="0"/> + <style:text-properties fo:font-size="12pt" fo:font-style="italic" style:font-size-asian="12pt" style:font-style-asian="italic" style:font-name-complex="FreeSans1" style:font-family-complex="FreeSans" style:font-family-generic-complex="swiss" style:font-size-complex="12pt" style:font-style-complex="italic"/> + </style:style> + <style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index"> + <style:paragraph-properties text:number-lines="false" text:line-number="0"/> + <style:text-properties style:font-size-asian="12pt" style:font-name-complex="FreeSans1" style:font-family-complex="FreeSans" style:font-family-generic-complex="swiss"/> + </style:style> + <text:outline-style style:name="Outline"> + <text:outline-level-style text:level="1" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.3in" fo:text-indent="-0.3in" fo:margin-left="0.3in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="2" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.4in" fo:text-indent="-0.4in" fo:margin-left="0.4in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="3" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.5in" fo:text-indent="-0.5in" fo:margin-left="0.5in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="4" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.6in" fo:text-indent="-0.6in" fo:margin-left="0.6in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="5" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.7in" fo:text-indent="-0.7in" fo:margin-left="0.7in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="6" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.8in" fo:text-indent="-0.8in" fo:margin-left="0.8in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="7" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.9in" fo:text-indent="-0.9in" fo:margin-left="0.9in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="8" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1in" fo:text-indent="-1in" fo:margin-left="1in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="9" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.1in" fo:text-indent="-1.1in" fo:margin-left="1.1in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="10" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.2in" fo:text-indent="-1.2in" fo:margin-left="1.2in"/> + </style:list-level-properties> + </text:outline-level-style> + </text:outline-style> + <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/> + <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/> + <text:linenumbering-configuration text:number-lines="false" text:offset="0.1965in" style:num-format="1" text:number-position="left" text:increment="5"/> + </office:styles> + <office:automatic-styles> + <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard"> + <style:text-properties style:font-name="Open Sans" fo:font-size="12pt" officeooo:rsid="0000614b" officeooo:paragraph-rsid="0000614b" style:font-size-asian="10.5pt" style:font-size-complex="12pt"/> + </style:style> + <style:style style:name="P2" style:family="paragraph" style:parent-style-name="Standard"> + <style:text-properties style:font-name="Open Sans" fo:font-size="12pt" officeooo:rsid="00054216" officeooo:paragraph-rsid="00054216" style:font-size-asian="10.5pt" style:font-size-complex="12pt"/> + </style:style> + <style:style style:name="P3" style:family="paragraph" style:parent-style-name="Standard"> + <style:text-properties style:font-name="Open Sans" fo:font-size="12pt" officeooo:rsid="001b47cb" officeooo:paragraph-rsid="001b47cb" style:font-size-asian="10.5pt" style:font-size-complex="12pt"/> + </style:style> + <style:style style:name="P4" style:family="paragraph" style:parent-style-name="Standard"> + <style:text-properties style:font-name="Open Sans" fo:font-size="12pt" officeooo:rsid="001b47cb" officeooo:paragraph-rsid="0000614b" style:font-size-asian="10.5pt" style:font-size-complex="12pt"/> + </style:style> + <style:style style:name="P5" style:family="paragraph" style:parent-style-name="Standard"> + <style:text-properties style:font-name="Open Sans" fo:font-size="12pt" officeooo:rsid="00054216" officeooo:paragraph-rsid="00054216" style:font-size-asian="10.5pt" style:font-size-complex="12pt"/> + </style:style> + <style:style style:name="P6" style:family="paragraph" style:parent-style-name="Standard"> + <style:text-properties style:font-name="Open Sans" fo:font-size="12pt" officeooo:rsid="0000614b" officeooo:paragraph-rsid="0000614b" style:font-size-asian="10.5pt" style:font-size-complex="12pt"/> + </style:style> + <style:page-layout style:name="pm1"> + <style:page-layout-properties fo:page-width="8.2681in" fo:page-height="11.6929in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.278in" style:layout-grid-ruby-height="0.139in" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0in"> + <style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/> + </style:page-layout-properties> + <style:header-style/> + <style:footer-style/> + </style:page-layout> + </office:automatic-styles> + <office:master-styles> + <style:master-page style:name="Standard" style:page-layout-name="pm1"/> + </office:master-styles> + <office:body> + <office:text> + <office:forms form:automatic-focus="false" form:apply-design-mode="false"/> + <text:tracked-changes text:track-changes="false"> + <text:changed-region xml:id="ct148588784" text:id="ct148588784"> + <text:insertion> + <office:change-info> + <dc:creator>Aron Budea</dc:creator> + <dc:date>2018-02-23T13:32:12</dc:date> + </office:change-info> + </text:insertion> + </text:changed-region> + <text:changed-region xml:id="ct148756000" text:id="ct148756000"> + <text:insertion> + <office:change-info> + <dc:creator>Aron Budea</dc:creator> + <dc:date>2018-02-23T13:32:15</dc:date> + </office:change-info> + </text:insertion> + </text:changed-region> + </text:tracked-changes> + <text:sequence-decls> + <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/> + <text:sequence-decl text:display-outline-level="0" text:name="Table"/> + <text:sequence-decl text:display-outline-level="0" text:name="Text"/> + <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/> + </text:sequence-decls> + <text:p text:style-name="P2"/> + <text:p text:style-name="P2"/> + <text:p text:style-name="P2"/> + <text:p text:style-name="P2"/> + <text:p text:style-name="P2"><text:change-start text:change-id="ct148588784"/>abcd<text:change-end text:change-id="ct148588784"/></text:p> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P2"><text:change-start text:change-id="ct148756000"/>abcd<text:change-end text:change-id="ct148756000"/></text:p> + <text:p text:style-name="P2"/> + <text:p text:style-name="P2"/> + </office:text> + </office:body> +</office:document>
\ No newline at end of file diff --git a/sw/qa/extras/tiledrendering/data/savedauthorfield.odt b/sw/qa/extras/tiledrendering/data/savedauthorfield.odt Binary files differnew file mode 100644 index 0000000000..a060c5892f --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/savedauthorfield.odt diff --git a/sw/qa/extras/tiledrendering/data/search.odt b/sw/qa/extras/tiledrendering/data/search.odt Binary files differnew file mode 100644 index 0000000000..76ab2e1936 --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/search.odt diff --git a/sw/qa/extras/tiledrendering/data/semi-transparent.odt b/sw/qa/extras/tiledrendering/data/semi-transparent.odt Binary files differnew file mode 100644 index 0000000000..eb76980e74 --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/semi-transparent.odt diff --git a/sw/qa/extras/tiledrendering/data/shape-with-text.fodt b/sw/qa/extras/tiledrendering/data/shape-with-text.fodt new file mode 100644 index 0000000000..ce76aaf1b5 --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/shape-with-text.fodt @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:body> + <office:text> + <text:sequence-decls> + <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/> + <text:sequence-decl text:display-outline-level="0" text:name="Table"/> + <text:sequence-decl text:display-outline-level="0" text:name="Text"/> + <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/> + </text:sequence-decls> + <text:p><draw:custom-shape text:anchor-type="paragraph" draw:z-index="0" svg:width="4.883cm" svg:height="3.225cm" svg:x="2.602cm" svg:y="1.178cm"> + <text:p>Shape text</text:p> + <draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 3163 3163 0 10800 3163 18437 10800 21600 18437 18437 21600 10800 18437 3163" draw:text-areas="3163 3163 18437 18437" draw:type="ellipse" draw:enhanced-path="U 10800 10800 10800 10800 0 360 Z N"/> + </draw:custom-shape>Hello.</text:p> + </office:text> + </office:body> +</office:document> diff --git a/sw/qa/extras/tiledrendering/data/shape.fodt b/sw/qa/extras/tiledrendering/data/shape.fodt new file mode 100644 index 0000000000..4e9a7f629b --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/shape.fodt @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<office:document xmlns:officeooo="http://openoffice.org/2009/office" xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rpt="http://openoffice.org/2005/report" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:xforms="http://www.w3.org/2002/xforms" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:styles> + <style:default-style style:family="graphic"> + <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:flow-with-text="false"/> + <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" style:writing-mode="lr-tb" style:font-independent-line-spacing="false"> + <style:tab-stops/> + </style:paragraph-properties> + <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="en" fo:country="GB" style:letter-kerning="true" style:font-name-asian="Noto Serif CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Droid Sans Devanagari1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/> + </style:default-style> + </office:styles> + <office:automatic-styles> + <style:style style:name="gr1" style:family="graphic"> + <style:graphic-properties draw:textarea-horizontal-align="justify" draw:textarea-vertical-align="middle" draw:auto-grow-height="false" fo:min-height="1.9cm" fo:min-width="3.471cm" style:run-through="foreground" style:wrap="run-through" style:number-wrapped-paragraphs="no-limit" style:vertical-pos="from-top" style:vertical-rel="paragraph" style:horizontal-pos="from-left" style:horizontal-rel="paragraph"/> + </style:style> + </office:automatic-styles> + <office:body> + <office:text> + <text:sequence-decls> + <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/> + <text:sequence-decl text:display-outline-level="0" text:name="Table"/> + <text:sequence-decl text:display-outline-level="0" text:name="Text"/> + <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/> + <text:sequence-decl text:display-outline-level="0" text:name="Figure"/> + </text:sequence-decls> + <text:p text:style-name="Standard"><draw:custom-shape text:anchor-type="paragraph" draw:z-index="0" draw:name="Shape1" draw:style-name="gr1" svg:width="4.908cm" svg:height="2.687cm" svg:x="1.575cm" svg:y="-0.132cm"> + <text:p/> + <draw:enhanced-geometry svg:viewBox="0 0 21600 21600" draw:glue-points="10800 0 3163 3163 0 10800 3163 18437 10800 21600 18437 18437 21600 10800 18437 3163" draw:text-areas="3163 3163 18437 18437" draw:type="ellipse" draw:enhanced-path="U 10800 10800 10800 10800 0 360 Z N"/> + </draw:custom-shape>Hello.</text:p> + </office:text> + </office:body> +</office:document> diff --git a/sw/qa/extras/tiledrendering/data/splitnode_redline_callback.fodt b/sw/qa/extras/tiledrendering/data/splitnode_redline_callback.fodt new file mode 100644 index 0000000000..bffc3a42c9 --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/splitnode_redline_callback.fodt @@ -0,0 +1,294 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:rpt="http://openoffice.org/2005/report" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:css3t="http://www.w3.org/TR/css3-text/" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:meta><meta:creation-date>2016-05-17T06:56:18.472497792</meta:creation-date><meta:generator>LibreOfficeDev/6.1.0.0.alpha0$Linux_X86_64 LibreOffice_project/d64ce643275e0b2b0dea9e532fc261391dc8793c</meta:generator><meta:editing-duration>PT1H57M11S</meta:editing-duration><meta:editing-cycles>31</meta:editing-cycles><dc:date>2018-03-01T05:44:45.452664252</dc:date><meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="2" meta:word-count="2" meta:character-count="8" meta:non-whitespace-character-count="8"/></office:meta> + <office:settings> + <config:config-item-set config:name="ooo:view-settings"> + <config:config-item config:name="ViewAreaTop" config:type="long">0</config:config-item> + <config:config-item config:name="ViewAreaLeft" config:type="long">0</config:config-item> + <config:config-item config:name="ViewAreaWidth" config:type="long">32228</config:config-item> + <config:config-item config:name="ViewAreaHeight" config:type="long">20348</config:config-item> + <config:config-item config:name="ShowRedlineChanges" config:type="boolean">true</config:config-item> + <config:config-item config:name="InBrowseMode" config:type="boolean">false</config:config-item> + <config:config-item-map-indexed config:name="Views"> + <config:config-item-map-entry> + <config:config-item config:name="ViewId" config:type="string">view2</config:config-item> + <config:config-item config:name="ViewLeft" config:type="long">7613</config:config-item> + <config:config-item config:name="ViewTop" config:type="long">8846</config:config-item> + <config:config-item config:name="VisibleLeft" config:type="long">0</config:config-item> + <config:config-item config:name="VisibleTop" config:type="long">0</config:config-item> + <config:config-item config:name="VisibleRight" config:type="long">32226</config:config-item> + <config:config-item config:name="VisibleBottom" config:type="long">20346</config:config-item> + <config:config-item config:name="ZoomType" config:type="short">0</config:config-item> + <config:config-item config:name="ViewLayoutColumns" config:type="short">1</config:config-item> + <config:config-item config:name="ViewLayoutBookMode" config:type="boolean">false</config:config-item> + <config:config-item config:name="ZoomFactor" config:type="short">100</config:config-item> + <config:config-item config:name="IsSelectedFrame" config:type="boolean">false</config:config-item> + <config:config-item config:name="AnchoredTextOverflowLegacy" config:type="boolean">false</config:config-item> + </config:config-item-map-entry> + </config:config-item-map-indexed> + </config:config-item-set> + <config:config-item-set config:name="ooo:configuration-settings"> + <config:config-item config:name="PrintProspect" config:type="boolean">false</config:config-item> + <config:config-item config:name="PrintLeftPages" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintGraphics" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintPageBackground" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintControls" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintAnnotationMode" config:type="short">0</config:config-item> + <config:config-item config:name="PrintRightPages" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintFaxName" config:type="string"/> + <config:config-item config:name="PrintPaperFromSetup" config:type="boolean">false</config:config-item> + <config:config-item config:name="PrintTextPlaceholder" config:type="boolean">false</config:config-item> + <config:config-item config:name="DisableOffPagePositioning" config:type="boolean">true</config:config-item> + <config:config-item config:name="ApplyParagraphMarkFormatToNumbering" config:type="boolean">false</config:config-item> + <config:config-item config:name="PrintReversed" config:type="boolean">false</config:config-item> + <config:config-item config:name="TabOverMargin" config:type="boolean">false</config:config-item> + <config:config-item config:name="EmbedFonts" config:type="boolean">false</config:config-item> + <config:config-item config:name="SurroundTextWrapSmall" config:type="boolean">false</config:config-item> + <config:config-item config:name="BackgroundParaOverDrawings" config:type="boolean">false</config:config-item> + <config:config-item config:name="ClippedPictures" config:type="boolean">false</config:config-item> + <config:config-item config:name="FloattableNomargins" config:type="boolean">false</config:config-item> + <config:config-item config:name="UnbreakableNumberings" config:type="boolean">false</config:config-item> + <config:config-item config:name="EmbedSystemFonts" config:type="boolean">false</config:config-item> + <config:config-item config:name="TabOverflow" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintTables" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintSingleJobs" config:type="boolean">false</config:config-item> + <config:config-item config:name="SmallCapsPercentage66" config:type="boolean">false</config:config-item> + <config:config-item config:name="CollapseEmptyCellPara" config:type="boolean">true</config:config-item> + <config:config-item config:name="RsidRoot" config:type="int">24907</config:config-item> + <config:config-item config:name="IsLabelDocument" config:type="boolean">false</config:config-item> + <config:config-item config:name="PrinterName" config:type="string"/> + <config:config-item config:name="OutlineLevelYieldsNumbering" config:type="boolean">false</config:config-item> + <config:config-item config:name="UpdateFromTemplate" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintBlackFonts" config:type="boolean">false</config:config-item> + <config:config-item config:name="TableRowKeep" config:type="boolean">false</config:config-item> + <config:config-item config:name="EmbeddedDatabaseName" config:type="string"/> + <config:config-item config:name="IgnoreTabsAndBlanksForLineCalculation" config:type="boolean">false</config:config-item> + <config:config-item config:name="UseOldPrinterMetrics" config:type="boolean">false</config:config-item> + <config:config-item config:name="InvertBorderSpacing" config:type="boolean">false</config:config-item> + <config:config-item config:name="SaveGlobalDocumentLinks" config:type="boolean">false</config:config-item> + <config:config-item config:name="TabsRelativeToIndent" config:type="boolean">true</config:config-item> + <config:config-item config:name="Rsid" config:type="int">1952207</config:config-item> + <config:config-item config:name="PrintProspectRTL" config:type="boolean">false</config:config-item> + <config:config-item config:name="PrintEmptyPages" config:type="boolean">true</config:config-item> + <config:config-item config:name="ApplyUserData" config:type="boolean">true</config:config-item> + <config:config-item config:name="PrintHiddenText" config:type="boolean">false</config:config-item> + <config:config-item config:name="AddParaTableSpacingAtStart" config:type="boolean">true</config:config-item> + <config:config-item config:name="FieldAutoUpdate" config:type="boolean">true</config:config-item> + <config:config-item config:name="UseOldNumbering" config:type="boolean">false</config:config-item> + <config:config-item config:name="CharacterCompressionType" config:type="short">0</config:config-item> + <config:config-item config:name="SaveVersionOnClose" config:type="boolean">false</config:config-item> + <config:config-item config:name="PrinterIndependentLayout" config:type="string">high-resolution</config:config-item> + <config:config-item config:name="IsKernAsianPunctuation" config:type="boolean">false</config:config-item> + <config:config-item config:name="ChartAutoUpdate" config:type="boolean">true</config:config-item> + <config:config-item config:name="MsWordCompTrailingBlanks" config:type="boolean">false</config:config-item> + <config:config-item config:name="IgnoreFirstLineIndentInNumbering" config:type="boolean">false</config:config-item> + <config:config-item config:name="PrinterPaperFromSetup" config:type="boolean">false</config:config-item> + <config:config-item config:name="AddParaTableSpacing" config:type="boolean">true</config:config-item> + <config:config-item config:name="UseFormerObjectPositioning" config:type="boolean">false</config:config-item> + <config:config-item config:name="TreatSingleColumnBreakAsPageBreak" config:type="boolean">false</config:config-item> + <config:config-item config:name="MathBaselineAlignment" config:type="boolean">true</config:config-item> + <config:config-item config:name="AddFrameOffsets" config:type="boolean">false</config:config-item> + <config:config-item config:name="AddVerticalFrameOffsets" config:type="boolean">false</config:config-item> + <config:config-item config:name="SubtractFlysAnchoredAtFlys" config:type="boolean">true</config:config-item> + <config:config-item config:name="AddParaSpacingToTableCells" config:type="boolean">true</config:config-item> + <config:config-item config:name="AddExternalLeading" config:type="boolean">true</config:config-item> + <config:config-item config:name="CurrentDatabaseDataSource" config:type="string"/> + <config:config-item config:name="AllowPrintJobCancel" config:type="boolean">true</config:config-item> + <config:config-item config:name="ProtectForm" config:type="boolean">false</config:config-item> + <config:config-item config:name="UseFormerLineSpacing" config:type="boolean">false</config:config-item> + <config:config-item config:name="PrintDrawings" config:type="boolean">true</config:config-item> + <config:config-item config:name="UseFormerTextWrapping" config:type="boolean">false</config:config-item> + <config:config-item config:name="UnxForceZeroExtLeading" config:type="boolean">false</config:config-item> + <config:config-item config:name="TabAtLeftIndentForParagraphsInList" config:type="boolean">false</config:config-item> + <config:config-item config:name="RedlineProtectionKey" config:type="base64Binary"/> + <config:config-item config:name="PropLineSpacingShrinksFirstLine" config:type="boolean">false</config:config-item> + <config:config-item config:name="ConsiderTextWrapOnObjPos" config:type="boolean">false</config:config-item> + <config:config-item config:name="StylesNoDefault" config:type="boolean">false</config:config-item> + <config:config-item config:name="LinkUpdateMode" config:type="short">1</config:config-item> + <config:config-item config:name="AlignTabStopPosition" config:type="boolean">true</config:config-item> + <config:config-item config:name="DoNotJustifyLinesWithManualBreak" config:type="boolean">false</config:config-item> + <config:config-item config:name="DoNotResetParaAttrsForNumFont" config:type="boolean">false</config:config-item> + <config:config-item config:name="CurrentDatabaseCommandType" config:type="int">0</config:config-item> + <config:config-item config:name="LoadReadonly" config:type="boolean">false</config:config-item> + <config:config-item config:name="DoNotCaptureDrawObjsOnPage" config:type="boolean">false</config:config-item> + <config:config-item config:name="CurrentDatabaseCommand" config:type="string"/> + <config:config-item config:name="PrinterSetup" config:type="base64Binary"/> + <config:config-item config:name="ClipAsCharacterAnchoredWriterFlyFrames" config:type="boolean">false</config:config-item> + </config:config-item-set> + </office:settings> + <office:scripts> + <office:script script:language="ooo:Basic"> + <ooo:libraries xmlns:ooo="http://openoffice.org/2004/office" xmlns:xlink="http://www.w3.org/1999/xlink"/> + </office:script> + </office:scripts> + <office:font-face-decls> + <style:font-face style:name="FreeSans1" svg:font-family="FreeSans" style:font-family-generic="swiss"/> + <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/> + <style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/> + <style:font-face style:name="Open Sans" svg:font-family="'Open Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/> + <style:font-face style:name="Droid Sans" svg:font-family="'Droid Sans'" style:font-family-generic="system" style:font-pitch="variable"/> + <style:font-face style:name="FreeSans" svg:font-family="FreeSans" style:font-family-generic="system" style:font-pitch="variable"/> + </office:font-face-decls> + <office:styles> + <style:default-style style:family="graphic"> + <style:graphic-properties svg:stroke-color="#808080" draw:fill-color="#cfe7f5" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.1181in" draw:shadow-offset-y="0.1181in" draw:start-line-spacing-horizontal="0.1114in" draw:start-line-spacing-vertical="0.1114in" draw:end-line-spacing-horizontal="0.1114in" draw:end-line-spacing-vertical="0.1114in" style:flow-with-text="false"/> + <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" style:font-independent-line-spacing="false"> + <style:tab-stops/> + </style:paragraph-properties> + <style:text-properties style:use-window-font-color="true" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Droid Sans" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="FreeSans" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/> + </style:default-style> + <style:default-style style:family="paragraph"> + <style:paragraph-properties fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="0.4925in" style:writing-mode="page"/> + <style:text-properties style:use-window-font-color="true" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Droid Sans" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="FreeSans" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2"/> + </style:default-style> + <style:default-style style:family="table"> + <style:table-properties table:border-model="collapsing"/> + </style:default-style> + <style:default-style style:family="table-row"> + <style:table-row-properties fo:keep-together="auto"/> + </style:default-style> + <style:style style:name="Standard" style:family="paragraph" style:class="text"/> + <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="text"> + <style:paragraph-properties fo:margin-top="0.1665in" fo:margin-bottom="0.0835in" loext:contextual-spacing="false" fo:keep-with-next="always"/> + <style:text-properties style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt" style:font-name-asian="Droid Sans" style:font-family-asian="'Droid Sans'" style:font-family-generic-asian="system" style:font-pitch-asian="variable" style:font-size-asian="14pt" style:font-name-complex="FreeSans" style:font-family-complex="FreeSans" style:font-family-generic-complex="system" style:font-pitch-complex="variable" style:font-size-complex="14pt"/> + </style:style> + <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text"> + <style:paragraph-properties fo:margin-top="0in" fo:margin-bottom="0.0835in" loext:contextual-spacing="false"/> + </style:style> + <style:style style:name="List" style:family="paragraph" style:parent-style-name="Text_20_body" style:class="list"> + <style:text-properties style:font-size-asian="12pt" style:font-name-complex="FreeSans1" style:font-family-complex="FreeSans" style:font-family-generic-complex="swiss"/> + </style:style> + <style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra"> + <style:paragraph-properties fo:margin-top="0.0835in" fo:margin-bottom="0.0835in" loext:contextual-spacing="false" text:number-lines="false" text:line-number="0"/> + <style:text-properties fo:font-size="12pt" fo:font-style="italic" style:font-size-asian="12pt" style:font-style-asian="italic" style:font-name-complex="FreeSans1" style:font-family-complex="FreeSans" style:font-family-generic-complex="swiss" style:font-size-complex="12pt" style:font-style-complex="italic"/> + </style:style> + <style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index"> + <style:paragraph-properties text:number-lines="false" text:line-number="0"/> + <style:text-properties style:font-size-asian="12pt" style:font-name-complex="FreeSans1" style:font-family-complex="FreeSans" style:font-family-generic-complex="swiss"/> + </style:style> + <text:outline-style style:name="Outline"> + <text:outline-level-style text:level="1" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.3in" fo:text-indent="-0.3in" fo:margin-left="0.3in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="2" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.4in" fo:text-indent="-0.4in" fo:margin-left="0.4in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="3" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.5in" fo:text-indent="-0.5in" fo:margin-left="0.5in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="4" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.6in" fo:text-indent="-0.6in" fo:margin-left="0.6in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="5" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.7in" fo:text-indent="-0.7in" fo:margin-left="0.7in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="6" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.8in" fo:text-indent="-0.8in" fo:margin-left="0.8in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="7" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="0.9in" fo:text-indent="-0.9in" fo:margin-left="0.9in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="8" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1in" fo:text-indent="-1in" fo:margin-left="1in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="9" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.1in" fo:text-indent="-1.1in" fo:margin-left="1.1in"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="10" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab" text:list-tab-stop-position="1.2in" fo:text-indent="-1.2in" fo:margin-left="1.2in"/> + </style:list-level-properties> + </text:outline-level-style> + </text:outline-style> + <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/> + <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/> + <text:linenumbering-configuration text:number-lines="false" text:offset="0.1965in" style:num-format="1" text:number-position="left" text:increment="5"/> + </office:styles> + <office:automatic-styles> + <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard"> + <style:text-properties style:font-name="Open Sans" fo:font-size="12pt" officeooo:rsid="0000614b" officeooo:paragraph-rsid="0000614b" style:font-size-asian="10.5pt" style:font-size-complex="12pt"/> + </style:style> + <style:style style:name="P2" style:family="paragraph" style:parent-style-name="Standard"> + <style:text-properties style:font-name="Open Sans" fo:font-size="12pt" officeooo:rsid="00054216" officeooo:paragraph-rsid="00054216" style:font-size-asian="10.5pt" style:font-size-complex="12pt"/> + </style:style> + <style:style style:name="P3" style:family="paragraph" style:parent-style-name="Standard"> + <style:text-properties style:font-name="Open Sans" fo:font-size="12pt" officeooo:rsid="001b47cb" officeooo:paragraph-rsid="001b47cb" style:font-size-asian="10.5pt" style:font-size-complex="12pt"/> + </style:style> + <style:style style:name="P4" style:family="paragraph" style:parent-style-name="Standard"> + <style:text-properties style:font-name="Open Sans" fo:font-size="12pt" officeooo:rsid="001b47cb" officeooo:paragraph-rsid="0000614b" style:font-size-asian="10.5pt" style:font-size-complex="12pt"/> + </style:style> + <style:style style:name="P5" style:family="paragraph" style:parent-style-name="Standard"> + <style:text-properties style:font-name="Open Sans" fo:font-size="12pt" officeooo:rsid="00054216" officeooo:paragraph-rsid="00054216" style:font-size-asian="10.5pt" style:font-size-complex="12pt"/> + </style:style> + <style:page-layout style:name="pm1"> + <style:page-layout-properties fo:page-width="8.2681in" fo:page-height="11.6929in" style:num-format="1" style:print-orientation="portrait" fo:margin-top="0.7874in" fo:margin-bottom="0.7874in" fo:margin-left="0.7874in" fo:margin-right="0.7874in" style:writing-mode="lr-tb" style:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.278in" style:layout-grid-ruby-height="0.139in" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" style:footnote-max-height="0in"> + <style:footnote-sep style:width="0.0071in" style:distance-before-sep="0.0398in" style:distance-after-sep="0.0398in" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/> + </style:page-layout-properties> + <style:header-style/> + <style:footer-style/> + </style:page-layout> + </office:automatic-styles> + <office:master-styles> + <style:master-page style:name="Standard" style:page-layout-name="pm1"/> + </office:master-styles> + <office:body> + <office:text> + <office:forms form:automatic-focus="false" form:apply-design-mode="false"/> + <text:tracked-changes text:track-changes="false"> + <text:changed-region xml:id="ct47155616" text:id="ct47155616"> + <text:insertion> + <office:change-info> + <dc:creator>Aron Budea</dc:creator> + <dc:date>2018-02-23T13:32:12</dc:date> + </office:change-info> + </text:insertion> + </text:changed-region> + <text:changed-region xml:id="ct37269280" text:id="ct37269280"> + <text:insertion> + <office:change-info> + <dc:creator>Aron Budea</dc:creator> + <dc:date>2018-02-23T13:32:15</dc:date> + </office:change-info> + </text:insertion> + </text:changed-region> + </text:tracked-changes> + <text:sequence-decls> + <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/> + <text:sequence-decl text:display-outline-level="0" text:name="Table"/> + <text:sequence-decl text:display-outline-level="0" text:name="Text"/> + <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/> + </text:sequence-decls> + <text:p text:style-name="P2"/> + <text:p text:style-name="P2"/> + <text:p text:style-name="P2"><text:change-start text:change-id="ct47155616"/>abcd<text:change-end text:change-id="ct47155616"/></text:p> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P1"/> + <text:p text:style-name="P2"><text:change-start text:change-id="ct37269280"/>abcd<text:change-end text:change-id="ct37269280"/></text:p> + <text:p text:style-name="P2"/> + <text:p text:style-name="P2"/> + </office:text> + </office:body> +</office:document>
\ No newline at end of file diff --git a/sw/qa/extras/tiledrendering/data/table-paint-invalidate.odt b/sw/qa/extras/tiledrendering/data/table-paint-invalidate.odt Binary files differnew file mode 100644 index 0000000000..b42c5cc515 --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/table-paint-invalidate.odt diff --git a/sw/qa/extras/tiledrendering/data/tdf114799_highlight.docx b/sw/qa/extras/tiledrendering/data/tdf114799_highlight.docx Binary files differnew file mode 100644 index 0000000000..3a64d71d7c --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/tdf114799_highlight.docx diff --git a/sw/qa/extras/tiledrendering/data/tdf114799_shd.docx b/sw/qa/extras/tiledrendering/data/tdf114799_shd.docx Binary files differnew file mode 100644 index 0000000000..730ef91dce --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/tdf114799_shd.docx diff --git a/sw/qa/extras/tiledrendering/data/tdf115088.odt b/sw/qa/extras/tiledrendering/data/tdf115088.odt Binary files differnew file mode 100644 index 0000000000..b29681eb8e --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/tdf115088.odt diff --git a/sw/qa/extras/tiledrendering/data/tdf117448.fodt b/sw/qa/extras/tiledrendering/data/tdf117448.fodt new file mode 100644 index 0000000000..20846d8cdd --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/tdf117448.fodt @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:styles> + <style:style style:name="Standard" style:family="paragraph" style:class="text"/> + <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text"> + <style:paragraph-properties fo:margin-top="0pt" fo:margin-bottom="7pt" loext:contextual-spacing="false" fo:line-height="115%"/> + </style:style> + </office:styles> + <office:automatic-styles> + <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Standard"> + <style:paragraph-properties fo:margin-top="8pt" fo:margin-bottom="8pt" loext:contextual-spacing="false" fo:line-height="2pt"/> + <style:text-properties fo:color="#000000" loext:opacity="100%" style:font-name="Liberation Serif" fo:font-size="12pt"/> + </style:style> + </office:automatic-styles> + <office:master-styles> + <style:master-page style:name="Standard" style:page-layout-name="pm1"/> + </office:master-styles> + <office:body> + <office:text> + <table:table table:name="Táblázat1" table:style-name="Táblázat1"> + <table:table-column table:style-name="Táblázat1.A"/> + <table:table-row> + <table:table-cell table:style-name="Táblázat1.A1" office:value-type="string"> + <text:p text:style-name="P1">Text without clipping.</text:p> + </table:table-cell> + </table:table-row> + </table:table> + </office:text> + </office:body> +</office:document> diff --git a/sw/qa/extras/tiledrendering/data/tdf43244_SpacesOnMargin.odt b/sw/qa/extras/tiledrendering/data/tdf43244_SpacesOnMargin.odt Binary files differnew file mode 100644 index 0000000000..76c293dc0b --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/tdf43244_SpacesOnMargin.odt diff --git a/sw/qa/extras/tiledrendering/data/testTableCommentRemoveCallback.odt b/sw/qa/extras/tiledrendering/data/testTableCommentRemoveCallback.odt Binary files differnew file mode 100644 index 0000000000..09941166a7 --- /dev/null +++ b/sw/qa/extras/tiledrendering/data/testTableCommentRemoveCallback.odt diff --git a/sw/qa/extras/tiledrendering/tiledrendering.cxx b/sw/qa/extras/tiledrendering/tiledrendering.cxx new file mode 100644 index 0000000000..2325ce93b4 --- /dev/null +++ b/sw/qa/extras/tiledrendering/tiledrendering.cxx @@ -0,0 +1,4048 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 <swmodeltestbase.hxx> + +#include <string> +#include <string_view> + +#include <boost/property_tree/json_parser.hpp> + +#include <com/sun/star/frame/DispatchResultState.hpp> +#include <com/sun/star/frame/XDispatchResultListener.hpp> +#include <com/sun/star/frame/XStorable.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/text/XTextViewCursorSupplier.hpp> +#include <com/sun/star/text/XTextField.hpp> +#include <com/sun/star/text/AuthorDisplayFormat.hpp> +#include <com/sun/star/datatransfer/XTransferable2.hpp> + +#include <test/helper/transferable.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/dispatchcommand.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/string.hxx> +#include <comphelper/lok.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdview.hxx> +#include <vcl/virdev.hxx> +#include <editeng/editview.hxx> +#include <editeng/outliner.hxx> +#include <editeng/wghtitem.hxx> +#include <svl/srchitem.hxx> +#include <svl/slstitm.hxx> +#include <svl/stritem.hxx> +#include <svl/voiditem.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/lokhelper.hxx> +#include <vcl/scheduler.hxx> +#include <vcl/vclevent.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/ITiledRenderable.hxx> +#include <tools/json_writer.hxx> +#include <unotools/mediadescriptor.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <test/lokcallback.hxx> + +#include <drawdoc.hxx> +#include <ndtxt.hxx> +#include <wrtsh.hxx> +#include <view.hxx> +#include <UndoManager.hxx> +#include <cmdid.h> +#include <redline.hxx> +#include <IDocumentDrawModelAccess.hxx> +#include <IDocumentRedlineAccess.hxx> +#include <flddat.hxx> +#include <basesh.hxx> +#include <unotxdoc.hxx> +#include <docsh.hxx> +#include <txtfrm.hxx> +#include <rootfrm.hxx> +#include <fmtanchr.hxx> +#include <textcontentcontrol.hxx> +#include <swmodule.hxx> +#include <swdll.hxx> + +static std::ostream& operator<<(std::ostream& os, ViewShellId id) +{ + os << static_cast<sal_Int32>(id); + return os; +} + +/// Testsuite for the SwXTextDocument methods implementing the vcl::ITiledRenderable interface. +class SwTiledRenderingTest : public SwModelTestBase +{ +public: + SwTiledRenderingTest(); + virtual void setUp() override; + virtual void tearDown() override; + +protected: + SwXTextDocument* createDoc(const char* pName = nullptr); + void setupLibreOfficeKitViewCallback(SfxViewShell* pViewShell); + static void callback(int nType, const char* pPayload, void* pData); + void callbackImpl(int nType, const char* pPayload); + // First invalidation. + tools::Rectangle m_aInvalidation; + /// Union of all invalidations. + tools::Rectangle m_aInvalidations; + Size m_aDocumentSize; + OString m_aTextSelection; + bool m_bFound; + std::vector<OString> m_aSearchResultSelection; + std::vector<int> m_aSearchResultPart; + int m_nSelectionBeforeSearchResult; + int m_nSelectionAfterSearchResult; + int m_nInvalidations; + int m_nRedlineTableSizeChanged; + int m_nRedlineTableEntryModified; + int m_nTrackedChangeIndex; + bool m_bFullInvalidateSeen; + OString m_sHyperlinkText; + OString m_sHyperlinkLink; + OString m_aFormFieldButton; + OString m_aContentControl; + OString m_ShapeSelection; + TestLokCallbackWrapper m_callbackWrapper; +}; + +SwTiledRenderingTest::SwTiledRenderingTest() + : SwModelTestBase("/sw/qa/extras/tiledrendering/data/"), + m_bFound(true), + m_nSelectionBeforeSearchResult(0), + m_nSelectionAfterSearchResult(0), + m_nInvalidations(0), + m_nRedlineTableSizeChanged(0), + m_nRedlineTableEntryModified(0), + m_nTrackedChangeIndex(-1), + m_bFullInvalidateSeen(false), + m_callbackWrapper(&callback, this) +{ +} + +void SwTiledRenderingTest::setUp() +{ + SwModelTestBase::setUp(); + + SwGlobals::ensure(); + SW_MOD()->ClearRedlineAuthors(); + + comphelper::LibreOfficeKit::setActive(true); +} + +void SwTiledRenderingTest::tearDown() +{ + if (mxComponent.is()) + { + SwXTextDocument* pTextDocument = dynamic_cast<SwXTextDocument*>(mxComponent.get()); + if (pTextDocument) + { + SwWrtShell* pWrtShell = pTextDocument->GetDocShell()->GetWrtShell(); + if (pWrtShell) + { + pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(nullptr); + } + } + mxComponent->dispose(); + mxComponent.clear(); + } + m_callbackWrapper.clear(); + comphelper::LibreOfficeKit::setActive(false); + + test::BootstrapFixture::tearDown(); +} + +SwXTextDocument* SwTiledRenderingTest::createDoc(const char* pName) +{ + if (!pName) + createSwDoc(); + else + createSwDoc(pName); + + SwXTextDocument* pTextDocument = dynamic_cast<SwXTextDocument*>(mxComponent.get()); + CPPUNIT_ASSERT(pTextDocument); + pTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + return pTextDocument; +} + +void SwTiledRenderingTest::setupLibreOfficeKitViewCallback(SfxViewShell* pViewShell) +{ + pViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper); + m_callbackWrapper.setLOKViewId(SfxLokHelper::getView(pViewShell)); +} + +void SwTiledRenderingTest::callback(int nType, const char* pPayload, void* pData) +{ + static_cast<SwTiledRenderingTest*>(pData)->callbackImpl(nType, pPayload); +} + +void SwTiledRenderingTest::callbackImpl(int nType, const char* pPayload) +{ + OString aPayload(pPayload); + switch (nType) + { + case LOK_CALLBACK_INVALIDATE_TILES: + { + tools::Rectangle aInvalidation; + uno::Sequence<OUString> aSeq = comphelper::string::convertCommaSeparated(OUString::createFromAscii(pPayload)); + if (std::string_view("EMPTY") == pPayload) + { + m_bFullInvalidateSeen = true; + return; + } + + CPPUNIT_ASSERT(aSeq.getLength() == 4 || aSeq.getLength() == 5); + aInvalidation.SetLeft(aSeq[0].toInt32()); + aInvalidation.SetTop(aSeq[1].toInt32()); + aInvalidation.setWidth(aSeq[2].toInt32()); + aInvalidation.setHeight(aSeq[3].toInt32()); + if (m_aInvalidation.IsEmpty()) + { + m_aInvalidation = aInvalidation; + } + m_aInvalidations.Union(aInvalidation); + ++m_nInvalidations; + } + break; + case LOK_CALLBACK_DOCUMENT_SIZE_CHANGED: + { + uno::Sequence<OUString> aSeq = comphelper::string::convertCommaSeparated(OUString::createFromAscii(pPayload)); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), aSeq.getLength()); + m_aDocumentSize.setWidth(aSeq[0].toInt32()); + m_aDocumentSize.setHeight(aSeq[1].toInt32()); + } + break; + case LOK_CALLBACK_TEXT_SELECTION: + { + m_aTextSelection = pPayload; + if (m_aSearchResultSelection.empty()) + ++m_nSelectionBeforeSearchResult; + else + ++m_nSelectionAfterSearchResult; + } + break; + case LOK_CALLBACK_SEARCH_NOT_FOUND: + { + m_bFound = false; + } + break; + case LOK_CALLBACK_SEARCH_RESULT_SELECTION: + { + m_aSearchResultSelection.clear(); + boost::property_tree::ptree aTree; + std::stringstream aStream(pPayload); + boost::property_tree::read_json(aStream, aTree); + for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("searchResultSelection")) + { + m_aSearchResultSelection.emplace_back(rValue.second.get<std::string>("rectangles").c_str()); + m_aSearchResultPart.push_back(std::atoi(rValue.second.get<std::string>("part").c_str())); + } + } + break; + case LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED: + { + ++m_nRedlineTableSizeChanged; + } + break; + case LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED: + { + ++m_nRedlineTableEntryModified; + } + break; + case LOK_CALLBACK_STATE_CHANGED: + { + OString aTrackedChangeIndexPrefix(".uno:TrackedChangeIndex="_ostr); + if (aPayload.startsWith(aTrackedChangeIndexPrefix)) + { + OString sIndex = aPayload.copy(aTrackedChangeIndexPrefix.getLength()); + if (sIndex.isEmpty()) + m_nTrackedChangeIndex = -1; + else + m_nTrackedChangeIndex = sIndex.toInt32(); + } + } + break; + case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: + { + if (comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation()) + { + boost::property_tree::ptree aTree; + std::stringstream aStream(pPayload); + boost::property_tree::read_json(aStream, aTree); + boost::property_tree::ptree &aChild = aTree.get_child("hyperlink"); + m_sHyperlinkText = OString(aChild.get("text", "")); + m_sHyperlinkLink = OString(aChild.get("link", "")); + } + } + break; + case LOK_CALLBACK_FORM_FIELD_BUTTON: + { + m_aFormFieldButton = OString(pPayload); + } + break; + case LOK_CALLBACK_CONTENT_CONTROL: + { + m_aContentControl = OString(pPayload); + } + break; + case LOK_CALLBACK_GRAPHIC_SELECTION: + { + m_ShapeSelection = OString(pPayload); + } + break; + } + +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testRegisterCallback) +{ + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + // Insert a character at the beginning of the document. + pWrtShell->Insert("x"); + Scheduler::ProcessEventsToIdle(); + + // Check that the top left 256x256px tile would be invalidated. + CPPUNIT_ASSERT(!m_aInvalidation.IsEmpty()); + tools::Rectangle aTopLeft(0, 0, 256*15, 256*15); // 1 px = 15 twips, assuming 96 DPI. + CPPUNIT_ASSERT(m_aInvalidation.Overlaps(aTopLeft)); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testPostKeyEvent) +{ + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); + SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); + // Did we manage to go after the first character? + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), pShellCursor->GetPoint()->GetContentIndex()); + + emulateTyping(*pXTextDocument, u"x"); + // Did we manage to insert the character after the first one? + CPPUNIT_ASSERT_EQUAL(OUString("Axaa bbb."), pShellCursor->GetPoint()->GetNode().GetTextNode()->GetText()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testPostMouseEvent) +{ + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); + SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); + // Did we manage to go after the first character? + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(1), pShellCursor->GetPoint()->GetContentIndex()); + + Point aStart = pShellCursor->GetSttPos(); + aStart.setX(aStart.getX() - 1000); + pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aStart.getX(), aStart.getY(), 1, MOUSE_LEFT, 0); + pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, aStart.getX(), aStart.getY(), 1, MOUSE_LEFT, 0); + Scheduler::ProcessEventsToIdle(); + // The new cursor position must be before the first word. + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(0), pShellCursor->GetPoint()->GetContentIndex()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSetTextSelection) +{ + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + // Move the cursor into the second word. + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 5, /*bBasicCall=*/false); + // Create a selection on the word. + pWrtShell->SelWrd(); + SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); + // Did we indeed manage to select the second word? + CPPUNIT_ASSERT_EQUAL(OUString("bbb"), pShellCursor->GetText()); + + // Now use setTextSelection() to move the start of the selection 1000 twips left. + Point aStart = pShellCursor->GetSttPos(); + aStart.setX(aStart.getX() - 1000); + pXTextDocument->setTextSelection(LOK_SETTEXTSELECTION_START, aStart.getX(), aStart.getY()); + // The new selection must include the first word, too -- but not the ending dot. + CPPUNIT_ASSERT_EQUAL(OUString("Aaa bbb"), pShellCursor->GetText()); + + // Next: test that LOK_SETTEXTSELECTION_RESET + LOK_SETTEXTSELECTION_END can be used to create a selection. + pXTextDocument->setTextSelection(LOK_SETTEXTSELECTION_RESET, aStart.getX(), aStart.getY()); + pXTextDocument->setTextSelection(LOK_SETTEXTSELECTION_END, aStart.getX() + 1000, aStart.getY()); + CPPUNIT_ASSERT_EQUAL(OUString("Aaa b"), pShellCursor->GetText()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testGetTextSelection) +{ + SwXTextDocument* pXTextDocument = createDoc("shape-with-text.fodt"); + // No crash, just empty output for unexpected mime type. + CPPUNIT_ASSERT_EQUAL(OString(), apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "foo/bar"_ostr)); + + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + // Move the cursor into the first word. + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 2, /*bBasicCall=*/false); + // Create a selection by on the word. + pWrtShell->SelWrd(); + + // Make sure that we selected text from the body text. + CPPUNIT_ASSERT_EQUAL("Hello"_ostr, apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/plain;charset=utf-8"_ostr)); + + // Make sure we produce something for HTML. + CPPUNIT_ASSERT(!apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/html"_ostr).isEmpty()); + + // Now select some shape text and check again. + SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + SdrView* pView = pWrtShell->GetDrawView(); + pView->SdrBeginTextEdit(pObject); + CPPUNIT_ASSERT(pView->GetTextEditObject()); + EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); + ESelection aWordSelection(0, 0, 0, 5); + rEditView.SetSelection(aWordSelection); + CPPUNIT_ASSERT_EQUAL("Shape"_ostr, apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/plain;charset=utf-8"_ostr)); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testGetTextSelectionLineLimit) +{ + static OStringLiteral sOriginalText(u8"Estonian employs the Latin script as the basis for its alphabet, which adds the letters ä, ö, ü, and õ, plus the later additions š and ž. The letters c, q, w, x and y are limited to proper names of foreign origin, and f, z, š, and ž appear in loanwords and foreign names only. Ö and Ü are pronounced similarly to their equivalents in Swedish and German. Unlike in standard German but like Swedish (when followed by 'r') and Finnish, Ä is pronounced [æ], as in English mat. The vowels Ä, Ö and Ü are clearly separate phonemes and inherent in Estonian, although the letter shapes come from German. The letter õ denotes /ɤ/, unrounded /o/, or a close-mid back unrounded vowel. It is almost identical to the Bulgarian ъ /ɤ̞/ and the Vietnamese ơ, and is also used to transcribe the Russian ы."); + static OStringLiteral sExpectedHtml(u8"Estonian employs the <a href=\"https://en.wikipedia.org/wiki/Latin_script\">Latin script</a> as the basis for <a href=\"https://en.wikipedia.org/wiki/Estonian_alphabet\">its alphabet</a>, which adds the letters <a href=\"https://en.wikipedia.org/wiki/%C3%84\"><i>ä</i></a>, <a href=\"https://en.wikipedia.org/wiki/%C3%96\"><i>ö</i></a>, <a href=\"https://en.wikipedia.org/wiki/%C3%9C\"><i>ü</i></a>, and <a href=\"https://en.wikipedia.org/wiki/%C3%95\"><i>õ</i></a>, plus the later additions <a href=\"https://en.wikipedia.org/wiki/%C5%A0\"><i>š</i></a> and <a href=\"https://en.wikipedia.org/wiki/%C5%BD\"><i>ž</i></a>. The letters <i>c</i>, <i>q</i>, <i>w</i>, <i>x</i> and <i>y</i> are limited to <a href=\"https://en.wikipedia.org/wiki/Proper_names\">proper names</a> of foreign origin, and <i>f</i>, <i>z</i>, <i>š</i>, and <i>ž</i> appear in loanwords and foreign names only. <i>Ö</i> and <i>Ü</i> are pronounced similarly to their equivalents in Swedish and German. Unlike in standard German but like Swedish (when followed by 'r') and Finnish, <i>Ä</i> is pronounced [æ], as in English <i>mat</i>. The vowels Ä, Ö and Ü are clearly separate <a href=\"https://en.wikipedia.org/wiki/Phonemes\">phonemes</a> and inherent in Estonian, although the letter shapes come from German. The letter <a href=\"https://en.wikipedia.org/wiki/%C3%95\"><i>õ</i></a> denotes /ɤ/, unrounded /o/, or a <a href=\"https://en.wikipedia.org/wiki/Close-mid_back_unrounded_vowel\">close-mid back unrounded vowel</a>. It is almost identical to the <a href=\"https://en.wikipedia.org/wiki/Bulgarian_language\">Bulgarian</a> <a href=\"https://en.wikipedia.org/wiki/%D0%AA\">ъ</a> /ɤ̞/ and the <a href=\"https://en.wikipedia.org/wiki/Vietnamese_language\">Vietnamese</a> <a href=\"https://en.wikipedia.org/wiki/%C6%A0\">ơ</a>, and is also used to transcribe the Russian <a href=\"https://en.wikipedia.org/wiki/%D0%AB\">ы</a>."); + + SwXTextDocument* pXTextDocument = createDoc("estonian.odt"); + + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + // Move the cursor into the first word. + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 2, /*bBasicCall=*/false); + // Create a selection. + pWrtShell->SelAll(); + + OString sPlainText = apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/plain;charset=utf-8"_ostr); + + CPPUNIT_ASSERT_EQUAL(OString(sOriginalText), sPlainText.trim()); + + OString sHtmlText = apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/html"_ostr); + + int nStart = sHtmlText.indexOf(u8"Estonian"); + + CPPUNIT_ASSERT(sHtmlText.match(sExpectedHtml, nStart)); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testGetTextSelectionMultiLine) +{ + // Test will check if correct number of new line marks / paragraphs is generated + static OStringLiteral sOriginalText(u8"Heading\n\ +Let's have text; we need to be able to select the text inside the shape, but also the various individual ones too:\n\ +\n\ +\n\ +\n\ +\n\ +\n\ +And this is all for Writer shape objects\n\ +Heading on second page"); + + static OStringLiteral sExpectedHtml(u8"Heading</h2>\n\ +<p>Let's have text; we need to be able to select the text inside the shape, but also the various individual ones too:</p>\n\ +<p><br/><br/></p>\n\ +<p><br/><br/></p>\n\ +<p><br/><br/></p>\n\ +<p><br/><br/></p>\n\ +<p><br/><br/></p>\n\ +<h1 class=\"western\">And this is all for Writer shape objects</h1>\n\ +<h2 class=\"western\">Heading on second page</h2>"); + + SwXTextDocument* pXTextDocument = createDoc("multiline.odt"); + + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + // Create a selection. + pWrtShell->SelAll(); + + OString sPlainText = apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/plain;charset=utf-8"_ostr); + + CPPUNIT_ASSERT_EQUAL(OString(sOriginalText), sPlainText.trim()); + + OString sHtmlText = apitest::helper::transferable::getTextSelection(pXTextDocument->getSelection(), "text/html"_ostr); + + int nStart = sHtmlText.indexOf(u8"Heading"); + + CPPUNIT_ASSERT(sHtmlText.match(sExpectedHtml, nStart)); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSetGraphicSelection) +{ + SwXTextDocument* pXTextDocument = createDoc("shape.fodt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + pWrtShell->SelectObj(Point(), 0, pObject); + SdrHdlList handleList(nullptr); + pObject->AddToHdlList(handleList); + // Make sure the rectangle has 8 handles: at each corner and at the center of each edge. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(8), handleList.GetHdlCount()); + // Take the bottom center one. + SdrHdl* pHdl = handleList.GetHdl(6); + CPPUNIT_ASSERT_EQUAL(int(SdrHdlKind::Lower), static_cast<int>(pHdl->GetKind())); + tools::Rectangle aShapeBefore = pObject->GetSnapRect(); + // Resize. + pXTextDocument->setGraphicSelection(LOK_SETGRAPHICSELECTION_START, pHdl->GetPos().getX(), pHdl->GetPos().getY()); + pXTextDocument->setGraphicSelection(LOK_SETGRAPHICSELECTION_END, pHdl->GetPos().getX(), pHdl->GetPos().getY() + 1000); + tools::Rectangle aShapeAfter = pObject->GetSnapRect(); + // Check that a resize happened, but aspect ratio is not kept. + CPPUNIT_ASSERT_EQUAL(aShapeBefore.getOpenWidth(), aShapeAfter.getOpenWidth()); + CPPUNIT_ASSERT_EQUAL(aShapeBefore.getOpenHeight() + 1000, aShapeAfter.getOpenHeight()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testResetSelection) +{ + SwXTextDocument* pXTextDocument = createDoc("shape.fodt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + // Select one character. + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, 1, /*bBasicCall=*/false); + SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); + // We have a text selection. + CPPUNIT_ASSERT(pShellCursor->HasMark()); + + pXTextDocument->resetSelection(); + // We no longer have a text selection. + CPPUNIT_ASSERT(!pShellCursor->HasMark()); + + SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + Point aPoint = pObject->GetSnapRect().Center(); + // Select the shape. + pWrtShell->EnterSelFrameMode(&aPoint); + // We have a graphic selection. + CPPUNIT_ASSERT(pWrtShell->IsSelFrameMode()); + + pXTextDocument->resetSelection(); + // We no longer have a graphic selection. + CPPUNIT_ASSERT(!pWrtShell->IsSelFrameMode()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testInsertShape) +{ + SwXTextDocument* pXTextDocument = createDoc("2-pages.odt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + + pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000)); + comphelper::dispatchCommand(".uno:BasicShapes.circle", uno::Sequence<beans::PropertyValue>()); + + // check that the shape was inserted in the visible area, not outside + IDocumentDrawModelAccess &rDrawModelAccess = pWrtShell->GetDoc()->getIDocumentDrawModelAccess(); + SdrPage* pPage = rDrawModelAccess.GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + + CPPUNIT_ASSERT_EQUAL(tools::Rectangle(Point(3299, 299), Size(3403, 3403)), pObject->GetSnapRect()); + + // check that it is in the foreground layer + CPPUNIT_ASSERT_EQUAL(rDrawModelAccess.GetHeavenId().get(), pObject->GetLayer().get()); +} + +static void lcl_search(bool bBackward) +{ + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"SearchItem.SearchString", uno::Any(OUString("shape"))}, + {"SearchItem.Backward", uno::Any(bBackward)} + })); + comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSearch) +{ + SwXTextDocument* pXTextDocument = createDoc("search.odt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + SwNodeOffset nNode = pWrtShell->getShellCursor(false)->Start()->GetNode().GetIndex(); + + // First hit, in the second paragraph, before the shape. + lcl_search(false); + CPPUNIT_ASSERT(!pWrtShell->GetDrawView()->GetTextEditObject()); + SwNodeOffset nActual = pWrtShell->getShellCursor(false)->Start()->GetNode().GetIndex(); + CPPUNIT_ASSERT_EQUAL(nNode + 1, nActual); + /// Make sure we get search result selection for normal find as well, not only find all. + CPPUNIT_ASSERT(!m_aSearchResultSelection.empty()); + + // Next hit, in the shape. + lcl_search(false); + CPPUNIT_ASSERT(pWrtShell->GetDrawView()->GetTextEditObject()); + + // Next hit, in the shape, still. + lcl_search(false); + CPPUNIT_ASSERT(pWrtShell->GetDrawView()->GetTextEditObject()); + + // Last hit, in the last paragraph, after the shape. + lcl_search(false); + CPPUNIT_ASSERT(!pWrtShell->GetDrawView()->GetTextEditObject()); + nActual = pWrtShell->getShellCursor(false)->Start()->GetNode().GetIndex(); + CPPUNIT_ASSERT_EQUAL(nNode + 7, nActual); + + // Now change direction and make sure that the first 2 hits are in the shape, but not the 3rd one. + lcl_search(true); + CPPUNIT_ASSERT(pWrtShell->GetDrawView()->GetTextEditObject()); + lcl_search(true); + CPPUNIT_ASSERT(pWrtShell->GetDrawView()->GetTextEditObject()); + lcl_search(true); + CPPUNIT_ASSERT(!pWrtShell->GetDrawView()->GetTextEditObject()); + nActual = pWrtShell->getShellCursor(false)->Start()->GetNode().GetIndex(); + CPPUNIT_ASSERT_EQUAL(nNode + 1, nActual); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSearchViewArea) +{ + SwXTextDocument* pXTextDocument = createDoc("search.odt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + // Go to the second page, 1-based. + pWrtShell->GotoPage(2, false); + SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); + // Get the ~top left corner of the second page. + Point aPoint = pShellCursor->GetSttPos(); + + // Go back to the first page, search while the cursor is there, but the + // visible area is the second page. + pWrtShell->GotoPage(1, false); + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"SearchItem.SearchString", uno::Any(OUString("Heading"))}, + {"SearchItem.Backward", uno::Any(false)}, + {"SearchItem.SearchStartPointX", uno::Any(static_cast<sal_Int32>(aPoint.getX()))}, + {"SearchItem.SearchStartPointY", uno::Any(static_cast<sal_Int32>(aPoint.getY()))} + })); + comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues); + // This was just "Heading", i.e. SwView::SearchAndWrap() did not search from only the top of the second page. + CPPUNIT_ASSERT_EQUAL(OUString("Heading on second page"), pShellCursor->GetPoint()->GetNode().GetTextNode()->GetText()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSearchTextFrame) +{ + SwXTextDocument* pXTextDocument = createDoc("search.odt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"SearchItem.SearchString", uno::Any(OUString("TextFrame"))}, + {"SearchItem.Backward", uno::Any(false)}, + })); + comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues); + // This was empty: nothing was highlighted after searching for 'TextFrame'. + CPPUNIT_ASSERT(!m_aTextSelection.isEmpty()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSearchTextFrameWrapAround) +{ + SwXTextDocument* pXTextDocument = createDoc("search.odt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"SearchItem.SearchString", uno::Any(OUString("TextFrame"))}, + {"SearchItem.Backward", uno::Any(false)}, + })); + comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues); + CPPUNIT_ASSERT(m_bFound); + comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues); + // This failed, i.e. the second time 'not found' was reported, instead of wrapping around. + CPPUNIT_ASSERT(m_bFound); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testDocumentSizeChanged) +{ + // Get the current document size. + SwXTextDocument* pXTextDocument = createDoc("2-pages.odt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + Size aSize = pXTextDocument->getDocumentSize(); + + // Delete the second page and see how the size changes. + pWrtShell->Down(false); + pWrtShell->DelLeft(); + // Document width should not change, this was 0. + CPPUNIT_ASSERT_EQUAL(aSize.getWidth(), m_aDocumentSize.getWidth()); + // Document height should be smaller now. + CPPUNIT_ASSERT(aSize.getHeight() > m_aDocumentSize.getHeight()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSearchAll) +{ + SwXTextDocument* pXTextDocument = createDoc("search.odt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"SearchItem.SearchString", uno::Any(OUString("shape"))}, + {"SearchItem.Backward", uno::Any(false)}, + {"SearchItem.Command", uno::Any(static_cast<sal_uInt16>(SvxSearchCmd::FIND_ALL))}, + })); + comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues); + // This was 0; should be 2 results in the body text. + CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(2), m_aSearchResultSelection.size()); + // Writer documents are always a single part. + CPPUNIT_ASSERT_EQUAL(0, m_aSearchResultPart[0]); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSearchAllNotifications) +{ + SwXTextDocument* pXTextDocument = createDoc("search.odt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + // Reset notification counter before search. + m_nSelectionBeforeSearchResult = 0; + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"SearchItem.SearchString", uno::Any(OUString("shape"))}, + {"SearchItem.Backward", uno::Any(false)}, + {"SearchItem.Command", uno::Any(static_cast<sal_uInt16>(SvxSearchCmd::FIND_ALL))}, + })); + comphelper::dispatchCommand(".uno:ExecuteSearch", aPropertyValues); + Scheduler::ProcessEventsToIdle(); + + // This was 5, make sure that we get no notifications about selection changes during search. + CPPUNIT_ASSERT_EQUAL(0, m_nSelectionBeforeSearchResult); + // But we do get the selection afterwards. + CPPUNIT_ASSERT(m_nSelectionAfterSearchResult > 0); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testPageDownInvalidation) +{ + SwXTextDocument* pXTextDocument = createDoc("pagedown-invalidation.odt"); + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {".uno:HideWhitespace", uno::Any(true)}, + })); + pXTextDocument->initializeForTiledRendering(aPropertyValues); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + comphelper::dispatchCommand(".uno:PageDown", uno::Sequence<beans::PropertyValue>()); + + // This was 2. + CPPUNIT_ASSERT_EQUAL(0, m_nInvalidations); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testPartHash) +{ + SwXTextDocument* pXTextDocument = createDoc("pagedown-invalidation.odt"); + int nParts = pXTextDocument->getParts(); + for (int it = 0; it < nParts; it++) + { + CPPUNIT_ASSERT(!pXTextDocument->getPartHash(it).isEmpty()); + } +} + +namespace { + + /// A view callback tracks callbacks invoked on one specific view. + class ViewCallback final + { + SfxViewShell* mpViewShell; + int mnView; + public: + bool m_bOwnCursorInvalidated; + int m_nOwnCursorInvalidatedBy; + bool m_bOwnCursorAtOrigin; + tools::Rectangle m_aOwnCursor; + bool m_bViewCursorInvalidated; + tools::Rectangle m_aViewCursor; + bool m_bOwnSelectionSet; + bool m_bViewSelectionSet; + OString m_aViewSelection; + bool m_bTilesInvalidated; + bool m_bViewCursorVisible; + bool m_bGraphicViewSelection; + bool m_bGraphicSelection; + bool m_bViewLock; + /// Set if any callback was invoked. + bool m_bCalled; + /// Redline table size changed payload + boost::property_tree::ptree m_aRedlineTableChanged; + /// Redline table modified payload + boost::property_tree::ptree m_aRedlineTableModified; + /// Post-it / annotation payload. + boost::property_tree::ptree m_aComment; + TestLokCallbackWrapper m_callbackWrapper; + + ViewCallback(SfxViewShell* pViewShell = nullptr, std::function<void(ViewCallback&)> const & rBeforeInstallFunc = {}) + : m_bOwnCursorInvalidated(false), + m_nOwnCursorInvalidatedBy(-1), + m_bOwnCursorAtOrigin(false), + m_bViewCursorInvalidated(false), + m_bOwnSelectionSet(false), + m_bViewSelectionSet(false), + m_bTilesInvalidated(false), + m_bViewCursorVisible(false), + m_bGraphicViewSelection(false), + m_bGraphicSelection(false), + m_bViewLock(false), + m_bCalled(false), + m_callbackWrapper(&callback, this) + { + // Because one call-site wants to set the bool fields up before the callback is installed + if (rBeforeInstallFunc) + rBeforeInstallFunc(*this); + + mpViewShell = pViewShell ? pViewShell : SfxViewShell::Current(); + mpViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper); + mnView = SfxLokHelper::getView(); + m_callbackWrapper.setLOKViewId( mnView ); + } + + ~ViewCallback() + { + SfxLokHelper::setView(mnView); + mpViewShell->setLibreOfficeKitViewCallback(nullptr); + } + + static void callback(int nType, const char* pPayload, void* pData) + { + static_cast<ViewCallback*>(pData)->callbackImpl(nType, pPayload); + } + + void callbackImpl(int nType, const char* pPayload) + { + OString aPayload(pPayload); + m_bCalled = true; + switch (nType) + { + case LOK_CALLBACK_INVALIDATE_TILES: + { + m_bTilesInvalidated = true; + } + break; + case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: + { + m_bOwnCursorInvalidated = true; + + OString sRect; + if(comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation()) + { + std::stringstream aStream(pPayload); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + sRect = OString(aTree.get_child("rectangle").get_value<std::string>()); + m_nOwnCursorInvalidatedBy = aTree.get_child("viewId").get_value<int>(); + } + else + sRect = aPayload; + uno::Sequence<OUString> aSeq = comphelper::string::convertCommaSeparated(OUString::fromUtf8(sRect)); + if (std::string_view("EMPTY") == pPayload) + return; + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(4), aSeq.getLength()); + m_aOwnCursor.SetLeft(aSeq[0].toInt32()); + m_aOwnCursor.SetTop(aSeq[1].toInt32()); + m_aOwnCursor.setWidth(aSeq[2].toInt32()); + m_aOwnCursor.setHeight(aSeq[3].toInt32()); + if (m_aOwnCursor.Left() == 0 && m_aOwnCursor.Top() == 0) + m_bOwnCursorAtOrigin = true; + } + break; + case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR: + { + m_bViewCursorInvalidated = true; + std::stringstream aStream(pPayload); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + OString aRect( aTree.get_child("rectangle").get_value<std::string>() ); + + uno::Sequence<OUString> aSeq = comphelper::string::convertCommaSeparated(OUString::fromUtf8(aRect)); + if (std::string_view("EMPTY") == pPayload) + return; + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(4), aSeq.getLength()); + m_aViewCursor.SetLeft(aSeq[0].toInt32()); + m_aViewCursor.SetTop(aSeq[1].toInt32()); + m_aViewCursor.setWidth(aSeq[2].toInt32()); + m_aViewCursor.setHeight(aSeq[3].toInt32()); + } + break; + case LOK_CALLBACK_TEXT_SELECTION: + { + m_bOwnSelectionSet = true; + } + break; + case LOK_CALLBACK_TEXT_VIEW_SELECTION: + { + m_bViewSelectionSet = true; + m_aViewSelection = aPayload; + } + break; + case LOK_CALLBACK_VIEW_CURSOR_VISIBLE: + { + std::stringstream aStream(pPayload); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + m_bViewCursorVisible = aTree.get_child("visible").get_value<std::string>() == "true"; + } + break; + case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION: + { + std::stringstream aStream(pPayload); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + m_bGraphicViewSelection = aTree.get_child("selection").get_value<std::string>() != "EMPTY"; + } + break; + case LOK_CALLBACK_GRAPHIC_SELECTION: + { + m_bGraphicSelection = aPayload != "EMPTY"; + } + break; + case LOK_CALLBACK_VIEW_LOCK: + { + std::stringstream aStream(pPayload); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + m_bViewLock = aTree.get_child("rectangle").get_value<std::string>() != "EMPTY"; + } + break; + case LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED: + { + m_aRedlineTableChanged.clear(); + std::stringstream aStream(pPayload); + boost::property_tree::read_json(aStream, m_aRedlineTableChanged); + m_aRedlineTableChanged = m_aRedlineTableChanged.get_child("redline"); + } + break; + case LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED: + { + m_aRedlineTableModified.clear(); + std::stringstream aStream(pPayload); + boost::property_tree::read_json(aStream, m_aRedlineTableModified); + m_aRedlineTableModified = m_aRedlineTableModified.get_child("redline"); + } + break; + case LOK_CALLBACK_COMMENT: + { + m_aComment.clear(); + std::stringstream aStream(pPayload); + boost::property_tree::read_json(aStream, m_aComment); + m_aComment = m_aComment.get_child("comment"); + } + break; + } + } + }; + + class TestResultListener : public cppu::WeakImplHelper<css::frame::XDispatchResultListener> + { + public: + sal_uInt32 m_nDocRepair; + + TestResultListener() : m_nDocRepair(0) + { + } + + virtual void SAL_CALL dispatchFinished(const css::frame::DispatchResultEvent& rEvent) override + { + if (rEvent.State == frame::DispatchResultState::SUCCESS) + { + rEvent.Result >>= m_nDocRepair; + } + } + + virtual void SAL_CALL disposing(const css::lang::EventObject&) override + { + } + }; + +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testMissingInvalidation) +{ + // Create two views. + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + ViewCallback aView1; + int nView1 = SfxLokHelper::getView(); + SfxLokHelper::createView(); + ViewCallback aView2; + int nView2 = SfxLokHelper::getView(); + + // First view: put the cursor into the first word. + SfxLokHelper::setView(nView1); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); + + // Second view: select the first word. + SfxLokHelper::setView(nView2); + CPPUNIT_ASSERT(pXTextDocument->GetDocShell()->GetWrtShell() != pWrtShell); + pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); + pWrtShell->SelWrd(); + + // Now delete the selected word and make sure both views are invalidated. + Scheduler::ProcessEventsToIdle(); + aView1.m_bTilesInvalidated = false; + aView2.m_bTilesInvalidated = false; + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DELETE); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DELETE); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(aView1.m_bTilesInvalidated); + CPPUNIT_ASSERT(aView2.m_bTilesInvalidated); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testViewCursors) +{ + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + ViewCallback aView1; + SfxLokHelper::createView(); + ViewCallback aView2; + + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(aView1.m_bOwnCursorInvalidated); + CPPUNIT_ASSERT(aView1.m_bViewCursorInvalidated); + CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated); + // This failed: the cursor position of view1 was only known to view2 once + // it changed. + CPPUNIT_ASSERT(aView2.m_bViewCursorInvalidated); + + // Make sure that aView1 gets a view-only selection notification, while + // aView2 gets a real selection notification. + aView1.m_bOwnSelectionSet = false; + aView1.m_bViewSelectionSet = false; + aView2.m_bOwnSelectionSet = false; + aView2.m_bViewSelectionSet = false; + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + // Move the cursor into the second word. + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 5, /*bBasicCall=*/false); + // Create a selection on the word. + pWrtShell->SelWrd(); + Scheduler::ProcessEventsToIdle(); + SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); + // Did we indeed manage to select the second word? + CPPUNIT_ASSERT_EQUAL(OUString("bbb"), pShellCursor->GetText()); + CPPUNIT_ASSERT(!aView1.m_bOwnSelectionSet); + // This failed, aView1 did not get notification about selection changes in + // aView2. + CPPUNIT_ASSERT(aView1.m_bViewSelectionSet); + CPPUNIT_ASSERT(aView2.m_bOwnSelectionSet); + CPPUNIT_ASSERT(!aView2.m_bViewSelectionSet); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testShapeViewCursors) +{ + // Load a document and create a view, so we have 2 ones. + SwXTextDocument* pXTextDocument = createDoc("shape.fodt"); + ViewCallback aView1; + SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + ViewCallback aView2; + SwWrtShell* pWrtShell2 = pXTextDocument->GetDocShell()->GetWrtShell(); + + // Start shape text in the second view. + SdrPage* pPage = pWrtShell2->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + SdrView* pView = pWrtShell2->GetDrawView(); + pWrtShell2->GetView().BeginTextEdit(pObject, pView->GetSdrPageView(), pWrtShell2->GetWin()); + emulateTyping(*pXTextDocument, u"x"); + // Press a key in the second view, while the first one observes this. + aView1.m_bViewCursorInvalidated = false; + aView2.m_bOwnCursorInvalidated = false; + const tools::Rectangle aLastOwnCursor1 = aView1.m_aOwnCursor; + const tools::Rectangle aLastViewCursor1 = aView1.m_aViewCursor; + const tools::Rectangle aLastOwnCursor2 = aView2.m_aOwnCursor; + const tools::Rectangle aLastViewCursor2 = aView2.m_aViewCursor; + + emulateTyping(*pXTextDocument, u"y"); + // Make sure that aView1 gets a view-only cursor notification, while + // aView2 gets a real cursor notification. + CPPUNIT_ASSERT_EQUAL(aView1.m_aOwnCursor, aLastOwnCursor1); + CPPUNIT_ASSERT(aView1.m_bViewCursorInvalidated); + CPPUNIT_ASSERT(aLastViewCursor1 != aView1.m_aViewCursor); + CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated); + CPPUNIT_ASSERT(aLastOwnCursor2 != aView2.m_aOwnCursor); + CPPUNIT_ASSERT_EQUAL(aLastViewCursor2, aView2.m_aViewCursor); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testViewCursorVisibility) +{ + // Load a document that has a shape and create two views. + SwXTextDocument* pXTextDocument = createDoc("shape.fodt"); + ViewCallback aView1; + SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + ViewCallback aView2; + // This failed, initially the view cursor in the second view wasn't visible. + CPPUNIT_ASSERT(aView2.m_bViewCursorVisible); + + // Click on the shape in the second view. + aView1.m_bViewCursorVisible = true; + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + Point aCenter = pObject->GetSnapRect().Center(); + pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aCenter.getX(), aCenter.getY(), 1, MOUSE_LEFT, 0); + pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, aCenter.getX(), aCenter.getY(), 1, MOUSE_LEFT, 0); + Scheduler::ProcessEventsToIdle(); + // Make sure the "view/text" cursor of the first view gets a notification. + CPPUNIT_ASSERT(!aView1.m_bViewCursorVisible); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testViewCursorCleanup) +{ + // Load a document that has a shape and create two views. + SwXTextDocument* pXTextDocument = createDoc("shape.fodt"); + ViewCallback aView1; + int nView2 = SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + { + ViewCallback aView2; + + // Click on the shape in the second view. + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + Point aCenter = pObject->GetSnapRect().Center(); + aView1.m_bGraphicViewSelection = false; + pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aCenter.getX(), aCenter.getY(), 1, MOUSE_LEFT, 0); + pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, aCenter.getX(), aCenter.getY(), 1, MOUSE_LEFT, 0); + Scheduler::ProcessEventsToIdle(); + // Make sure there is a graphic view selection on the first view. + CPPUNIT_ASSERT(aView1.m_bGraphicViewSelection); + } + // Now destroy the second view. + SfxLokHelper::destroyView(nView2); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(static_cast<std::size_t>(1), SfxLokHelper::getViewsCount(0)); + // Make sure that the graphic view selection on the first view is cleaned up. + CPPUNIT_ASSERT(!aView1.m_bGraphicViewSelection); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testViewLock) +{ + // Load a document that has a shape and create two views. + SwXTextDocument* pXTextDocument = createDoc("shape.fodt"); + ViewCallback aView1; + SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + ViewCallback aView2; + + // Begin text edit in the second view and assert that the first gets a lock + // notification. + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + SdrView* pView = pWrtShell->GetDrawView(); + aView1.m_bViewLock = false; + pWrtShell->GetView().BeginTextEdit(pObject, pView->GetSdrPageView(), pWrtShell->GetWin()); + CPPUNIT_ASSERT(aView1.m_bViewLock); + + // End text edit in the second view, and assert that the lock is removed in + // the first view. + pWrtShell->EndTextEdit(); + CPPUNIT_ASSERT(!aView1.m_bViewLock); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTextEditViewInvalidations) +{ + // Load a document that has a shape and create two views. + SwXTextDocument* pXTextDocument = createDoc("shape.fodt"); + ViewCallback aView1; + SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + ViewCallback aView2; + + // Begin text edit in the second view. + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + SdrView* pView = pWrtShell->GetDrawView(); + pWrtShell->GetView().BeginTextEdit(pObject, pView->GetSdrPageView(), pWrtShell->GetWin()); + emulateTyping(*pXTextDocument, u"x"); + + // Assert that both views are invalidated when pressing a key while in text edit. + aView1.m_bTilesInvalidated = false; + emulateTyping(*pXTextDocument, u"y"); + + CPPUNIT_ASSERT(aView1.m_bTilesInvalidated); + + pWrtShell->EndTextEdit(); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoInvalidations) +{ + // Load a document and create two views. + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + ViewCallback aView1; + int nView1 = SfxLokHelper::getView(); + SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + ViewCallback aView2; + SfxLokHelper::setView(nView1); + + // Insert a character the end of the document. + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + pWrtShell->EndOfSection(); + emulateTyping(*pXTextDocument, u"c"); + // ProcessEventsToIdle resets the view; set it again + SfxLokHelper::setView(nView1); + SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); + CPPUNIT_ASSERT_EQUAL(OUString("Aaa bbb.c"), pShellCursor->GetPoint()->GetNode().GetTextNode()->GetText()); + + // Undo and assert that both views are invalidated. + Scheduler::ProcessEventsToIdle(); + aView1.m_bTilesInvalidated = false; + aView2.m_bTilesInvalidated = false; + comphelper::dispatchCommand(".uno:Undo", {}); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(aView1.m_bTilesInvalidated); + // Undo was dispatched on the first view, this second view was not invalidated. + CPPUNIT_ASSERT(aView2.m_bTilesInvalidated); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoLimiting) +{ + // Load a document and create two views. + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + SwWrtShell* pWrtShell1 = pXTextDocument->GetDocShell()->GetWrtShell(); + int nView1 = SfxLokHelper::getView(); + int nView2 = SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + + // Insert a character the end of the document in the second view. + SwWrtShell* pWrtShell2 = pXTextDocument->GetDocShell()->GetWrtShell(); + pWrtShell2->EndOfSection(); + emulateTyping(*pXTextDocument, u"c"); + SwShellCursor* pShellCursor = pWrtShell2->getShellCursor(false); + CPPUNIT_ASSERT_EQUAL(OUString("Aaa bbb.c"), pShellCursor->GetPoint()->GetNode().GetTextNode()->GetText()); + + // Assert that the first view can't undo, but the second view can. + CPPUNIT_ASSERT(!pWrtShell1->GetLastUndoInfo(nullptr, nullptr, &pWrtShell1->GetView())); + CPPUNIT_ASSERT(pWrtShell2->GetLastUndoInfo(nullptr, nullptr, &pWrtShell2->GetView())); + + SfxLokHelper::setView(nView1); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); + SfxLokHelper::setView(nView2); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoReordering) +{ + // Create two views and a document of 2 paragraphs. + SwXTextDocument* pXTextDocument = createDoc(); + SwWrtShell* pWrtShell1 = pXTextDocument->GetDocShell()->GetWrtShell(); + int nView1 = SfxLokHelper::getView(); + int nView2 = SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + SwWrtShell* pWrtShell2 = pXTextDocument->GetDocShell()->GetWrtShell(); + pWrtShell2->SplitNode(); + SfxLokHelper::setView(nView1); + pWrtShell1->SttEndDoc(/*bStt=*/true); + SwTextNode* pTextNode1 = pWrtShell1->GetCursor()->GetPointNode().GetTextNode(); + // View 1 types into the first paragraph. + emulateTyping(*pXTextDocument, u"a"); + SfxLokHelper::setView(nView2); + pWrtShell2->SttEndDoc(/*bStt=*/false); + SwTextNode* pTextNode2 = pWrtShell2->GetCursor()->GetPointNode().GetTextNode(); + // View 2 types into the second paragraph. + emulateTyping(*pXTextDocument, u"z"); + CPPUNIT_ASSERT_EQUAL(OUString("a"), pTextNode1->GetText()); + CPPUNIT_ASSERT_EQUAL(OUString("z"), pTextNode2->GetText()); + + // When view 1 presses undo: + SfxLokHelper::setView(nView1); + dispatchCommand(mxComponent, ".uno:Undo", {}); + + // Then make sure view 1's last undo action is invoked, out of order: + // Without the accompanying fix in place, this test would have failed with: + // - Expression: pTextNode1->GetText().isEmpty() + // i.e. the "a" in the first paragraph was not removed. + CPPUNIT_ASSERT(pTextNode1->GetText().isEmpty()); + // Last undo action is not invoked, as it belongs to view 2. + CPPUNIT_ASSERT_EQUAL(OUString("z"), pTextNode2->GetText()); + SfxLokHelper::setView(nView1); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); + SfxLokHelper::setView(nView2); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoReorderingRedo) +{ + // Create two views and a document of 2 paragraphs. + SwXTextDocument* pXTextDocument = createDoc(); + SwWrtShell* pWrtShell1 = pXTextDocument->GetDocShell()->GetWrtShell(); + int nView1 = SfxLokHelper::getView(); + int nView2 = SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + SwWrtShell* pWrtShell2 = pXTextDocument->GetDocShell()->GetWrtShell(); + pWrtShell2->SplitNode(); + SfxLokHelper::setView(nView1); + pWrtShell1->SttEndDoc(/*bStt=*/true); + SwTextNode* pTextNode1 = pWrtShell1->GetCursor()->GetPointNode().GetTextNode(); + // View 1 types into the first paragraph, twice. + emulateTyping(*pXTextDocument, u"f"); + // Go to the start of the paragraph, to avoid grouping. + pWrtShell1->SttEndDoc(/*bStt=*/true); + emulateTyping(*pXTextDocument, u"s"); + SfxLokHelper::setView(nView2); + pWrtShell2->SttEndDoc(/*bStt=*/false); + SwTextNode* pTextNode2 = pWrtShell2->GetCursor()->GetPointNode().GetTextNode(); + // View 2 types into the second paragraph. + emulateTyping(*pXTextDocument, u"z"); + CPPUNIT_ASSERT_EQUAL(OUString("sf"), pTextNode1->GetText()); + CPPUNIT_ASSERT_EQUAL(OUString("z"), pTextNode2->GetText()); + + // When view 1 presses undo, twice: + SfxLokHelper::setView(nView1); + dispatchCommand(mxComponent, ".uno:Undo", {}); + // First just s(econd) is erased: + CPPUNIT_ASSERT_EQUAL(OUString("f"), pTextNode1->GetText()); + dispatchCommand(mxComponent, ".uno:Undo", {}); + + // Then make sure view 1's undo actions are invoked, out of order: + // Without the accompanying fix in place, this test would have failed with: + // - Expression: pTextNode1->GetText().isEmpty() + // i.e. out of order undo was executed only once, not twice. + CPPUNIT_ASSERT(pTextNode1->GetText().isEmpty()); + // The top undo action is not invoked, as it belongs to view 2. + CPPUNIT_ASSERT_EQUAL(OUString("z"), pTextNode2->GetText()); + SfxLokHelper::setView(nView1); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); + SfxLokHelper::setView(nView2); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoReorderingRedo2) +{ + // Create two views. + SwXTextDocument* pXTextDocument = createDoc(); + SwWrtShell* pWrtShell1 = pXTextDocument->GetDocShell()->GetWrtShell(); + int nView1 = SfxLokHelper::getView(); + int nView2 = SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + SwWrtShell* pWrtShell2 = pXTextDocument->GetDocShell()->GetWrtShell(); + + // Type in the first view. + SfxLokHelper::setView(nView1); + pWrtShell1->SttEndDoc(/*bStt=*/true); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'f', 0); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'f', 0); + Scheduler::ProcessEventsToIdle(); + + // Type to the same paragraph in the second view. + SfxLokHelper::setView(nView2); + pWrtShell2->SttEndDoc(/*bStt=*/true); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 's', 0); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 's', 0); + Scheduler::ProcessEventsToIdle(); + + // Delete in the first view and undo. + SfxLokHelper::setView(nView1); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::BACKSPACE); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::BACKSPACE); + Scheduler::ProcessEventsToIdle(); + dispatchCommand(mxComponent, ".uno:Undo", {}); + Scheduler::ProcessEventsToIdle(); + + // Query the undo state, now that a "delete" is on the redo stack and an "insert" belongs to the + // view on the undo stack, so the types are different. + SwUndoId nUndoId(SwUndoId::EMPTY); + // Without the accompanying fix in place, this test would have failed with: + // runtime error: downcast which does not point to an object of type 'const SwUndoInsert' + // note: object is of type 'SwUndoDelete' + // in an UBSan build. + pWrtShell1->GetLastUndoInfo(nullptr, &nUndoId, &pWrtShell1->GetView()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoReorderingMulti) +{ + // Create two views and a document of 2 paragraphs. + SwXTextDocument* pXTextDocument = createDoc(); + SwWrtShell* pWrtShell1 = pXTextDocument->GetDocShell()->GetWrtShell(); + int nView1 = SfxLokHelper::getView(); + int nView2 = SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + SwWrtShell* pWrtShell2 = pXTextDocument->GetDocShell()->GetWrtShell(); + pWrtShell2->SplitNode(); + SfxLokHelper::setView(nView1); + pWrtShell1->SttEndDoc(/*bStt=*/true); + SwTextNode* pTextNode1 = pWrtShell1->GetCursor()->GetPointNode().GetTextNode(); + // View 1 types into the first paragraph. + emulateTyping(*pXTextDocument, u"a"); + SfxLokHelper::setView(nView2); + pWrtShell2->SttEndDoc(/*bStt=*/false); + SwTextNode* pTextNode2 = pWrtShell2->GetCursor()->GetPointNode().GetTextNode(); + // View 2 types into the second paragraph, twice. + emulateTyping(*pXTextDocument, u"x"); + // Go to the start of the paragraph, to avoid grouping. + pWrtShell2->SttPara(); + emulateTyping(*pXTextDocument, u"y"); + CPPUNIT_ASSERT_EQUAL(OUString("a"), pTextNode1->GetText()); + CPPUNIT_ASSERT_EQUAL(OUString("yx"), pTextNode2->GetText()); + + // When view 1 presses undo: + SfxLokHelper::setView(nView1); + dispatchCommand(mxComponent, ".uno:Undo", {}); + + // Then make sure view 1's undo action is invoked, out of order: + // Without the accompanying fix in place, this test would have failed with: + // - Expression: pTextNode1->GetText().isEmpty() + // i.e. out of order undo was not executed, the first paragraph was still "a". + CPPUNIT_ASSERT(pTextNode1->GetText().isEmpty()); + // The top 2 undo actions are not invoked, as they belong to view 2. + CPPUNIT_ASSERT_EQUAL(OUString("yx"), pTextNode2->GetText()); + SfxLokHelper::setView(nView1); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); + SfxLokHelper::setView(nView2); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoShapeLimiting) +{ + // Load a document and create a view. + SwXTextDocument* pXTextDocument = createDoc("shape.fodt"); + SwWrtShell* pWrtShell1 = pXTextDocument->GetDocShell()->GetWrtShell(); + int nView1 = SfxLokHelper::getView(); + int nView2 = SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + SwWrtShell* pWrtShell2 = pXTextDocument->GetDocShell()->GetWrtShell(); + + // Start shape text in the second view. + SdrPage* pPage = pWrtShell2->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + SdrView* pView = pWrtShell2->GetDrawView(); + pWrtShell2->GetView().BeginTextEdit(pObject, pView->GetSdrPageView(), pWrtShell2->GetWin()); + emulateTyping(*pXTextDocument, u"x"); + pWrtShell2->EndTextEdit(); + + // Assert that the first view can't and the second view can undo the insertion. + SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc(); + sw::UndoManager& rUndoManager = pDoc->GetUndoManager(); + rUndoManager.SetView(&pWrtShell1->GetView()); + // This was 1: first view could undo the change of the second view. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), rUndoManager.GetUndoActionCount()); + rUndoManager.SetView(&pWrtShell2->GetView()); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rUndoManager.GetUndoActionCount()); + + rUndoManager.SetView(nullptr); + + SfxLokHelper::setView(nView1); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); + SfxLokHelper::setView(nView2); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoDispatch) +{ + // Load a document and create two views. + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + int nView1 = SfxLokHelper::getView(); + SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + int nView2 = SfxLokHelper::getView(); + + // Insert a character in the first view. + SfxLokHelper::setView(nView1); + emulateTyping(*pXTextDocument, u"c"); + + // Click before the first word in the second view. + SfxLokHelper::setView(nView2); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); + Point aStart = pShellCursor->GetSttPos(); + aStart.setX(aStart.getX() - 1000); + pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aStart.getX(), aStart.getY(), 1, MOUSE_LEFT, 0); + pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, aStart.getX(), aStart.getY(), 1, MOUSE_LEFT, 0); + Scheduler::ProcessEventsToIdle(); + uno::Reference<frame::XDesktop2> xDesktop = frame::Desktop::create(comphelper::getProcessComponentContext()); + uno::Reference<frame::XFrame> xFrame2 = xDesktop->getActiveFrame(); + + // Now switch back to the first view, and make sure that the active frame is updated. + SfxLokHelper::setView(nView1); + uno::Reference<frame::XFrame> xFrame1 = xDesktop->getActiveFrame(); + // This failed: setView() did not update the active frame. + CPPUNIT_ASSERT(xFrame1 != xFrame2); + + SfxLokHelper::setView(nView1); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); + SfxLokHelper::setView(nView2); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoRepairDispatch) +{ + // Load a document and create two views. + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + int nView1 = SfxLokHelper::getView(); + SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + int nView2 = SfxLokHelper::getView(); + + // Insert a character in the first view. + SfxLokHelper::setView(nView1); + emulateTyping(*pXTextDocument, u"c"); + + // Assert that by default the second view can't undo the action. + SfxLokHelper::setView(nView2); + SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc(); + sw::UndoManager& rUndoManager = pDoc->GetUndoManager(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rUndoManager.GetUndoActionCount()); + comphelper::dispatchCommand(".uno:Undo", {}); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rUndoManager.GetUndoActionCount()); + + // But the same is allowed in repair mode. + SfxLokHelper::setView(nView2); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), rUndoManager.GetUndoActionCount()); + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"Repair", uno::Any(true)} + })); + comphelper::dispatchCommand(".uno:Undo", aPropertyValues); + Scheduler::ProcessEventsToIdle(); + // This was 1: repair mode couldn't undo the action, either. + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), rUndoManager.GetUndoActionCount()); + + SfxLokHelper::setView(nView1); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); + SfxLokHelper::setView(nView2); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testShapeTextUndoShells) +{ + // Load a document and create a view. + SwXTextDocument* pXTextDocument = createDoc("shape.fodt"); + sal_Int32 nView1 = SfxLokHelper::getView(); + + // Begin text edit. + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + SdrView* pView = pWrtShell->GetDrawView(); + pWrtShell->GetView().BeginTextEdit(pObject, pView->GetSdrPageView(), pWrtShell->GetWin()); + emulateTyping(*pXTextDocument, u"x"); + pWrtShell->EndTextEdit(); + + // Make sure that the undo item remembers who created it. + SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc(); + sw::UndoManager& rUndoManager = pDoc->GetUndoManager(); + CPPUNIT_ASSERT_EQUAL(size_t(1), rUndoManager.GetUndoActionCount()); + CPPUNIT_ASSERT_EQUAL(OUString("Edit text of Shape 'Shape1'"), rUndoManager.GetUndoActionComment(0)); + + // This was -1: the view shell id for the undo action wasn't known. + CPPUNIT_ASSERT_EQUAL(ViewShellId(nView1), rUndoManager.GetUndoAction()->GetViewShellId()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testShapeTextUndoGroupShells) +{ + // Load a document and create a view. + SwXTextDocument* pXTextDocument = createDoc("shape.fodt"); + ViewCallback aView1; + sal_Int32 nView1 = SfxLokHelper::getView(); + + // Begin text edit. + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + SdrView* pView = pWrtShell->GetDrawView(); + pWrtShell->GetView().BeginTextEdit(pObject, pView->GetSdrPageView(), pWrtShell->GetWin()); + emulateTyping(*pXTextDocument, u"x"); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::BACKSPACE); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::BACKSPACE); + Scheduler::ProcessEventsToIdle(); + + // Make sure that the undo item remembers who created it. + SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc(); + sw::UndoManager& rUndoManager = pDoc->GetUndoManager(); + CPPUNIT_ASSERT_EQUAL(size_t(0), rUndoManager.GetUndoActionCount()); + + pWrtShell->EndTextEdit(); + pWrtShell->GetView().BeginTextEdit(pObject, pView->GetSdrPageView(), pWrtShell->GetWin()); + + CPPUNIT_ASSERT_EQUAL(size_t(1), rUndoManager.GetUndoActionCount()); + CPPUNIT_ASSERT_EQUAL(OUString("Edit text of Shape 'Shape1'"), rUndoManager.GetUndoActionComment(0)); + + // This was -1: the view shell id for the (top) undo list action wasn't known. + CPPUNIT_ASSERT_EQUAL(ViewShellId(nView1), rUndoManager.GetUndoAction()->GetViewShellId()); + + // Create an editeng text selection in the first view. + EditView& rEditView = pView->GetTextEditOutlinerView()->GetEditView(); + emulateTyping(*pXTextDocument, u"x"); + // 0th para, 0th char -> 0th para, 1st char. + ESelection aWordSelection(0, 0, 0, 1); + rEditView.SetSelection(aWordSelection); + + // Create a second view, and make sure that the new view sees the same + // cursor position as the old one. + SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering({}); + ViewCallback aView2; + // Difference was 935 twips, the new view didn't see the editeng cursor of + // the old one. The new difference should be <1px, but here we deal with twips. + CPPUNIT_ASSERT(std::abs(aView1.m_aOwnCursor.Top() - aView2.m_aViewCursor.Top()) < 10); + // This was false, editeng text selection of the first view wasn't noticed + // by the second view. + CPPUNIT_ASSERT(aView2.m_bViewSelectionSet); + // This was false, the new view wasn't aware of the shape text lock created + // by the old view. + CPPUNIT_ASSERT(aView2.m_bViewLock); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTrackChanges) +{ + // Load a document. + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + + // Turn on track changes, type "zzz" at the end, and move to the start. + uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RecordChanges", uno::Any(true)); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + ViewCallback aView(pWrtShell->GetSfxViewShell()); + pWrtShell->EndOfSection(); + pWrtShell->Insert("zzz"); + pWrtShell->StartOfSection(); + + // Get the redline just created + const SwRedlineTable& rTable = pWrtShell->GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(1), rTable.size()); + SwRangeRedline* pRedline = rTable[0]; + + // Reject the change by id, while the cursor does not cover the tracked change. + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"RejectTrackedChange", uno::Any(o3tl::narrowing<sal_uInt16>(pRedline->GetId()))} + })); + comphelper::dispatchCommand(".uno:RejectTrackedChange", aPropertyValues); + Scheduler::ProcessEventsToIdle(); + + // Assert that the reject was performed. + SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); + // This was 'Aaa bbb.zzz', the change wasn't rejected. + CPPUNIT_ASSERT_EQUAL(OUString("Aaa bbb."), pShellCursor->GetPoint()->GetNode().GetTextNode()->GetText()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTrackChangesCallback) +{ + // Load a document. + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + + // Turn on track changes and type "x". + uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RecordChanges", uno::Any(true)); + m_nRedlineTableSizeChanged = 0; + pWrtShell->Insert("x"); + + // Assert that we get exactly one notification about the redline insert. + // This was 0, as LOK_CALLBACK_REDLINE_TABLE_SIZE_CHANGED wasn't sent. + CPPUNIT_ASSERT_EQUAL(1, m_nRedlineTableSizeChanged); + + CPPUNIT_ASSERT_EQUAL(-1, m_nTrackedChangeIndex); + pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); + SfxItemSet aSet(pWrtShell->GetDoc()->GetAttrPool(), svl::Items<FN_REDLINE_ACCEPT_DIRECT, FN_REDLINE_ACCEPT_DIRECT>); + SfxVoidItem aItem(FN_REDLINE_ACCEPT_DIRECT); + aSet.Put(aItem); + pWrtShell->GetView().GetState(aSet); + // This failed, LOK_CALLBACK_STATE_CHANGED wasn't sent. + CPPUNIT_ASSERT_EQUAL(0, m_nTrackedChangeIndex); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testRedlineUpdateCallback) +{ + // Load a document. + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + + // Turn on track changes, type "xx" and delete the second one. + uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RecordChanges", uno::Any(true)); + pWrtShell->Insert("xx"); + m_nRedlineTableEntryModified = 0; + pWrtShell->DelLeft(); + + // Assert that we get exactly one notification about the redline update. + // This was 0, as LOK_CALLBACK_REDLINE_TABLE_ENTRY_MODIFIED wasn't sent. + CPPUNIT_ASSERT_EQUAL(1, m_nRedlineTableEntryModified); + + // Turn off the change tracking mode, make some modification to left of the + // redline so that its position changes + xPropertySet->setPropertyValue("RecordChanges", uno::Any(false)); + pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); + pWrtShell->Insert("This text is left of the redline"); + + // Position of the redline has changed => Modify callback + CPPUNIT_ASSERT_EQUAL(2, m_nRedlineTableEntryModified); + + pWrtShell->DelLeft(); + // Deletion also emits Modify callback + CPPUNIT_ASSERT_EQUAL(3, m_nRedlineTableEntryModified); + + // Make changes to the right of the redline => no position change in redline + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 100/*Go enough right */, /*bBasicCall=*/false); + pWrtShell->Insert("This text is right of the redline"); + + // No Modify callbacks + CPPUNIT_ASSERT_EQUAL(3, m_nRedlineTableEntryModified); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testGetViewRenderState) +{ + SwXTextDocument* pXTextDocument = createDoc(); + int nFirstViewId = SfxLokHelper::getView(); + ViewCallback aView1; + { + SwViewOption aViewOptions; + aViewOptions.SetViewMetaChars(true); + aViewOptions.SetOnlineSpell(true); + pXTextDocument->GetDocShell()->GetWrtShell()->ApplyViewOptions(aViewOptions); + } + CPPUNIT_ASSERT_EQUAL("PS;Default"_ostr, pXTextDocument->getViewRenderState()); + + // Create a second view + SfxLokHelper::createView(); + int nSecondViewId = SfxLokHelper::getView(); + ViewCallback aView2; + { + // Give the second view different options + SwViewOption aViewOptions; + aViewOptions.SetViewMetaChars(false); + aViewOptions.SetOnlineSpell(true); + pXTextDocument->GetDocShell()->GetWrtShell()->ApplyViewOptions(aViewOptions); + } + CPPUNIT_ASSERT_EQUAL("S;Default"_ostr, pXTextDocument->getViewRenderState()); + + // Switch back to the first view, and check that the options string is the same + SfxLokHelper::setView(nFirstViewId); + CPPUNIT_ASSERT_EQUAL("PS;Default"_ostr, pXTextDocument->getViewRenderState()); + + // Switch back to the second view, and change to dark mode + SfxLokHelper::setView(nSecondViewId); + { + SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc(); + SwView* pView = pDoc->GetDocShell()->GetView(); + uno::Reference<frame::XFrame> xFrame = pView->GetViewFrame().GetFrame().GetFrameInterface(); + uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence( + { + { "NewTheme", uno::Any(OUString("Dark")) }, + } + ); + comphelper::dispatchCommand(".uno:ChangeTheme", xFrame, aPropertyValues); + } + CPPUNIT_ASSERT_EQUAL("S;Dark"_ostr, pXTextDocument->getViewRenderState()); + // Switch back to the first view, and check that the options string is the same + SfxLokHelper::setView(nFirstViewId); + CPPUNIT_ASSERT_EQUAL("PS;Default"_ostr, pXTextDocument->getViewRenderState()); +} + +// Helper function to get a tile to a bitmap and check the pixel color +static void assertTilePixelColor(SwXTextDocument* pXTextDocument, int nPixelX, int nPixelY, Color aColor) +{ + size_t nCanvasSize = 1024; + size_t nTileSize = 256; + std::vector<unsigned char> aPixmap(nCanvasSize * nCanvasSize * 4, 0); + ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA); + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasSize, nCanvasSize), + Fraction(1.0), Point(), aPixmap.data()); + pXTextDocument->paintTile(*pDevice, nCanvasSize, nCanvasSize, 0, 0, 15360, 7680); + pDevice->EnableMapMode(false); + Bitmap aBitmap = pDevice->GetBitmap(Point(0, 0), Size(nTileSize, nTileSize)); + BitmapScopedReadAccess pAccess(aBitmap); + Color aActualColor(pAccess->GetPixel(nPixelX, nPixelY)); + CPPUNIT_ASSERT_EQUAL(aColor, aActualColor); +} + +// Test that changing the theme in one view doesn't change it in the other view +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testThemeViewSeparation) +{ + Color aDarkColor(0x1c, 0x1c, 0x1c); + // Add a minimal dark scheme + { + svtools::EditableColorConfig aColorConfig; + svtools::ColorConfigValue aValue; + aValue.bIsVisible = true; + aValue.nColor = aDarkColor; + aColorConfig.SetColorValue(svtools::DOCCOLOR, aValue); + aColorConfig.AddScheme(u"Dark"_ustr); + } + // Add a minimal light scheme + { + svtools::EditableColorConfig aColorConfig; + svtools::ColorConfigValue aValue; + aValue.bIsVisible = true; + aValue.nColor = COL_WHITE; + aColorConfig.SetColorValue(svtools::DOCCOLOR, aValue); + aColorConfig.AddScheme(u"Light"_ustr); + } + SwXTextDocument* pXTextDocument = createDoc(); + int nFirstViewId = SfxLokHelper::getView(); + ViewCallback aView1; + // Set first view to light scheme + { + SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc(); + SwView* pView = pDoc->GetDocShell()->GetView(); + uno::Reference<frame::XFrame> xFrame = pView->GetViewFrame().GetFrame().GetFrameInterface(); + uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence( + { + { "NewTheme", uno::Any(OUString("Light")) }, + } + ); + comphelper::dispatchCommand(".uno:ChangeTheme", xFrame, aPropertyValues); + } + // First view is in light scheme + assertTilePixelColor(pXTextDocument, 255, 255, COL_WHITE); + // Create second view + SfxLokHelper::createView(); + int nSecondViewId = SfxLokHelper::getView(); + ViewCallback aView2; + // Set second view to dark scheme + { + SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc(); + SwView* pView = pDoc->GetDocShell()->GetView(); + uno::Reference<frame::XFrame> xFrame = pView->GetViewFrame().GetFrame().GetFrameInterface(); + uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence( + { + { "NewTheme", uno::Any(OUString("Dark")) }, + } + ); + comphelper::dispatchCommand(".uno:ChangeTheme", xFrame, aPropertyValues); + } + assertTilePixelColor(pXTextDocument, 255, 255, aDarkColor); + // First view still in light scheme + SfxLokHelper::setView(nFirstViewId); + assertTilePixelColor(pXTextDocument, 255, 255, COL_WHITE); + // Second view still in dark scheme + SfxLokHelper::setView(nSecondViewId); + assertTilePixelColor(pXTextDocument, 255, 255, aDarkColor); + // Switch second view back to light scheme + { + SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc(); + SwView* pView = pDoc->GetDocShell()->GetView(); + uno::Reference<frame::XFrame> xFrame = pView->GetViewFrame().GetFrame().GetFrameInterface(); + uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence( + { + { "NewTheme", uno::Any(OUString("Light")) }, + } + ); + comphelper::dispatchCommand(".uno:ChangeTheme", xFrame, aPropertyValues); + } + // Now in light scheme + assertTilePixelColor(pXTextDocument, 255, 255, COL_WHITE); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSetViewGraphicSelection) +{ + // Load a document. + SwXTextDocument* pXTextDocument = createDoc("frame.odt"); + int nView1 = SfxLokHelper::getView(); + ViewCallback aView1; + // Create a second view, and switch back to the first view. + SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering({}); + SfxLokHelper::setView(nView1); + + // Mark the textframe in the first view. + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + SdrView* pView = pWrtShell->GetDrawView(); + pView->MarkObj(pObject, pView->GetSdrPageView()); + CPPUNIT_ASSERT(aView1.m_bGraphicSelection); + + // Now start to switch to the second view (part of setView()). + pWrtShell->ShellLoseFocus(); + // This failed, mark handles were hidden in the first view. + CPPUNIT_ASSERT(!pView->areMarkHandlesHidden()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testCreateViewGraphicSelection) +{ + // Load a document. + SwXTextDocument* pXTextDocument = createDoc("frame.odt"); + ViewCallback aView1; + + // Mark the textframe in the first view. + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + SdrView* pView = pWrtShell->GetDrawView(); + aView1.m_bGraphicSelection = true; + pView->MarkObj(pObject, pView->GetSdrPageView()); + pWrtShell->HideCursor(); + CPPUNIT_ASSERT(aView1.m_bGraphicSelection); + + // Create a second view. + SfxLokHelper::createView(); + // This was false, creating a second view cleared the selection of the + // first one. + CPPUNIT_ASSERT(aView1.m_bGraphicSelection); + + // Make sure that the hidden text cursor isn't visible in the second view, either. + ViewCallback aView2(SfxViewShell::Current(), + [](ViewCallback& rView) { rView.m_bViewCursorVisible = true; }); + // This was true, the second view didn't get the visibility of the text + // cursor of the first view. + CPPUNIT_ASSERT(!aView2.m_bViewCursorVisible); + // This was false, the second view didn't get the graphic selection of the + // first view. + CPPUNIT_ASSERT(aView2.m_bGraphicViewSelection); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testCreateViewTextSelection) +{ + // Load a document. + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + ViewCallback aView1; + + // Create a text selection: + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + // Move the cursor into the second word. + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 5, /*bBasicCall=*/false); + // Create a selection on the word. + pWrtShell->SelWrd(); + SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); + // Did we indeed manage to select the second word? + CPPUNIT_ASSERT_EQUAL(OUString("bbb"), pShellCursor->GetText()); + + // Create a second view. + SfxLokHelper::createView(); + + // Make sure that the text selection is visible in the second view. + ViewCallback aView2; + // This failed, the second view didn't get the text selection of the first view. + CPPUNIT_ASSERT(!aView2.m_aViewSelection.isEmpty()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testRedlineColors) +{ + // Load a document. + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + + // Turn on track changes, type "zzz" at the end. + uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RecordChanges", uno::Any(true)); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + pWrtShell->EndOfSection(); + pWrtShell->Insert("zzz"); + + // Assert that info about exactly one author is returned. + tools::JsonWriter aJsonWriter; + pXTextDocument->getTrackedChangeAuthors(aJsonWriter); + std::stringstream aStream((std::string(aJsonWriter.finishAndGetAsOString()))); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), aTree.get_child("authors").size()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testCommentEndTextEdit) +{ + // Create a document, type a character and remember the cursor position. + SwXTextDocument* pXTextDocument = createDoc(); + ViewCallback aView1; + emulateTyping(*pXTextDocument, u"x"); + tools::Rectangle aBodyCursor = aView1.m_aOwnCursor; + + // Create a comment and type a character there as well. + const int nCtrlAltC = KEY_MOD1 + KEY_MOD2 + 512 + 'c' - 'a'; + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'c', nCtrlAltC); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 'c', nCtrlAltC); + emulateTyping(*pXTextDocument, u"x"); + // End comment text edit by clicking in the body text area, and assert that + // no unexpected cursor callbacks are emitted at origin (top left corner of + // the document). + aView1.m_bOwnCursorAtOrigin = false; + pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aBodyCursor.Left(), aBodyCursor.Top(), 1, MOUSE_LEFT, 0); + pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, aBodyCursor.Left(), aBodyCursor.Top(), 1, MOUSE_LEFT, 0); + Scheduler::ProcessEventsToIdle(); + // This failed, the cursor was at 0, 0 at some point during end text edit + // of the comment. + CPPUNIT_ASSERT(!aView1.m_bOwnCursorAtOrigin); + + // Hit enter and expect invalidation. + Scheduler::ProcessEventsToIdle(); + aView1.m_bTilesInvalidated = false; + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RETURN); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(aView1.m_bTilesInvalidated); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testCommentInsert) +{ + // Load a document with an as-char image in it. + comphelper::LibreOfficeKit::setTiledAnnotations(false); + SwXTextDocument* pXTextDocument = createDoc("image-comment.odt"); + SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc(); + SwView* pView = pDoc->GetDocShell()->GetView(); + + selectShape(1); + + // Add a comment. + uno::Reference<frame::XFrame> xFrame = pView->GetViewFrame().GetFrame().GetFrameInterface(); + uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence( + { + {"Text", uno::Any(OUString("some text"))}, + {"Author", uno::Any(OUString("me"))}, + }); + ViewCallback aView; + comphelper::dispatchCommand(".uno:InsertAnnotation", xFrame, aPropertyValues); + Scheduler::ProcessEventsToIdle(); + OString aAnchorPos(aView.m_aComment.get_child("anchorPos").get_value<std::string>()); + // Without the accompanying fix in place, this test would have failed with + // - Expected: 1418, 1418, 0, 0 + // - Actual : 1418, 1418, 1024, 1024 + // i.e. the anchor position was a non-empty rectangle. + CPPUNIT_ASSERT_EQUAL("1418, 1418, 0, 0"_ostr, aAnchorPos); + comphelper::LibreOfficeKit::setTiledAnnotations(true); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testCursorPosition) +{ + // Load a document and register a callback, should get an own cursor. + SwXTextDocument* pXTextDocument = createDoc(); + ViewCallback aView1; + + // Create a second view, so the first view gets a collaborative cursor. + SfxLokHelper::createView(); + pXTextDocument->initializeForTiledRendering({}); + ViewCallback aView2; + + // Make sure the two are exactly the same. + // This failed, own cursor was at '1418, 1418', collaborative cursor was at + // '1425, 1425', due to pixel alignment. + CPPUNIT_ASSERT_EQUAL(aView1.m_aOwnCursor.toString(), aView1.m_aViewCursor.toString()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testPaintCallbacks) +{ + // Test that paintTile() never results in callbacks, which can cause a + // paint <-> invalidate loop. + + // Load a document and register a callback for the first view. + SwXTextDocument* pXTextDocument = createDoc(); + ViewCallback aView1; + + // Create a second view and paint a tile on that second view. + SfxLokHelper::createView(); + int nCanvasWidth = 256; + int nCanvasHeight = 256; + std::vector<unsigned char> aBuffer(nCanvasWidth * nCanvasHeight * 4); + ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA); + pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), Fraction(1.0), Point(), aBuffer.data()); + // Make sure that painting a tile in the second view doesn't invoke + // callbacks on the first view. + aView1.m_bCalled = false; + pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, /*nTilePosY=*/0, /*nTileWidth=*/3840, /*nTileHeight=*/3840); + CPPUNIT_ASSERT(!aView1.m_bCalled); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testUndoRepairResult) +{ + // Load a document and create two views. + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + int nView1 = SfxLokHelper::getView(); + SfxLokHelper::createView(); + rtl::Reference<TestResultListener> pResult2 = new TestResultListener(); + pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + int nView2 = SfxLokHelper::getView(); + + // Insert a character in the second view. + SfxLokHelper::setView(nView2); + emulateTyping(*pXTextDocument, u"b"); + + // Insert a character in the first view. + SfxLokHelper::setView(nView1); + emulateTyping(*pXTextDocument, u"a"); + + // Assert that by default the second view can't undo the action. + SfxLokHelper::setView(nView2); + comphelper::dispatchCommand(".uno:Undo", {}, pResult2); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(SID_REPAIRPACKAGE), pResult2->m_nDocRepair); + + SfxLokHelper::setView(nView1); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); + SfxLokHelper::setView(nView2); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testRedoRepairResult) +{ + // Load a document and create two views. + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + int nView1 = SfxLokHelper::getView(); + SfxLokHelper::createView(); + rtl::Reference<TestResultListener> pResult2 = new TestResultListener(); + pXTextDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); + int nView2 = SfxLokHelper::getView(); + + // Insert a character in the second view. + SfxLokHelper::setView(nView2); + emulateTyping(*pXTextDocument, u"b"); + + // Insert a character in the first view. + SfxLokHelper::setView(nView1); + emulateTyping(*pXTextDocument, u"a"); + + comphelper::dispatchCommand(".uno:Undo", {}, pResult2); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(0), pResult2->m_nDocRepair); + + // Assert that by default the second view can't redo the action. + SfxLokHelper::setView(nView2); + comphelper::dispatchCommand(".uno:Redo", {}, pResult2); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(SID_REPAIRPACKAGE), pResult2->m_nDocRepair); + + SfxLokHelper::setView(nView1); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); + SfxLokHelper::setView(nView2); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); +} + +namespace { + + void checkUndoRepairStates(SwXTextDocument* pXTextDocument, SwView* pView1, SwView* pView2) + { + SfxItemSet aItemSet1(pXTextDocument->GetDocShell()->GetDoc()->GetAttrPool(), svl::Items<SID_UNDO, SID_UNDO>); + SfxItemSet aItemSet2(pXTextDocument->GetDocShell()->GetDoc()->GetAttrPool(), svl::Items<SID_UNDO, SID_UNDO>); + // first view, undo enabled + pView1->GetState(aItemSet1); + CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aItemSet1.GetItemState(SID_UNDO)); + const SfxUInt32Item *pUnsetItem = dynamic_cast<const SfxUInt32Item*>(aItemSet1.GetItem(SID_UNDO)); + CPPUNIT_ASSERT(!pUnsetItem); + // second view, undo conflict + pView2->GetState(aItemSet2); + CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aItemSet2.GetItemState(SID_UNDO)); + const SfxUInt32Item* pUInt32Item = dynamic_cast<const SfxUInt32Item*>(aItemSet2.GetItem(SID_UNDO)); + CPPUNIT_ASSERT(pUInt32Item); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(SID_REPAIRPACKAGE), pUInt32Item->GetValue()); + }; + +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testDisableUndoRepair) +{ + // Create two views. + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + ViewCallback aView1; + SwView* pView1 = dynamic_cast<SwView*>(SfxViewShell::Current()); + CPPUNIT_ASSERT(pView1); + int nView1 = SfxLokHelper::getView(); + SfxLokHelper::createView(); + ViewCallback aView2; + SwView* pView2 = dynamic_cast<SwView*>(SfxViewShell::Current()); + CPPUNIT_ASSERT(pView2); + int nView2 = SfxLokHelper::getView(); + + { + SfxItemSet aItemSet1(pXTextDocument->GetDocShell()->GetDoc()->GetAttrPool(), svl::Items<SID_UNDO, SID_UNDO>); + SfxItemSet aItemSet2(pXTextDocument->GetDocShell()->GetDoc()->GetAttrPool(), svl::Items<SID_UNDO, SID_UNDO>); + pView1->GetState(aItemSet1); + CPPUNIT_ASSERT_EQUAL(SfxItemState::DISABLED, aItemSet1.GetItemState(SID_UNDO)); + pView2->GetState(aItemSet2); + CPPUNIT_ASSERT_EQUAL(SfxItemState::DISABLED, aItemSet2.GetItemState(SID_UNDO)); + } + + // Insert a character in the first view. + SfxLokHelper::setView(nView1); + emulateTyping(*pXTextDocument, u"k"); + checkUndoRepairStates(pXTextDocument, pView1, pView2); + + // Insert a character in the second view. + SfxLokHelper::setView(nView2); + emulateTyping(*pXTextDocument, u"u"); + { + SfxItemSet aItemSet1(pXTextDocument->GetDocShell()->GetDoc()->GetAttrPool(), svl::Items<SID_UNDO, SID_UNDO>); + SfxItemSet aItemSet2(pXTextDocument->GetDocShell()->GetDoc()->GetAttrPool(), svl::Items<SID_UNDO, SID_UNDO>); + // second view, undo enabled + pView2->GetState(aItemSet2); + CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aItemSet2.GetItemState(SID_UNDO)); + const SfxUInt32Item *pUnsetItem = dynamic_cast<const SfxUInt32Item*>(aItemSet2.GetItem(SID_UNDO)); + CPPUNIT_ASSERT(!pUnsetItem); + // first view, undo conflict + pView1->GetState(aItemSet1); + CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aItemSet1.GetItemState(SID_UNDO)); + const SfxUInt32Item* pUInt32Item = dynamic_cast<const SfxUInt32Item*>(aItemSet1.GetItem(SID_UNDO)); + CPPUNIT_ASSERT(pUInt32Item); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt32>(SID_REPAIRPACKAGE), pUInt32Item->GetValue()); + } + + // Insert a character in the first view. + SfxLokHelper::setView(nView1); + emulateTyping(*pXTextDocument, u"l"); + checkUndoRepairStates(pXTextDocument, pView1, pView2); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testAllTrackedChanges) +{ + // Load a document. + createDoc("dummy.fodt"); + + uno::Reference<beans::XPropertySet> xPropSet(mxComponent, uno::UNO_QUERY); + xPropSet->setPropertyValue("RecordChanges", uno::Any(true)); + + // view #1 + SwView* pView1 = dynamic_cast<SwView*>(SfxViewShell::Current()); + CPPUNIT_ASSERT(pView1); + SwWrtShell* pWrtShell1 = pView1->GetWrtShellPtr(); + + // view #2 + int nView1 = SfxLokHelper::getView(); + int nView2 = SfxLokHelper::createView(); + SwView* pView2 = dynamic_cast<SwView*>(SfxViewShell::Current()); + CPPUNIT_ASSERT(pView2); + CPPUNIT_ASSERT(pView1 != pView2); + SwWrtShell* pWrtShell2 = pView2->GetWrtShellPtr(); + // Insert text and reject all + { + pWrtShell1->StartOfSection(); + pWrtShell1->Insert("hxx"); + + pWrtShell2->EndOfSection(); + pWrtShell2->Insert("cxx"); + } + + // Get the redline + const SwRedlineTable& rTable = pWrtShell2->GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(2), rTable.size()); + { + SfxVoidItem aItem(FN_REDLINE_REJECT_ALL); + pView1->GetViewFrame().GetDispatcher()->ExecuteList(FN_REDLINE_REJECT_ALL, + SfxCallMode::SYNCHRON, { &aItem }); + } + + // The reject all was performed. + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(0), rTable.size()); + { + SwShellCursor* pShellCursor = pWrtShell1->getShellCursor(false); + CPPUNIT_ASSERT_EQUAL(OUString("Aaa bbb."), pShellCursor->GetPoint()->GetNode().GetTextNode()->GetText()); + } + + // Insert text and accept all + { + pWrtShell1->StartOfSection(); + pWrtShell1->Insert("hyy"); + + pWrtShell2->EndOfSection(); + pWrtShell2->Insert("cyy"); + } + + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(2), rTable.size()); + { + SfxVoidItem aItem(FN_REDLINE_ACCEPT_ALL); + pView1->GetViewFrame().GetDispatcher()->ExecuteList(FN_REDLINE_ACCEPT_ALL, + SfxCallMode::SYNCHRON, { &aItem }); + } + + // The accept all was performed + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(0), rTable.size()); + { + SwShellCursor* pShellCursor = pWrtShell2->getShellCursor(false); + CPPUNIT_ASSERT_EQUAL(OUString("hyyAaa bbb.cyy"), pShellCursor->GetPoint()->GetNode().GetTextNode()->GetText()); + } + + SfxLokHelper::setView(nView1); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); + SfxLokHelper::setView(nView2); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testDocumentRepair) +{ + // Create two views. + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + // view #1 + SfxViewShell* pView1 = SfxViewShell::Current(); + + // view #2 + int nView1 = SfxLokHelper::getView(); + SfxLokHelper::createView(); + SfxViewShell* pView2 = SfxViewShell::Current(); + int nView2 = SfxLokHelper::getView(); + CPPUNIT_ASSERT(pView1 != pView2); + { + std::unique_ptr<SfxBoolItem> pItem1; + std::unique_ptr<SfxBoolItem> pItem2; + pView1->GetViewFrame().GetBindings().QueryState(SID_DOC_REPAIR, pItem1); + pView2->GetViewFrame().GetBindings().QueryState(SID_DOC_REPAIR, pItem2); + CPPUNIT_ASSERT(pItem1); + CPPUNIT_ASSERT(pItem2); + CPPUNIT_ASSERT_EQUAL(false, pItem1->GetValue()); + CPPUNIT_ASSERT_EQUAL(false, pItem2->GetValue()); + } + + // Insert a character in the second view. + SfxLokHelper::setView(nView2); + emulateTyping(*pXTextDocument, u"u"); + { + std::unique_ptr<SfxBoolItem> pItem1; + std::unique_ptr<SfxBoolItem> pItem2; + pView1->GetViewFrame().GetBindings().QueryState(SID_DOC_REPAIR, pItem1); + pView2->GetViewFrame().GetBindings().QueryState(SID_DOC_REPAIR, pItem2); + CPPUNIT_ASSERT(pItem1); + CPPUNIT_ASSERT(pItem2); + CPPUNIT_ASSERT_EQUAL(true, pItem1->GetValue()); + CPPUNIT_ASSERT_EQUAL(true, pItem2->GetValue()); + } + + SfxLokHelper::setView(nView1); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); + SfxLokHelper::setView(nView2); + SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr); +} + +namespace { + + void checkPageHeaderOrFooter(const SfxViewShell* pViewShell, TypedWhichId<SfxStringListItem> nWhich, bool bValue) + { + uno::Sequence<OUString> aSeq; + SfxPoolItemHolder aResult; + pViewShell->GetDispatcher()->QueryState(nWhich, aResult); + const SfxStringListItem* pListItem(static_cast<const SfxStringListItem*>(aResult.getItem())); + CPPUNIT_ASSERT(pListItem); + pListItem->GetStringList(aSeq); + if (bValue) + { + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aSeq.getLength()); + CPPUNIT_ASSERT_EQUAL(OUString("Default Page Style"), aSeq[0]); + } + else + CPPUNIT_ASSERT(!aSeq.hasElements()); + }; + +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testPageHeader) +{ + createDoc("dummy.fodt"); + SfxViewShell* pViewShell = SfxViewShell::Current(); + // Check Page Header State + checkPageHeaderOrFooter(pViewShell, FN_INSERT_PAGEHEADER, false); + // Insert Page Header + { + SfxStringItem aStyle(FN_INSERT_PAGEHEADER, "Default Page Style"); + SfxBoolItem aItem(FN_PARAM_1, true); + pViewShell->GetDispatcher()->ExecuteList(FN_INSERT_PAGEHEADER, SfxCallMode::API | SfxCallMode::SYNCHRON, {&aStyle, &aItem}); + } + // Check Page Header State + checkPageHeaderOrFooter(pViewShell, FN_INSERT_PAGEHEADER, true); + + // Remove Page Header + { + SfxStringItem aStyle(FN_INSERT_PAGEHEADER, "Default Page Style"); + SfxBoolItem aItem(FN_PARAM_1, false); + pViewShell->GetDispatcher()->ExecuteList(FN_INSERT_PAGEHEADER, SfxCallMode::API | SfxCallMode::SYNCHRON, {&aStyle, &aItem}); + } + // Check Page Header State + checkPageHeaderOrFooter(pViewShell, FN_INSERT_PAGEHEADER, false); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testPageFooter) +{ + createDoc("dummy.fodt"); + SfxViewShell* pViewShell = SfxViewShell::Current(); + // Check Page Footer State + checkPageHeaderOrFooter(pViewShell, FN_INSERT_PAGEFOOTER, false); + // Insert Page Footer + { + SfxStringItem aPageStyle(FN_INSERT_PAGEFOOTER, "Default Page Style"); + SfxBoolItem aItem(FN_PARAM_1, true); + pViewShell->GetDispatcher()->ExecuteList(FN_INSERT_PAGEFOOTER, SfxCallMode::API | SfxCallMode::SYNCHRON, {&aPageStyle, &aItem}); + } + // Check Page Footer State + checkPageHeaderOrFooter(pViewShell, FN_INSERT_PAGEFOOTER, true); + + // Remove Page Footer + { + SfxStringItem aPageStyle(FN_INSERT_PAGEFOOTER, "Default Page Style"); + SfxBoolItem aItem(FN_PARAM_1, false); + pViewShell->GetDispatcher()->ExecuteList(FN_INSERT_PAGEFOOTER, SfxCallMode::API | SfxCallMode::SYNCHRON, {&aPageStyle, &aItem}); + } + // Check Footer State + checkPageHeaderOrFooter(pViewShell, FN_INSERT_PAGEFOOTER, false); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTdf115088) +{ + // We have three lines in the test document and we try to copy the second and third line + // To the beginning of the document + SwXTextDocument* pXTextDocument = createDoc("tdf115088.odt"); + + // Select and copy second and third line + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_HOME | KEY_MOD1); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_HOME | KEY_MOD1); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN | KEY_SHIFT); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN | KEY_SHIFT); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT | KEY_SHIFT); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT | KEY_SHIFT); + Scheduler::ProcessEventsToIdle(); + comphelper::dispatchCommand(".uno:Copy", uno::Sequence<beans::PropertyValue>()); + + // Move cursor to the beginning of the first line and paste + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_HOME | KEY_MOD1); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_HOME | KEY_MOD1); + Scheduler::ProcessEventsToIdle(); + comphelper::dispatchCommand(".uno:PasteUnformatted", uno::Sequence<beans::PropertyValue>()); + Scheduler::ProcessEventsToIdle(); + + // Check the resulting text in the document. (it was 1Text\n1\n1\n1) + CPPUNIT_ASSERT_EQUAL(OUString("1\n1Text\n1\n1"), pXTextDocument->getText()->getString()); + + mxComponent->dispose(); + mxComponent.clear(); + comphelper::LibreOfficeKit::setActive(false); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testRedlineField) +{ + // Load a document. + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + + // Turn on track changes and type "x". + uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY); + xPropertySet->setPropertyValue("RecordChanges", uno::Any(true)); + + SwDateTimeField aDate(static_cast<SwDateTimeFieldType*>(pWrtShell->GetFieldType(0, SwFieldIds::DateTime))); + //aDate->SetDateTime(::DateTime(::DateTime::SYSTEM)); + pWrtShell->InsertField2(aDate); + + // Get the redline just created + const SwRedlineTable& rTable = pWrtShell->GetDoc()->getIDocumentRedlineAccess().GetRedlineTable(); + CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(1), rTable.size()); + SwRangeRedline* pRedline = rTable[0]; + CPPUNIT_ASSERT(pRedline->GetDescr().indexOf(aDate.GetFieldName())!= -1); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testIMESupport) +{ + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + VclPtr<vcl::Window> pDocWindow = pXTextDocument->getDocWindow(); + + SwView* pView = dynamic_cast<SwView*>(SfxViewShell::Current()); + assert(pView); + SwWrtShell* pWrtShell = pView->GetWrtShellPtr(); + + // sequence of chinese IME compositions when 'nihao' is typed in an IME + const std::vector<OString> aUtf8Inputs{ "年"_ostr, "你"_ostr, "你好"_ostr, "你哈"_ostr, "你好"_ostr, "你好"_ostr }; + std::vector<OUString> aInputs; + std::transform(aUtf8Inputs.begin(), aUtf8Inputs.end(), + std::back_inserter(aInputs), [](OString aInput) { + return OUString::fromUtf8(aInput); + }); + for (const auto& aInput: aInputs) + { + pDocWindow->PostExtTextInputEvent(VclEventId::ExtTextInput, aInput); + } + pDocWindow->PostExtTextInputEvent(VclEventId::EndExtTextInput, ""); + + // the cursor should be at position 2nd + SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), pShellCursor->GetPoint()->GetContentIndex()); + + // content contains only the last IME composition, not all + CPPUNIT_ASSERT_EQUAL(OUString(aInputs[aInputs.size() - 1] + "Aaa bbb."), pShellCursor->GetPoint()->GetNode().GetTextNode()->GetText()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testIMEFormattingAtEndOfParagraph) +{ + comphelper::LibreOfficeKit::setActive(); + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + VclPtr<vcl::Window> pDocWindow = pXTextDocument->getDocWindow(); + + SwView* pView = dynamic_cast<SwView*>(SfxViewShell::Current()); + assert(pView); + SwWrtShell* pWrtShell = pView->GetWrtShellPtr(); + + // delete all characters + + for (int i = 0; i < 9; i++) + { + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DELETE); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DELETE); + } + + Scheduler::ProcessEventsToIdle(); + + pDocWindow->PostExtTextInputEvent(VclEventId::ExtTextInput, "a"); + pDocWindow->PostExtTextInputEvent(VclEventId::EndExtTextInput, ""); + + // status: "a" + + comphelper::dispatchCommand(".uno:Bold", uno::Sequence<beans::PropertyValue>()); + Scheduler::ProcessEventsToIdle(); + + pDocWindow->PostExtTextInputEvent(VclEventId::ExtTextInput, "b"); + pDocWindow->PostExtTextInputEvent(VclEventId::EndExtTextInput, ""); + + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RETURN); + Scheduler::ProcessEventsToIdle(); + + // status: "a<bold>b</bold>\n" + + pDocWindow->PostExtTextInputEvent(VclEventId::ExtTextInput, "a"); + pDocWindow->PostExtTextInputEvent(VclEventId::EndExtTextInput, ""); + + std::unique_ptr<SvxWeightItem> pWeightItem; + pView->GetViewFrame().GetBindings().QueryState(SID_ATTR_CHAR_WEIGHT, pWeightItem); + CPPUNIT_ASSERT(pWeightItem); + + CPPUNIT_ASSERT_EQUAL(FontWeight::WEIGHT_BOLD, pWeightItem->GetWeight()); + + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RETURN); + Scheduler::ProcessEventsToIdle(); + + // status: "a<bold>b</bold>\n + // <bold>a</bold>\n" + + comphelper::dispatchCommand(".uno:Bold", uno::Sequence<beans::PropertyValue>()); + Scheduler::ProcessEventsToIdle(); + + pDocWindow->PostExtTextInputEvent(VclEventId::ExtTextInput, "b"); + pDocWindow->PostExtTextInputEvent(VclEventId::EndExtTextInput, ""); + + std::unique_ptr<SvxWeightItem> pWeightItem2; + pView->GetViewFrame().GetBindings().QueryState(SID_ATTR_CHAR_WEIGHT, pWeightItem2); + CPPUNIT_ASSERT(pWeightItem2); + + CPPUNIT_ASSERT_EQUAL(FontWeight::WEIGHT_NORMAL, pWeightItem2->GetWeight()); + + // status: "a<bold>b</bold>\n + // <bold>a</bold>\n" + // b" + + comphelper::dispatchCommand(".uno:Bold", uno::Sequence<beans::PropertyValue>()); + Scheduler::ProcessEventsToIdle(); + + pDocWindow->PostExtTextInputEvent(VclEventId::ExtTextInput, "a"); + pDocWindow->PostExtTextInputEvent(VclEventId::EndExtTextInput, ""); + + std::unique_ptr<SvxWeightItem> pWeightItem3; + pView->GetViewFrame().GetBindings().QueryState(SID_ATTR_CHAR_WEIGHT, pWeightItem3); + CPPUNIT_ASSERT(pWeightItem3); + + CPPUNIT_ASSERT_EQUAL(FontWeight::WEIGHT_BOLD, pWeightItem3->GetWeight()); + + comphelper::dispatchCommand(".uno:Bold", uno::Sequence<beans::PropertyValue>()); + Scheduler::ProcessEventsToIdle(); + + pDocWindow->PostExtTextInputEvent(VclEventId::ExtTextInput, "b"); + pDocWindow->PostExtTextInputEvent(VclEventId::EndExtTextInput, ""); + + std::unique_ptr<SvxWeightItem> pWeightItem4; + pView->GetViewFrame().GetBindings().QueryState(SID_ATTR_CHAR_WEIGHT, pWeightItem4); + CPPUNIT_ASSERT(pWeightItem4); + + CPPUNIT_ASSERT_EQUAL(FontWeight::WEIGHT_NORMAL, pWeightItem4->GetWeight()); + + // status: "a<bold>b</bold>\n + // <bold>a</bold>\n" + // b<bold>a</bold>b" + + // the cursor should be at position 3rd + SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(3), pShellCursor->GetPoint()->GetContentIndex()); + + // check the content + CPPUNIT_ASSERT_EQUAL(OUString("bab"), pShellCursor->GetPoint()->GetNode().GetTextNode()->GetText()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testIMEFormattingAfterHeader) +{ + comphelper::LibreOfficeKit::setActive(); + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + VclPtr<vcl::Window> pDocWindow = pXTextDocument->getDocWindow(); + + SwView* pView = dynamic_cast<SwView*>(SfxViewShell::Current()); + assert(pView); + + // delete all characters + + comphelper::dispatchCommand(".uno:SelectAll", uno::Sequence<beans::PropertyValue>()); + Scheduler::ProcessEventsToIdle(); + + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DELETE); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DELETE); + + Scheduler::ProcessEventsToIdle(); + + pDocWindow->PostExtTextInputEvent(VclEventId::ExtTextInput, "a"); + pDocWindow->PostExtTextInputEvent(VclEventId::EndExtTextInput, ""); + + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RETURN); + Scheduler::ProcessEventsToIdle(); + + // status: "a\n" + + comphelper::dispatchCommand( + ".uno:StyleApply?Style:string=Heading 2&FamilyName:string=ParagraphStyles", + uno::Sequence<beans::PropertyValue>()); + Scheduler::ProcessEventsToIdle(); + + pDocWindow->PostExtTextInputEvent(VclEventId::ExtTextInput, "b"); + pDocWindow->PostExtTextInputEvent(VclEventId::EndExtTextInput, ""); + + pDocWindow->PostExtTextInputEvent(VclEventId::ExtTextInput, "b"); + pDocWindow->PostExtTextInputEvent(VclEventId::EndExtTextInput, ""); + Scheduler::ProcessEventsToIdle(); + + std::unique_ptr<SvxWeightItem> pWeightItem; + pView->GetViewFrame().GetBindings().QueryState(SID_ATTR_CHAR_WEIGHT, pWeightItem); + CPPUNIT_ASSERT(pWeightItem); + + CPPUNIT_ASSERT_EQUAL(FontWeight::WEIGHT_BOLD, pWeightItem->GetWeight()); + + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RETURN); + Scheduler::ProcessEventsToIdle(); + + // status: "a\n + // <h2>bb</h2>\n" + + pDocWindow->PostExtTextInputEvent(VclEventId::ExtTextInput, "c"); + pDocWindow->PostExtTextInputEvent(VclEventId::EndExtTextInput, ""); + Scheduler::ProcessEventsToIdle(); + + // status: "a\n + // <h2>bb</h2>\n" + // c" + + std::unique_ptr<SvxWeightItem> pWeightItem2; + pView->GetViewFrame().GetBindings().QueryState(SID_ATTR_CHAR_WEIGHT, pWeightItem2); + CPPUNIT_ASSERT(pWeightItem2); + + CPPUNIT_ASSERT_EQUAL(FontWeight::WEIGHT_NORMAL, pWeightItem2->GetWeight()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSplitNodeRedlineCallback) +{ + // Load a document. + SwXTextDocument* pXTextDocument = createDoc("splitnode_redline_callback.fodt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + + // 1. test case + // Move cursor between the two tracked changes + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN); + Scheduler::ProcessEventsToIdle(); + + // Add a new line + m_nRedlineTableEntryModified = 0; + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RETURN); + Scheduler::ProcessEventsToIdle(); + + // Assert that we get a notification about redline modification + // The redline after the inserted node gets a different vertical position + CPPUNIT_ASSERT_EQUAL(1, m_nRedlineTableEntryModified); + + // 2. test case + // Move cursor back to the first line, so adding new line will affect both tracked changes + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_HOME | KEY_MOD1); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_HOME | KEY_MOD1); + Scheduler::ProcessEventsToIdle(); + + // Add a new line + m_nRedlineTableEntryModified = 0; + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RETURN); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(2, m_nRedlineTableEntryModified); + + // 3. test case + // Move cursor to the end of the document, so adding a new line won't affect any tracked changes + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_END | KEY_MOD1); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_END | KEY_MOD1); + Scheduler::ProcessEventsToIdle(); + + // Add a new line + m_nRedlineTableEntryModified = 0; + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RETURN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RETURN); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(0, m_nRedlineTableEntryModified); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testDeleteNodeRedlineCallback) +{ + // Load a document. + SwXTextDocument* pXTextDocument = createDoc("removenode_redline_callback.fodt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + + // 1. test case + // Move cursor between the two tracked changes + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN); + Scheduler::ProcessEventsToIdle(); + + // Remove one (empty) line + m_nRedlineTableEntryModified = 0; + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DELETE); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DELETE); + Scheduler::ProcessEventsToIdle(); + + // Assert that we get a notification about redline modification + // The redline after the removed node gets a different vertical position + CPPUNIT_ASSERT_EQUAL(1, m_nRedlineTableEntryModified); + + // 2. test case + // Move cursor back to the first line, so removing one line will affect both tracked changes + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_HOME | KEY_MOD1); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_HOME | KEY_MOD1); + Scheduler::ProcessEventsToIdle(); + + // Remove a new line + m_nRedlineTableEntryModified = 0; + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DELETE); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DELETE); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(2, m_nRedlineTableEntryModified); + + // 3. test case + // Move cursor to the end of the document, so removing one line won't affect any tracked changes + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_END | KEY_MOD1); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_END | KEY_MOD1); + Scheduler::ProcessEventsToIdle(); + + // Remove a line + m_nRedlineTableEntryModified = 0; + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_BACKSPACE); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_BACKSPACE); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(0, m_nRedlineTableEntryModified); +} + + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testVisCursorInvalidation) +{ + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + ViewCallback aView1; + int nView1 = SfxLokHelper::getView(); + + SfxLokHelper::createView(); + int nView2 = SfxLokHelper::getView(); + ViewCallback aView2; + Scheduler::ProcessEventsToIdle(); + + // Move visible cursor in the first view + SfxLokHelper::setView(nView1); + Scheduler::ProcessEventsToIdle(); + + aView1.m_bOwnCursorInvalidated = false; + aView1.m_bViewCursorInvalidated = false; + aView2.m_bOwnCursorInvalidated = false; + aView2.m_bViewCursorInvalidated = false; + + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT); + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT(!aView1.m_bViewCursorInvalidated); + CPPUNIT_ASSERT(aView1.m_bOwnCursorInvalidated); + CPPUNIT_ASSERT(aView2.m_bViewCursorInvalidated); + CPPUNIT_ASSERT(!aView2.m_bOwnCursorInvalidated); + + // Insert text in the second view which moves the other view's cursor too + SfxLokHelper::setView(nView2); + + Scheduler::ProcessEventsToIdle(); + aView1.m_bOwnCursorInvalidated = false; + aView1.m_bViewCursorInvalidated = false; + aView2.m_bOwnCursorInvalidated = false; + aView2.m_bViewCursorInvalidated = false; + + emulateTyping(*pXTextDocument, u"x"); + + CPPUNIT_ASSERT(aView1.m_bViewCursorInvalidated); + CPPUNIT_ASSERT(aView1.m_bOwnCursorInvalidated); + CPPUNIT_ASSERT(aView2.m_bViewCursorInvalidated); + CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated); + // Check that views have correct location for the other's cursor. + CPPUNIT_ASSERT_EQUAL(aView1.m_aOwnCursor, aView2.m_aViewCursor); + CPPUNIT_ASSERT_EQUAL(aView2.m_aOwnCursor, aView1.m_aViewCursor); + // Their cursors should be on the same line, first view's more to the right. + CPPUNIT_ASSERT_EQUAL(aView1.m_aOwnCursor.getY(), aView2.m_aOwnCursor.getY()); + CPPUNIT_ASSERT_GREATER(aView2.m_aOwnCursor.getX(), aView1.m_aOwnCursor.getX()); + + // Do the same as before, but set the related compatibility flag first + SfxLokHelper::setView(nView2); + + comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(true); + + Scheduler::ProcessEventsToIdle(); + aView1.m_bOwnCursorInvalidated = false; + aView1.m_bViewCursorInvalidated = false; + aView2.m_bOwnCursorInvalidated = false; + aView2.m_bViewCursorInvalidated = false; + + emulateTyping(*pXTextDocument, u"x"); + + CPPUNIT_ASSERT(aView1.m_bViewCursorInvalidated); + CPPUNIT_ASSERT(aView1.m_bOwnCursorInvalidated); + CPPUNIT_ASSERT_EQUAL(nView2, aView1.m_nOwnCursorInvalidatedBy); + CPPUNIT_ASSERT(aView2.m_bViewCursorInvalidated); + CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated); + CPPUNIT_ASSERT_EQUAL(nView2, aView2.m_nOwnCursorInvalidatedBy); + CPPUNIT_ASSERT_EQUAL(aView1.m_aOwnCursor, aView2.m_aViewCursor); + CPPUNIT_ASSERT_EQUAL(aView2.m_aOwnCursor, aView1.m_aViewCursor); + // Their cursors should be on the same line, first view's more to the right. + CPPUNIT_ASSERT_EQUAL(aView1.m_aOwnCursor.getY(), aView2.m_aOwnCursor.getY()); + CPPUNIT_ASSERT_GREATER(aView2.m_aOwnCursor.getX(), aView1.m_aOwnCursor.getX()); + + comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(false); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testDeselectCustomShape) +{ + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); + Point aStart = pShellCursor->GetSttPos(); + aStart.setX(aStart.getX() - 1000); + aStart.setY(aStart.getY() - 1000); + + comphelper::dispatchCommand(".uno:BasicShapes.hexagon", uno::Sequence<beans::PropertyValue>()); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pWrtShell->GetDrawView()->GetMarkedObjectList().GetMarkCount()); + + pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aStart.getX(), aStart.getY(), 1, MOUSE_LEFT, 0); + pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, aStart.getX(), aStart.getY(), 1, MOUSE_LEFT, 0); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), pWrtShell->GetDrawView()->GetMarkedObjectList().GetMarkCount()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSemiTransparent) +{ + // Load a document where the top left tile contains a semi-transparent rectangle shape. + SwXTextDocument* pXTextDocument = createDoc("semi-transparent.odt"); + + // Render a larger area, and then get the color of the bottom right corner of our tile. + size_t nCanvasWidth = 1024; + size_t nCanvasHeight = 512; + size_t nTileSize = 256; + std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0); + ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA); + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), + Fraction(1.0), Point(), aPixmap.data()); + pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, + /*nTilePosY=*/0, /*nTileWidth=*/15360, /*nTileHeight=*/7680); + pDevice->EnableMapMode(false); + Bitmap aBitmap = pDevice->GetBitmap(Point(0, 0), Size(nTileSize, nTileSize)); + BitmapScopedReadAccess pAccess(aBitmap); + Color aColor(pAccess->GetPixel(255, 255)); + + // Without the accompanying fix in place, this test would have failed with 'Expected greater or + // equal than: 190; Actual: 159'. This means the semi-transparent gray rectangle was darker than + // expected, as it was painted twice. + CPPUNIT_ASSERT_GREATEREQUAL(190, static_cast<int>(aColor.GetRed())); + CPPUNIT_ASSERT_GREATEREQUAL(190, static_cast<int>(aColor.GetGreen())); + CPPUNIT_ASSERT_GREATEREQUAL(190, static_cast<int>(aColor.GetBlue())); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testHighlightNumbering) +{ + // Load a document where the top left tile contains a semi-transparent rectangle shape. + SwXTextDocument* pXTextDocument = createDoc("tdf114799_highlight.docx"); + + // Render a larger area, and then get the color of the bottom right corner of our tile. + size_t nCanvasWidth = 1024; + size_t nCanvasHeight = 512; + size_t nTileSize = 256; + std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0); + ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA); + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), + Fraction(1.0), Point(), aPixmap.data()); + pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, + /*nTilePosY=*/0, /*nTileWidth=*/15360, /*nTileHeight=*/7680); + pDevice->EnableMapMode(false); + Bitmap aBitmap = pDevice->GetBitmap(Point(0, 0), Size(nTileSize, nTileSize)); + BitmapScopedReadAccess pAccess(aBitmap); + + // Yellow highlighting over numbering + Color aColor(pAccess->GetPixel(103, 148)); + CPPUNIT_ASSERT_EQUAL(COL_YELLOW, aColor); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testHighlightNumbering_shd) +{ + // Load a document where the top left tile contains a semi-transparent rectangle shape. + SwXTextDocument* pXTextDocument = createDoc("tdf114799_shd.docx"); + + // Render a larger area, and then get the color of the bottom right corner of our tile. + size_t nCanvasWidth = 1024; + size_t nCanvasHeight = 512; + size_t nTileSize = 256; + std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0); + ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA); + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), + Fraction(1.0), Point(), aPixmap.data()); + pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, + /*nTilePosY=*/0, /*nTileWidth=*/15360, /*nTileHeight=*/7680); + pDevice->EnableMapMode(false); + Bitmap aBitmap = pDevice->GetBitmap(Point(0, 0), Size(nTileSize, nTileSize)); + BitmapScopedReadAccess pAccess(aBitmap); + + // No highlighting over numbering - w:shd does not apply to numbering. + Color aColor(pAccess->GetPixel(103, 148)); + CPPUNIT_ASSERT_EQUAL(COL_WHITE, aColor); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testPilcrowRedlining) +{ + // Load a document where the top left tile contains + // paragraph and line break symbols with redlining. + SwXTextDocument* pXTextDocument = createDoc("pilcrow-redlining.fodt"); + + // show non printing characters, including pilcrow and + // line break symbols with redlining + comphelper::dispatchCommand(".uno:ControlCodes", {}); + + // Render a larger area, and then get the color of the bottom right corner of our tile. + size_t nCanvasWidth = 2048; + size_t nCanvasHeight = 1024; + size_t nTileSize = 512; + std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0); + ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA); + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), + Fraction(1.0), Point(), aPixmap.data()); + pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, + /*nTilePosY=*/0, /*nTileWidth=*/15360, /*nTileHeight=*/7680); + pDevice->EnableMapMode(false); + Bitmap aBitmap = pDevice->GetBitmap(Point(100, 100), Size(nTileSize, nTileSize)); + BitmapScopedReadAccess pAccess(aBitmap); + + const char* aTexts[] = { + "Insert paragraph break", + "Insert paragraph break (empty line)", + "Delete paragraph break", + "Delete paragraph break (empty line)", + "Insert line break", + "Insert line break (empty line)", + "Delete line break", + "Delete line break (empty line)" + }; + + // Check redlining (strike out and underline) over the paragraph and line break symbols + for (int nLine = 0; nLine < 8; ++nLine) + { + bool bHasRedlineColor = false; + for (int i = 0; i < 36 && !bHasRedlineColor; ++i) + { + int nY = 96 + nLine * 36 + i; + for (sal_uInt32 j = 0; j < nTileSize - 1; ++j) + { + Color aColor(pAccess->GetPixel(nY, j)); + Color aColor2(pAccess->GetPixel(nY+1, j)); + Color aColor3(pAccess->GetPixel(nY, j+1)); + Color aColor4(pAccess->GetPixel(nY+1, j+1)); + // 4-pixel same color square sign strike out or underline of redlining + // if its color is not white, black or non-printing character color + if ( aColor == aColor2 && aColor == aColor3 && aColor == aColor4 && + aColor != COL_WHITE && aColor != COL_BLACK && + aColor != NON_PRINTING_CHARACTER_COLOR ) + { + bHasRedlineColor = true; + break; + } + } + } + + CPPUNIT_ASSERT_MESSAGE(aTexts[nLine], bHasRedlineColor); + } + + comphelper::dispatchCommand(".uno:ControlCodes", {}); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testShowHiddenCharsWhenShowFormatting) +{ + // In LOKit, ignore the config setting for + // Tools - Options - Writer - Formatting Aids - Display Formatting - Hidden characters + // and always show hidden content when showing pilcrow formatting + + createSwDoc("hiddenLoremIpsum.docx"); + + // Since LOKit is active in TiledRendering, turning on "Show formatting" will show hidden text. + comphelper::dispatchCommand(".uno:ControlCodes", {}); // show format marks + Scheduler::ProcessEventsToIdle(); + + // Without this patch, no body text would be visible - so only 1 page instead of 3. + CPPUNIT_ASSERT_EQUAL(3, getPages()); + + comphelper::dispatchCommand(".uno:ControlCodes", {}); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testDoubleUnderlineAndStrikeOut) +{ + // Load a document where the tracked text moving is visible with + // double underline and strike out character formatting + SwXTextDocument* pXTextDocument = createDoc("double-underline_and_strike-out.fodt"); + + // Render a larger area, and then get the color of the bottom right corner of our tile. + size_t nCanvasWidth = 700; + size_t nCanvasHeight = 350; + size_t nTileSize = 350; + std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0); + ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA); + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), + Fraction(1.0), Point(), aPixmap.data()); + pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, + /*nTilePosY=*/0, /*nTileWidth=*/15360, /*nTileHeight=*/7680); + pDevice->EnableMapMode(false); + Bitmap aBitmap = pDevice->GetBitmap(Point(0, 0), Size(nTileSize, nTileSize)); + BitmapScopedReadAccess pAccess(aBitmap); + bool bGreenLine = false; + size_t nGreenLine = 0; + // count green horizontal lines by tracking a column of pixels counting the + // separated continuous green pixel sequences. + for (size_t nLine = 0; nLine < nTileSize; ++nLine) + { + Color aColor(pAccess->GetPixel(nLine, 100)); + if ( aColor == COL_GREEN ) + { + if ( bGreenLine == false ) + { + ++nGreenLine; + bGreenLine = true; + } + } + else + bGreenLine = false; + } + // tdf#152214 this was 0 (missing double underline and double strike out) + CPPUNIT_ASSERT_EQUAL(size_t(4), nGreenLine); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTdf43244_SpacesOnMargin) +{ + // Load a document where the top left tile contains + // paragraph and line break symbols with redlining. + SwXTextDocument* pXTextDocument = createDoc("tdf43244_SpacesOnMargin.odt"); + + // show non printing characters, including pilcrow and + // line break symbols with redlining + comphelper::dispatchCommand(".uno:ControlCodes", {}); + + // Render a larger area, and then get the colors from the right side of the page. + size_t nCanvasWidth = 1024; + size_t nCanvasHeight = 512; + size_t nTileSize = 64; + std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0); + ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA); + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), + Fraction(1.0), Point(), aPixmap.data()); + pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, + /*nTilePosY=*/0, /*nTileWidth=*/15360, /*nTileHeight=*/7680); + pDevice->EnableMapMode(false); + Bitmap aBitmap = pDevice->GetBitmap(Point(730, 120), Size(nTileSize, nTileSize)); + BitmapScopedReadAccess pAccess(aBitmap); + + //Test if we see any spaces on the right margin in a 47x48 rectangle + bool bSpaceFound = false; + for (int i = 1; i < 48 && !bSpaceFound; i++) + { + for (int j = 0; j < i; j++) + { + Color aColor2(pAccess->GetPixel(j, i)); + Color aColor1(pAccess->GetPixel(i, j + 1)); + + if (aColor1.GetRed() < 255 || aColor2.GetRed() < 255) + { + bSpaceFound = true; + break; + } + } + } + CPPUNIT_ASSERT(bSpaceFound); + + comphelper::dispatchCommand(".uno:ControlCodes", {}); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testClipText) +{ + // Load a document where the top left tile contains table text with + // too small line height, but with top and bottom paragraph margins, + // avoiding of clipping top and bottom parts of the characters. + SwXTextDocument* pXTextDocument = createDoc("tdf117448.fodt"); + + // Render a larger area, and then get the top and bottom of the text in that tile + size_t nCanvasWidth = 1024; + size_t nCanvasHeight = 512; + size_t nTileSize = 256; + std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0); + ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA); + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), + Fraction(1.0), Point(), aPixmap.data()); + pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, + /*nTilePosY=*/0, /*nTileWidth=*/15360, /*nTileHeight=*/7680); + pDevice->EnableMapMode(false); + Bitmap aBitmap = pDevice->GetBitmap(Point(0, 0), Size(nTileSize, nTileSize)); + BitmapScopedReadAccess pAccess(aBitmap); + + // check top margin, it's not white completely (i.e. showing top of letter "T") + bool bClipTop = true; + for (int i = 0; i < 150; i++) + { + Color aTopTextColor(pAccess->GetPixel(98, 98 + i)); + if (aTopTextColor.GetRed() < 255) + { + bClipTop = false; + break; + } + } + CPPUNIT_ASSERT(!bClipTop); + // switch off because of false alarm on some platform, maybe related to font replacements +#if 0 + // check bottom margin, it's not white completely (i.e. showing bottom of letter "g") + bool bClipBottom = true; + for (int i = 0; i < 150; i++) + { + Color aBottomTextColor(pAccess->GetPixel(110, 98 + i)); + if (aBottomTextColor.R < 255) + { + bClipBottom = false; + break; + } + } + CPPUNIT_ASSERT(!bClipBottom); +#endif +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testAnchorTypes) +{ + SwXTextDocument* pXTextDocument = createDoc("shape.fodt"); + selectShape(1); + + SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc(); + SwView* pView = pXTextDocument->GetDocShell()->GetView(); + SfxItemSet aSet(pDoc->GetAttrPool(), svl::Items<FN_TOOL_ANCHOR_PAGE, FN_TOOL_ANCHOR_PAGE>); + SfxBoolItem aItem(FN_TOOL_ANCHOR_PAGE); + aSet.Put(aItem); + auto pShell = dynamic_cast<SwBaseShell*>(pView->GetCurShell()); + pShell->GetState(aSet); + // Without the accompanying fix in place, this test would have failed, setting the anchor type + // to other than as/at-char was possible. + CPPUNIT_ASSERT(!aSet.HasItem(FN_TOOL_ANCHOR_PAGE)); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testLanguageStatus) +{ + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + SwView* pView = pXTextDocument->GetDocShell()->GetView(); + std::unique_ptr<SfxPoolItem> pItem; + pView->GetViewFrame().GetBindings().QueryState(SID_LANGUAGE_STATUS, pItem); + auto pStringListItem = dynamic_cast<SfxStringListItem*>(pItem.get()); + CPPUNIT_ASSERT(pStringListItem); + + uno::Sequence< OUString > aList; + pStringListItem->GetStringList(aList); + CPPUNIT_ASSERT_EQUAL(OUString("English (USA);en-US"), aList[0]); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testRedlineNotificationDuringSave) +{ + // Load a document with redlines which are hidden at a layout level. + // It's an empty document, just settings.xml and content.xml are custom. + SwXTextDocument* pXTextDocument = createDoc("redline-notification-during-save.odt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + + // Save the document. + utl::MediaDescriptor aMediaDescriptor; + aMediaDescriptor["FilterName"] <<= OUString("writer8"); + uno::Reference<frame::XStorable> xStorable(mxComponent, uno::UNO_QUERY); + // Without the accompanying fix in place, this test would have never returned due to an infinite + // loop while sending not needed LOK notifications for redline changes during save. + xStorable->storeToURL(maTempFile.GetURL(), aMediaDescriptor.getAsConstPropertyValueList()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testHyperlink) +{ + comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(true); + SwXTextDocument* pXTextDocument = createDoc("hyperlink.odt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + m_callbackWrapper.setLOKViewId(SfxLokHelper::getView(pWrtShell->GetSfxViewShell())); + SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); + + Point aStart = pShellCursor->GetSttPos(); + aStart.setX(aStart.getX() + 1800); + pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, aStart.getX(), aStart.getY(), 1, + MOUSE_LEFT, 0); + pXTextDocument->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, aStart.getX(), aStart.getY(), 1, + MOUSE_LEFT, 0); + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT_EQUAL("hyperlink"_ostr, m_sHyperlinkText); + CPPUNIT_ASSERT_EQUAL("http://example.com/"_ostr, m_sHyperlinkLink); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testFieldmark) +{ + // Without the accompanying fix in place, this crashed on load. + createDoc("fieldmark.docx"); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testDropDownFormFieldButton) +{ + SwXTextDocument* pXTextDocument = createDoc("drop_down_form_field.odt"); + pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000)); + + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + + // Move the cursor to trigger displaying of the field button. + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); + CPPUNIT_ASSERT(m_aFormFieldButton.isEmpty()); + + // Do a tile rendering to trigger the button message with a valid text area + size_t nCanvasWidth = 1024; + size_t nCanvasHeight = 512; + std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0); + ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA); + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), + Fraction(1.0), Point(), aPixmap.data()); + pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, + /*nTilePosY=*/0, /*nTileWidth=*/10000, /*nTileHeight=*/4000); + + CPPUNIT_ASSERT(!m_aFormFieldButton.isEmpty()); + { + std::stringstream aStream((std::string(m_aFormFieldButton))); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + + OString sAction( aTree.get_child("action").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("show"_ostr, sAction); + + OString sType( aTree.get_child("type").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("drop-down"_ostr, sType); + + OString sTextArea( aTree.get_child("textArea").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("1538, 1418, 1026, 275"_ostr, sTextArea); + + boost::property_tree::ptree aItems = aTree.get_child("params").get_child("items"); + CPPUNIT_ASSERT_EQUAL(size_t(6), aItems.size()); + + OStringBuffer aItemList; + for (auto &item : aItems) + { + aItemList.append(item.second.get_value<std::string>().c_str() + + OString::Concat(";")); + } + CPPUNIT_ASSERT_EQUAL("2019/2020;2020/2021;2021/2022;2022/2023;2023/2024;2024/2025;"_ostr, aItemList.toString()); + + OString sSelected( aTree.get_child("params").get_child("selected").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("1"_ostr, sSelected); + + OString sPlaceholder( aTree.get_child("params").get_child("placeholderText").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("No Item specified"_ostr, sPlaceholder); + } + + // Move the cursor back so the button becomes hidden. + pWrtShell->Left(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); + + CPPUNIT_ASSERT(!m_aFormFieldButton.isEmpty()); + { + std::stringstream aStream((std::string(m_aFormFieldButton))); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + + OString sAction( aTree.get_child("action").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("hide"_ostr, sAction); + + OString sType( aTree.get_child("type").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("drop-down"_ostr, sType); + } +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testDropDownFormFieldButtonEditing) +{ + SwXTextDocument* pXTextDocument = createDoc("drop_down_form_field2.odt"); + pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000)); + + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + + // Move the cursor to trigger displaying of the field button. + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); + CPPUNIT_ASSERT(m_aFormFieldButton.isEmpty()); + + // Do a tile rendering to trigger the button message with a valid text area + size_t nCanvasWidth = 1024; + size_t nCanvasHeight = 512; + std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0); + ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA); + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), + Fraction(1.0), Point(), aPixmap.data()); + pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, + /*nTilePosY=*/0, /*nTileWidth=*/10000, /*nTileHeight=*/4000); + + // The item with the index '1' is selected by default + CPPUNIT_ASSERT(!m_aFormFieldButton.isEmpty()); + { + std::stringstream aStream((std::string(m_aFormFieldButton))); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + + OString sSelected( aTree.get_child("params").get_child("selected").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("1"_ostr, sSelected); + } + m_aFormFieldButton = ""_ostr; + + // Trigger a form field event to select a different item. + vcl::ITiledRenderable::StringMap aArguments; + aArguments["type"] = "drop-down"; + aArguments["cmd"] = "selected"; + aArguments["data"] = "3"; + pXTextDocument->executeFromFieldEvent(aArguments); + + // Do a tile rendering to trigger the button message. + pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, + /*nTilePosY=*/0, /*nTileWidth=*/10000, /*nTileHeight=*/4000); + + CPPUNIT_ASSERT(!m_aFormFieldButton.isEmpty()); + { + std::stringstream aStream((std::string(m_aFormFieldButton))); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + + OString sSelected( aTree.get_child("params").get_child("selected").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("3"_ostr, sSelected); + } +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testDropDownFormFieldButtonNoSelection) +{ + SwXTextDocument* pXTextDocument = createDoc("drop_down_form_field_noselection.odt"); + pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000)); + + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + + // Move the cursor to trigger displaying of the field button. + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); + CPPUNIT_ASSERT(m_aFormFieldButton.isEmpty()); + + // Do a tile rendering to trigger the button message with a valid text area + size_t nCanvasWidth = 1024; + size_t nCanvasHeight = 512; + std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0); + ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA); + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), + Fraction(1.0), Point(), aPixmap.data()); + pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, + /*nTilePosY=*/0, /*nTileWidth=*/10000, /*nTileHeight=*/4000); + + // None of the items is selected + CPPUNIT_ASSERT(!m_aFormFieldButton.isEmpty()); + { + std::stringstream aStream((std::string(m_aFormFieldButton))); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + + OString sSelected( aTree.get_child("params").get_child("selected").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("-1"_ostr, sSelected); + } +} + +static void lcl_extractHandleParameters(std::string_view selection, sal_Int32& id, sal_Int32& x, sal_Int32& y) +{ + OString extraInfo( selection.substr(selection.find("{")) ); + std::stringstream aStream((std::string(extraInfo))); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + boost::property_tree::ptree + handle0 = aTree + .get_child("handles") + .get_child("kinds") + .get_child("rectangle") + .get_child("1") + .begin()->second; + id = handle0.get_child("id").get_value<int>(); + x = handle0.get_child("point").get_child("x").get_value<int>(); + y = handle0.get_child("point").get_child("y").get_value<int>(); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testMoveShapeHandle) +{ + comphelper::LibreOfficeKit::setActive(); + SwXTextDocument* pXTextDocument = createDoc("shape.fodt"); + + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); + SdrObject* pObject = pPage->GetObj(0); + pWrtShell->SelectObj(Point(), 0, pObject); + Scheduler::ProcessEventsToIdle(); + + CPPUNIT_ASSERT(!m_ShapeSelection.isEmpty()); + { + sal_Int32 id, x, y; + lcl_extractHandleParameters(m_ShapeSelection, id, x ,y); + sal_Int32 oldX = x; + sal_Int32 oldY = y; + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {"HandleNum", uno::Any(id)}, + {"NewPosX", uno::Any(x+1)}, + {"NewPosY", uno::Any(y+1)} + })); + comphelper::dispatchCommand(".uno:MoveShapeHandle", aPropertyValues); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(!m_ShapeSelection.isEmpty()); + lcl_extractHandleParameters(m_ShapeSelection, id, x ,y); + CPPUNIT_ASSERT_EQUAL(x-1, oldX); + CPPUNIT_ASSERT_EQUAL(y-1, oldY); + } +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testDropDownFormFieldButtonNoItem) +{ + SwXTextDocument* pXTextDocument = createDoc("drop_down_form_field_noitem.odt"); + pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000)); + + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + + // Move the cursor to trigger displaying of the field button. + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, 1, /*bBasicCall=*/false); + CPPUNIT_ASSERT(m_aFormFieldButton.isEmpty()); + + // Do a tile rendering to trigger the button message with a valid text area + size_t nCanvasWidth = 1024; + size_t nCanvasHeight = 512; + std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0); + ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA); + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), + Fraction(1.0), Point(), aPixmap.data()); + pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, + /*nTilePosY=*/0, /*nTileWidth=*/10000, /*nTileHeight=*/4000); + + // There is not item specified for the field + CPPUNIT_ASSERT(!m_aFormFieldButton.isEmpty()); + { + std::stringstream aStream((std::string(m_aFormFieldButton))); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + + boost::property_tree::ptree aItems = aTree.get_child("params").get_child("items"); + CPPUNIT_ASSERT_EQUAL(size_t(0), aItems.size()); + + OString sSelected( aTree.get_child("params").get_child("selected").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("-1"_ostr, sSelected); + } +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTablePaintInvalidate) +{ + // Load a document with a table in it. + SwXTextDocument* pXTextDocument = createDoc("table-paint-invalidate.odt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + // Enter the table. + pWrtShell->Down(/*bSelect=*/false); + Scheduler::ProcessEventsToIdle(); + m_nInvalidations = 0; + + // Paint a tile. + size_t nCanvasWidth = 256; + size_t nCanvasHeight = 256; + std::vector<unsigned char> aPixmap(nCanvasWidth * nCanvasHeight * 4, 0); + ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA); + pDevice->SetBackground(Wallpaper(COL_TRANSPARENT)); + pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), + Fraction(1.0), Point(), aPixmap.data()); + pXTextDocument->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, m_aInvalidation.Left(), + m_aInvalidation.Top(), /*nTileWidth=*/1000, + /*nTileHeight=*/1000); + Scheduler::ProcessEventsToIdle(); + + // Without the accompanying fix in place, this test would have failed with + // - Expected: 0 + // - Actual : 5 + // i.e. paint generated an invalidation, which caused a loop. + CPPUNIT_ASSERT_EQUAL(0, m_nInvalidations); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTableCommentRemoveCallback) +{ + comphelper::LibreOfficeKit::setActive(); + comphelper::LibreOfficeKit::setTiledAnnotations(false); + + // Load a document with a comment in a table. + SwXTextDocument* pXTextDocument = createDoc("testTableCommentRemoveCallback.odt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + ViewCallback aView; + + // delete all characters + comphelper::dispatchCommand(".uno:SelectAll", uno::Sequence<beans::PropertyValue>()); + Scheduler::ProcessEventsToIdle(); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DELETE); + pXTextDocument->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DELETE); + Scheduler::ProcessEventsToIdle(); + + //check for comment remove callback + OString sAction(aView.m_aComment.get_child("action").get_value<std::string>()); + CPPUNIT_ASSERT_EQUAL("Remove"_ostr, sAction); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSpellOnlineRenderParameter) +{ + SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + const SwViewOption* pOpt = pWrtShell->GetViewOptions(); + bool bSet = pOpt->IsOnlineSpell(); + + uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( + { + {".uno:SpellOnline", uno::Any(!bSet)}, + })); + pXTextDocument->initializeForTiledRendering(aPropertyValues); + CPPUNIT_ASSERT_EQUAL(!bSet, pOpt->IsOnlineSpell()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testExtTextInputReadOnly) +{ + // Create a document with a protected section + a normal paragraph after it. + SwXTextDocument* pXTextDocument = createDoc(); + uno::Reference<text::XTextViewCursorSupplier> xController( + pXTextDocument->getCurrentController(), uno::UNO_QUERY); + uno::Reference<text::XTextViewCursor> xCursor = xController->getViewCursor(); + uno::Reference<text::XText> xText = xCursor->getText(); + uno::Reference<text::XTextContent> xSection( + pXTextDocument->createInstance("com.sun.star.text.TextSection"), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xSectionProps(xSection, uno::UNO_QUERY); + xSectionProps->setPropertyValue("IsProtected", uno::Any(true)); + xText->insertTextContent(xCursor, xSection, /*bAbsorb=*/true); + + // First paragraph is the protected section, is it empty? + VclPtr<vcl::Window> pEditWin = pXTextDocument->getDocWindow(); + CPPUNIT_ASSERT(pEditWin); + CPPUNIT_ASSERT(getParagraph(1)->getString().isEmpty()); + + // Try to type into the protected section, is it still empty? + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + pWrtShell->SttEndDoc(/*bStt=*/true); + SfxLokHelper::postExtTextEventAsync(pEditWin, LOK_EXT_TEXTINPUT, "x"); + SfxLokHelper::postExtTextEventAsync(pEditWin, LOK_EXT_TEXTINPUT_END, "x"); + Scheduler::ProcessEventsToIdle(); + // Without the accompanying fix in place, this test would have failed, as it was possible to + // type into the protected section. + CPPUNIT_ASSERT(getParagraph(1)->getString().isEmpty()); + + // Second paragraph is a normal paragraph, is it empty? + pWrtShell->Down(/*bSelect=*/false); + CPPUNIT_ASSERT(getParagraph(2)->getString().isEmpty()); + + // Try to type into the protected section, does it have the typed content? + SfxLokHelper::postExtTextEventAsync(pEditWin, LOK_EXT_TEXTINPUT, "x"); + SfxLokHelper::postExtTextEventAsync(pEditWin, LOK_EXT_TEXTINPUT_END, "x"); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT_EQUAL(OUString("x"), getParagraph(2)->getString()); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testBulletDeleteInvalidation) +{ + // Given a document with 3 paragraphs: first 2 is bulleted, the last is not. + SwXTextDocument* pXTextDocument = createDoc(); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + pWrtShell->SplitNode(); + pWrtShell->Up(/*bSelect=*/false); + pWrtShell->StartAllAction(); + pWrtShell->BulletOn(); + pWrtShell->EndAllAction(); + pWrtShell->Insert2("a"); + pWrtShell->SplitNode(); + pWrtShell->Insert2("b"); + pWrtShell->Down(/*bSelect=*/false); + pWrtShell->GetLayout()->PaintSwFrame(*pWrtShell->GetOut(), + pWrtShell->GetLayout()->getFrameArea()); + Scheduler::ProcessEventsToIdle(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + m_aInvalidations = tools::Rectangle(); + + // When pressing backspace in the last paragraph. + pWrtShell->DelLeft(); + + // Then the first paragraph should not be invalidated. + SwRootFrame* pRoot = pWrtShell->GetLayout(); + SwFrame* pPage = pRoot->GetLower(); + SwFrame* pBody = pPage->GetLower(); + SwFrame* pFirstText = pBody->GetLower(); + tools::Rectangle aFirstTextRect = pFirstText->getFrameArea().SVRect(); + CPPUNIT_ASSERT(!aFirstTextRect.Overlaps(m_aInvalidations)); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTdf155349) +{ + SwXTextDocument* pXTextDocument = createDoc(); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + Scheduler::ProcessEventsToIdle(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + pWrtShell->Insert2("a"); + Scheduler::ProcessEventsToIdle(); + pWrtShell->Insert2("b"); + m_bFullInvalidateSeen = false; + Scheduler::ProcessEventsToIdle(); + // before fix for tdf#155349 the total area got invalidated when changing one line + CPPUNIT_ASSERT(!m_bFullInvalidateSeen); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testBulletNoNumInvalidation) +{ + // Given a document with 3 paragraphs: all are bulleted. + SwXTextDocument* pXTextDocument = createDoc(); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + pWrtShell->StartAllAction(); + pWrtShell->BulletOn(); + pWrtShell->EndAllAction(); + pWrtShell->Insert2("a"); + pWrtShell->SplitNode(); + pWrtShell->Insert2("b"); + pWrtShell->SplitNode(); + pWrtShell->GetLayout()->PaintSwFrame(*pWrtShell->GetOut(), + pWrtShell->GetLayout()->getFrameArea()); + Scheduler::ProcessEventsToIdle(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + m_aInvalidations = tools::Rectangle(); + + // When pressing backspace in the last paragraph to turn bullets off. + pWrtShell->StartAllAction(); + pWrtShell->NumOrNoNum(/*bDelete=*/false); + pWrtShell->EndAllAction(); + + // Then the first paragraph should not be invalidated. + SwRootFrame* pRoot = pWrtShell->GetLayout(); + SwFrame* pPage = pRoot->GetLower(); + SwFrame* pBody = pPage->GetLower(); + SwFrame* pFirstText = pBody->GetLower(); + tools::Rectangle aFirstTextRect = pFirstText->getFrameArea().SVRect(); + CPPUNIT_ASSERT(!aFirstTextRect.Overlaps(m_aInvalidations)); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testBulletMultiDeleteInvalidation) +{ + // Given a document with 5 paragraphs: all are bulleted. + SwXTextDocument* pXTextDocument = createDoc(); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + pWrtShell->StartAllAction(); + pWrtShell->BulletOn(); + pWrtShell->EndAllAction(); + // There is already an initial text node, so type 5 times, but split 4 times. + for (int i = 0; i < 4; ++i) + { + pWrtShell->Insert2("a"); + pWrtShell->SplitNode(); + } + pWrtShell->Insert2("a"); + // Go to the end of the 4th para. + pWrtShell->Up(/*bSelect=*/false); + pWrtShell->GetLayout()->PaintSwFrame(*pWrtShell->GetOut(), + pWrtShell->GetLayout()->getFrameArea()); + Scheduler::ProcessEventsToIdle(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + m_aInvalidations = tools::Rectangle(); + + // When selecting and deleting several bullets: select till the end of the 2nd para and delete. + pWrtShell->Up(/*bSelect=*/true, /*nCount=*/2); + pWrtShell->DelRight(); + + // Then the first paragraph should not be invalidated. + SwRootFrame* pRoot = pWrtShell->GetLayout(); + SwFrame* pPage = pRoot->GetLower(); + SwFrame* pBody = pPage->GetLower(); + SwFrame* pFirstText = pBody->GetLower(); + tools::Rectangle aFirstTextRect = pFirstText->getFrameArea().SVRect(); + CPPUNIT_ASSERT(!aFirstTextRect.Overlaps(m_aInvalidations)); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testCondCollCopy) +{ + // Given a document with a custom Text Body cond style: + SwXTextDocument* pXTextDocument = createDoc("cond-coll-copy.odt"); + uno::Sequence<beans::PropertyValue> aPropertyValues + = { comphelper::makePropertyValue("Style", OUString("Text body")), + comphelper::makePropertyValue("FamilyName", OUString("ParagraphStyles")) }; + dispatchCommand(mxComponent, ".uno:StyleApply", aPropertyValues); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + pWrtShell->SelAll(); + + // When getting the text selection, then make sure it doesn't crash: + uno::Reference<datatransfer::XTransferable2> xTransferable(pXTextDocument->getSelection(), + css::uno::UNO_QUERY); + datatransfer::DataFlavor aFlavor; + aFlavor.MimeType = "text/plain;charset=utf-16"; + aFlavor.DataType = cppu::UnoType<OUString>::get(); + CPPUNIT_ASSERT(xTransferable->isDataFlavorSupported(aFlavor)); + // Without the accompanying fix in place, this test would have crashed. + xTransferable->getTransferData(aFlavor); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testRedlinePortions) +{ + // Given a document with 3 portions: before insert redline (foo), the insert redline (ins) and after insert + // redline (bar): + SwXTextDocument* pXTextDocument = createDoc(); + SwDocShell* pDocShell = pXTextDocument->GetDocShell(); + SwView* pView = pDocShell->GetView(); + pView->SetRedlineAuthor("first"); + pDocShell->SetView(pView); + SwWrtShell* pWrtShell = pDocShell->GetWrtShell(); + pWrtShell->Insert("foo"); + pDocShell->SetChangeRecording(true); + pWrtShell->Insert("ins"); + pDocShell->SetChangeRecording(false); + pWrtShell->Insert("bar after"); + + // When deleting "fooinsbar": + pView->SetRedlineAuthor("second"); + pDocShell->SetView(pView); + pWrtShell->SttEndDoc(/*bStt*/true); + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/true, /*nCount=*/9, /*bBasicCall=*/false); + pDocShell->SetChangeRecording(true); + pWrtShell->Delete(); + + // Then make sure that the portion list is updated, so "bar" can be marked as deleted without + // marking " after" as well: + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "//SwParaPortion/SwLineLayout/SwLinePortion[1]"_ostr, "portion"_ostr, "foo"); + assertXPath(pXmlDoc, "//SwParaPortion/SwLineLayout/SwLinePortion[2]"_ostr, "portion"_ostr, "ins"); + // Without the accompanying fix in place, this test would have failed width: + // - Expected: bar + // - Actual : bar after + // i.e. the portion list was outdated, even " after" was marked as deleted. + assertXPath(pXmlDoc, "//SwParaPortion/SwLineLayout/SwLinePortion[3]"_ostr, "portion"_ostr, "bar"); + assertXPath(pXmlDoc, "//SwParaPortion/SwLineLayout/SwLinePortion[4]"_ostr, "portion"_ostr, " after"); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testContentControl) +{ + // Given a document with a content control: + SwXTextDocument* pXTextDocument = createDoc(); + uno::Reference<text::XText> xText = pXTextDocument->getText(); + uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor(); + xText->insertString(xCursor, "test", /*bAbsorb=*/false); + xCursor->gotoStart(/*bExpand=*/false); + xCursor->gotoEnd(/*bExpand=*/true); + uno::Reference<text::XTextContent> xContentControl( + pXTextDocument->createInstance("com.sun.star.text.ContentControl"), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY); + xContentControlProps->setPropertyValue("Alias", uno::Any(OUString("my alias"))); + xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + pWrtShell->SttEndDoc(/*bStt=*/true); + m_aContentControl.clear(); + + // When entering that content control (chars 2-7 are the content control): + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, /*nCount=*/5, /*bBasicCall=*/false); + + // Then make sure that the callback is emitted: + // Without the accompanying fix in place, this test would have failed, no callback was emitted. + CPPUNIT_ASSERT(!m_aContentControl.isEmpty()); + { + std::stringstream aStream((std::string(m_aContentControl))); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + OString sAction( aTree.get_child("action").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("show"_ostr, sAction); + OString sRectangles( aTree.get_child("rectangles").get_value<std::string>() ); + CPPUNIT_ASSERT(!sRectangles.isEmpty()); + // Without the accompanying fix in place, this test would have failed width: + // uncaught exception of type std::exception (or derived). + // - No such node (alias) + OString sAlias( aTree.get_child("alias").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("my alias"_ostr, sAlias); + } + + // And when leaving that content control: + pWrtShell->SttEndDoc(/*bStt=*/true); + + // Then make sure that the callback is emitted again: + std::stringstream aStream((std::string(m_aContentControl))); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + OString sAction( aTree.get_child("action").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("hide"_ostr, sAction); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testDropDownContentControl) +{ + // Given a document with a dropdown content control: + SwXTextDocument* pXTextDocument = createDoc(); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + uno::Reference<lang::XMultiServiceFactory> xMSF(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XText> xText = xTextDocument->getText(); + uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor(); + xText->insertString(xCursor, "choose an item", /*bAbsorb=*/false); + xCursor->gotoStart(/*bExpand=*/false); + xCursor->gotoEnd(/*bExpand=*/true); + uno::Reference<text::XTextContent> xContentControl( + xMSF->createInstance("com.sun.star.text.ContentControl"), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY); + { + uno::Sequence<beans::PropertyValues> aListItems = { + { + comphelper::makePropertyValue("DisplayText", uno::Any(OUString("red"))), + comphelper::makePropertyValue("Value", uno::Any(OUString("R"))), + }, + { + comphelper::makePropertyValue("DisplayText", uno::Any(OUString("green"))), + comphelper::makePropertyValue("Value", uno::Any(OUString("G"))), + }, + { + comphelper::makePropertyValue("DisplayText", uno::Any(OUString("blue"))), + comphelper::makePropertyValue("Value", uno::Any(OUString("B"))), + }, + }; + xContentControlProps->setPropertyValue("ListItems", uno::Any(aListItems)); + } + xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true); + pWrtShell->SttEndDoc(/*bStt=*/true); + m_aContentControl.clear(); + + // When entering that content control: + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, /*nCount=*/1, /*bBasicCall=*/false); + + // Then make sure that the callback is emitted: + CPPUNIT_ASSERT(!m_aContentControl.isEmpty()); + { + std::stringstream aStream((std::string(m_aContentControl))); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + OString sAction( aTree.get_child("action").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("show"_ostr, sAction); + OString sRectangles( aTree.get_child("rectangles").get_value<std::string>() ); + CPPUNIT_ASSERT(!sRectangles.isEmpty()); + boost::optional<boost::property_tree::ptree&> oItems = aTree.get_child_optional("items"); + CPPUNIT_ASSERT(oItems); + static const std::vector<std::string> vExpected = { "red", "green", "blue" }; + size_t i = 0; + for (const auto& rItem : *oItems) + { + CPPUNIT_ASSERT_EQUAL(vExpected[i++], rItem.second.get_value<std::string>()); + } + } + + // And when selecting the 2nd item (green): + std::map<OUString, OUString> aArguments; + aArguments.emplace("type", "drop-down"); + aArguments.emplace("selected", "1"); + pXTextDocument->executeContentControlEvent(aArguments); + + // Then make sure that the document is updated accordingly: + SwTextNode* pTextNode = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: green + // - Actual : choose an item + // i.e. the document text was not updated. + CPPUNIT_ASSERT_EQUAL(OUString("green"), pTextNode->GetExpandText(pWrtShell->GetLayout())); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testPictureContentControl) +{ + // Given a document with a picture content control: + SwXTextDocument* pXTextDocument = createDoc(); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + uno::Reference<lang::XMultiServiceFactory> xMSF(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XText> xText = xTextDocument->getText(); + uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor(); + uno::Reference<beans::XPropertySet> xTextGraphic( + xMSF->createInstance("com.sun.star.text.TextGraphicObject"), uno::UNO_QUERY); + xTextGraphic->setPropertyValue("AnchorType", + uno::Any(text::TextContentAnchorType_AS_CHARACTER)); + uno::Reference<text::XTextContent> xTextContent(xTextGraphic, uno::UNO_QUERY); + xText->insertTextContent(xCursor, xTextContent, false); + xCursor->gotoStart(/*bExpand=*/false); + xCursor->gotoEnd(/*bExpand=*/true); + uno::Reference<text::XTextContent> xContentControl( + xMSF->createInstance("com.sun.star.text.ContentControl"), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY); + xContentControlProps->setPropertyValue("ShowingPlaceHolder", uno::Any(true)); + xContentControlProps->setPropertyValue("Picture", uno::Any(true)); + xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true); + pWrtShell->SttEndDoc(/*bStt=*/true); + m_aContentControl.clear(); + + // When clicking on that content control: + pWrtShell->GotoObj(/*bNext=*/true, GotoObjFlags::Any); + pWrtShell->EnterSelFrameMode(); + const SwFrameFormat* pFlyFormat = pWrtShell->GetFlyFrameFormat(); + const SwFormatAnchor& rFormatAnchor = pFlyFormat->GetAnchor(); + const SwNode* pAnchorNode = rFormatAnchor.GetAnchorNode(); + const SwTextNode* pTextNode = pAnchorNode->GetTextNode(); + SwTextAttr* pAttr = pTextNode->GetTextAttrForCharAt(0, RES_TXTATR_CONTENTCONTROL); + auto pTextContentControl = static_txtattr_cast<SwTextContentControl*>(pAttr); + auto& rFormatContentControl + = static_cast<SwFormatContentControl&>(pTextContentControl->GetAttr()); + pWrtShell->GotoContentControl(rFormatContentControl); + + // Then make sure that the callback is emitted: + // Without the accompanying fix in place, this test would have failed, no callback was emitted. + CPPUNIT_ASSERT(!m_aContentControl.isEmpty()); + std::stringstream aStream((std::string(m_aContentControl))); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + OString sAction( aTree.get_child("action").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("change-picture"_ostr, sAction); + + // And when replacing the image: + std::map<OUString, OUString> aArguments; + aArguments.emplace("type", "picture"); + OUString aURL = m_directories.getURLFromSrc(u"sw/qa/extras/uiwriter/data/ole2.png"); + aArguments.emplace("changed", aURL); + pXTextDocument->executeContentControlEvent(aArguments); + + // Then make sure that the document is updated accordingly: + uno::Reference<drawing::XShape> xShape = getShape(1); + auto xGraphic = getProperty<uno::Reference<beans::XPropertySet>>(xShape, "Graphic"); + // Without the accompanying fix in place, this test would have failed, xGraphic was empty after + // executeContentControlEvent(). + CPPUNIT_ASSERT(xGraphic.is()); + CPPUNIT_ASSERT_EQUAL(OUString("image/png"), getProperty<OUString>(xGraphic, "MimeType")); + +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testDateContentControl) +{ + // Given a document with a date content control: + SwXTextDocument* pXTextDocument = createDoc(); + SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + uno::Reference<lang::XMultiServiceFactory> xMSF(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY); + uno::Reference<text::XText> xText = xTextDocument->getText(); + uno::Reference<text::XTextCursor> xCursor = xText->createTextCursor(); + xText->insertString(xCursor, "choose a date", /*bAbsorb=*/false); + xCursor->gotoStart(/*bExpand=*/false); + xCursor->gotoEnd(/*bExpand=*/true); + uno::Reference<text::XTextContent> xContentControl( + xMSF->createInstance("com.sun.star.text.ContentControl"), uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xContentControlProps(xContentControl, uno::UNO_QUERY); + xContentControlProps->setPropertyValue("Date", uno::Any(true)); + xContentControlProps->setPropertyValue("DateFormat", uno::Any(OUString("YYYY-MM-DD"))); + xContentControlProps->setPropertyValue("DateLanguage", uno::Any(OUString("en-US"))); + xText->insertTextContent(xCursor, xContentControl, /*bAbsorb=*/true); + pWrtShell->SttEndDoc(/*bStt=*/true); + m_aContentControl.clear(); + + // When entering that content control: + pWrtShell->Right(SwCursorSkipMode::Chars, /*bSelect=*/false, /*nCount=*/1, /*bBasicCall=*/false); + + // Then make sure that the callback is emitted: + CPPUNIT_ASSERT(!m_aContentControl.isEmpty()); + { + std::stringstream aStream((std::string(m_aContentControl))); + boost::property_tree::ptree aTree; + boost::property_tree::read_json(aStream, aTree); + OString sAction( aTree.get_child("action").get_value<std::string>() ); + CPPUNIT_ASSERT_EQUAL("show"_ostr, sAction); + OString sRectangles( aTree.get_child("rectangles").get_value<std::string>() ); + CPPUNIT_ASSERT(!sRectangles.isEmpty()); + boost::optional<boost::property_tree::ptree&> oDate = aTree.get_child_optional("date"); + CPPUNIT_ASSERT(oDate); + } + + // And when selecting a date: + std::map<OUString, OUString> aArguments; + aArguments.emplace("type", "date"); + aArguments.emplace("selected", "2022-05-30T00:00:00Z"); + pXTextDocument->executeContentControlEvent(aArguments); + + // Then make sure that the document is updated accordingly: + SwTextNode* pTextNode = pWrtShell->GetCursor()->GetPointNode().GetTextNode(); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 2022-05-30 + // - Actual : choose a date + // i.e. the document text was not updated. + CPPUNIT_ASSERT_EQUAL(OUString("2022-05-30"), pTextNode->GetExpandText(pWrtShell->GetLayout())); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testAuthorField) +{ + SwXTextDocument* pXTextDocument = createDoc(); + static constexpr OUString sAuthor(u"Abcd Xyz"_ustr); + + uno::Sequence<beans::PropertyValue> aPropertyValues1(comphelper::InitPropertySequence( + { + {".uno:Author", uno::Any(sAuthor)}, + })); + pXTextDocument->initializeForTiledRendering(aPropertyValues1); + + auto insertAuthorField = [this]() + { + uno::Reference<lang::XMultiServiceFactory> const xMSF(mxComponent, uno::UNO_QUERY_THROW); + uno::Reference<text::XTextDocument> const xTD(mxComponent, uno::UNO_QUERY_THROW); + + auto const xText = xTD->getText(); + auto const xTextCursor = xText->createTextCursor(); + CPPUNIT_ASSERT(xTextCursor.is()); + + xTextCursor->gotoEnd(false); + + uno::Reference<text::XTextField> const xTextField( + xMSF->createInstance("com.sun.star.text.textfield.Author"), uno::UNO_QUERY_THROW); + + uno::Reference<beans::XPropertySet> xTextFieldProps(xTextField, uno::UNO_QUERY_THROW); + xTextFieldProps->setPropertyValue("FullName", uno::Any(true)); + + xText->insertTextContent(xTextCursor, xTextField, false); + }; + + insertAuthorField(); + Scheduler::ProcessEventsToIdle(); + + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + + assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion[1]/SwLineLayout[1]/SwFieldPortion[1]"_ostr, "expand"_ostr, sAuthor); +} + +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSavedAuthorField) +{ + SwXTextDocument* pXTextDocument = createDoc("savedauthorfield.odt"); + static constexpr OUString sAuthor(u"XYZ ABCD"_ustr); + uno::Sequence<beans::PropertyValue> aPropertyValues1(comphelper::InitPropertySequence( + { + {".uno:Author", uno::Any(sAuthor)}, + })); + pXTextDocument->initializeForTiledRendering(aPropertyValues1); + + Scheduler::ProcessEventsToIdle(); + + xmlDocUniquePtr pXmlDoc = parseLayoutDump(); + assertXPath(pXmlDoc, "/root/page[1]/body/txt[1]/SwParaPortion[1]/SwLineLayout[1]/SwFieldPortion[1]"_ostr, "expand"_ostr, sAuthor); +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |