summaryrefslogtreecommitdiffstats
path: root/sc/qa/unit/tiledrendering
diff options
context:
space:
mode:
Diffstat (limited to 'sc/qa/unit/tiledrendering')
-rw-r--r--sc/qa/unit/tiledrendering/data/RangeCopyPaste.odsbin0 -> 10034 bytes
-rw-r--r--sc/qa/unit/tiledrendering/data/cursor-away.odsbin0 -> 9230 bytes
-rw-r--r--sc/qa/unit/tiledrendering/data/empty.odsbin0 -> 6727 bytes
-rw-r--r--sc/qa/unit/tiledrendering/data/insert_delete_sheet.odsbin0 -> 6977 bytes
-rw-r--r--sc/qa/unit/tiledrendering/data/invalidation-loop.fods40
-rw-r--r--sc/qa/unit/tiledrendering/data/multiline.odsbin0 -> 10413 bytes
-rw-r--r--sc/qa/unit/tiledrendering/data/select-row-cols.odsbin0 -> 9030 bytes
-rw-r--r--sc/qa/unit/tiledrendering/data/shape.odsbin0 -> 9170 bytes
-rw-r--r--sc/qa/unit/tiledrendering/data/small.odsbin0 -> 7755 bytes
-rw-r--r--sc/qa/unit/tiledrendering/data/smile.pngbin0 -> 10903 bytes
-rw-r--r--sc/qa/unit/tiledrendering/data/sort-range.odsbin0 -> 7930 bytes
-rw-r--r--sc/qa/unit/tiledrendering/data/two_sheets.odsbin0 -> 7082 bytes
-rw-r--r--sc/qa/unit/tiledrendering/data/validity.xlsxbin0 -> 5702 bytes
-rw-r--r--sc/qa/unit/tiledrendering/tiledrendering.cxx3118
14 files changed, 3158 insertions, 0 deletions
diff --git a/sc/qa/unit/tiledrendering/data/RangeCopyPaste.ods b/sc/qa/unit/tiledrendering/data/RangeCopyPaste.ods
new file mode 100644
index 0000000000..9d5e2495a2
--- /dev/null
+++ b/sc/qa/unit/tiledrendering/data/RangeCopyPaste.ods
Binary files differ
diff --git a/sc/qa/unit/tiledrendering/data/cursor-away.ods b/sc/qa/unit/tiledrendering/data/cursor-away.ods
new file mode 100644
index 0000000000..c593fc7db0
--- /dev/null
+++ b/sc/qa/unit/tiledrendering/data/cursor-away.ods
Binary files differ
diff --git a/sc/qa/unit/tiledrendering/data/empty.ods b/sc/qa/unit/tiledrendering/data/empty.ods
new file mode 100644
index 0000000000..3cdfa65704
--- /dev/null
+++ b/sc/qa/unit/tiledrendering/data/empty.ods
Binary files differ
diff --git a/sc/qa/unit/tiledrendering/data/insert_delete_sheet.ods b/sc/qa/unit/tiledrendering/data/insert_delete_sheet.ods
new file mode 100644
index 0000000000..b0ee4e005e
--- /dev/null
+++ b/sc/qa/unit/tiledrendering/data/insert_delete_sheet.ods
Binary files differ
diff --git a/sc/qa/unit/tiledrendering/data/invalidation-loop.fods b/sc/qa/unit/tiledrendering/data/invalidation-loop.fods
new file mode 100644
index 0000000000..f7594c3ec4
--- /dev/null
+++ b/sc/qa/unit/tiledrendering/data/invalidation-loop.fods
@@ -0,0 +1,40 @@
+<?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:presentation="urn:oasis:names:tc:opendocument:xmlns:presentation: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" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.spreadsheet">
+ <office:styles>
+ </office:styles>
+ <office:automatic-styles>
+ <style:style style:name="co1" style:family="table-column">
+ <style:table-column-properties fo:break-before="auto" style:column-width="17.73mm"/>
+ </style:style>
+ <style:style style:name="co2" style:family="table-column">
+ <style:table-column-properties fo:break-before="auto" style:column-width="19.84mm"/>
+ </style:style>
+ <style:page-layout style:name="pm1">
+ <style:page-layout-properties fo:page-width="210.01mm" fo:page-height="297mm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="24.99mm" fo:margin-bottom="12.51mm" fo:margin-left="20mm" fo:margin-right="20mm" style:first-page-number="continue" style:scale-to="100%" style:writing-mode="lr-tb"/>
+ </style:page-layout>
+ </office:automatic-styles>
+ <office:master-styles>
+ <style:master-page style:name="PageStyle_5f_Branco" style:display-name="PageStyle_Branco" style:page-layout-name="pm1"/>
+ </office:master-styles>
+ <office:body>
+ <office:spreadsheet>
+ <table:table table:name="Branco" table:style-name="ta1">
+ <office:forms form:automatic-focus="false" form:apply-design-mode="false">
+ <form:form form:name="Standard" form:apply-filter="true" form:control-implementation="ooo:com.sun.star.form.component.Form" office:target-frame="">
+ <form:fixed-text form:name="Label2" form:control-implementation="ooo:com.sun.star.form.component.FixedText" xml:id="control1" form:id="control1" form:multi-line="true"/>
+ </form:form>
+ </office:forms>
+ <table:table-column table:style-name="co1" table:number-columns-repeated="11" table:default-cell-style-name="Default"/>
+ <table:table-column table:style-name="co2" table:number-columns-repeated="2" table:default-cell-style-name="Default"/>
+ <table:table-column table:style-name="co1" table:number-columns-repeated="1011" table:default-cell-style-name="Default"/>
+ <table:table-row>
+ <table:table-cell>
+ <draw:g draw:z-index="0" draw:name="Grupo 4">
+ <draw:control draw:name="Label2" draw:text-style-name="P1" svg:width="259.72mm" svg:height="155.83mm" svg:x="17.73mm" svg:y="4.26mm" draw:control="control1"/>
+ </draw:g>
+ </table:table-cell>
+ </table:table-row>
+ </table:table>
+ </office:spreadsheet>
+ </office:body>
+</office:document>
diff --git a/sc/qa/unit/tiledrendering/data/multiline.ods b/sc/qa/unit/tiledrendering/data/multiline.ods
new file mode 100644
index 0000000000..847835690a
--- /dev/null
+++ b/sc/qa/unit/tiledrendering/data/multiline.ods
Binary files differ
diff --git a/sc/qa/unit/tiledrendering/data/select-row-cols.ods b/sc/qa/unit/tiledrendering/data/select-row-cols.ods
new file mode 100644
index 0000000000..83cb8f1b38
--- /dev/null
+++ b/sc/qa/unit/tiledrendering/data/select-row-cols.ods
Binary files differ
diff --git a/sc/qa/unit/tiledrendering/data/shape.ods b/sc/qa/unit/tiledrendering/data/shape.ods
new file mode 100644
index 0000000000..36da8a70c1
--- /dev/null
+++ b/sc/qa/unit/tiledrendering/data/shape.ods
Binary files differ
diff --git a/sc/qa/unit/tiledrendering/data/small.ods b/sc/qa/unit/tiledrendering/data/small.ods
new file mode 100644
index 0000000000..ebaae64a09
--- /dev/null
+++ b/sc/qa/unit/tiledrendering/data/small.ods
Binary files differ
diff --git a/sc/qa/unit/tiledrendering/data/smile.png b/sc/qa/unit/tiledrendering/data/smile.png
new file mode 100644
index 0000000000..ba0b89172b
--- /dev/null
+++ b/sc/qa/unit/tiledrendering/data/smile.png
Binary files differ
diff --git a/sc/qa/unit/tiledrendering/data/sort-range.ods b/sc/qa/unit/tiledrendering/data/sort-range.ods
new file mode 100644
index 0000000000..cd9e4e4019
--- /dev/null
+++ b/sc/qa/unit/tiledrendering/data/sort-range.ods
Binary files differ
diff --git a/sc/qa/unit/tiledrendering/data/two_sheets.ods b/sc/qa/unit/tiledrendering/data/two_sheets.ods
new file mode 100644
index 0000000000..6aa1d03be6
--- /dev/null
+++ b/sc/qa/unit/tiledrendering/data/two_sheets.ods
Binary files differ
diff --git a/sc/qa/unit/tiledrendering/data/validity.xlsx b/sc/qa/unit/tiledrendering/data/validity.xlsx
new file mode 100644
index 0000000000..54a92acd59
--- /dev/null
+++ b/sc/qa/unit/tiledrendering/data/validity.xlsx
Binary files differ
diff --git a/sc/qa/unit/tiledrendering/tiledrendering.cxx b/sc/qa/unit/tiledrendering/tiledrendering.cxx
new file mode 100644
index 0000000000..00fda9336f
--- /dev/null
+++ b/sc/qa/unit/tiledrendering/tiledrendering.cxx
@@ -0,0 +1,3118 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <test/unoapixml_test.hxx>
+#include <test/helper/transferable.hxx>
+#include <boost/property_tree/json_parser.hpp>
+
+#include <LibreOfficeKit/LibreOfficeKitEnums.h>
+#include <com/sun/star/frame/Desktop.hpp>
+#include <com/sun/star/frame/DispatchHelper.hpp>
+#include <com/sun/star/datatransfer/clipboard/LokClipboard.hpp>
+#include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp>
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <comphelper/servicehelper.hxx>
+#include <osl/conditn.hxx>
+#include <sfx2/dispatch.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <svl/stritem.hxx>
+
+#include <comphelper/lok.hxx>
+#include <comphelper/propertyvalue.hxx>
+#include <sfx2/childwin.hxx>
+#include <sfx2/lokhelper.hxx>
+#include <svx/svdpage.hxx>
+#include <vcl/scheduler.hxx>
+#include <vcl/vclevent.hxx>
+#include <vcl/virdev.hxx>
+#include <sc.hrc>
+#include <comphelper/string.hxx>
+#include <tools/json_writer.hxx>
+#include <docoptio.hxx>
+#include <postit.hxx>
+#include <test/lokcallback.hxx>
+#include <osl/file.hxx>
+#include <unotools/tempfile.hxx>
+
+#include <chrono>
+#include <cstddef>
+
+#include <attrib.hxx>
+#include <scitems.hxx>
+#include <tabvwsh.hxx>
+#include <docsh.hxx>
+#include <document.hxx>
+#include <docuno.hxx>
+#include <drwlayer.hxx>
+#include <editutil.hxx>
+#include <undomanager.hxx>
+
+using namespace css;
+
+static std::ostream& operator<<(std::ostream& os, ViewShellId const & id)
+{
+ os << static_cast<sal_Int32>(id); return os;
+}
+
+class ScTiledRenderingTest : public UnoApiXmlTest
+{
+public:
+ ScTiledRenderingTest();
+ virtual void setUp() override;
+ virtual void tearDown() override;
+
+protected:
+ ScModelObj* createDoc(const char* pName);
+ void setupLibreOfficeKitViewCallback(SfxViewShell* pViewShell);
+ static void callback(int nType, const char* pPayload, void* pData);
+ void callbackImpl(int nType, const char* pPayload);
+
+ /// document size changed callback.
+ osl::Condition m_aDocSizeCondition;
+ Size m_aDocumentSize;
+
+ TestLokCallbackWrapper m_callbackWrapper;
+};
+
+ScTiledRenderingTest::ScTiledRenderingTest()
+ : UnoApiXmlTest("/sc/qa/unit/tiledrendering/data/"),
+ m_callbackWrapper(&callback, this)
+{
+}
+
+void ScTiledRenderingTest::setUp()
+{
+ UnoApiXmlTest::setUp();
+
+ comphelper::LibreOfficeKit::setActive(true);
+}
+
+void ScTiledRenderingTest::tearDown()
+{
+ if (mxComponent.is())
+ {
+ mxComponent->dispose();
+ mxComponent.clear();
+ }
+
+ m_callbackWrapper.clear();
+
+ comphelper::LibreOfficeKit::resetCompatFlag();
+
+ comphelper::LibreOfficeKit::setActive(false);
+
+ UnoApiXmlTest::tearDown();
+}
+
+ScModelObj* ScTiledRenderingTest::createDoc(const char* pName)
+{
+ loadFromFile(OUString::createFromAscii(pName));
+
+ ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
+ CPPUNIT_ASSERT(pModelObj);
+ pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ return pModelObj;
+}
+
+void ScTiledRenderingTest::setupLibreOfficeKitViewCallback(SfxViewShell* pViewShell)
+{
+ pViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+ m_callbackWrapper.setLOKViewId(SfxLokHelper::getView(pViewShell));
+}
+
+void ScTiledRenderingTest::callback(int nType, const char* pPayload, void* pData)
+{
+ static_cast<ScTiledRenderingTest*>(pData)->callbackImpl(nType, pPayload);
+}
+
+void ScTiledRenderingTest::callbackImpl(int nType, const char* pPayload)
+{
+ switch (nType)
+ {
+ case LOK_CALLBACK_DOCUMENT_SIZE_CHANGED:
+ {
+ OString aPayload(pPayload);
+ sal_Int32 nIndex = 0;
+ OString aToken = aPayload.getToken(0, ',', nIndex);
+ m_aDocumentSize.setWidth(aToken.toInt32());
+ aToken = aPayload.getToken(0, ',', nIndex);
+ m_aDocumentSize.setHeight(aToken.toInt32());
+ m_aDocSizeCondition.set();
+ }
+ break;
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testRowColumnSelections)
+{
+ ScModelObj* pModelObj = createDoc("select-row-cols.ods");
+
+ // Select the 5th row with no modifier
+ uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
+ { "Row", uno::Any(sal_Int32(5 - 1)) },
+ { "Modifier", uno::Any(sal_uInt16(0)) }
+ }));
+ dispatchCommand(mxComponent, ".uno:SelectRow", aArgs);
+
+ // Check if it is selected
+ OString aResult = apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr);
+ OString aExpected("1\t2\t3\t4\t5\t6\t7\t8\t9\t10\t11\t12\t13\t14\t15\t16\t17\t18\t19\t20\t21\n"_ostr);
+ CPPUNIT_ASSERT_EQUAL(aExpected, aResult);
+
+ // Select the 10th row with shift modifier
+ aArgs = comphelper::InitPropertySequence({ { "Row", uno::Any(static_cast<sal_Int32>(10 - 1)) },
+ { "Modifier", uno::Any(KEY_SHIFT) } });
+ dispatchCommand(mxComponent, ".uno:SelectRow", aArgs);
+
+ // Check if all the rows from 5th to 10th get selected
+ aResult = apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr);
+ aExpected = "1\t2\t3\t4\t5\t6\t7\t8\t9\t10\t11\t12\t13\t14\t15\t16\t17\t18\t19\t20\t21\n2\t3\t4\t5\t6\t7\t8\t9\t10\t11\t12\t13\t14\t15\t16\t17\t18\t19\t20\t21\t22\n3\t4\t5\t6\t7\t8\t9\t10\t11\t12\t13\t14\t15\t16\t17\t18\t19\t20\t21\t22\t23\n4\t5\t6\t7\t8\t9\t10\t11\t12\t13\t14\t15\t16\t17\t18\t19\t20\t21\t22\t23\t24\n5\t6\t7\t8\t9\t10\t11\t12\t13\t14\t15\t16\t17\t18\t19\t20\t21\t22\t23\t24\t25\n6\t7\t8\t9\t10\t11\t12\t13\t14\t15\t16\t17\t18\t19\t20\t21\t22\t23\t24\t25\t26\n"_ostr;
+ CPPUNIT_ASSERT_EQUAL(aExpected, aResult);
+
+ // Select the 10th row with ctrl modifier
+ aArgs = comphelper::InitPropertySequence({ { "Row", uno::Any(static_cast<sal_Int32>(13 - 1)) },
+ { "Modifier", uno::Any(KEY_MOD1) } });
+ dispatchCommand(mxComponent, ".uno:SelectRow", aArgs);
+
+ // When we copy this, we don't get anything useful, but we must not crash
+ // (used to happen)
+ aResult = apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr);
+ CPPUNIT_ASSERT_EQUAL(OString(), aResult);
+
+ // TODO check that we really selected what we wanted here
+
+ // Select Column 5 with ctrl modifier
+ aArgs = comphelper::InitPropertySequence({ { "Col", uno::Any(static_cast<sal_Int32>(5 - 1)) },
+ { "Modifier", uno::Any(KEY_MOD1) } });
+ dispatchCommand(mxComponent, ".uno:SelectColumn", aArgs);
+
+ // When we copy this, we don't get anything useful, but we must not crash
+ // (used to happen)
+ aResult = apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr);
+ CPPUNIT_ASSERT_EQUAL(OString(), aResult);
+
+ // TODO check that we really selected what we wanted here
+
+ // Test for deselection of already selected rows
+ // First Deselect Row 13 because copy doesn't work for multiple selections
+ aArgs = comphelper::InitPropertySequence({ { "Row", uno::Any(static_cast<sal_Int32>(13 - 1)) },
+ { "Modifier", uno::Any(KEY_MOD1) } });
+ dispatchCommand(mxComponent, ".uno:SelectRow", aArgs);
+
+ // Deselect row 10
+ aArgs = comphelper::InitPropertySequence({ { "Row", uno::Any(static_cast<sal_Int32>(10 - 1)) },
+ { "Modifier", uno::Any(KEY_MOD1) } });
+ dispatchCommand(mxComponent, ".uno:SelectRow", aArgs);
+
+ // Click at row 6 holding shift
+ aArgs = comphelper::InitPropertySequence({ { "Row", uno::Any(static_cast<sal_Int32>(6 - 1)) },
+ { "Modifier", uno::Any(KEY_SHIFT) } });
+ dispatchCommand(mxComponent, ".uno:SelectRow", aArgs);
+
+ // only row 5 should remain selected
+ aResult = apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr);
+ aExpected = "1\t2\t3\t4\t5\t6\t7\t8\t9\t10\t11\t12\t13\t14\t15\t16\t17\t18\t19\t20\t21\n"_ostr;
+ CPPUNIT_ASSERT_EQUAL(aExpected, aResult);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testPartHash)
+{
+ ScModelObj* pModelObj = createDoc("sort-range.ods");
+
+ int nParts = pModelObj->getParts();
+ for (int it = 0; it < nParts; it++)
+ {
+ CPPUNIT_ASSERT(!pModelObj->getPartHash(it).isEmpty());
+ }
+
+ // check part that it does not exists
+ CPPUNIT_ASSERT(pModelObj->getPartHash(100).isEmpty());
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDocumentSize)
+{
+ ScModelObj* pModelObj = createDoc("sort-range.ods");
+ ScDocShell* pDocSh = dynamic_cast< ScDocShell* >( pModelObj->GetEmbeddedObject() );
+ CPPUNIT_ASSERT(pDocSh);
+
+ ScTabViewShell* pViewShell = pDocSh->GetBestViewShell(false);
+ CPPUNIT_ASSERT(pViewShell);
+
+ setupLibreOfficeKitViewCallback(pViewShell);
+
+ // check initial document size
+ Size aDocSize = pModelObj->getDocumentSize();
+ CPPUNIT_ASSERT(aDocSize.Width() > 0);
+ CPPUNIT_ASSERT(aDocSize.Height() > 0);
+
+ // Set cursor column
+ pViewShell->SetCursor(100, 0);
+ // 2 seconds
+ osl::Condition::Result aResult = m_aDocSizeCondition.wait(std::chrono::seconds(2));
+ CPPUNIT_ASSERT_EQUAL(osl::Condition::result_ok, aResult);
+
+ // Set cursor row
+ pViewShell->SetCursor(0, 100);
+ // 2 seconds
+ aResult = m_aDocSizeCondition.wait(std::chrono::seconds(2));
+ CPPUNIT_ASSERT_EQUAL(osl::Condition::result_ok, aResult);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testEmptyColumnSelection)
+{
+ ScModelObj* pModelObj = createDoc("select-row-cols.ods");
+
+ // Select empty column, 1000
+ uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
+ { "Col", uno::Any(sal_Int32(1000 - 1)) },
+ { "Modifier", uno::Any(sal_uInt16(0)) }
+ }));
+ dispatchCommand(mxComponent, ".uno:SelectColumn", aArgs);
+
+ // should be an empty string
+ CPPUNIT_ASSERT_EQUAL(OString(), apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr));
+}
+
+namespace
+{
+struct EditCursorMessage final {
+ tools::Rectangle m_aRelRect;
+ Point m_aRefPoint;
+
+ void clear()
+ {
+ m_aRelRect.SetEmpty();
+ m_aRefPoint = Point(-1, -1);
+ }
+
+ bool empty()
+ {
+ return m_aRelRect.IsEmpty() &&
+ m_aRefPoint.X() == -1 &&
+ m_aRefPoint.Y() == -1;
+ }
+
+ void parseMessage(const char* pMessage)
+ {
+ clear();
+ if (!pMessage || !comphelper::LibreOfficeKit::isCompatFlagSet(
+ comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs) ||
+ !comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation())
+ return;
+
+ std::stringstream aStream(pMessage);
+ boost::property_tree::ptree aTree;
+ boost::property_tree::read_json(aStream, aTree);
+ std::string aVal;
+ boost::property_tree::ptree::const_assoc_iterator it = aTree.find("refpoint");
+ if (it != aTree.not_found())
+ aVal = aTree.get_child("refpoint").get_value<std::string>();
+ else
+ return; // happens in testTextBoxInsert test
+
+ uno::Sequence<OUString> aSeq = comphelper::string::convertCommaSeparated(OUString::createFromAscii(aVal));
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aSeq.getLength());
+ m_aRefPoint.setX(aSeq[0].toInt32());
+ m_aRefPoint.setY(aSeq[1].toInt32());
+
+ aVal = aTree.get_child("relrect").get_value<std::string>();
+ aSeq = comphelper::string::convertCommaSeparated(OUString::createFromAscii(aVal));
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(4), aSeq.getLength());
+ m_aRelRect.SetLeft(aSeq[0].toInt32());
+ m_aRelRect.SetTop(aSeq[1].toInt32());
+ m_aRelRect.setWidth(aSeq[2].toInt32());
+ m_aRelRect.setHeight(aSeq[3].toInt32());
+ }
+
+ tools::Rectangle getBounds()
+ {
+ tools::Rectangle aBounds = m_aRelRect;
+ aBounds.Move(m_aRefPoint.X(), m_aRefPoint.Y());
+ return aBounds;
+ }
+};
+
+struct TextSelectionMessage
+{
+ std::vector<tools::Rectangle> m_aRelRects;
+ Point m_aRefPoint;
+
+ void clear() {
+ m_aRefPoint.setX(0);
+ m_aRefPoint.setY(0);
+ m_aRelRects.clear();
+ }
+
+ bool empty() {
+ return m_aRelRects.empty();
+ }
+
+ void parseMessage(const char* pMessage)
+ {
+ clear();
+ if (!pMessage)
+ return;
+
+ std::string aStr(pMessage);
+ if (aStr.find(",") == std::string::npos)
+ return;
+
+ size_t nRefDelimStart = aStr.find("::");
+ std::string aRectListString = (nRefDelimStart == std::string::npos) ? aStr : aStr.substr(0, nRefDelimStart);
+ std::string aRefPointString = (nRefDelimStart == std::string::npos) ?
+ std::string("0, 0") :
+ aStr.substr(nRefDelimStart + 2, aStr.length() - 2 - nRefDelimStart);
+ uno::Sequence<OUString> aSeq = comphelper::string::convertCommaSeparated(OUString::createFromAscii(aRefPointString));
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aSeq.getLength());
+ m_aRefPoint.setX(aSeq[0].toInt32());
+ m_aRefPoint.setY(aSeq[1].toInt32());
+
+ size_t nStart = 0;
+ size_t nEnd = aRectListString.find(";");
+ if (nEnd == std::string::npos)
+ nEnd = aRectListString.length();
+ do
+ {
+ std::string aRectString = aRectListString.substr(nStart, nEnd - nStart);
+ {
+ aSeq = comphelper::string::convertCommaSeparated(OUString::createFromAscii(aRectString));
+ CPPUNIT_ASSERT_EQUAL(sal_Int32(4), aSeq.getLength());
+ tools::Rectangle aRect;
+ aRect.SetLeft(aSeq[0].toInt32());
+ aRect.SetTop(aSeq[1].toInt32());
+ aRect.setWidth(aSeq[2].toInt32());
+ aRect.setHeight(aSeq[3].toInt32());
+
+ m_aRelRects.push_back(aRect);
+ }
+
+ nStart = nEnd + 1;
+ nEnd = aRectListString.find(";", nStart);
+ }
+ while(nEnd != std::string::npos);
+ }
+
+ tools::Rectangle getBounds(size_t nIndex)
+ {
+ if (nIndex >= m_aRelRects.size())
+ return tools::Rectangle();
+
+ tools::Rectangle aBounds = m_aRelRects[nIndex];
+ aBounds.Move(m_aRefPoint.X(), m_aRefPoint.Y());
+ return aBounds;
+ }
+
+};
+
+/// A view callback tracks callbacks invoked on one specific view.
+class ViewCallback final
+{
+ SfxViewShell* mpViewShell;
+ int mnView;
+public:
+ bool m_bOwnCursorInvalidated;
+ bool m_bViewCursorInvalidated;
+ bool m_bTextViewSelectionInvalidated;
+ bool m_bGraphicSelection;
+ bool m_bGraphicViewSelection;
+ bool m_bFullInvalidateTiles;
+ bool m_bInvalidateTiles;
+ std::vector<tools::Rectangle> m_aInvalidations;
+ tools::Rectangle m_aCellCursorBounds;
+ std::vector<int> m_aInvalidationsParts;
+ std::vector<int> m_aInvalidationsMode;
+ bool m_bViewLock;
+ OString m_sCellFormula;
+ boost::property_tree::ptree m_aCommentCallbackResult;
+ EditCursorMessage m_aInvalidateCursorResult;
+ TextSelectionMessage m_aTextSelectionResult;
+ OString m_sInvalidateHeader;
+ OString m_sInvalidateSheetGeometry;
+ OString m_ShapeSelection;
+ TestLokCallbackWrapper m_callbackWrapper;
+
+ ViewCallback(bool bDeleteListenerOnDestruct=true)
+ : m_bOwnCursorInvalidated(false),
+ m_bViewCursorInvalidated(false),
+ m_bTextViewSelectionInvalidated(false),
+ m_bGraphicSelection(false),
+ m_bGraphicViewSelection(false),
+ m_bFullInvalidateTiles(false),
+ m_bInvalidateTiles(false),
+ m_bViewLock(false),
+ m_callbackWrapper(&callback, this)
+ {
+ mpViewShell = SfxViewShell::Current();
+ mpViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper);
+ mnView = SfxLokHelper::getView();
+ m_callbackWrapper.setLOKViewId( mnView );
+ if (!bDeleteListenerOnDestruct)
+ mpViewShell = nullptr;
+ }
+
+ ~ViewCallback()
+ {
+ if (mpViewShell)
+ {
+ 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)
+ {
+ switch (nType)
+ {
+ case LOK_CALLBACK_CELL_CURSOR:
+ {
+ m_bOwnCursorInvalidated = true;
+ uno::Sequence<OUString> aSeq = comphelper::string::convertCommaSeparated(OUString::createFromAscii(pPayload));
+ m_aCellCursorBounds = tools::Rectangle();
+ if (aSeq.getLength() == 6) {
+ m_aCellCursorBounds.SetLeft(aSeq[0].toInt32());
+ m_aCellCursorBounds.SetTop(aSeq[1].toInt32());
+ m_aCellCursorBounds.setWidth(aSeq[2].toInt32());
+ m_aCellCursorBounds.setHeight(aSeq[3].toInt32());
+ }
+ }
+ break;
+ case LOK_CALLBACK_CELL_VIEW_CURSOR:
+ {
+ m_bViewCursorInvalidated = true;
+ }
+ break;
+ case LOK_CALLBACK_TEXT_VIEW_SELECTION:
+ {
+ m_bTextViewSelectionInvalidated = true;
+ }
+ 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_GRAPHIC_SELECTION:
+ {
+ m_bGraphicSelection = true;
+ m_ShapeSelection = OString(pPayload);
+ }
+ break;
+ case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION:
+ {
+ m_bGraphicViewSelection = true;
+ }
+ break;
+ case LOK_CALLBACK_INVALIDATE_TILES:
+ {
+ OString text(pPayload);
+ if (text.startsWith("EMPTY"))
+ {
+ m_bFullInvalidateTiles = true;
+ }
+ else
+ {
+ uno::Sequence<OUString> aSeq = comphelper::string::convertCommaSeparated(OUString::createFromAscii(pPayload));
+ CPPUNIT_ASSERT(aSeq.getLength() == 4 || aSeq.getLength() == 6);
+ tools::Rectangle aInvalidationRect;
+ aInvalidationRect.SetLeft(aSeq[0].toInt32());
+ aInvalidationRect.SetTop(aSeq[1].toInt32());
+ aInvalidationRect.setWidth(aSeq[2].toInt32());
+ aInvalidationRect.setHeight(aSeq[3].toInt32());
+ m_aInvalidations.push_back(aInvalidationRect);
+ if (aSeq.getLength() == 6)
+ {
+ m_aInvalidationsParts.push_back(aSeq[4].toInt32());
+ m_aInvalidationsMode.push_back(aSeq[5].toInt32());
+ }
+ m_bInvalidateTiles = true;
+ }
+ }
+ break;
+ case LOK_CALLBACK_CELL_FORMULA:
+ {
+ m_sCellFormula = pPayload;
+ }
+ break;
+ case LOK_CALLBACK_COMMENT:
+ {
+ m_aCommentCallbackResult.clear();
+ std::stringstream aStream(pPayload);
+ boost::property_tree::read_json(aStream, m_aCommentCallbackResult);
+ m_aCommentCallbackResult = m_aCommentCallbackResult.get_child("comment");
+ }
+ break;
+ case LOK_CALLBACK_INVALIDATE_HEADER:
+ {
+ m_sInvalidateHeader = pPayload;
+ }
+ break;
+ case LOK_CALLBACK_INVALIDATE_SHEET_GEOMETRY:
+ {
+ m_sInvalidateSheetGeometry = pPayload;
+ }
+ break;
+ case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR:
+ {
+ m_aInvalidateCursorResult.parseMessage(pPayload);
+ }
+ break;
+ case LOK_CALLBACK_TEXT_SELECTION:
+ {
+ m_aTextSelectionResult.parseMessage(pPayload);
+ }
+ }
+ }
+};
+} //namespace
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testViewCursors)
+{
+ ScModelObj* pModelObj = createDoc("select-row-cols.ods");
+ ViewCallback aView1;
+ SfxLokHelper::createView();
+ pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView2(/*bDeleteListenerOnDestruct*/false);
+ // This was false, the new view did not get the view (cell) cursor of the old view.
+ CPPUNIT_ASSERT(aView2.m_bViewCursorInvalidated);
+ CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DOWN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DOWN);
+ Scheduler::ProcessEventsToIdle();
+ SfxLokHelper::destroyView(SfxLokHelper::getView());
+ CPPUNIT_ASSERT(aView1.m_bViewCursorInvalidated);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSpellOnlineRenderParameter)
+{
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ ScDocument* pDoc = pModelObj->GetDocument();
+ bool bSet = pDoc->GetDocOptions().IsAutoSpell();
+
+ uno::Sequence<beans::PropertyValue> aPropertyValues =
+ {
+ comphelper::makePropertyValue(".uno:SpellOnline", uno::Any(!bSet)),
+ };
+ pModelObj->initializeForTiledRendering(aPropertyValues);
+
+ CPPUNIT_ASSERT_EQUAL(!bSet, pDoc->GetDocOptions().IsAutoSpell());
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testTextViewSelection)
+{
+ // Create two views, and leave the second one current.
+ ScModelObj* pModelObj = createDoc("select-row-cols.ods");
+ ViewCallback aView1;
+ SfxLokHelper::createView();
+ pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView2;
+
+ // Create a selection on two cells in the second view, that's a text selection in LOK terms.
+ aView1.m_bTextViewSelectionInvalidated = false;
+ dispatchCommand(mxComponent, ".uno:GoRightSel", {});
+ // Make sure the first view got its notification.
+ CPPUNIT_ASSERT(aView1.m_bTextViewSelectionInvalidated);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDocumentSizeChanged)
+{
+ // Load a document that doesn't have much content.
+ createDoc("small.ods");
+ setupLibreOfficeKitViewCallback(SfxViewShell::Current());
+
+ // Go to the A30 cell -- that will extend the document size.
+ uno::Sequence<beans::PropertyValue> aPropertyValues =
+ {
+ comphelper::makePropertyValue("ToPoint", OUString("$A$30")),
+ };
+ dispatchCommand(mxComponent, ".uno:GoToCell", aPropertyValues);
+ // Assert that the size in the payload is not 0.
+ CPPUNIT_ASSERT(m_aDocumentSize.getWidth() > 0);
+ CPPUNIT_ASSERT(m_aDocumentSize.getHeight() > 0);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testViewLock)
+{
+ // Load a document that has a shape and create two views.
+ ScModelObj* pModelObj = createDoc("shape.ods");
+ ViewCallback aView1;
+ SfxLokHelper::createView();
+ pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView2;
+
+ // Begin text edit in the second view and assert that the first gets a lock
+ // notification.
+ const ScViewData* pViewData = ScDocShell::GetViewData();
+ ScTabViewShell* pViewShell = pViewData->GetViewShell();
+ CPPUNIT_ASSERT(pViewShell);
+ SdrModel* pDrawModel = pViewData->GetDocument().GetDrawLayer();
+ SdrPage* pDrawPage = pDrawModel->GetPage(0);
+ SdrObject* pObject = pDrawPage->GetObj(0);
+ SdrView* pView = pViewShell->GetScDrawView();
+ aView1.m_bViewLock = false;
+ pView->SdrBeginTextEdit(pObject);
+ CPPUNIT_ASSERT(aView1.m_bViewLock);
+
+ // End text edit in the second view, and assert that the lock is removed in
+ // the first view.
+ pView->SdrEndTextEdit();
+ CPPUNIT_ASSERT(!aView1.m_bViewLock);
+}
+
+namespace
+{
+void lcl_extractHandleParameters(std::string_view selection, sal_uInt32& id, sal_uInt32& x, sal_uInt32& 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>();
+}
+} //namespace
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testMoveShapeHandle)
+{
+ ScModelObj* pModelObj = createDoc("shape.ods");
+ ViewCallback aView1;
+ pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, /*x=*/ 1,/*y=*/ 1,/*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
+ pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, /*x=*/ 1, /*y=*/ 1, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
+ Scheduler::ProcessEventsToIdle();
+
+ CPPUNIT_ASSERT(!aView1.m_ShapeSelection.isEmpty());
+ {
+ sal_uInt32 id, x, y;
+ lcl_extractHandleParameters(aView1.m_ShapeSelection, id, x ,y);
+ sal_uInt32 oldX = x;
+ sal_uInt32 oldY = y;
+ uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
+ {
+ {"HandleNum", uno::Any(id)},
+ {"NewPosX", uno::Any(x+1)},
+ {"NewPosY", uno::Any(y+1)}
+ }));
+ dispatchCommand(mxComponent, ".uno:MoveShapeHandle", aPropertyValues);
+ CPPUNIT_ASSERT(!aView1.m_ShapeSelection.isEmpty());
+ lcl_extractHandleParameters(aView1.m_ShapeSelection, id, x ,y);
+ CPPUNIT_ASSERT_EQUAL(x-1, oldX);
+ CPPUNIT_ASSERT_EQUAL(y-1, oldY);
+ }
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testColRowResize)
+{
+ ScModelObj* pModelObj = createDoc("sort-range.ods");
+ ScDocShell* pDocSh = dynamic_cast< ScDocShell* >( pModelObj->GetEmbeddedObject() );
+ CPPUNIT_ASSERT(pDocSh);
+
+ ScTabViewShell* pViewShell = pDocSh->GetBestViewShell(false);
+ CPPUNIT_ASSERT(pViewShell);
+
+ setupLibreOfficeKitViewCallback(pViewShell);
+
+ ScDocument& rDoc = pDocSh->GetDocument();
+
+ // Col 3, Tab 0
+ uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
+ { "ColumnWidth", uno::Any(sal_uInt16(4000)) }, // 4cm
+ { "Column", uno::Any(sal_Int16(3)) }
+ }));
+ dispatchCommand(mxComponent, ".uno:ColumnWidth", aArgs);
+
+ sal_uInt16 nWidth = o3tl::convert(rDoc.GetColWidth(static_cast<SCCOL>(2), static_cast<SCTAB>(0), false), o3tl::Length::twip, o3tl::Length::mm100);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(4001), nWidth);
+
+ // Row 5, Tab 0
+ uno::Sequence<beans::PropertyValue> aArgs2( comphelper::InitPropertySequence({
+ { "RowHeight", uno::Any(sal_uInt16(2000)) },
+ { "Row", uno::Any(sal_Int16(5)) },
+ }));
+ dispatchCommand(mxComponent, ".uno:RowHeight", aArgs2);
+
+ sal_uInt16 nHeight = o3tl::convert(rDoc.GetRowHeight(static_cast<SCROW>(4), static_cast<SCTAB>(0), false), o3tl::Length::twip, o3tl::Length::mm100);
+ CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(2000), nHeight);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testUndoShells)
+{
+ ScModelObj* pModelObj = createDoc("small.ods");
+ // Clear the currently selected cell.
+ dispatchCommand(mxComponent, ".uno:ClearContents", {});
+
+ auto pDocShell = dynamic_cast<ScDocShell*>(pModelObj->GetEmbeddedObject());
+ CPPUNIT_ASSERT(pDocShell);
+ ScDocument& rDoc = pDocShell->GetDocument();
+ ScUndoManager* pUndoManager = rDoc.GetUndoManager();
+ CPPUNIT_ASSERT(pUndoManager);
+ CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pUndoManager->GetUndoActionCount());
+ sal_Int32 nView1 = SfxLokHelper::getView();
+ // This was -1: ScSimpleUndo did not remember what view shell created it.
+ CPPUNIT_ASSERT_EQUAL(ViewShellId(nView1), pUndoManager->GetUndoAction()->GetViewShellId());
+}
+
+namespace
+{
+bool lcl_hasEditView(const ScViewData& rViewData)
+{
+ bool bResult = false;
+ for (unsigned int i=0; i<4; i++)
+ {
+ bResult = rViewData.HasEditView( static_cast<ScSplitPos>(i) );
+ if (bResult) break;
+ }
+ return bResult;
+}
+} // namespace
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testTextEditViews)
+{
+ ScModelObj* pModelObj = createDoc("small.ods");
+ CPPUNIT_ASSERT(pModelObj);
+ ScViewData* pViewData = ScDocShell::GetViewData();
+ CPPUNIT_ASSERT(pViewData);
+
+ // view #1
+ ViewCallback aView1;
+ CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
+
+ // text edit a cell in view #1
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(lcl_hasEditView(*pViewData));
+
+ // view #2
+ SfxLokHelper::createView();
+ pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView2;
+
+ // move cell cursor i view #2
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DOWN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DOWN);
+ Scheduler::ProcessEventsToIdle();
+
+ // check that text edit view in view #1 has not be killed
+ CPPUNIT_ASSERT(lcl_hasEditView(*pViewData));
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testTextEditViewInvalidations)
+{
+ ScModelObj* pModelObj = createDoc("small.ods");
+ CPPUNIT_ASSERT(pModelObj);
+ ScViewData* pViewData = ScDocShell::GetViewData();
+ CPPUNIT_ASSERT(pViewData);
+
+ // view #1
+ int nView1 = SfxLokHelper::getView();
+ ViewCallback aView1;
+ CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
+
+ // view #2
+ SfxLokHelper::createView();
+ pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView2;
+
+ // text edit a cell in view #1
+ SfxLokHelper::setView(nView1);
+ aView2.m_bInvalidateTiles = false;
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(lcl_hasEditView(*pViewData));
+ CPPUNIT_ASSERT(aView2.m_bInvalidateTiles);
+
+ // text edit a cell in view #1 until
+ // we can be sure we are out of the initial tile
+ for (int i = 0; i < 40; ++i)
+ {
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ }
+ Scheduler::ProcessEventsToIdle();
+
+ // text edit a cell in view #1 inside the new tile and
+ // check that view #2 receive a tile invalidate message
+ aView2.m_bInvalidateTiles = false;
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(aView2.m_bInvalidateTiles);
+
+ // view #3
+ SfxLokHelper::createView();
+ pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView3;
+
+ // text edit a cell in view #1
+ SfxLokHelper::setView(nView1);
+ aView3.m_bInvalidateTiles = false;
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'y', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'y', 0);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(aView3.m_bInvalidateTiles);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testCreateViewGraphicSelection)
+{
+ // Load a document that has a shape and create two views.
+ ScModelObj* pModelObj = createDoc("shape.ods");
+ ViewCallback aView1;
+
+ // Mark the graphic in the first view.
+ const ScViewData* pViewData = ScDocShell::GetViewData();
+ ScTabViewShell* pViewShell = pViewData->GetViewShell();
+ CPPUNIT_ASSERT(pViewShell);
+ SdrModel* pDrawModel = pViewData->GetDocument().GetDrawLayer();
+ SdrPage* pDrawPage = pDrawModel->GetPage(0);
+ SdrObject* pObject = pDrawPage->GetObj(0);
+ SdrView* pView = pViewShell->GetScDrawView();
+ aView1.m_bGraphicSelection = false;
+ aView1.m_bGraphicViewSelection = false;
+ pView->MarkObj(pObject, pView->GetSdrPageView());
+ CPPUNIT_ASSERT(aView1.m_bGraphicSelection);
+
+ // Create a second view.
+ int nView1 = SfxLokHelper::getView();
+ SfxLokHelper::createView();
+ pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView2;
+ CPPUNIT_ASSERT(aView2.m_bGraphicViewSelection);
+ CPPUNIT_ASSERT(aView1.m_bGraphicViewSelection);
+
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testGraphicInvalidate)
+{
+ // Load a document that has a shape and create two views.
+ ScModelObj* pModelObj = createDoc("shape.ods");
+ ViewCallback aView;
+
+ // Click to select graphic
+ aView.m_bGraphicSelection = false;
+ pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, /*x=*/ 1,/*y=*/ 1,/*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
+ pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, /*x=*/ 1, /*y=*/ 1, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(aView.m_bGraphicSelection);
+
+ // Drag Drop graphic
+ aView.m_bGraphicSelection = false;
+ pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, /*x=*/ 1,/*y=*/ 1,/*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
+ pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEMOVE, /*x=*/ 1,/*y=*/ 10,/*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
+ pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, /*x=*/ 1, /*y=*/ 10, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(!aView.m_bFullInvalidateTiles);
+
+ // Check again
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(!aView.m_bFullInvalidateTiles);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testAutoSum)
+{
+ createDoc("small.ods");
+
+ ViewCallback aView;
+
+ uno::Sequence<beans::PropertyValue> aArgs;
+ dispatchCommand(mxComponent, ".uno:AutoSum", aArgs);
+ CPPUNIT_ASSERT(aView.m_sCellFormula.startsWith("=SUM("));
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testHideColRow)
+{
+ createDoc("small.ods");
+ {
+ uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
+ { "Col", uno::Any(sal_Int32(2 - 1)) },
+ { "Modifier", uno::Any(KEY_SHIFT) }
+ }));
+ dispatchCommand(mxComponent, ".uno:SelectColumn", aArgs);
+
+ uno::Sequence<beans::PropertyValue> aArgs2( comphelper::InitPropertySequence({
+ { "Col", uno::Any(sal_Int32(3 - 1)) },
+ { "Modifier", uno::Any(sal_uInt16(0)) }
+ }));
+
+ dispatchCommand(mxComponent, ".uno:SelectColumn", aArgs2);
+ }
+
+ SCCOL nOldCurX = ScDocShell::GetViewData()->GetCurX();
+ SCROW nOldCurY = ScDocShell::GetViewData()->GetCurY();
+ {
+ uno::Sequence<beans::PropertyValue> aArgs;
+ dispatchCommand(mxComponent, ".uno:HideColumn", aArgs);
+ }
+
+ SCCOL nNewCurX = ScDocShell::GetViewData()->GetCurX();
+ SCROW nNewCurY = ScDocShell::GetViewData()->GetCurY();
+ CPPUNIT_ASSERT(nNewCurX > nOldCurX);
+ CPPUNIT_ASSERT_EQUAL(nOldCurY, nNewCurY);
+ {
+ uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
+ { "Row", uno::Any(sal_Int32(6 - 1)) },
+ { "Modifier", uno::Any(KEY_SHIFT) }
+ }));
+ dispatchCommand(mxComponent, ".uno:SelectRow", aArgs);
+
+ uno::Sequence<beans::PropertyValue> aArgs2( comphelper::InitPropertySequence({
+ { "Row", uno::Any(sal_Int32(7 - 1)) },
+ { "Modifier", uno::Any(sal_uInt16(0)) }
+ }));
+ dispatchCommand(mxComponent, ".uno:SelectRow", aArgs2);
+ }
+
+ nOldCurX = ScDocShell::GetViewData()->GetCurX();
+ nOldCurY = ScDocShell::GetViewData()->GetCurY();
+ {
+ uno::Sequence<beans::PropertyValue> aArgs;
+ dispatchCommand(mxComponent, ".uno:HideRow", aArgs);
+ }
+ nNewCurX = ScDocShell::GetViewData()->GetCurX();
+ nNewCurY = ScDocShell::GetViewData()->GetCurY();
+ CPPUNIT_ASSERT(nNewCurY > nOldCurY);
+ CPPUNIT_ASSERT_EQUAL(nOldCurX, nNewCurX);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testInvalidateOnCopyPasteCells)
+{
+ ScModelObj* pModelObj = createDoc("small.ods");
+ CPPUNIT_ASSERT(pModelObj);
+
+ // view
+ ViewCallback aView;
+
+ uno::Sequence<beans::PropertyValue> aArgs;
+ // select and copy cells
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_HOME | KEY_MOD1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_HOME | KEY_MOD1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN | KEY_SHIFT);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN | KEY_SHIFT);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT | KEY_SHIFT);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT | KEY_SHIFT);
+ Scheduler::ProcessEventsToIdle();
+ dispatchCommand(mxComponent, ".uno:Copy", aArgs);
+
+ // move to destination cell
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN | KEY_MOD1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN | KEY_MOD1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_UP);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_UP);
+ Scheduler::ProcessEventsToIdle();
+
+ // paste cells
+ aView.m_bInvalidateTiles = false;
+ dispatchCommand(mxComponent, ".uno:Paste", aArgs);
+ CPPUNIT_ASSERT(aView.m_bInvalidateTiles);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testInvalidateOnInserRowCol)
+{
+ ScModelObj* pModelObj = createDoc("small.ods");
+ CPPUNIT_ASSERT(pModelObj);
+
+ // view
+ ViewCallback aView;
+
+ uno::Sequence<beans::PropertyValue> aArgs;
+ // move downward
+ for (int i = 0; i < 200; ++i)
+ {
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
+ }
+ Scheduler::ProcessEventsToIdle();
+
+ // insert row
+ aView.m_bInvalidateTiles = false;
+ aView.m_aInvalidations.clear();
+ dispatchCommand(mxComponent, ".uno:InsertRows", aArgs);
+ CPPUNIT_ASSERT(aView.m_bInvalidateTiles);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aView.m_aInvalidations.size());
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(-75, 51240, 32212230, 63990), aView.m_aInvalidations[0]);
+
+ // move on the right
+ for (int i = 0; i < 200; ++i)
+ {
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT);
+ }
+ Scheduler::ProcessEventsToIdle();
+
+ // insert column
+ aView.m_bInvalidateTiles = false;
+ aView.m_aInvalidations.clear();
+ dispatchCommand(mxComponent, ".uno:InsertColumns", aArgs);
+ CPPUNIT_ASSERT(aView.m_bInvalidateTiles);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aView.m_aInvalidations.size());
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(254925, -15, 32212230, 63990), aView.m_aInvalidations[0]);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testCommentCallback)
+{
+ // Comments callback are emitted only if tiled annotations are off
+ comphelper::LibreOfficeKit::setTiledAnnotations(false);
+
+ // FIXME: Hack because previous tests do not destroy ScDocument(with annotations) on exit (?).
+ ScPostIt::mnLastPostItId = 1;
+
+ {
+ ScModelObj* pModelObj = createDoc("small.ods");
+ ViewCallback aView1;
+ int nView1 = SfxLokHelper::getView();
+
+ // Create a 2nd view
+ SfxLokHelper::createView();
+ pModelObj->initializeForTiledRendering({});
+ ViewCallback aView2;
+
+ SfxLokHelper::setView(nView1);
+
+ ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
+ if (pTabViewShell)
+ pTabViewShell->SetCursor(4, 4);
+
+ // Add a new comment
+ uno::Sequence<beans::PropertyValue> aArgs(comphelper::InitPropertySequence(
+ {
+ {"Text", uno::Any(OUString("Comment"))},
+ {"Author", uno::Any(OUString("LOK User1"))},
+ }));
+ dispatchCommand(mxComponent, ".uno:InsertAnnotation", aArgs);
+
+ // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action
+ CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
+ CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
+ CPPUNIT_ASSERT_EQUAL(std::string("1"), aView1.m_aCommentCallbackResult.get<std::string>("id"));
+ CPPUNIT_ASSERT_EQUAL(std::string("1"), aView2.m_aCommentCallbackResult.get<std::string>("id"));
+ CPPUNIT_ASSERT_EQUAL(std::string("0"), aView1.m_aCommentCallbackResult.get<std::string>("tab"));
+ CPPUNIT_ASSERT_EQUAL(std::string("0"), aView2.m_aCommentCallbackResult.get<std::string>("tab"));
+ CPPUNIT_ASSERT_EQUAL(std::string("LOK User1"), aView1.m_aCommentCallbackResult.get<std::string>("author"));
+ CPPUNIT_ASSERT_EQUAL(std::string("LOK User1"), aView2.m_aCommentCallbackResult.get<std::string>("author"));
+ CPPUNIT_ASSERT_EQUAL(std::string("Comment"), aView1.m_aCommentCallbackResult.get<std::string>("text"));
+ CPPUNIT_ASSERT_EQUAL(std::string("Comment"), aView2.m_aCommentCallbackResult.get<std::string>("text"));
+ CPPUNIT_ASSERT_EQUAL(std::string("4 4 4 4"), aView1.m_aCommentCallbackResult.get<std::string>("cellRange"));
+ CPPUNIT_ASSERT_EQUAL(std::string("4 4 4 4"), aView2.m_aCommentCallbackResult.get<std::string>("cellRange"));
+
+ // Ensure deleting rows updates comments
+ if (pTabViewShell)
+ pTabViewShell->SetCursor(2, 2);
+
+ dispatchCommand(mxComponent, ".uno:DeleteRows", {});
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(std::string("4 3 4 3"), aView1.m_aCommentCallbackResult.get<std::string>("cellRange"));
+ CPPUNIT_ASSERT_EQUAL(std::string("4 3 4 3"), aView2.m_aCommentCallbackResult.get<std::string>("cellRange"));
+
+ // Ensure deleting columns updates comments
+ if (pTabViewShell)
+ pTabViewShell->SetCursor(2, 2);
+
+ dispatchCommand(mxComponent, ".uno:DeleteColumns", {});
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(std::string("3 3 3 3"), aView1.m_aCommentCallbackResult.get<std::string>("cellRange"));
+ CPPUNIT_ASSERT_EQUAL(std::string("3 3 3 3"), aView2.m_aCommentCallbackResult.get<std::string>("cellRange"));
+
+ std::string aCommentId = aView1.m_aCommentCallbackResult.get<std::string>("id");
+
+ // Edit a comment
+ // Select some random cell, we should be able to edit the cell note without
+ // selecting the cell
+ if (pTabViewShell)
+ pTabViewShell->SetCursor(3, 100);
+ aArgs = comphelper::InitPropertySequence(
+ {
+ {"Id", uno::Any(OUString::createFromAscii(aCommentId))},
+ {"Text", uno::Any(OUString("Edited comment"))},
+ {"Author", uno::Any(OUString("LOK User2"))},
+ });
+ dispatchCommand(mxComponent, ".uno:EditAnnotation", aArgs);
+
+ // We received a LOK_CALLBACK_COMMENT callback with comment 'Modify' action
+ CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
+ CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
+ CPPUNIT_ASSERT_EQUAL(aCommentId, aView1.m_aCommentCallbackResult.get<std::string>("id"));
+ CPPUNIT_ASSERT_EQUAL(aCommentId, aView2.m_aCommentCallbackResult.get<std::string>("id"));
+ CPPUNIT_ASSERT_EQUAL(std::string("LOK User2"), aView1.m_aCommentCallbackResult.get<std::string>("author"));
+ CPPUNIT_ASSERT_EQUAL(std::string("LOK User2"), aView2.m_aCommentCallbackResult.get<std::string>("author"));
+ CPPUNIT_ASSERT_EQUAL(std::string("Edited comment"), aView1.m_aCommentCallbackResult.get<std::string>("text"));
+ CPPUNIT_ASSERT_EQUAL(std::string("Edited comment"), aView2.m_aCommentCallbackResult.get<std::string>("text"));
+ CPPUNIT_ASSERT_EQUAL(std::string("3 3 3 3"), aView1.m_aCommentCallbackResult.get<std::string>("cellRange"));
+ CPPUNIT_ASSERT_EQUAL(std::string("3 3 3 3"), aView2.m_aCommentCallbackResult.get<std::string>("cellRange"));
+
+ // Delete the comment
+ if (pTabViewShell)
+ pTabViewShell->SetCursor(4, 43);
+ aArgs = comphelper::InitPropertySequence(
+ {
+ {"Id", uno::Any(OUString::createFromAscii(aCommentId))}
+ });
+ dispatchCommand(mxComponent, ".uno:DeleteNote", aArgs);
+
+ // We received a LOK_CALLBACK_COMMENT callback with comment 'Remove' action
+ CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
+ CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
+ CPPUNIT_ASSERT_EQUAL(aCommentId, aView1.m_aCommentCallbackResult.get<std::string>("id"));
+ CPPUNIT_ASSERT_EQUAL(aCommentId, aView2.m_aCommentCallbackResult.get<std::string>("id"));
+ }
+ comphelper::LibreOfficeKit::setTiledAnnotations(true);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testUndoLimiting)
+{
+ ScModelObj* pModelObj = createDoc("small.ods");
+ CPPUNIT_ASSERT(pModelObj);
+ ScDocument* pDoc = pModelObj->GetDocument();
+ CPPUNIT_ASSERT(pDoc);
+ ScUndoManager* pUndoManager = pDoc->GetUndoManager();
+ CPPUNIT_ASSERT(pUndoManager);
+
+ // view #1
+ int nView1 = SfxLokHelper::getView();
+ ViewCallback aView1;
+
+ // view #2
+ SfxLokHelper::createView();
+ int nView2 = SfxLokHelper::getView();
+ pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView2;
+
+ // text edit a cell in view #1
+ SfxLokHelper::setView(nView1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
+ Scheduler::ProcessEventsToIdle();
+
+ // check that undo action count in not 0
+ CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
+
+ // try to execute undo in view #2
+ SfxLokHelper::setView(nView2);
+ dispatchCommand(mxComponent, ".uno:Undo", {});
+ // check that undo has not been executed on view #2
+ CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
+
+ // try to execute undo in view #1
+ SfxLokHelper::setView(nView1);
+ dispatchCommand(mxComponent, ".uno:Undo", {});
+ // check that undo has been executed on view #1
+ CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetUndoActionCount());
+
+ // check that redo action count in not 0
+ CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetRedoActionCount());
+
+ // try to execute redo in view #2
+ SfxLokHelper::setView(nView2);
+ dispatchCommand(mxComponent, ".uno:Redo", {});
+ // check that redo has not been executed on view #2
+ CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetRedoActionCount());
+
+ // try to execute redo in view #1
+ SfxLokHelper::setView(nView1);
+ dispatchCommand(mxComponent, ".uno:Redo", {});
+ // check that redo has been executed on view #1
+ CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetRedoActionCount());
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testUndoRepairDispatch)
+{
+ ScModelObj* pModelObj = createDoc("small.ods");
+ CPPUNIT_ASSERT(pModelObj);
+ ScDocument* pDoc = pModelObj->GetDocument();
+ CPPUNIT_ASSERT(pDoc);
+ ScUndoManager* pUndoManager = pDoc->GetUndoManager();
+ CPPUNIT_ASSERT(pUndoManager);
+
+ // view #1
+ int nView1 = SfxLokHelper::getView();
+ ViewCallback aView1;
+
+ // view #2
+ SfxLokHelper::createView();
+ int nView2 = SfxLokHelper::getView();
+ pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView2;
+
+ // text edit a cell in view #1
+ SfxLokHelper::setView(nView1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
+ Scheduler::ProcessEventsToIdle();
+
+ // check that undo action count in not 0
+ CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
+
+ // try to execute undo in view #2
+ SfxLokHelper::setView(nView2);
+ dispatchCommand(mxComponent, ".uno:Undo", {});
+ // check that undo has not been executed on view #2
+ CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
+
+ // try to execute undo in view #2 in repair mode
+ SfxLokHelper::setView(nView2);
+ uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
+ {
+ {"Repair", uno::Any(true)}
+ }));
+ dispatchCommand(mxComponent, ".uno:Undo", aPropertyValues);
+ // check that undo has been executed on view #2 in repair mode
+ CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetUndoActionCount());
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testInsertGraphicInvalidations)
+{
+ ScModelObj* pModelObj = createDoc("small.ods");
+ CPPUNIT_ASSERT(pModelObj);
+ ScViewData* pViewData = ScDocShell::GetViewData();
+ CPPUNIT_ASSERT(pViewData);
+
+ // view
+ ViewCallback aView;
+
+ // we need to paint a tile in the view for triggering the tile invalidation solution
+ 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());
+ pModelObj->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, /*nTilePosY=*/0, /*nTileWidth=*/3840, /*nTileHeight=*/3840);
+ Scheduler::ProcessEventsToIdle();
+
+ // insert an image in view and see if both views are invalidated
+ aView.m_bInvalidateTiles = false;
+ uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
+ { "FileName", uno::Any(createFileURL(u"smile.png")) }
+ }));
+ dispatchCommand(mxComponent, ".uno:InsertGraphic", aArgs);
+ CPPUNIT_ASSERT(aView.m_bInvalidateTiles);
+
+ // undo image insertion in view and see if both views are invalidated
+ aView.m_bInvalidateTiles = false;
+ uno::Sequence<beans::PropertyValue> aArgs2;
+ dispatchCommand(mxComponent, ".uno:Undo", aArgs2);
+ CPPUNIT_ASSERT(aView.m_bInvalidateTiles);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDocumentSizeWithTwoViews)
+{
+ // Open a document that has the cursor far away & paint a tile
+ ScModelObj* pModelObj = createDoc("cursor-away.ods");
+
+ // Set the visible area, and press page down
+ pModelObj->setClientVisibleArea(tools::Rectangle(750, 1861, 20583, 6997));
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEDOWN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEDOWN);
+ Scheduler::ProcessEventsToIdle();
+
+ int nCanvasWidth = 256;
+ int nCanvasHeight = 256;
+ std::vector<unsigned char> aBuffer1(nCanvasWidth * nCanvasHeight * 4);
+ ScopedVclPtrInstance<VirtualDevice> pDevice1(DeviceFormat::WITHOUT_ALPHA);
+ pDevice1->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), Fraction(1.0), Point(), aBuffer1.data());
+ pModelObj->paintTile(*pDevice1, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, /*nTilePosY=*/291840, /*nTileWidth=*/3840, /*nTileHeight=*/3840);
+ Scheduler::ProcessEventsToIdle();
+
+ // Create a new view
+ int nView1 = SfxLokHelper::getView();
+ SfxLokHelper::createView();
+
+ std::vector<unsigned char> aBuffer2(nCanvasWidth * nCanvasHeight * 4);
+ ScopedVclPtrInstance<VirtualDevice> pDevice2(DeviceFormat::WITHOUT_ALPHA);
+ pDevice2->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), Fraction(1.0), Point(), aBuffer2.data());
+ pModelObj->paintTile(*pDevice2, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, /*nTilePosY=*/291840, /*nTileWidth=*/3840, /*nTileHeight=*/3840);
+ Scheduler::ProcessEventsToIdle();
+
+ // Check that the tiles actually have the same content
+ for (size_t i = 0; i < aBuffer1.size(); ++i)
+ CPPUNIT_ASSERT_EQUAL(aBuffer1[i], aBuffer2[i]);
+
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDisableUndoRepair)
+{
+ ScModelObj* pModelObj = createDoc("cursor-away.ods");
+ CPPUNIT_ASSERT(pModelObj);
+
+ // view #1
+ int nView1 = SfxLokHelper::getView();
+ SfxViewShell* pView1 = SfxViewShell::Current();
+
+ // view #2
+ SfxLokHelper::createView();
+ int nView2 = SfxLokHelper::getView();
+ SfxViewShell* pView2 = SfxViewShell::Current();
+ CPPUNIT_ASSERT(pView1 != pView2);
+
+ // both views have UNDO disabled
+ {
+ SfxItemSet aSet1(pView1->GetPool(), svl::Items<SID_UNDO, SID_UNDO>);
+ SfxItemSet aSet2(pView2->GetPool(), svl::Items<SID_UNDO, SID_UNDO>);
+ pView1->GetSlotState(SID_UNDO, nullptr, &aSet1);
+ pView2->GetSlotState(SID_UNDO, nullptr, &aSet2);
+ CPPUNIT_ASSERT_EQUAL(SfxItemState::DISABLED, aSet1.GetItemState(SID_UNDO));
+ CPPUNIT_ASSERT_EQUAL(SfxItemState::DISABLED, aSet2.GetItemState(SID_UNDO));
+ }
+
+ // text edit a cell in view #1
+ SfxLokHelper::setView(nView1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'h', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'h', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
+ Scheduler::ProcessEventsToIdle();
+ // view1 has UNDO enabled, view2 is in UNDO-repair
+ {
+ SfxItemSet aSet1(pView1->GetPool(), svl::Items<SID_UNDO, SID_UNDO>);
+ SfxItemSet aSet2(pView2->GetPool(), svl::Items<SID_UNDO, SID_UNDO>);
+ pView1->GetSlotState(SID_UNDO, nullptr, &aSet1);
+ pView2->GetSlotState(SID_UNDO, nullptr, &aSet2);
+ CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aSet1.GetItemState(SID_UNDO));
+ CPPUNIT_ASSERT(dynamic_cast< const SfxStringItem* >(aSet1.GetItem(SID_UNDO)));
+ CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aSet2.GetItemState(SID_UNDO));
+ CPPUNIT_ASSERT(dynamic_cast< const SfxUInt32Item* >(aSet2.GetItem(SID_UNDO)));
+ const SfxUInt32Item* pUInt32Item = dynamic_cast<const SfxUInt32Item*>(aSet2.GetItem(SID_UNDO));
+ CPPUNIT_ASSERT(pUInt32Item);
+ CPPUNIT_ASSERT_EQUAL(static_cast< sal_uInt32 >(SID_REPAIRPACKAGE), pUInt32Item->GetValue());
+ }
+
+ // text edit a cell in view #2
+ SfxLokHelper::setView(nView2);
+ pModelObj->setPart(1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'c', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'c', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
+ // both views have UNDO enabled
+ Scheduler::ProcessEventsToIdle();
+ {
+ SfxItemSet aSet1(pView1->GetPool(), svl::Items<SID_UNDO, SID_UNDO>);
+ SfxItemSet aSet2(pView2->GetPool(), svl::Items<SID_UNDO, SID_UNDO>);
+ pView1->GetSlotState(SID_UNDO, nullptr, &aSet1);
+ pView2->GetSlotState(SID_UNDO, nullptr, &aSet2);
+ CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aSet1.GetItemState(SID_UNDO));
+ CPPUNIT_ASSERT(dynamic_cast< const SfxStringItem* >(aSet1.GetItem(SID_UNDO)));
+ CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aSet2.GetItemState(SID_UNDO));
+ CPPUNIT_ASSERT(dynamic_cast< const SfxStringItem* >(aSet2.GetItem(SID_UNDO)));
+ }
+
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDocumentRepair)
+{
+ // Create two views.
+ ScModelObj* pModelObj = createDoc("cursor-away.ods");
+ CPPUNIT_ASSERT(pModelObj);
+
+ // 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);
+ pModelObj->setPart(1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'c', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'c', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
+ Scheduler::ProcessEventsToIdle();
+ {
+ 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());
+ }
+
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testLanguageStatus)
+{
+ ScModelObj* pModelObj = createDoc("small.ods");
+ CPPUNIT_ASSERT(pModelObj);
+ ScDocShell* pDocSh = dynamic_cast< ScDocShell* >( pModelObj->GetEmbeddedObject() );
+ CPPUNIT_ASSERT(pDocSh);
+
+ // view #1
+ SfxViewShell* pView1 = SfxViewShell::Current();
+
+ // view #2
+ int nView1 = SfxLokHelper::getView();
+ SfxLokHelper::createView();
+ SfxViewShell* pView2 = SfxViewShell::Current();
+ CPPUNIT_ASSERT(pView1 != pView2);
+ {
+ std::unique_ptr<SfxPoolItem> xItem1;
+ std::unique_ptr<SfxPoolItem> xItem2;
+ pView1->GetViewFrame().GetBindings().QueryState(SID_LANGUAGE_STATUS, xItem1);
+ pView2->GetViewFrame().GetBindings().QueryState(SID_LANGUAGE_STATUS, xItem2);
+ const SfxStringItem* pItem1 = dynamic_cast<const SfxStringItem*>(xItem1.get());
+ const SfxStringItem* pItem2 = dynamic_cast<const SfxStringItem*>(xItem2.get());
+ CPPUNIT_ASSERT(pItem1);
+ CPPUNIT_ASSERT(pItem2);
+ CPPUNIT_ASSERT(!pItem1->GetValue().isEmpty());
+ CPPUNIT_ASSERT(!pItem2->GetValue().isEmpty());
+ }
+
+ {
+ SfxStringItem aLangString(SID_LANGUAGE_STATUS, "Default_Spanish (Bolivia)");
+ pView1->GetViewFrame().GetDispatcher()->ExecuteList(SID_LANGUAGE_STATUS,
+ SfxCallMode::SYNCHRON, { &aLangString });
+ }
+
+ {
+ std::unique_ptr<SfxPoolItem> xItem1;
+ std::unique_ptr<SfxPoolItem> xItem2;
+ pView1->GetViewFrame().GetBindings().QueryState(SID_LANGUAGE_STATUS, xItem1);
+ pView2->GetViewFrame().GetBindings().QueryState(SID_LANGUAGE_STATUS, xItem2);
+ const SfxStringItem* pItem1 = dynamic_cast<const SfxStringItem*>(xItem1.get());
+ const SfxStringItem* pItem2 = dynamic_cast<const SfxStringItem*>(xItem2.get());
+ CPPUNIT_ASSERT(pItem1);
+ CPPUNIT_ASSERT(pItem2);
+ constexpr OUString aLangBolivia(u"Spanish (Bolivia);es-BO"_ustr);
+ CPPUNIT_ASSERT_EQUAL(aLangBolivia, pItem1->GetValue());
+ CPPUNIT_ASSERT_EQUAL(aLangBolivia, pItem2->GetValue());
+ }
+
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testMultiViewCopyPaste)
+{
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ ScDocument* pDoc = pModelObj->GetDocument();
+ CPPUNIT_ASSERT(pDoc);
+
+ pDoc->SetString(ScAddress(0, 0, 0), "TestCopy1");
+ pDoc->SetString(ScAddress(1, 0, 0), "TestCopy2");
+
+ // view #1
+ ScTabViewShell* pView1 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
+ CPPUNIT_ASSERT(pView1);
+ // emulate clipboard
+ pView1->GetViewData().GetActiveWin()->SetClipboard(css::datatransfer::clipboard::LokClipboard::create(comphelper::getProcessComponentContext()));
+
+ // view #2
+ int nView1 = SfxLokHelper::getView();
+ SfxLokHelper::createView();
+ ScTabViewShell* pView2 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
+ // emulate clipboard
+ pView2->GetViewData().GetActiveWin()->SetClipboard(css::datatransfer::clipboard::LokClipboard::create(comphelper::getProcessComponentContext()));
+ CPPUNIT_ASSERT(pView2);
+ CPPUNIT_ASSERT(pView1 != pView2);
+ CPPUNIT_ASSERT(pView1->GetViewData().GetActiveWin()->GetClipboard() != pView2->GetViewData().GetActiveWin()->GetClipboard());
+
+ // copy text view 1
+ pView1->SetCursor(0, 0);
+ pView1->GetViewFrame().GetBindings().Execute(SID_COPY);
+
+ // copy text view 2
+ pView2->SetCursor(1, 0);
+ pView2->GetViewFrame().GetBindings().Execute(SID_COPY);
+
+ // paste text view 1
+ pView1->SetCursor(0, 1);
+ pView1->GetViewFrame().GetBindings().Execute(SID_PASTE);
+
+ // paste text view 2
+ pView2->SetCursor(1, 1);
+ pView2->GetViewFrame().GetBindings().Execute(SID_PASTE);
+
+ CPPUNIT_ASSERT_EQUAL(OUString("TestCopy1"), pDoc->GetString(ScAddress(0, 1, 0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("TestCopy2"), pDoc->GetString(ScAddress(1, 1, 0)));
+
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testIMESupport)
+{
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ VclPtr<vcl::Window> pDocWindow = pModelObj->getDocWindow();
+ ScDocument* pDoc = pModelObj->GetDocument();
+
+ ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
+ CPPUNIT_ASSERT(pView);
+
+ pView->SetCursor(0, 0);
+ // 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, "");
+
+ // commit the string to the cell
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
+ Scheduler::ProcessEventsToIdle();
+
+ CPPUNIT_ASSERT_EQUAL(aInputs[aInputs.size() - 1], pDoc->GetString(ScAddress(0, 0, 0)));
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testFilterDlg)
+{
+ createDoc("empty.ods");
+
+ // view #1
+ SfxViewShell* pView1 = SfxViewShell::Current();
+ int nView1 = SfxLokHelper::getView();
+
+ // view #2
+ SfxLokHelper::createView();
+ SfxViewShell* pView2 = SfxViewShell::Current();
+ CPPUNIT_ASSERT(pView1 != pView2);
+ {
+ pView2->GetViewFrame().GetDispatcher()->Execute(SID_FILTER,
+ SfxCallMode::SLOT|SfxCallMode::RECORD);
+ }
+
+ Scheduler::ProcessEventsToIdle();
+ SfxChildWindow* pRefWindow = pView2->GetViewFrame().GetChildWindow(SID_FILTER);
+ CPPUNIT_ASSERT(pRefWindow);
+
+ // switch to view 1
+ SfxLokHelper::setView(nView1);
+ CPPUNIT_ASSERT_EQUAL(true, pView2->GetViewFrame().GetDispatcher()->IsLocked());
+ CPPUNIT_ASSERT_EQUAL(false, pView1->GetViewFrame().GetDispatcher()->IsLocked());
+
+ pRefWindow->GetController()->response(RET_CANCEL);
+
+ CPPUNIT_ASSERT_EQUAL(false, pView2->GetViewFrame().GetDispatcher()->IsLocked());
+ CPPUNIT_ASSERT_EQUAL(false, pView1->GetViewFrame().GetDispatcher()->IsLocked());
+
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testFunctionDlg)
+{
+ createDoc("empty.ods");
+
+ // view #1
+ SfxViewShell* pView1 = SfxViewShell::Current();
+ int nView1 = SfxLokHelper::getView();
+ {
+ pView1->GetViewFrame().GetDispatcher()->Execute(SID_OPENDLG_FUNCTION,
+ SfxCallMode::SLOT|SfxCallMode::RECORD);
+ }
+ Scheduler::ProcessEventsToIdle();
+ SfxChildWindow* pRefWindow = pView1->GetViewFrame().GetChildWindow(SID_OPENDLG_FUNCTION);
+ CPPUNIT_ASSERT(pRefWindow);
+
+ // view #2
+ int nView2 = SfxLokHelper::createView();
+ SfxViewShell* pView2 = SfxViewShell::Current();
+ CPPUNIT_ASSERT(pView1 != pView2);
+
+ // check locking
+ CPPUNIT_ASSERT_EQUAL(true, pView1->GetViewFrame().GetDispatcher()->IsLocked());
+ CPPUNIT_ASSERT_EQUAL(false, pView2->GetViewFrame().GetDispatcher()->IsLocked());
+
+ SfxLokHelper::setView(nView1);
+ pRefWindow->GetController()->response(RET_CANCEL);
+
+ CPPUNIT_ASSERT_EQUAL(false, pView1->GetViewFrame().GetDispatcher()->IsLocked());
+ CPPUNIT_ASSERT_EQUAL(false, pView2->GetViewFrame().GetDispatcher()->IsLocked());
+
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+ SfxLokHelper::setView(nView2);
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSpellOnlineParameter)
+{
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ ScDocument* pDoc = pModelObj->GetDocument();
+ bool bSet = pDoc->GetDocOptions().IsAutoSpell();
+
+ uno::Sequence<beans::PropertyValue> params =
+ {
+ comphelper::makePropertyValue("Enable", uno::Any(!bSet)),
+ };
+ dispatchCommand(mxComponent, ".uno:SpellOnline", params);
+ CPPUNIT_ASSERT_EQUAL(!bSet, pDoc->GetDocOptions().IsAutoSpell());
+
+ // set the same state as now and we don't expect any change (no-toggle)
+ params =
+ {
+ comphelper::makePropertyValue("Enable", uno::Any(!bSet)),
+ };
+ dispatchCommand(mxComponent, ".uno:SpellOnline", params);
+ CPPUNIT_ASSERT_EQUAL(!bSet, pDoc->GetDocOptions().IsAutoSpell());
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testVbaRangeCopyPaste)
+{
+ ScModelObj* pModelObj = createDoc("RangeCopyPaste.ods");
+ ScDocShell* pDocShell = dynamic_cast< ScDocShell* >( pModelObj->GetEmbeddedObject() );
+ CPPUNIT_ASSERT(pDocShell);
+
+ uno::Any aRet;
+ uno::Sequence< uno::Any > aOutParam;
+ uno::Sequence< uno::Any > aParams;
+ uno::Sequence< sal_Int16 > aOutParamIndex;
+
+ SfxObjectShell::CallXScript(
+ mxComponent,
+ "vnd.sun.Star.script:Standard.Module1.Test_RangeCopyPaste?language=Basic&location=document",
+ aParams, aRet, aOutParamIndex, aOutParam);
+
+ CPPUNIT_ASSERT(!pDocShell->GetClipData().is());
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testInvalidationLoop)
+{
+ // Load the document with a form control.
+ createDoc("invalidation-loop.fods");
+ // Without the accompanying fix in place, this test would have never returned due to an infinite
+ // invalidation loop between ScGridWindow::Paint() and vcl::Window::ImplPosSizeWindow().
+ Scheduler::ProcessEventsToIdle();
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testPageDownInvalidation)
+{
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ ScViewData* pViewData = ScDocShell::GetViewData();
+ CPPUNIT_ASSERT(pViewData);
+
+ int nView1 = SfxLokHelper::getView();
+ ViewCallback aView1;
+ CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
+
+ SfxLokHelper::setView(nView1);
+ aView1.m_bInvalidateTiles = false;
+ aView1.m_aInvalidations.clear();
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, awt::Key::PAGEDOWN, 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, awt::Key::PAGEDOWN, 0);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
+ CPPUNIT_ASSERT_EQUAL(size_t(3), aView1.m_aInvalidations.size());
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(15, 15, 1230, 225), aView1.m_aInvalidations[0]);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSheetChangeInvalidation)
+{
+ const bool oldPartInInvalidation = comphelper::LibreOfficeKit::isPartInInvalidation();
+ comphelper::LibreOfficeKit::setPartInInvalidation(true);
+
+ ScModelObj* pModelObj = createDoc("two_sheets.ods");
+ ScDocument* pDoc = pModelObj->GetDocument();
+ ScViewData* pViewData = ScDocShell::GetViewData();
+ CPPUNIT_ASSERT(pViewData);
+
+ int nView1 = SfxLokHelper::getView();
+ ViewCallback aView1;
+ CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
+
+ SfxLokHelper::setView(nView1);
+ aView1.m_bInvalidateTiles = false;
+ aView1.m_aInvalidations.clear();
+ aView1.m_aInvalidationsParts.clear();
+ aView1.m_aInvalidationsMode.clear();
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEDOWN | KEY_MOD1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEDOWN | KEY_MOD1);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), aView1.m_aInvalidations.size());
+ const ScSheetLimits& rLimits = pDoc->GetSheetLimits();
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(0, 0, 1280 * rLimits.GetMaxColCount(),
+ 256 * rLimits.GetMaxRowCount()),
+ aView1.m_aInvalidations[0]);
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(0, 0, 1000000000, 1000000000), aView1.m_aInvalidations[1]);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), aView1.m_aInvalidationsParts.size());
+ CPPUNIT_ASSERT_EQUAL(pModelObj->getPart(), aView1.m_aInvalidationsParts[0]);
+ CPPUNIT_ASSERT_EQUAL(pModelObj->getPart(), aView1.m_aInvalidationsParts[1]);
+ CPPUNIT_ASSERT_EQUAL(size_t(2), aView1.m_aInvalidationsMode.size());
+ CPPUNIT_ASSERT_EQUAL(pModelObj->getEditMode(), aView1.m_aInvalidationsMode[0]);
+ CPPUNIT_ASSERT_EQUAL(pModelObj->getEditMode(), aView1.m_aInvalidationsMode[1]);
+ comphelper::LibreOfficeKit::setPartInInvalidation(oldPartInInvalidation);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testInsertDeletePageInvalidation)
+{
+ ScModelObj* pModelObj = createDoc("insert_delete_sheet.ods");
+ // the document has 1 sheet
+ CPPUNIT_ASSERT_EQUAL(1, pModelObj->getParts());
+ ScViewData* pViewData = ScDocShell::GetViewData();
+ CPPUNIT_ASSERT(pViewData);
+
+ int nView1 = SfxLokHelper::getView();
+ ViewCallback aView1;
+ CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
+
+ SfxLokHelper::setView(nView1);
+ aView1.m_bInvalidateTiles = false;
+ aView1.m_aInvalidations.clear();
+
+ uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
+ { "Name", uno::Any(OUString("")) },
+ { "Index", uno::Any(sal_Int32(1)) }
+ }));
+ dispatchCommand(mxComponent, ".uno:Insert", aArgs);
+ CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
+ CPPUNIT_ASSERT_EQUAL(size_t(6), aView1.m_aInvalidations.size());
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(0, 0, 1000000000, 1000000000), aView1.m_aInvalidations[0]);
+ CPPUNIT_ASSERT_EQUAL(2, pModelObj->getParts());
+
+ // Delete sheet
+ aView1.m_bInvalidateTiles = false;
+ aView1.m_aInvalidations.clear();
+ uno::Sequence<beans::PropertyValue> aArgs2( comphelper::InitPropertySequence({
+ { "Index", uno::Any(sal_Int32(1)) }
+ }));
+ dispatchCommand(mxComponent, ".uno:Remove", aArgs2);
+ CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
+ CPPUNIT_ASSERT_EQUAL(size_t(5), aView1.m_aInvalidations.size());
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(0, 0, 1000000000, 1000000000), aView1.m_aInvalidations[0]);
+ CPPUNIT_ASSERT_EQUAL(1, pModelObj->getParts());
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testGetRowColumnHeadersInvalidation)
+{
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ ScViewData* pViewData = ScDocShell::GetViewData();
+ CPPUNIT_ASSERT(pViewData);
+
+ int nView1 = SfxLokHelper::getView();
+ ViewCallback aView1;
+ CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
+
+ SfxLokHelper::setView(nView1);
+ aView1.m_bInvalidateTiles = false;
+ aView1.m_aInvalidations.clear();
+ tools::JsonWriter aJsonWriter1;
+ pModelObj->getRowColumnHeaders(tools::Rectangle(0, 15, 19650, 5400), aJsonWriter1);
+ aJsonWriter1.finishAndGetAsOString();
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aView1.m_aInvalidations.size());
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(26775, 0, 49725, 13005), aView1.m_aInvalidations[0]);
+
+ // Extend area top-to-bottom
+ aView1.m_bInvalidateTiles = false;
+ aView1.m_aInvalidations.clear();
+ tools::JsonWriter aJsonWriter2;
+ pModelObj->getRowColumnHeaders(tools::Rectangle(0, 5400, 19650, 9800), aJsonWriter2);
+ aJsonWriter2.finishAndGetAsOString();
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aView1.m_aInvalidations.size());
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(0, 13005, 49725, 19380), aView1.m_aInvalidations[0]);
+
+ // Extend area left-to-right
+ aView1.m_bInvalidateTiles = false;
+ aView1.m_aInvalidations.clear();
+ tools::JsonWriter aJsonWriter3;
+ pModelObj->getRowColumnHeaders(tools::Rectangle(5400, 5400, 25050, 9800), aJsonWriter3);
+ aJsonWriter3.finishAndGetAsOString();
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aView1.m_aInvalidations.size());
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(49725, 0, 75225, 19380), aView1.m_aInvalidations[0]);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testJumpHorizontallyInvalidation)
+{
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ ScViewData* pViewData = ScDocShell::GetViewData();
+ CPPUNIT_ASSERT(pViewData);
+
+ int nView1 = SfxLokHelper::getView();
+ ViewCallback aView1;
+ CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
+
+ SfxLokHelper::setView(nView1);
+ aView1.m_bInvalidateTiles = false;
+ aView1.m_aInvalidations.clear();
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEDOWN | KEY_MOD2);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEDOWN | KEY_MOD2);
+ Scheduler::ProcessEventsToIdle();
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEDOWN | KEY_MOD2);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEDOWN | KEY_MOD2);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aView1.m_aInvalidations.size());
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(26775, 0, 39525, 13005), aView1.m_aInvalidations[0]);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testJumpToLastRowInvalidation)
+{
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ ScViewData* pViewData = ScDocShell::GetViewData();
+ CPPUNIT_ASSERT(pViewData);
+
+ int nView1 = SfxLokHelper::getView();
+ ViewCallback aView1;
+ CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
+
+ SfxLokHelper::setView(nView1);
+ aView1.m_bInvalidateTiles = false;
+ aView1.m_aInvalidations.clear();
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DOWN | KEY_MOD1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DOWN | KEY_MOD1);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
+ CPPUNIT_ASSERT_EQUAL(size_t(1), aView1.m_aInvalidations.size());
+ // 261375 because we limit how far we jump into empty space in online, 267386880 if we don't limit
+ CPPUNIT_ASSERT_EQUAL(tools::Rectangle(0, 13005, 26775, 261375), aView1.m_aInvalidations[0]);
+}
+
+// We need to ensure that views are not perterbed by rendering (!?) hmm ...
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testRowColumnHeaders)
+{
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ ScViewData* pViewData = ScDocShell::GetViewData();
+ CPPUNIT_ASSERT(pViewData);
+
+ // view #1
+ ViewCallback aView1;
+ int nView1 = SfxLokHelper::getView();
+ CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
+
+ // view #2
+ SfxLokHelper::createView();
+ int nView2 = SfxLokHelper::getView();
+ ViewCallback aView2;
+ pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+
+ // ViewRowColumnHeaders test
+ SfxLokHelper::setView(nView1);
+ tools::JsonWriter aJsonWriter1;
+ pModelObj->getRowColumnHeaders(tools::Rectangle(65,723,10410,4695), aJsonWriter1);
+ OString aHeaders1 = aJsonWriter1.finishAndGetAsOString();
+
+ SfxLokHelper::setView(nView2);
+ // 50% zoom
+ pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 22474, 47333));
+ pModelObj->setClientZoom(256, 256, 6636, 6636);
+ tools::JsonWriter aJsonWriter2;
+ pModelObj->getRowColumnHeaders(tools::Rectangle(65,723,10410,4695), aJsonWriter2);
+ OString aHeaders2 = aJsonWriter2.finishAndGetAsOString();
+
+ // Check vs. view #1
+ SfxLokHelper::setView(nView1);
+ tools::JsonWriter aJsonWriter3;
+ pModelObj->getRowColumnHeaders(tools::Rectangle(65,723,10410,4695), aJsonWriter3);
+ OString aHeaders1_2 = aJsonWriter3.finishAndGetAsOString();
+ CPPUNIT_ASSERT_EQUAL(aHeaders1, aHeaders1_2);
+
+ // Check vs. view #2
+ SfxLokHelper::setView(nView2);
+ tools::JsonWriter aJsonWriter4;
+ pModelObj->getRowColumnHeaders(tools::Rectangle(65,723,10410,4695), aJsonWriter4);
+ OString aHeaders2_2 = aJsonWriter4.finishAndGetAsOString();
+ CPPUNIT_ASSERT_EQUAL(aHeaders2, aHeaders2_2);
+
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+ SfxLokHelper::setView(nView2);
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+}
+
+namespace
+{
+// Helper structs for setup and testing of ScModelObj::getSheetGeometryData()
+struct SpanEntry
+{
+ size_t nVal;
+ SCCOLROW nEnd;
+};
+
+struct SheetDimData
+{
+ typedef std::vector<SpanEntry> SpanList;
+ SpanList aSizes;
+ SpanList aHidden;
+ SpanList aFiltered;
+ // TODO: Add group info too to test.
+
+ void setDataToDoc(ScDocument* pDoc, bool bCol) const
+ {
+ SCCOLROW nStart = 0;
+ // sizes
+ for (const auto& rSpan : aSizes)
+ {
+ if (bCol)
+ {
+ for (SCCOLROW nIdx = nStart; nIdx <= rSpan.nEnd; ++nIdx)
+ pDoc->SetColWidthOnly(nIdx, 0, rSpan.nVal);
+ }
+ else
+ pDoc->SetRowHeightOnly(nStart, rSpan.nEnd, 0, rSpan.nVal);
+
+ nStart = rSpan.nEnd + 1;
+ }
+
+ nStart = 0;
+ // hidden
+ for (const auto& rSpan : aHidden)
+ {
+ if (bCol)
+ pDoc->SetColHidden(nStart, rSpan.nEnd, 0, !!rSpan.nVal);
+ else
+ pDoc->SetRowHidden(nStart, rSpan.nEnd, 0, !!rSpan.nVal);
+
+ nStart = rSpan.nEnd + 1;
+ }
+
+ // There is no ScDocument interface to set ScTable::mpFilteredCols
+ // It seems ScTable::mpFilteredCols is not really used !?
+ if (bCol)
+ return;
+
+ nStart = 0;
+ // filtered
+ for (const auto& rSpan : aFiltered)
+ {
+ pDoc->SetRowFiltered(nStart, rSpan.nEnd, 0, !!rSpan.nVal);
+ nStart = rSpan.nEnd + 1;
+ }
+ }
+
+ void testPropertyTree(const boost::property_tree::ptree& rTree, bool bCol) const
+ {
+ struct SpanListWithKey
+ {
+ OString aKey;
+ const SpanList& rSpanList;
+ };
+
+ const SpanListWithKey aPairList[] = {
+ { "sizes"_ostr, aSizes },
+ { "hidden"_ostr, aHidden },
+ { "filtered"_ostr, aFiltered }
+ };
+
+ for (const auto& rEntry : aPairList)
+ {
+ // There is no ScDocument interface to set ScTable::mpFilteredCols
+ // It seems ScTable::mpFilteredCols is not really used !?
+ if (bCol && rEntry.aKey == "filtered")
+ continue;
+
+ bool bBooleanValue = rEntry.aKey != "sizes";
+ OString aExpectedEncoding;
+ bool bFirst = true;
+ for (const auto& rSpan : rEntry.rSpanList)
+ {
+ size_t nVal = rSpan.nVal;
+ if (bBooleanValue && bFirst)
+ nVal = static_cast<size_t>(!!nVal);
+ if (!bBooleanValue || bFirst)
+ aExpectedEncoding += OString::number(nVal) + ":";
+ aExpectedEncoding += OString::number(rSpan.nEnd) + " ";
+ bFirst = false;
+ }
+
+ // Get the tree's value for the property key ("sizes"/"hidden"/"filtered").
+ OString aTreeValue(rTree.get<std::string>(rEntry.aKey.getStr()));
+
+ CPPUNIT_ASSERT_EQUAL(aExpectedEncoding, aTreeValue);
+ }
+ }
+};
+
+class SheetGeometryData
+{
+ SheetDimData aCols;
+ SheetDimData aRows;
+
+public:
+
+ SheetGeometryData(const SheetDimData& rCols, const SheetDimData& rRows) :
+ aCols(rCols), aRows(rRows)
+ {}
+
+ void setDataToDoc(ScDocument* pDoc) const
+ {
+ aCols.setDataToDoc(pDoc, true);
+ aRows.setDataToDoc(pDoc, false);
+ }
+
+ void parseTest(const OString& rJSON) const
+ {
+ // Assumes all flags passed to getSheetGeometryData() are true.
+ boost::property_tree::ptree aTree;
+ std::stringstream aStream((std::string(rJSON)));
+ boost::property_tree::read_json(aStream, aTree);
+
+ CPPUNIT_ASSERT_EQUAL(".uno:SheetGeometryData"_ostr, OString(aTree.get<std::string>("commandName")));
+
+ aCols.testPropertyTree(aTree.get_child("columns"), true);
+ aRows.testPropertyTree(aTree.get_child("rows"), false);
+ }
+};
+} //namespace
+
+// getSheetGeometryData() should return the exact same message
+// irrespective of client zoom and view-area. Switching views
+// should also not alter it.
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSheetGeometryDataInvariance)
+{
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ ScDocument* pDoc = pModelObj->GetDocument();
+ const SheetGeometryData aSGData(
+ // cols
+ {
+ // width spans
+ {
+ { STD_COL_WIDTH, 20 },
+ { 2*STD_COL_WIDTH, 26 },
+ { STD_COL_WIDTH, pDoc->MaxCol() }
+ },
+
+ // hidden spans
+ {
+ { 0, 5 },
+ { 1, 12 },
+ { 0, pDoc->MaxCol() }
+ },
+
+ // filtered spans
+ {
+ { 0, 50 },
+ { 1, 59 },
+ { 0, pDoc->MaxCol() }
+ }
+ },
+
+ // rows
+ {
+ // height spans
+ {
+ { 300, 50 },
+ { 600, 65 },
+ { 300, pDoc->MaxRow() }
+ },
+
+ // hidden spans
+ {
+ { 1, 100 },
+ { 0, 500 },
+ { 1, 578 },
+ { 0, pDoc->MaxRow() }
+ },
+
+ // filtered spans
+ {
+ { 0, 150 },
+ { 1, 159 },
+ { 0, pDoc->MaxRow() }
+ }
+ }
+ );
+
+ ScViewData* pViewData = ScDocShell::GetViewData();
+ CPPUNIT_ASSERT(pViewData);
+
+ // view #1
+ ViewCallback aView1;
+ int nView1 = SfxLokHelper::getView();
+
+ // view #2
+ SfxLokHelper::createView();
+ int nView2 = SfxLokHelper::getView();
+ ViewCallback aView2;
+ pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+
+ // Try with the default empty document once (nIdx = 0) and then with sheet geometry settings (nIdx = 1)
+ for (size_t nIdx = 0; nIdx < 2; ++nIdx)
+ {
+ if (nIdx)
+ aSGData.setDataToDoc(pDoc);
+
+ SfxLokHelper::setView(nView1);
+ OString aGeomStr1 = pModelObj->getSheetGeometryData(/*bColumns*/ true, /*bRows*/ true, /*bSizes*/ true,
+ /*bHidden*/ true, /*bFiltered*/ true, /*bGroups*/ true);
+
+ SfxLokHelper::setView(nView2);
+ pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 22474, 47333));
+ pModelObj->setClientZoom(256, 256, 6636, 6636);
+ OString aGeomStr2 = pModelObj->getSheetGeometryData(/*bColumns*/ true, /*bRows*/ true, /*bSizes*/ true,
+ /*bHidden*/ true, /*bFiltered*/ true, /*bGroups*/ true);
+
+ // Check vs. view #1
+ SfxLokHelper::setView(nView1);
+ OString aGeomStr1_2 = pModelObj->getSheetGeometryData(/*bColumns*/ true, /*bRows*/ true, /*bSizes*/ true,
+ /*bHidden*/ true, /*bFiltered*/ true, /*bGroups*/ true);
+ CPPUNIT_ASSERT_EQUAL(aGeomStr1, aGeomStr1_2);
+
+ // Check vs. view #2
+ SfxLokHelper::setView(nView2);
+ OString aGeomStr2_2 = pModelObj->getSheetGeometryData(/*bColumns*/ true, /*bRows*/ true, /*bSizes*/ true,
+ /*bHidden*/ true, /*bFiltered*/ true, /*bGroups*/ true);
+ CPPUNIT_ASSERT_EQUAL(aGeomStr2, aGeomStr2_2);
+ }
+
+ SfxLokHelper::setView(nView1);
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+ SfxLokHelper::setView(nView2);
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSheetGeometryDataCorrectness)
+{
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ ScDocument* pDoc = pModelObj->GetDocument();
+ const SheetGeometryData aDefaultSGData(
+ // cols
+ {
+ // width spans
+ { { STD_COL_WIDTH, pDoc->MaxCol() } },
+ // hidden spans
+ { { 0, pDoc->MaxCol() } },
+ // filtered spans
+ { { 0, pDoc->MaxCol() } }
+ },
+ // rows
+ {
+ // height spans
+ { { ScGlobal::nStdRowHeight, pDoc->MaxRow() } },
+ // hidden spans
+ { { 0, pDoc->MaxRow() } },
+ // filtered spans
+ { { 0, pDoc->MaxRow() } }
+ }
+ );
+
+ const SheetGeometryData aSGData(
+ // cols
+ {
+ // width spans
+ {
+ { STD_COL_WIDTH, 20 },
+ { 2*STD_COL_WIDTH, 26 },
+ { STD_COL_WIDTH, pDoc->MaxCol() }
+ },
+
+ // hidden spans
+ {
+ { 0, 5 },
+ { 1, 12 },
+ { 0, pDoc->MaxCol() }
+ },
+
+ // filtered spans
+ {
+ { 0, 50 },
+ { 1, 59 },
+ { 0, pDoc->MaxCol() }
+ }
+ },
+
+ // rows
+ {
+ // height spans
+ {
+ { 300, 50 },
+ { 600, 65 },
+ { 300, pDoc->MaxRow() }
+ },
+
+ // hidden spans
+ {
+ { 1, 100 },
+ { 0, 500 },
+ { 1, 578 },
+ { 0, pDoc->MaxRow() }
+ },
+
+ // filtered spans
+ {
+ { 0, 150 },
+ { 1, 159 },
+ { 0, pDoc->MaxRow() }
+ }
+ }
+ );
+
+ ScViewData* pViewData = ScDocShell::GetViewData();
+ CPPUNIT_ASSERT(pViewData);
+
+ // view #1
+ ViewCallback aView1;
+
+ // with the default empty sheet and test the JSON encoding.
+ OString aGeomDefaultStr = pModelObj->getSheetGeometryData(/*bColumns*/ true, /*bRows*/ true, /*bSizes*/ true,
+ /*bHidden*/ true, /*bFiltered*/ true, /*bGroups*/ true);
+ aDefaultSGData.parseTest(aGeomDefaultStr);
+
+ // Apply geometry settings to the sheet and then test the resulting JSON encoding.
+ aSGData.setDataToDoc(pDoc);
+ OString aGeomStr = pModelObj->getSheetGeometryData(/*bColumns*/ true, /*bRows*/ true, /*bSizes*/ true,
+ /*bHidden*/ true, /*bFiltered*/ true, /*bGroups*/ true);
+ aSGData.parseTest(aGeomStr);
+
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDeleteCellMultilineContent)
+{
+ ScModelObj* pModelObj = createDoc("multiline.ods");
+ CPPUNIT_ASSERT(pModelObj);
+ ScViewData* pViewData = ScDocShell::GetViewData();
+ CPPUNIT_ASSERT(pViewData);
+ ScDocShell* pDocSh = dynamic_cast< ScDocShell* >( pModelObj->GetEmbeddedObject() );
+ CPPUNIT_ASSERT(pDocSh);
+
+ // view #1
+ ViewCallback aView1;
+ CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
+
+ aView1.m_sInvalidateHeader = ""_ostr;
+ ScDocument& rDoc = pDocSh->GetDocument();
+ sal_uInt16 nRow1Height = rDoc.GetRowHeight(static_cast<SCROW>(0), static_cast<SCTAB>(0), false);
+
+ // delete multiline cell content in view #1
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DOWN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DOWN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DELETE);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DELETE);
+ Scheduler::ProcessEventsToIdle();
+
+ // check if the row header has been invalidated and if the involved row is of the expected height
+ CPPUNIT_ASSERT_EQUAL("row"_ostr, aView1.m_sInvalidateHeader);
+ sal_uInt16 nRow2Height = rDoc.GetRowHeight(static_cast<SCROW>(0), static_cast<SCTAB>(0), false);
+ CPPUNIT_ASSERT_EQUAL(nRow1Height, nRow2Height);
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testPasteIntoWrapTextCell)
+{
+ comphelper::LibreOfficeKit::setCompatFlag(
+ comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
+
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ CPPUNIT_ASSERT(pModelObj);
+ ScDocument* pDoc = pModelObj->GetDocument();
+
+ // Set Wrap text in A3
+ pDoc->ApplyAttr(0, 2, 0, ScLineBreakCell(true));
+ const ScLineBreakCell* pItem = pDoc->GetAttr(0, 2, 0, ATTR_LINEBREAK);
+ CPPUNIT_ASSERT(pItem->GetValue());
+
+ ScViewData* pViewData = ScDocShell::GetViewData();
+ CPPUNIT_ASSERT(pViewData);
+
+ ViewCallback aView;
+ CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
+
+ ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
+ CPPUNIT_ASSERT(pView);
+
+ // create source text in A1
+ OUString sCopyContent("Very long text to copy");
+ pDoc->SetString(0, 0, 0, sCopyContent);
+
+ // copy A1
+ pView->SetCursor(0, 0);
+ Scheduler::ProcessEventsToIdle();
+ pView->GetViewFrame().GetBindings().Execute(SID_COPY);
+ Scheduler::ProcessEventsToIdle();
+
+ // verify clipboard
+ uno::Reference<datatransfer::clipboard::XClipboard> xClipboard1 = pView->GetViewData().GetActiveWin()->GetClipboard();
+ uno::Reference< datatransfer::XTransferable > xDataObj =
+ xClipboard1->getContents();
+ datatransfer::DataFlavor aFlavor;
+ SotExchange::GetFormatDataFlavor(SotClipboardFormatId::STRING, aFlavor);
+ uno::Any aData = xDataObj->getTransferData(aFlavor);
+ OUString aTmpText;
+ aData >>= aTmpText;
+ CPPUNIT_ASSERT_EQUAL(sCopyContent, aTmpText.trim());
+
+ // Go to A2 and paste.
+ pView->SetCursor(0, 1);
+ Scheduler::ProcessEventsToIdle();
+ aView.m_sInvalidateSheetGeometry = ""_ostr;
+ pView->GetViewFrame().GetBindings().Execute(SID_PASTE);
+ Scheduler::ProcessEventsToIdle();
+
+ CPPUNIT_ASSERT_EQUAL(sCopyContent, pDoc->GetString(0, 1, 0));
+ CPPUNIT_ASSERT_EQUAL("rows sizes"_ostr, aView.m_sInvalidateSheetGeometry);
+
+ // create new source text in A2
+ OUString sCopyContent2("Very long text to copy 2");
+ pDoc->SetString(0, 1, 0, sCopyContent2);
+ Scheduler::ProcessEventsToIdle();
+
+ // cut from A2
+ pView->GetViewFrame().GetBindings().Execute(SID_CUT);
+ Scheduler::ProcessEventsToIdle();
+
+ // verify clipboard
+ uno::Reference<datatransfer::clipboard::XClipboard> xClipboard2
+ = pView->GetViewData().GetActiveWin()->GetClipboard();
+ xDataObj = xClipboard2->getContents();
+ SotExchange::GetFormatDataFlavor(SotClipboardFormatId::STRING, aFlavor);
+ aData = xDataObj->getTransferData(aFlavor);
+ aData >>= aTmpText;
+ CPPUNIT_ASSERT_EQUAL(xClipboard1, xClipboard2);
+ CPPUNIT_ASSERT_EQUAL(sCopyContent2, aTmpText.trim());
+
+ // Go to A3 and paste.
+ pView->SetCursor(0, 2);
+ Scheduler::ProcessEventsToIdle();
+ aView.m_sInvalidateSheetGeometry = ""_ostr;
+ pView->GetViewFrame().GetBindings().Execute(SID_PASTE);
+ Scheduler::ProcessEventsToIdle();
+
+ // SG invalidations for all
+ CPPUNIT_ASSERT_EQUAL(sCopyContent2, pDoc->GetString(0, 1, 0));
+ CPPUNIT_ASSERT_EQUAL("all"_ostr, aView.m_sInvalidateSheetGeometry);
+
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSortAscendingDescending)
+{
+ comphelper::LibreOfficeKit::setCompatFlag(
+ comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
+ ScModelObj* pModelObj = createDoc("sort-range.ods");
+ ScDocument* pDoc = pModelObj->GetDocument();
+
+ ViewCallback aView;
+
+ // select the values in the first column
+ pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, 551, 129, 1, MOUSE_LEFT, 0);
+ pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEMOVE, 820, 1336, 1, MOUSE_LEFT, 0);
+ pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, 820, 1359, 1, MOUSE_LEFT, 0);
+ Scheduler::ProcessEventsToIdle();
+ aView.m_sInvalidateSheetGeometry = ""_ostr;
+
+ // sort ascending
+ uno::Sequence<beans::PropertyValue> aArgs;
+ dispatchCommand(mxComponent, ".uno:SortAscending", aArgs);
+
+ // check it's sorted
+ for (SCROW r = 0; r < 6; ++r)
+ {
+ CPPUNIT_ASSERT_EQUAL(double(r + 1), pDoc->GetValue(ScAddress(0, r, 0)));
+ }
+
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL("rows"_ostr, aView.m_sInvalidateSheetGeometry);
+
+ aView.m_sInvalidateSheetGeometry = ""_ostr;
+ // sort descending
+ dispatchCommand(mxComponent, ".uno:SortDescending", aArgs);
+
+ // check it's sorted
+ for (SCROW r = 0; r < 6; ++r)
+ {
+ CPPUNIT_ASSERT_EQUAL(double(6 - r), pDoc->GetValue(ScAddress(0, r, 0)));
+ }
+
+ // nothing else was sorted
+ CPPUNIT_ASSERT_EQUAL(double(1), pDoc->GetValue(ScAddress(1, 0, 0)));
+ CPPUNIT_ASSERT_EQUAL(double(3), pDoc->GetValue(ScAddress(1, 1, 0)));
+ CPPUNIT_ASSERT_EQUAL(double(2), pDoc->GetValue(ScAddress(1, 2, 0)));
+
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL("rows"_ostr, aView.m_sInvalidateSheetGeometry);
+}
+
+namespace
+{
+void lcl_typeCharsInCell(const std::string& aStr, SCCOL nCol, SCROW nRow, ScTabViewShell* pView,
+ ScModelObj* pModelObj, bool bInEdit = false, bool bCommit = true)
+{
+ if (!bInEdit)
+ pView->SetCursor(nCol, nRow);
+
+ for (const char& cChar : aStr)
+ {
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, cChar, 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, cChar, 0);
+ Scheduler::ProcessEventsToIdle();
+ }
+
+ if (bCommit)
+ {
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
+ Scheduler::ProcessEventsToIdle();
+ }
+}
+} //namespace
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testAutoInputStringBlock)
+{
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ CPPUNIT_ASSERT(pModelObj);
+ ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
+ CPPUNIT_ASSERT(pView);
+ ScDocument* pDoc = pModelObj->GetDocument();
+
+ pDoc->SetString(ScAddress(0, 3, 0), "ABC"); // A4
+ pDoc->SetString(ScAddress(0, 4, 0), "BAC"); // A5
+ ScFieldEditEngine& rEE = pDoc->GetEditEngine();
+ rEE.SetText("XYZ");
+ pDoc->SetEditText(ScAddress(0, 5, 0), rEE.CreateTextObject()); // A6
+ pDoc->SetValue(ScAddress(0, 6, 0), 123);
+ pDoc->SetString(ScAddress(0, 7, 0), "ZZZ"); // A8
+
+ ScAddress aA1(0, 0, 0);
+ lcl_typeCharsInCell("X", aA1.Col(), aA1.Row(), pView, pModelObj); // Type 'X' in A1
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("A1 should autocomplete", OUString("XYZ"), pDoc->GetString(aA1));
+
+ ScAddress aA3(0, 2, 0); // Adjacent to the string "superblock" A4:A8
+ lcl_typeCharsInCell("X", aA3.Col(), aA3.Row(), pView, pModelObj); // Type 'X' in A3
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("A3 should autocomplete", OUString("XYZ"), pDoc->GetString(aA3));
+
+ ScAddress aA9(0, 8, 0); // Adjacent to the string "superblock" A4:A8
+ lcl_typeCharsInCell("X", aA9.Col(), aA9.Row(), pView, pModelObj); // Type 'X' in A9
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("A9 should autocomplete", OUString("XYZ"), pDoc->GetString(aA9));
+
+ ScAddress aA11(0, 10, 0);
+ lcl_typeCharsInCell("X", aA11.Col(), aA11.Row(), pView, pModelObj); // Type 'X' in A11
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("A11 should autocomplete", OUString("XYZ"), pDoc->GetString(aA11));
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testAutoInputExactMatch)
+{
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ CPPUNIT_ASSERT(pModelObj);
+ ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
+ CPPUNIT_ASSERT(pView);
+ ScDocument* pDoc = pModelObj->GetDocument();
+
+ pDoc->SetString(ScAddress(0, 1, 0), "Simple"); // A2
+ pDoc->SetString(ScAddress(0, 2, 0), "Simple"); // A3
+ pDoc->SetString(ScAddress(0, 3, 0), "Sing"); // A4
+ ScFieldEditEngine& rEE = pDoc->GetEditEngine();
+ rEE.SetText("Case");
+ pDoc->SetEditText(ScAddress(0, 4, 0), rEE.CreateTextObject()); // A5
+ pDoc->SetString(ScAddress(0, 5, 0), "Time"); // A6
+ pDoc->SetString(ScAddress(0, 6, 0), "Castle"); // A7
+
+ ScAddress aA8(0, 7, 0);
+ lcl_typeCharsInCell("S", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "S" in A8
+ // Should show the partial completion "i".
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("1: A8 should have partial completion Si", OUString("Si"), pDoc->GetString(aA8));
+
+ lcl_typeCharsInCell("Si", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "Si" in A8
+ // Should not show any suggestions.
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("2: A8 should not show suggestions", OUString("Si"), pDoc->GetString(aA8));
+
+ lcl_typeCharsInCell("Sim", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "Sim" in A8
+ // Should autocomplete to "Simple" which is the only match.
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("3: A8 should autocomplete", OUString("Simple"), pDoc->GetString(aA8));
+
+ lcl_typeCharsInCell("Sin", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "Sin" in A8
+ // Should autocomplete to "Sing" which is the only match.
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("4: A8 should autocomplete", OUString("Sing"), pDoc->GetString(aA8));
+
+ lcl_typeCharsInCell("C", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "C" in A8
+ // Should show the partial completion "as".
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("5: A8 should have partial completion Cas", OUString("Cas"), pDoc->GetString(aA8));
+
+ lcl_typeCharsInCell("Cast", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "Cast" in A8
+ // Should autocomplete to "Castle" which is the only match.
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("6: A8 should autocomplete", OUString("Castle"), pDoc->GetString(aA8));
+
+ lcl_typeCharsInCell("T", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "T" in A8
+ // Should autocomplete to "Time" which is the only match.
+ CPPUNIT_ASSERT_EQUAL_MESSAGE("7: A8 should autocomplete", OUString("Time"), pDoc->GetString(aA8));
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testEditCursorBounds)
+{
+ comphelper::LibreOfficeKit::setCompatFlag(
+ comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ ScDocument* pDoc = pModelObj->GetDocument();
+
+ ViewCallback aView;
+ ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
+ CPPUNIT_ASSERT(pView);
+ comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(true);
+
+ // ~170% zoom.
+ pModelObj->setClientZoom(256, 256, 2222, 2222);
+ pModelObj->setClientVisibleArea(tools::Rectangle(7725, 379832, 16240, 6449));
+ Scheduler::ProcessEventsToIdle();
+
+ constexpr SCCOL nCol = 5;
+ constexpr SCROW nRow = 2048;
+ pDoc->SetValue(ScAddress(nCol, nRow, 0), 123);
+
+ aView.m_bOwnCursorInvalidated = false;
+ // Obtain the cell bounds via cursor.
+ pView->SetCursor(nCol, nRow);
+ Scheduler::ProcessEventsToIdle();
+
+ CPPUNIT_ASSERT(aView.m_bOwnCursorInvalidated);
+ CPPUNIT_ASSERT(!aView.m_aCellCursorBounds.IsEmpty());
+ tools::Rectangle aCellBounds(aView.m_aCellCursorBounds);
+
+ aView.m_aInvalidateCursorResult.clear();
+ // Enter edit mode in the same cell.
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::F2);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::F2);
+ Scheduler::ProcessEventsToIdle();
+
+ CPPUNIT_ASSERT(!aView.m_aInvalidateCursorResult.empty());
+ CPPUNIT_ASSERT_MESSAGE("Edit cursor must be in cell bounds!",
+ aCellBounds.Contains(aView.m_aInvalidateCursorResult.getBounds()));
+
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testTextSelectionBounds)
+{
+ comphelper::LibreOfficeKit::setCompatFlag(
+ comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ ScDocument* pDoc = pModelObj->GetDocument();
+
+ ViewCallback aView;
+ ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
+ CPPUNIT_ASSERT(pView);
+ comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(true);
+
+ // ~170% zoom.
+ pModelObj->setClientZoom(256, 256, 2222, 2222);
+ pModelObj->setClientVisibleArea(tools::Rectangle(7725, 379832, 16240, 6449));
+ Scheduler::ProcessEventsToIdle();
+
+ constexpr SCCOL nCol = 5;
+ constexpr SCROW nRow = 2048;
+ pDoc->SetValue(ScAddress(nCol, nRow, 0), 123);
+
+ aView.m_bOwnCursorInvalidated = false;
+ // Obtain the cell bounds via cursor.
+ pView->SetCursor(nCol, nRow);
+ Scheduler::ProcessEventsToIdle();
+
+ CPPUNIT_ASSERT(aView.m_bOwnCursorInvalidated);
+ CPPUNIT_ASSERT(!aView.m_aCellCursorBounds.IsEmpty());
+ tools::Rectangle aCellBounds(aView.m_aCellCursorBounds);
+
+ aView.m_aTextSelectionResult.clear();
+ // Enter edit mode in the same cell and select all text.
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::F2);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::F2);
+ Scheduler::ProcessEventsToIdle();
+
+ // CTRL + A
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_MOD1 | awt::Key::A);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_MOD1 | awt::Key::A);
+ Scheduler::ProcessEventsToIdle();
+
+ CPPUNIT_ASSERT(!aView.m_aTextSelectionResult.empty());
+ CPPUNIT_ASSERT_MESSAGE("Text selections must be in cell bounds!",
+ !aCellBounds.Intersection(aView.m_aTextSelectionResult.getBounds(0)).IsEmpty());
+
+ SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSheetViewDataCrash)
+{
+ ScModelObj* pModelObj = createDoc("empty.ods");
+
+ // view #1
+ int nView1 = SfxLokHelper::getView();
+ SfxLokHelper::setView(nView1);
+
+ // Imitate online while creating a new sheet on empty.ods.
+ uno::Sequence<beans::PropertyValue> aArgs(
+ comphelper::InitPropertySequence({
+ { "Name", uno::Any(OUString("NewSheet")) },
+ { "Index", uno::Any(sal_Int32(2)) }
+ }));
+ dispatchCommand(mxComponent, ".uno:Insert", aArgs);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEDOWN | KEY_MOD1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEDOWN | KEY_MOD1);
+ Scheduler::ProcessEventsToIdle();
+ ScTabViewShell* pView1 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
+ CPPUNIT_ASSERT(pView1);
+
+ // view #2
+ SfxLokHelper::createView();
+ ScTabViewShell* pView2 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
+ CPPUNIT_ASSERT(pView2);
+ Scheduler::ProcessEventsToIdle();
+
+ SfxLokHelper::setView(nView1);
+ // Delete a range.
+ pView1->SetCursor(1, 1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DOWN | KEY_SHIFT);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DOWN | KEY_SHIFT);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DELETE);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DELETE);
+ // It will crash at this point without the fix.
+ Scheduler::ProcessEventsToIdle();
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testTextBoxInsert)
+{
+ createDoc("empty.ods");
+ ViewCallback aView1;
+
+ // insert textbox
+ uno::Sequence<beans::PropertyValue> aArgs(
+ comphelper::InitPropertySequence({
+ { "CreateDirectly", uno::Any(true) }
+ }));
+ dispatchCommand(mxComponent, ".uno:DrawText", aArgs);
+
+ // check if we have textbox selected
+ CPPUNIT_ASSERT(!aView1.m_ShapeSelection.isEmpty());
+ CPPUNIT_ASSERT(aView1.m_ShapeSelection != "EMPTY");
+
+ Scheduler::ProcessEventsToIdle();
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testCommentCellCopyPaste)
+{
+ // Comments callback are emitted only if tiled annotations are off
+ comphelper::LibreOfficeKit::setTiledAnnotations(false);
+
+ // FIXME: Hack because previous tests do not destroy ScDocument(with annotations) on exit (?).
+ ScPostIt::mnLastPostItId = 1;
+
+ {
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ ViewCallback aView;
+ int nView = SfxLokHelper::getView();
+
+ SfxLokHelper::setView(nView);
+
+ ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
+ CPPUNIT_ASSERT(pTabViewShell);
+
+ lcl_typeCharsInCell("ABC", 0, 0, pTabViewShell, pModelObj); // Type "ABC" in A1
+
+ pTabViewShell->SetCursor(1, 1);
+
+ // Add a new comment
+ uno::Sequence<beans::PropertyValue> aArgs(comphelper::InitPropertySequence(
+ {
+ {"Text", uno::Any(OUString("LOK Comment Cell B2"))},
+ {"Author", uno::Any(OUString("LOK Client"))},
+ }));
+ dispatchCommand(mxComponent, ".uno:InsertAnnotation", aArgs);
+
+ // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action
+ CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView.m_aCommentCallbackResult.get<std::string>("action"));
+ CPPUNIT_ASSERT_EQUAL(std::string("1"), aView.m_aCommentCallbackResult.get<std::string>("id"));
+ CPPUNIT_ASSERT_EQUAL(std::string("0"), aView.m_aCommentCallbackResult.get<std::string>("tab"));
+ CPPUNIT_ASSERT_EQUAL(std::string("LOK Client"), aView.m_aCommentCallbackResult.get<std::string>("author"));
+ CPPUNIT_ASSERT_EQUAL(std::string("LOK Comment Cell B2"), aView.m_aCommentCallbackResult.get<std::string>("text"));
+
+ uno::Sequence<beans::PropertyValue> aCopyPasteArgs;
+
+ // We need separate tests for single cell copy-paste and cell-range copy-paste
+ // since they hit different code paths in ScColumn methods.
+
+ // Single cell(with comment) copy paste test
+ {
+ dispatchCommand(mxComponent, ".uno:Copy", aCopyPasteArgs);
+
+ pTabViewShell->SetCursor(1, 49);
+ Scheduler::ProcessEventsToIdle();
+ dispatchCommand(mxComponent, ".uno:Paste", aCopyPasteArgs); // Paste to cell B50
+
+ // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action
+ CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView.m_aCommentCallbackResult.get<std::string>("action"));
+ // Without the fix the id will be "1".
+ CPPUNIT_ASSERT_EQUAL(std::string("2"), aView.m_aCommentCallbackResult.get<std::string>("id"));
+ CPPUNIT_ASSERT_EQUAL(std::string("0"), aView.m_aCommentCallbackResult.get<std::string>("tab"));
+ CPPUNIT_ASSERT_EQUAL(std::string("LOK Client"), aView.m_aCommentCallbackResult.get<std::string>("author"));
+ CPPUNIT_ASSERT_EQUAL(std::string("LOK Comment Cell B2"), aView.m_aCommentCallbackResult.get<std::string>("text"));
+ }
+
+ // Cell range (with a comment) copy paste test
+ {
+ // Select range A1:C3
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_HOME | KEY_MOD1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_HOME | KEY_MOD1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN | KEY_SHIFT);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN | KEY_SHIFT);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN | KEY_SHIFT);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN | KEY_SHIFT);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT | KEY_SHIFT);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT | KEY_SHIFT);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT | KEY_SHIFT);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT | KEY_SHIFT);
+ Scheduler::ProcessEventsToIdle();
+
+ dispatchCommand(mxComponent, ".uno:Copy", aCopyPasteArgs);
+
+ pTabViewShell->SetCursor(3, 49);
+ Scheduler::ProcessEventsToIdle();
+ dispatchCommand(mxComponent, ".uno:Paste", aCopyPasteArgs); // Paste to cell D50
+
+ // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action
+ CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView.m_aCommentCallbackResult.get<std::string>("action"));
+ // Without the fix the id will be "1".
+ CPPUNIT_ASSERT_EQUAL(std::string("3"), aView.m_aCommentCallbackResult.get<std::string>("id"));
+ CPPUNIT_ASSERT_EQUAL(std::string("0"), aView.m_aCommentCallbackResult.get<std::string>("tab"));
+ CPPUNIT_ASSERT_EQUAL(std::string("LOK Client"), aView.m_aCommentCallbackResult.get<std::string>("author"));
+ CPPUNIT_ASSERT_EQUAL(std::string("LOK Comment Cell B2"), aView.m_aCommentCallbackResult.get<std::string>("text"));
+ }
+ }
+ comphelper::LibreOfficeKit::setTiledAnnotations(true);
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testInvalidEntrySave)
+{
+ loadFromFile(u"validity.xlsx");
+
+ // .uno:Save modifies the original file, make a copy first
+ saveAndReload("Calc Office Open XML");
+ ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
+ CPPUNIT_ASSERT(pModelObj);
+ pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ const ScDocument* pDoc = pModelObj->GetDocument();
+ ViewCallback aView;
+ int nView = SfxLokHelper::getView();
+
+ SfxLokHelper::setView(nView);
+
+ ScDocShell* pDocSh = dynamic_cast< ScDocShell* >( pModelObj->GetEmbeddedObject() );
+ ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
+ CPPUNIT_ASSERT(pTabViewShell);
+
+ // Type partial date "7/8" of "7/8/2013" that
+ // the validation cell at A8 can accept
+ lcl_typeCharsInCell("7/8", 0, 7, pTabViewShell, pModelObj,
+ false /* bInEdit */, false /* bCommit */); // Type "7/8" in A8
+
+ uno::Sequence<beans::PropertyValue> aArgs;
+ dispatchCommand(mxComponent, ".uno:Save", aArgs);
+
+ CPPUNIT_ASSERT_MESSAGE("Should not be marked modified after save", !pDocSh->IsModified());
+
+ // Complete the date in A8 by appending "/2013" and commit.
+ lcl_typeCharsInCell("/2013", 0, 7, pTabViewShell, pModelObj,
+ true /* bInEdit */, true /* bCommit */);
+
+ // This would hang if the date entered "7/8/2013" is not acceptable.
+ Scheduler::ProcessEventsToIdle();
+
+ // Ensure that the correct date is recorded in the document.
+ CPPUNIT_ASSERT_EQUAL(double(41463), pDoc->GetValue(ScAddress(0, 7, 0)));
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testUndoReordering)
+{
+ ScModelObj* pModelObj = createDoc("small.ods");
+ CPPUNIT_ASSERT(pModelObj);
+ ScDocument* pDoc = pModelObj->GetDocument();
+ CPPUNIT_ASSERT(pDoc);
+ ScUndoManager* pUndoManager = pDoc->GetUndoManager();
+ CPPUNIT_ASSERT(pUndoManager);
+
+ // view #1
+ int nView1 = SfxLokHelper::getView();
+ ViewCallback aView1;
+
+ // view #2
+ SfxLokHelper::createView();
+ int nView2 = SfxLokHelper::getView();
+ pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView2;
+
+ // text edit a cell in view #1
+ SfxLokHelper::setView(nView1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
+ Scheduler::ProcessEventsToIdle();
+
+ // check that undo action count is not 0
+ CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
+
+ // text edit a different cell in view #2
+ SfxLokHelper::setView(nView2);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
+ Scheduler::ProcessEventsToIdle();
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
+ Scheduler::ProcessEventsToIdle();
+
+ // check that undo action count is not 1
+ CPPUNIT_ASSERT_EQUAL(std::size_t(2), pUndoManager->GetUndoActionCount());
+
+ // try to execute undo in view #1
+ SfxLokHelper::setView(nView1);
+ dispatchCommand(mxComponent, ".uno:Undo", {});
+ // check that undo has been executed on view #1
+ CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
+
+ // try to execute undo in view #2
+ SfxLokHelper::setView(nView2);
+ dispatchCommand(mxComponent, ".uno:Undo", {});
+ // check that undo has been executed on view #2
+ CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetUndoActionCount());
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testUndoReorderingRedo)
+{
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ CPPUNIT_ASSERT(pModelObj);
+ ScDocument* pDoc = pModelObj->GetDocument();
+ CPPUNIT_ASSERT(pDoc);
+ ScUndoManager* pUndoManager = pDoc->GetUndoManager();
+ CPPUNIT_ASSERT(pUndoManager);
+ CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetUndoActionCount());
+
+ // view #1
+ int nView1 = SfxLokHelper::getView();
+ SfxViewShell* pView1 = SfxViewShell::Current();
+ ViewCallback aView1;
+
+ // view #2
+ SfxLokHelper::createView();
+ int nView2 = SfxLokHelper::getView();
+ SfxViewShell* pView2 = SfxViewShell::Current();
+ pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView2;
+
+ // text edit a cell in view #1
+ SfxLokHelper::setView(nView1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
+
+ // text edit another cell in view #1
+ SfxLokHelper::setView(nView1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'y', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'y', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'y', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'y', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(std::size_t(2), pUndoManager->GetUndoActionCount());
+ CPPUNIT_ASSERT_EQUAL(OUString("xx"), pDoc->GetString(ScAddress(0, 0, 0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("yy"), pDoc->GetString(ScAddress(0, 1, 0)));
+
+ // text edit a different cell in view #2
+ SfxLokHelper::setView(nView2);
+ ScTabViewShell* pViewShell2 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
+ pViewShell2->SetCursor(0, 2);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'C', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'C', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'C', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'C', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(std::size_t(3), pUndoManager->GetUndoActionCount());
+ CPPUNIT_ASSERT_EQUAL(OUString("xx"), pDoc->GetString(ScAddress(0, 0, 0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("yy"), pDoc->GetString(ScAddress(0, 1, 0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("CC"), pDoc->GetString(ScAddress(0, 2, 0)));
+
+ // View 1 presses undo, and the second cell is erased
+ SfxLokHelper::setView(nView1);
+ dispatchCommand(mxComponent, ".uno:Undo", {});
+ CPPUNIT_ASSERT_EQUAL(std::size_t(2), pUndoManager->GetUndoActionCount());
+ CPPUNIT_ASSERT_EQUAL(OUString("xx"), pDoc->GetString(ScAddress(0, 0, 0)));
+ CPPUNIT_ASSERT_EQUAL(OUString(""), pDoc->GetString(ScAddress(0, 1, 0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("CC"), pDoc->GetString(ScAddress(0, 2, 0)));
+
+ // Verify that the UNDO buttons/actions are still enabled
+ {
+ SfxItemSet aSet1(pView1->GetPool(), svl::Items<SID_UNDO, SID_UNDO>);
+ SfxItemSet aSet2(pView2->GetPool(), svl::Items<SID_UNDO, SID_UNDO>);
+ pView1->GetSlotState(SID_UNDO, nullptr, &aSet1);
+ pView2->GetSlotState(SID_UNDO, nullptr, &aSet2);
+ CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aSet1.GetItemState(SID_UNDO));
+ CPPUNIT_ASSERT(dynamic_cast< const SfxStringItem* >(aSet1.GetItem(SID_UNDO)));
+ CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aSet2.GetItemState(SID_UNDO));
+ CPPUNIT_ASSERT(dynamic_cast< const SfxStringItem* >(aSet2.GetItem(SID_UNDO)));
+ }
+
+ // View 1 presses undo again, and the first cell is erased
+ dispatchCommand(mxComponent, ".uno:Undo", {});
+ CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
+ CPPUNIT_ASSERT_EQUAL(OUString(""), pDoc->GetString(ScAddress(0, 0, 0)));
+ CPPUNIT_ASSERT_EQUAL(OUString(""), pDoc->GetString(ScAddress(0, 1, 0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("CC"), pDoc->GetString(ScAddress(0, 2, 0)));
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testUndoReorderingMulti)
+{
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ CPPUNIT_ASSERT(pModelObj);
+ ScDocument* pDoc = pModelObj->GetDocument();
+ CPPUNIT_ASSERT(pDoc);
+ ScUndoManager* pUndoManager = pDoc->GetUndoManager();
+ CPPUNIT_ASSERT(pUndoManager);
+ CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetUndoActionCount());
+
+ // view #1
+ int nView1 = SfxLokHelper::getView();
+ ViewCallback aView1;
+
+ // view #2
+ SfxLokHelper::createView();
+ int nView2 = SfxLokHelper::getView();
+ pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
+ ViewCallback aView2;
+
+ // text edit a cell in view #1
+ SfxLokHelper::setView(nView1);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
+
+ // text edit a different cell in view #2
+ SfxLokHelper::setView(nView2);
+ ScTabViewShell* pView2 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
+ pView2->SetCursor(0, 2);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'C', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'C', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'C', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'C', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(std::size_t(2), pUndoManager->GetUndoActionCount());
+ CPPUNIT_ASSERT_EQUAL(OUString("xx"), pDoc->GetString(ScAddress(0, 0, 0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("CC"), pDoc->GetString(ScAddress(0, 2, 0)));
+
+ // and another cell in view #2
+ pView2->SetCursor(0, 3);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'D', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'D', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'D', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'D', 0);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
+ pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
+ Scheduler::ProcessEventsToIdle();
+ CPPUNIT_ASSERT_EQUAL(std::size_t(3), pUndoManager->GetUndoActionCount());
+ CPPUNIT_ASSERT_EQUAL(OUString("xx"), pDoc->GetString(ScAddress(0, 0, 0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("CC"), pDoc->GetString(ScAddress(0, 2, 0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("DD"), pDoc->GetString(ScAddress(0, 3, 0)));
+
+ // View 1 presses undo
+ SfxLokHelper::setView(nView1);
+ dispatchCommand(mxComponent, ".uno:Undo", {});
+ CPPUNIT_ASSERT_EQUAL(std::size_t(2), pUndoManager->GetUndoActionCount());
+ CPPUNIT_ASSERT_EQUAL(OUString(""), pDoc->GetString(ScAddress(0, 0, 0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("CC"), pDoc->GetString(ScAddress(0, 2, 0)));
+ CPPUNIT_ASSERT_EQUAL(OUString("DD"), pDoc->GetString(ScAddress(0, 3, 0)));
+}
+
+CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testGetViewRenderState)
+{
+ // Add a pair of schemes, last added is the default
+ svtools::EditableColorConfig aColorConfig;
+ aColorConfig.AddScheme(u"Dark"_ustr);
+ aColorConfig.AddScheme(u"Light"_ustr);
+
+ ScModelObj* pModelObj = createDoc("empty.ods");
+ int nFirstViewId = SfxLokHelper::getView();
+ ViewCallback aView1;
+
+ CPPUNIT_ASSERT_EQUAL(";Default"_ostr, pModelObj->getViewRenderState());
+ // Create a second view
+ SfxLokHelper::createView();
+ ViewCallback aView2;
+ CPPUNIT_ASSERT_EQUAL(";Default"_ostr, pModelObj->getViewRenderState());
+ // Set second view to dark scheme
+ {
+ uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence(
+ {
+ { "NewTheme", uno::Any(OUString("Dark")) },
+ }
+ );
+ dispatchCommand(mxComponent, ".uno:ChangeTheme", aPropertyValues);
+ }
+ CPPUNIT_ASSERT_EQUAL(";Dark"_ostr, pModelObj->getViewRenderState());
+
+ // Switch back to first view and make sure it's the same
+ SfxLokHelper::setView(nFirstViewId);
+ CPPUNIT_ASSERT_EQUAL(";Default"_ostr, pModelObj->getViewRenderState());
+}
+
+CPPUNIT_PLUGIN_IMPLEMENT();
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */