tdf#154285 Check upper bound of arguments in SbRtl_Minute function
[LibreOffice.git] / sc / qa / unit / tiledrendering / tiledrendering.cxx
blob8d5280ea3128e51716afa852a095cfc1d046d38c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include "tiledrenderingmodeltestbase.cxx"
11 #include <test/helper/transferable.hxx>
13 #include <com/sun/star/datatransfer/clipboard/LokClipboard.hpp>
14 #include <com/sun/star/util/URLTransformer.hpp>
15 #include <comphelper/processfactory.hxx>
16 #include <comphelper/propertysequence.hxx>
17 #include <comphelper/servicehelper.hxx>
18 #include <sfx2/dispatch.hxx>
19 #include <sfx2/viewfrm.hxx>
20 #include <svl/stritem.hxx>
21 #include <svl/numformat.hxx>
22 #include <svl/zformat.hxx>
24 #include <comphelper/lok.hxx>
25 #include <comphelper/propertyvalue.hxx>
26 #include <comphelper/dispatchcommand.hxx>
27 #include <sfx2/msgpool.hxx>
28 #include <sfx2/childwin.hxx>
29 #include <sfx2/lokhelper.hxx>
30 #include <svx/svdpage.hxx>
31 #include <vcl/scheduler.hxx>
32 #include <vcl/vclevent.hxx>
33 #include <vcl/virdev.hxx>
34 #include <sc.hrc>
35 #include <tools/json_writer.hxx>
36 #include <postit.hxx>
37 #include <unotools/syslocaleoptions.hxx>
39 #include <attrib.hxx>
40 #include <scitems.hxx>
41 #include <document.hxx>
42 #include <docuno.hxx>
43 #include <drwlayer.hxx>
44 #include <editutil.hxx>
45 #include <undomanager.hxx>
47 static std::ostream& operator<<(std::ostream& os, ViewShellId const & id)
49 os << static_cast<sal_Int32>(id); return os;
52 namespace {
53 // for passing data to testInvalidateOnTextEditWithDifferentZoomLevels
54 struct ColRowZoom
56 SCCOL col;
57 SCROW row;
58 int zoom;
62 CPPUNIT_NS_BEGIN
63 namespace StringHelper
65 // used by CPPUNIT_TEST_PARAMETERIZED for testInvalidateOnTextEditWithDifferentZoomLevels
66 template<>
67 inline std::string toString(const ColRowZoom& item)
69 std::ostringstream ss;
70 ss << "zoom level: " << item.zoom << ", "
71 "col: " << item.col << ", "
72 "row: " << item.row;
73 return ss.str();
76 CPPUNIT_NS_END
78 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testRowColumnSelections)
80 ScModelObj* pModelObj = createDoc("select-row-cols.ods");
82 // Select the 5th row with no modifier
83 uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
84 { "Row", uno::Any(sal_Int32(5 - 1)) },
85 { "Modifier", uno::Any(sal_uInt16(0)) }
86 }));
87 dispatchCommand(mxComponent, u".uno:SelectRow"_ustr, aArgs);
89 // Check if it is selected
90 OString aResult = apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr);
91 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);
92 CPPUNIT_ASSERT_EQUAL(aExpected, aResult);
94 // Select the 10th row with shift modifier
95 aArgs = comphelper::InitPropertySequence({ { "Row", uno::Any(static_cast<sal_Int32>(10 - 1)) },
96 { "Modifier", uno::Any(KEY_SHIFT) } });
97 dispatchCommand(mxComponent, u".uno:SelectRow"_ustr, aArgs);
99 // Check if all the rows from 5th to 10th get selected
100 aResult = apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr);
101 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;
102 CPPUNIT_ASSERT_EQUAL(aExpected, aResult);
104 // Select the 10th row with ctrl modifier
105 aArgs = comphelper::InitPropertySequence({ { "Row", uno::Any(static_cast<sal_Int32>(13 - 1)) },
106 { "Modifier", uno::Any(KEY_MOD1) } });
107 dispatchCommand(mxComponent, u".uno:SelectRow"_ustr, aArgs);
109 // When we copy this, we don't get anything useful, but we must not crash
110 // (used to happen)
111 aResult = apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr);
112 CPPUNIT_ASSERT_EQUAL("9"_ostr, aResult);
114 // TODO check that we really selected what we wanted here
116 // Select Column 5 with ctrl modifier
117 aArgs = comphelper::InitPropertySequence({ { "Col", uno::Any(static_cast<sal_Int32>(5 - 1)) },
118 { "Modifier", uno::Any(KEY_MOD1) } });
119 dispatchCommand(mxComponent, u".uno:SelectColumn"_ustr, aArgs);
121 // When we copy this, we don't get anything useful, but we must not crash
122 // (used to happen)
123 aResult = apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr);
124 CPPUNIT_ASSERT_EQUAL("1"_ostr, aResult);
126 // TODO check that we really selected what we wanted here
128 // Test for deselection of already selected rows
129 // First Deselect Row 13 because copy doesn't work for multiple selections
130 aArgs = comphelper::InitPropertySequence({ { "Row", uno::Any(static_cast<sal_Int32>(13 - 1)) },
131 { "Modifier", uno::Any(KEY_MOD1) } });
132 dispatchCommand(mxComponent, u".uno:SelectRow"_ustr, aArgs);
134 // Deselect row 10
135 aArgs = comphelper::InitPropertySequence({ { "Row", uno::Any(static_cast<sal_Int32>(10 - 1)) },
136 { "Modifier", uno::Any(KEY_MOD1) } });
137 dispatchCommand(mxComponent, u".uno:SelectRow"_ustr, aArgs);
139 // Click at row 6 holding shift
140 aArgs = comphelper::InitPropertySequence({ { "Row", uno::Any(static_cast<sal_Int32>(6 - 1)) },
141 { "Modifier", uno::Any(KEY_SHIFT) } });
142 dispatchCommand(mxComponent, u".uno:SelectRow"_ustr, aArgs);
144 // only row 5 should remain selected
145 aResult = apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr);
146 aExpected = "1\t2\t3\t4\t5\t6\t7\t8\t9\t10\t11\t12\t13\t14\t15\t16\t17\t18\t19\t20\t21\n"_ostr;
147 CPPUNIT_ASSERT_EQUAL(aExpected, aResult);
150 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testPartHash)
152 ScModelObj* pModelObj = createDoc("sort-range.ods");
154 int nParts = pModelObj->getParts();
155 for (int it = 0; it < nParts; it++)
157 CPPUNIT_ASSERT(!pModelObj->getPartHash(it).isEmpty());
160 // check part that it does not exists
161 CPPUNIT_ASSERT(pModelObj->getPartHash(100).isEmpty());
164 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDocumentSize)
166 ScModelObj* pModelObj = createDoc("sort-range.ods");
167 ScDocShell* pDocSh = dynamic_cast< ScDocShell* >( pModelObj->GetEmbeddedObject() );
168 CPPUNIT_ASSERT(pDocSh);
170 ScTabViewShell* pViewShell = pDocSh->GetBestViewShell(false);
171 CPPUNIT_ASSERT(pViewShell);
173 setupLibreOfficeKitViewCallback(pViewShell);
175 // check initial document size
176 Size aDocSize = pModelObj->getDocumentSize();
177 CPPUNIT_ASSERT(aDocSize.Width() > 0);
178 CPPUNIT_ASSERT(aDocSize.Height() > 0);
180 // Set cursor column
181 pViewShell->SetCursor(100, 0);
182 // 2 seconds
183 osl::Condition::Result aResult = m_aDocSizeCondition.wait(std::chrono::seconds(2));
184 CPPUNIT_ASSERT_EQUAL(osl::Condition::result_ok, aResult);
186 // Set cursor row
187 pViewShell->SetCursor(0, 100);
188 // 2 seconds
189 aResult = m_aDocSizeCondition.wait(std::chrono::seconds(2));
190 CPPUNIT_ASSERT_EQUAL(osl::Condition::result_ok, aResult);
193 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testEmptyColumnSelection)
195 ScModelObj* pModelObj = createDoc("select-row-cols.ods");
197 // Select empty column, 1000
198 uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
199 { "Col", uno::Any(sal_Int32(1000 - 1)) },
200 { "Modifier", uno::Any(sal_uInt16(0)) }
201 }));
202 dispatchCommand(mxComponent, u".uno:SelectColumn"_ustr, aArgs);
204 // should be an empty string
205 CPPUNIT_ASSERT_EQUAL(OString(), apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr));
208 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testViewCursors)
210 ScModelObj* pModelObj = createDoc("select-row-cols.ods");
211 ViewCallback aView1;
212 SfxLokHelper::createView();
213 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
214 ViewCallback aView2(/*bDeleteListenerOnDestruct*/false);
215 // This was false, the new view did not get the view (cell) cursor of the old view.
216 CPPUNIT_ASSERT(aView2.m_bViewCursorInvalidated);
217 CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated);
218 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DOWN);
219 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DOWN);
220 Scheduler::ProcessEventsToIdle();
221 SfxLokHelper::destroyView(SfxLokHelper::getView());
222 CPPUNIT_ASSERT(aView1.m_bViewCursorInvalidated);
225 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSpellOnlineRenderParameter)
227 ScModelObj* pModelObj = createDoc("empty.ods");
229 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
230 CPPUNIT_ASSERT(pView);
232 bool bSet = pView->IsAutoSpell();
234 uno::Sequence<beans::PropertyValue> aPropertyValues =
236 comphelper::makePropertyValue(u".uno:SpellOnline"_ustr, uno::Any(!bSet)),
238 pModelObj->initializeForTiledRendering(aPropertyValues);
240 CPPUNIT_ASSERT_EQUAL(!bSet, pView->IsAutoSpell());
243 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testTextViewSelection)
245 // Create two views, and leave the second one current.
246 ScModelObj* pModelObj = createDoc("select-row-cols.ods");
247 ViewCallback aView1;
248 SfxLokHelper::createView();
249 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
250 ViewCallback aView2;
252 // Create a selection on two cells in the second view, that's a text selection in LOK terms.
253 aView1.m_bTextViewSelectionInvalidated = false;
254 dispatchCommand(mxComponent, u".uno:GoRightSel"_ustr, {});
255 // Make sure the first view got its notification.
256 CPPUNIT_ASSERT(aView1.m_bTextViewSelectionInvalidated);
259 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDocumentSizeChanged)
261 // Load a document that doesn't have much content.
262 createDoc("small.ods");
263 setupLibreOfficeKitViewCallback(SfxViewShell::Current());
265 // Go to the A30 cell -- that will extend the document size.
266 uno::Sequence<beans::PropertyValue> aPropertyValues =
268 comphelper::makePropertyValue(u"ToPoint"_ustr, u"$A$30"_ustr),
270 dispatchCommand(mxComponent, u".uno:GoToCell"_ustr, aPropertyValues);
271 // Assert that the size in the payload is not 0.
272 CPPUNIT_ASSERT(m_aDocumentSize.getWidth() > 0);
273 CPPUNIT_ASSERT(m_aDocumentSize.getHeight() > 0);
276 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testViewLock)
278 // Load a document that has a shape and create two views.
279 ScModelObj* pModelObj = createDoc("shape.ods");
280 ViewCallback aView1;
281 SfxLokHelper::createView();
282 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
283 ViewCallback aView2;
285 // Begin text edit in the second view and assert that the first gets a lock
286 // notification.
287 const ScViewData* pViewData = ScDocShell::GetViewData();
288 CPPUNIT_ASSERT(pViewData);
289 ScTabViewShell* pViewShell = pViewData->GetViewShell();
290 CPPUNIT_ASSERT(pViewShell);
291 SdrModel* pDrawModel = pViewData->GetDocument().GetDrawLayer();
292 SdrPage* pDrawPage = pDrawModel->GetPage(0);
293 SdrObject* pObject = pDrawPage->GetObj(0);
294 SdrView* pView = pViewShell->GetScDrawView();
295 aView1.m_bViewLock = false;
296 pView->SdrBeginTextEdit(pObject);
297 CPPUNIT_ASSERT(aView1.m_bViewLock);
299 // End text edit in the second view, and assert that the lock is removed in
300 // the first view.
301 pView->SdrEndTextEdit();
302 CPPUNIT_ASSERT(!aView1.m_bViewLock);
305 namespace
307 void lcl_extractHandleParameters(std::string_view selection, sal_uInt32& id, sal_uInt32& x, sal_uInt32& y)
309 OString extraInfo( selection.substr(selection.find("{")) );
310 std::stringstream aStream((std::string(extraInfo)));
311 boost::property_tree::ptree aTree;
312 boost::property_tree::read_json(aStream, aTree);
313 boost::property_tree::ptree
314 handle0 = aTree
315 .get_child("handles")
316 .get_child("kinds")
317 .get_child("rectangle")
318 .get_child("1")
319 .begin()->second;
320 id = handle0.get_child("id").get_value<int>();
321 x = handle0.get_child("point").get_child("x").get_value<int>();
322 y = handle0.get_child("point").get_child("y").get_value<int>();
324 } //namespace
326 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testMoveShapeHandle)
328 ScModelObj* pModelObj = createDoc("shape.ods");
329 ViewCallback aView1;
330 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, /*x=*/ 1,/*y=*/ 1,/*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
331 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, /*x=*/ 1, /*y=*/ 1, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
332 Scheduler::ProcessEventsToIdle();
334 CPPUNIT_ASSERT(!aView1.m_ShapeSelection.isEmpty());
336 sal_uInt32 id, x, y;
337 lcl_extractHandleParameters(aView1.m_ShapeSelection, id, x ,y);
338 sal_uInt32 oldX = x;
339 sal_uInt32 oldY = y;
340 uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
342 {"HandleNum", uno::Any(id)},
343 {"NewPosX", uno::Any(x+1)},
344 {"NewPosY", uno::Any(y+1)}
345 }));
346 dispatchCommand(mxComponent, u".uno:MoveShapeHandle"_ustr, aPropertyValues);
347 CPPUNIT_ASSERT(!aView1.m_ShapeSelection.isEmpty());
348 lcl_extractHandleParameters(aView1.m_ShapeSelection, id, x ,y);
349 CPPUNIT_ASSERT_EQUAL(x-1, oldX);
350 CPPUNIT_ASSERT_EQUAL(y-1, oldY);
354 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testColRowResize)
356 ScModelObj* pModelObj = createDoc("sort-range.ods");
357 ScDocShell* pDocSh = dynamic_cast< ScDocShell* >( pModelObj->GetEmbeddedObject() );
358 CPPUNIT_ASSERT(pDocSh);
360 ScTabViewShell* pViewShell = pDocSh->GetBestViewShell(false);
361 CPPUNIT_ASSERT(pViewShell);
363 setupLibreOfficeKitViewCallback(pViewShell);
365 ScDocument& rDoc = pDocSh->GetDocument();
367 // Col 3, Tab 0
368 uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
369 { "ColumnWidth", uno::Any(sal_uInt16(4000)) }, // 4cm
370 { "Column", uno::Any(sal_Int16(3)) }
371 }));
372 dispatchCommand(mxComponent, u".uno:ColumnWidth"_ustr, aArgs);
374 sal_uInt16 nWidth = o3tl::convert(rDoc.GetColWidth(static_cast<SCCOL>(2), static_cast<SCTAB>(0), false), o3tl::Length::twip, o3tl::Length::mm100);
375 CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(4001), nWidth);
377 // Row 5, Tab 0
378 uno::Sequence<beans::PropertyValue> aArgs2( comphelper::InitPropertySequence({
379 { "RowHeight", uno::Any(sal_uInt16(2000)) },
380 { "Row", uno::Any(sal_Int16(5)) },
381 }));
382 dispatchCommand(mxComponent, u".uno:RowHeight"_ustr, aArgs2);
384 sal_uInt16 nHeight = o3tl::convert(rDoc.GetRowHeight(static_cast<SCROW>(4), static_cast<SCTAB>(0), false), o3tl::Length::twip, o3tl::Length::mm100);
385 CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(2000), nHeight);
388 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testUndoShells)
390 ScModelObj* pModelObj = createDoc("small.ods");
391 // Clear the currently selected cell.
392 dispatchCommand(mxComponent, u".uno:ClearContents"_ustr, {});
394 auto pDocShell = dynamic_cast<ScDocShell*>(pModelObj->GetEmbeddedObject());
395 CPPUNIT_ASSERT(pDocShell);
396 ScDocument& rDoc = pDocShell->GetDocument();
397 ScUndoManager* pUndoManager = rDoc.GetUndoManager();
398 CPPUNIT_ASSERT(pUndoManager);
399 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pUndoManager->GetUndoActionCount());
400 sal_Int32 nView1 = SfxLokHelper::getView();
401 // This was -1: ScSimpleUndo did not remember what view shell created it.
402 CPPUNIT_ASSERT_EQUAL(ViewShellId(nView1), pUndoManager->GetUndoAction()->GetViewShellId());
405 namespace
407 bool lcl_hasEditView(const ScViewData& rViewData)
409 bool bResult = false;
410 for (unsigned int i=0; i<4; i++)
412 bResult = rViewData.HasEditView( static_cast<ScSplitPos>(i) );
413 if (bResult) break;
415 return bResult;
417 } // namespace
419 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testTextEditViews)
421 ScModelObj* pModelObj = createDoc("small.ods");
422 CPPUNIT_ASSERT(pModelObj);
423 ScViewData* pViewData = ScDocShell::GetViewData();
424 CPPUNIT_ASSERT(pViewData);
426 // view #1
427 ViewCallback aView1;
428 CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
430 // text edit a cell in view #1
431 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
432 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
433 Scheduler::ProcessEventsToIdle();
434 CPPUNIT_ASSERT(lcl_hasEditView(*pViewData));
436 // view #2
437 SfxLokHelper::createView();
438 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
439 ViewCallback aView2;
441 // move cell cursor i view #2
442 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DOWN);
443 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DOWN);
444 Scheduler::ProcessEventsToIdle();
446 // check that text edit view in view #1 has not be killed
447 CPPUNIT_ASSERT(lcl_hasEditView(*pViewData));
450 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testTextEditViewInvalidations)
452 ScModelObj* pModelObj = createDoc("small.ods");
453 CPPUNIT_ASSERT(pModelObj);
454 ScViewData* pViewData = ScDocShell::GetViewData();
455 CPPUNIT_ASSERT(pViewData);
457 // view #1
458 int nView1 = SfxLokHelper::getView();
459 ViewCallback aView1;
460 CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
462 // view #2
463 SfxLokHelper::createView();
464 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
465 ViewCallback aView2;
467 // text edit a cell in view #1
468 SfxLokHelper::setView(nView1);
469 aView2.m_bInvalidateTiles = false;
470 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
471 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
472 Scheduler::ProcessEventsToIdle();
473 CPPUNIT_ASSERT(lcl_hasEditView(*pViewData));
474 CPPUNIT_ASSERT(aView2.m_bInvalidateTiles);
476 // text edit a cell in view #1 until
477 // we can be sure we are out of the initial tile
478 for (int i = 0; i < 40; ++i)
480 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
481 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
483 Scheduler::ProcessEventsToIdle();
485 // text edit a cell in view #1 inside the new tile and
486 // check that view #2 receive a tile invalidate message
487 aView2.m_bInvalidateTiles = false;
488 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
489 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
490 Scheduler::ProcessEventsToIdle();
491 CPPUNIT_ASSERT(aView2.m_bInvalidateTiles);
493 // view #3
494 SfxLokHelper::createView();
495 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
496 ViewCallback aView3;
498 // text edit a cell in view #1
499 SfxLokHelper::setView(nView1);
500 aView3.m_bInvalidateTiles = false;
501 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'y', 0);
502 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'y', 0);
503 Scheduler::ProcessEventsToIdle();
504 CPPUNIT_ASSERT(aView3.m_bInvalidateTiles);
507 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testCreateViewGraphicSelection)
509 // Load a document that has a shape and create two views.
510 ScModelObj* pModelObj = createDoc("shape.ods");
511 ViewCallback aView1;
513 // Mark the graphic in the first view.
514 const ScViewData* pViewData = ScDocShell::GetViewData();
515 CPPUNIT_ASSERT(pViewData);
516 ScTabViewShell* pViewShell = pViewData->GetViewShell();
517 CPPUNIT_ASSERT(pViewShell);
518 SdrModel* pDrawModel = pViewData->GetDocument().GetDrawLayer();
519 SdrPage* pDrawPage = pDrawModel->GetPage(0);
520 SdrObject* pObject = pDrawPage->GetObj(0);
521 SdrView* pView = pViewShell->GetScDrawView();
522 aView1.m_bGraphicSelection = false;
523 aView1.m_bGraphicViewSelection = false;
524 pView->MarkObj(pObject, pView->GetSdrPageView());
525 CPPUNIT_ASSERT(aView1.m_bGraphicSelection);
527 // Create a second view.
528 int nView1 = SfxLokHelper::getView();
529 SfxLokHelper::createView();
530 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
531 ViewCallback aView2;
532 CPPUNIT_ASSERT(aView2.m_bGraphicViewSelection);
533 CPPUNIT_ASSERT(aView1.m_bGraphicViewSelection);
535 SfxLokHelper::setView(nView1);
536 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
539 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testGraphicInvalidate)
541 // Load a document that has a shape and create two views.
542 ScModelObj* pModelObj = createDoc("shape.ods");
543 ViewCallback aView;
545 // Click to select graphic
546 aView.m_bGraphicSelection = false;
547 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, /*x=*/ 1,/*y=*/ 1,/*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
548 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, /*x=*/ 1, /*y=*/ 1, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
549 Scheduler::ProcessEventsToIdle();
550 CPPUNIT_ASSERT(aView.m_bGraphicSelection);
552 // Drag Drop graphic
553 aView.m_bGraphicSelection = false;
554 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, /*x=*/ 1,/*y=*/ 1,/*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
555 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEMOVE, /*x=*/ 1,/*y=*/ 10,/*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
556 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, /*x=*/ 1, /*y=*/ 10, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
557 Scheduler::ProcessEventsToIdle();
558 CPPUNIT_ASSERT(!aView.m_bFullInvalidateTiles);
560 // Check again
561 Scheduler::ProcessEventsToIdle();
562 CPPUNIT_ASSERT(!aView.m_bFullInvalidateTiles);
565 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testAutoSum)
567 createDoc("small.ods");
569 ViewCallback aView;
571 uno::Sequence<beans::PropertyValue> aArgs;
572 dispatchCommand(mxComponent, u".uno:AutoSum"_ustr, aArgs);
573 CPPUNIT_ASSERT(aView.m_sCellFormula.startsWith("=SUM("));
576 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testHideColRow)
578 createDoc("small.ods");
580 uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
581 { "Col", uno::Any(sal_Int32(2 - 1)) },
582 { "Modifier", uno::Any(KEY_SHIFT) }
583 }));
584 dispatchCommand(mxComponent, u".uno:SelectColumn"_ustr, aArgs);
586 uno::Sequence<beans::PropertyValue> aArgs2( comphelper::InitPropertySequence({
587 { "Col", uno::Any(sal_Int32(3 - 1)) },
588 { "Modifier", uno::Any(sal_uInt16(0)) }
589 }));
591 dispatchCommand(mxComponent, u".uno:SelectColumn"_ustr, aArgs2);
594 ScViewData* pViewData = ScDocShell::GetViewData();
595 CPPUNIT_ASSERT(pViewData);
596 SCCOL nOldCurX = pViewData->GetCurX();
597 SCROW nOldCurY = pViewData->GetCurY();
599 uno::Sequence<beans::PropertyValue> aArgs;
600 dispatchCommand(mxComponent, u".uno:HideColumn"_ustr, aArgs);
603 SCCOL nNewCurX = pViewData->GetCurX();
604 SCROW nNewCurY = pViewData->GetCurY();
605 CPPUNIT_ASSERT(nNewCurX > nOldCurX);
606 CPPUNIT_ASSERT_EQUAL(nOldCurY, nNewCurY);
608 uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
609 { "Row", uno::Any(sal_Int32(6 - 1)) },
610 { "Modifier", uno::Any(KEY_SHIFT) }
611 }));
612 dispatchCommand(mxComponent, u".uno:SelectRow"_ustr, aArgs);
614 uno::Sequence<beans::PropertyValue> aArgs2( comphelper::InitPropertySequence({
615 { "Row", uno::Any(sal_Int32(7 - 1)) },
616 { "Modifier", uno::Any(sal_uInt16(0)) }
617 }));
618 dispatchCommand(mxComponent, u".uno:SelectRow"_ustr, aArgs2);
621 nOldCurX = pViewData->GetCurX();
622 nOldCurY = pViewData->GetCurY();
624 uno::Sequence<beans::PropertyValue> aArgs;
625 dispatchCommand(mxComponent, u".uno:HideRow"_ustr, aArgs);
627 nNewCurX = pViewData->GetCurX();
628 nNewCurY = pViewData->GetCurY();
629 CPPUNIT_ASSERT(nNewCurY > nOldCurY);
630 CPPUNIT_ASSERT_EQUAL(nOldCurX, nNewCurX);
633 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testInvalidateOnCopyPasteCells)
635 ScModelObj* pModelObj = createDoc("small.ods");
636 CPPUNIT_ASSERT(pModelObj);
638 // view
639 ViewCallback aView;
641 uno::Sequence<beans::PropertyValue> aArgs;
642 // select and copy cells
643 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_HOME | KEY_MOD1);
644 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_HOME | KEY_MOD1);
645 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN | KEY_SHIFT);
646 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN | KEY_SHIFT);
647 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT | KEY_SHIFT);
648 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT | KEY_SHIFT);
649 Scheduler::ProcessEventsToIdle();
650 dispatchCommand(mxComponent, u".uno:Copy"_ustr, aArgs);
652 // move to destination cell
653 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
654 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
655 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN | KEY_MOD1);
656 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN | KEY_MOD1);
657 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_UP);
658 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_UP);
659 Scheduler::ProcessEventsToIdle();
661 // paste cells
662 aView.m_bInvalidateTiles = false;
663 dispatchCommand(mxComponent, u".uno:Paste"_ustr, aArgs);
664 CPPUNIT_ASSERT(aView.m_bInvalidateTiles);
667 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testInvalidateOnInserRowCol)
669 ScModelObj* pModelObj = createDoc("small.ods");
670 CPPUNIT_ASSERT(pModelObj);
672 // view
673 ViewCallback aView;
675 uno::Sequence<beans::PropertyValue> aArgs;
676 // move downward
677 for (int i = 0; i < 200; ++i)
679 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
680 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
682 Scheduler::ProcessEventsToIdle();
684 // insert row
685 aView.m_bInvalidateTiles = false;
686 aView.m_aInvalidations.clear();
687 dispatchCommand(mxComponent, u".uno:InsertRows"_ustr, aArgs);
688 CPPUNIT_ASSERT(aView.m_bInvalidateTiles);
689 CPPUNIT_ASSERT_EQUAL(size_t(1), aView.m_aInvalidations.size());
690 CPPUNIT_ASSERT_EQUAL(tools::Rectangle(-75, 51240, 32212230, 63990), aView.m_aInvalidations[0]);
692 // move on the right
693 for (int i = 0; i < 200; ++i)
695 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT);
696 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT);
698 Scheduler::ProcessEventsToIdle();
700 // insert column
701 aView.m_bInvalidateTiles = false;
702 aView.m_aInvalidations.clear();
703 dispatchCommand(mxComponent, u".uno:InsertColumns"_ustr, aArgs);
704 CPPUNIT_ASSERT(aView.m_bInvalidateTiles);
705 CPPUNIT_ASSERT_EQUAL(size_t(1), aView.m_aInvalidations.size());
706 CPPUNIT_ASSERT_EQUAL(tools::Rectangle(254925, -15, 32212230, 63990), aView.m_aInvalidations[0]);
709 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testCommentCallback)
711 // Comments callback are emitted only if tiled annotations are off
712 comphelper::LibreOfficeKit::setTiledAnnotations(false);
714 // FIXME: Hack because previous tests do not destroy ScDocument(with annotations) on exit (?).
715 ScPostIt::mnLastPostItId = 1;
718 ScModelObj* pModelObj = createDoc("small.ods");
719 ViewCallback aView1;
720 int nView1 = SfxLokHelper::getView();
722 // Create a 2nd view
723 SfxLokHelper::createView();
724 pModelObj->initializeForTiledRendering({});
725 ViewCallback aView2;
727 SfxLokHelper::setView(nView1);
729 ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
730 if (pTabViewShell)
731 pTabViewShell->SetCursor(4, 4);
733 // Add a new comment
734 uno::Sequence<beans::PropertyValue> aArgs(comphelper::InitPropertySequence(
736 {"Text", uno::Any(u"Comment"_ustr)},
737 {"Author", uno::Any(u"LOK User1"_ustr)},
738 }));
739 dispatchCommand(mxComponent, u".uno:InsertAnnotation"_ustr, aArgs);
741 // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action
742 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
743 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
744 CPPUNIT_ASSERT_EQUAL(std::string("1"), aView1.m_aCommentCallbackResult.get<std::string>("id"));
745 CPPUNIT_ASSERT_EQUAL(std::string("1"), aView2.m_aCommentCallbackResult.get<std::string>("id"));
746 CPPUNIT_ASSERT_EQUAL(std::string("0"), aView1.m_aCommentCallbackResult.get<std::string>("tab"));
747 CPPUNIT_ASSERT_EQUAL(std::string("0"), aView2.m_aCommentCallbackResult.get<std::string>("tab"));
748 CPPUNIT_ASSERT_EQUAL(std::string("LOK User1"), aView1.m_aCommentCallbackResult.get<std::string>("author"));
749 CPPUNIT_ASSERT_EQUAL(std::string("LOK User1"), aView2.m_aCommentCallbackResult.get<std::string>("author"));
750 CPPUNIT_ASSERT_EQUAL(std::string("Comment"), aView1.m_aCommentCallbackResult.get<std::string>("text"));
751 CPPUNIT_ASSERT_EQUAL(std::string("Comment"), aView2.m_aCommentCallbackResult.get<std::string>("text"));
752 CPPUNIT_ASSERT_EQUAL(std::string("4 4 4 4"), aView1.m_aCommentCallbackResult.get<std::string>("cellRange"));
753 CPPUNIT_ASSERT_EQUAL(std::string("4 4 4 4"), aView2.m_aCommentCallbackResult.get<std::string>("cellRange"));
755 // Ensure deleting rows updates comments
756 if (pTabViewShell)
757 pTabViewShell->SetCursor(2, 2);
759 dispatchCommand(mxComponent, u".uno:DeleteRows"_ustr, {});
760 Scheduler::ProcessEventsToIdle();
761 CPPUNIT_ASSERT_EQUAL(std::string("4 3 4 3"), aView1.m_aCommentCallbackResult.get<std::string>("cellRange"));
762 CPPUNIT_ASSERT_EQUAL(std::string("4 3 4 3"), aView2.m_aCommentCallbackResult.get<std::string>("cellRange"));
764 // Ensure deleting columns updates comments
765 if (pTabViewShell)
766 pTabViewShell->SetCursor(2, 2);
768 dispatchCommand(mxComponent, u".uno:DeleteColumns"_ustr, {});
769 Scheduler::ProcessEventsToIdle();
770 CPPUNIT_ASSERT_EQUAL(std::string("3 3 3 3"), aView1.m_aCommentCallbackResult.get<std::string>("cellRange"));
771 CPPUNIT_ASSERT_EQUAL(std::string("3 3 3 3"), aView2.m_aCommentCallbackResult.get<std::string>("cellRange"));
773 std::string aCommentId = aView1.m_aCommentCallbackResult.get<std::string>("id");
775 // Edit a comment
776 // Select some random cell, we should be able to edit the cell note without
777 // selecting the cell
778 if (pTabViewShell)
779 pTabViewShell->SetCursor(3, 100);
780 aArgs = comphelper::InitPropertySequence(
782 {"Id", uno::Any(OUString::createFromAscii(aCommentId))},
783 {"Text", uno::Any(u"Edited comment"_ustr)},
784 {"Author", uno::Any(u"LOK User2"_ustr)},
786 dispatchCommand(mxComponent, u".uno:EditAnnotation"_ustr, aArgs);
788 // We received a LOK_CALLBACK_COMMENT callback with comment 'Modify' action
789 CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
790 CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
791 CPPUNIT_ASSERT_EQUAL(aCommentId, aView1.m_aCommentCallbackResult.get<std::string>("id"));
792 CPPUNIT_ASSERT_EQUAL(aCommentId, aView2.m_aCommentCallbackResult.get<std::string>("id"));
793 CPPUNIT_ASSERT_EQUAL(std::string("LOK User2"), aView1.m_aCommentCallbackResult.get<std::string>("author"));
794 CPPUNIT_ASSERT_EQUAL(std::string("LOK User2"), aView2.m_aCommentCallbackResult.get<std::string>("author"));
795 CPPUNIT_ASSERT_EQUAL(std::string("Edited comment"), aView1.m_aCommentCallbackResult.get<std::string>("text"));
796 CPPUNIT_ASSERT_EQUAL(std::string("Edited comment"), aView2.m_aCommentCallbackResult.get<std::string>("text"));
797 CPPUNIT_ASSERT_EQUAL(std::string("3 3 3 3"), aView1.m_aCommentCallbackResult.get<std::string>("cellRange"));
798 CPPUNIT_ASSERT_EQUAL(std::string("3 3 3 3"), aView2.m_aCommentCallbackResult.get<std::string>("cellRange"));
800 // Delete the comment
801 if (pTabViewShell)
802 pTabViewShell->SetCursor(4, 43);
803 aArgs = comphelper::InitPropertySequence(
805 {"Id", uno::Any(OUString::createFromAscii(aCommentId))}
807 dispatchCommand(mxComponent, u".uno:DeleteNote"_ustr, aArgs);
809 // We received a LOK_CALLBACK_COMMENT callback with comment 'Remove' action
810 CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
811 CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
812 CPPUNIT_ASSERT_EQUAL(aCommentId, aView1.m_aCommentCallbackResult.get<std::string>("id"));
813 CPPUNIT_ASSERT_EQUAL(aCommentId, aView2.m_aCommentCallbackResult.get<std::string>("id"));
815 comphelper::LibreOfficeKit::setTiledAnnotations(true);
818 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testUndoLimiting)
820 ScModelObj* pModelObj = createDoc("small.ods");
821 CPPUNIT_ASSERT(pModelObj);
822 ScDocument* pDoc = pModelObj->GetDocument();
823 CPPUNIT_ASSERT(pDoc);
824 ScUndoManager* pUndoManager = pDoc->GetUndoManager();
825 CPPUNIT_ASSERT(pUndoManager);
827 // view #1
828 int nView1 = SfxLokHelper::getView();
829 ViewCallback aView1;
831 // view #2
832 SfxLokHelper::createView();
833 int nView2 = SfxLokHelper::getView();
834 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
835 ViewCallback aView2;
837 // text edit a cell in view #1
838 SfxLokHelper::setView(nView1);
839 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
840 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
841 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
842 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
843 Scheduler::ProcessEventsToIdle();
845 // check that undo action count in not 0
846 CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
848 // try to execute undo in view #2
849 SfxLokHelper::setView(nView2);
850 dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});
851 // check that undo has not been executed on view #2
852 CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
854 // try to execute undo in view #1
855 SfxLokHelper::setView(nView1);
856 dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});
857 // check that undo has been executed on view #1
858 CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetUndoActionCount());
860 // check that redo action count in not 0
861 CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetRedoActionCount());
863 // try to execute redo in view #2
864 SfxLokHelper::setView(nView2);
865 dispatchCommand(mxComponent, u".uno:Redo"_ustr, {});
866 // check that redo has not been executed on view #2
867 CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetRedoActionCount());
869 // try to execute redo in view #1
870 SfxLokHelper::setView(nView1);
871 dispatchCommand(mxComponent, u".uno:Redo"_ustr, {});
872 // check that redo has been executed on view #1
873 CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetRedoActionCount());
876 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testUndoRepairDispatch)
878 ScModelObj* pModelObj = createDoc("small.ods");
879 CPPUNIT_ASSERT(pModelObj);
880 ScDocument* pDoc = pModelObj->GetDocument();
881 CPPUNIT_ASSERT(pDoc);
882 ScUndoManager* pUndoManager = pDoc->GetUndoManager();
883 CPPUNIT_ASSERT(pUndoManager);
885 // view #1
886 int nView1 = SfxLokHelper::getView();
887 ViewCallback aView1;
889 // view #2
890 SfxLokHelper::createView();
891 int nView2 = SfxLokHelper::getView();
892 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
893 ViewCallback aView2;
895 // text edit a cell in view #1
896 SfxLokHelper::setView(nView1);
897 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
898 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
899 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
900 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
901 Scheduler::ProcessEventsToIdle();
903 // check that undo action count in not 0
904 CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
906 // try to execute undo in view #2
907 SfxLokHelper::setView(nView2);
908 dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});
909 // check that undo has not been executed on view #2
910 CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
912 // try to execute undo in view #2 in repair mode
913 SfxLokHelper::setView(nView2);
914 uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
916 {"Repair", uno::Any(true)}
917 }));
918 dispatchCommand(mxComponent, u".uno:Undo"_ustr, aPropertyValues);
919 // check that undo has been executed on view #2 in repair mode
920 CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetUndoActionCount());
923 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testInsertGraphicInvalidations)
925 ScModelObj* pModelObj = createDoc("small.ods");
926 CPPUNIT_ASSERT(pModelObj);
927 ScViewData* pViewData = ScDocShell::GetViewData();
928 CPPUNIT_ASSERT(pViewData);
930 // view
931 ViewCallback aView;
933 // we need to paint a tile in the view for triggering the tile invalidation solution
934 int nCanvasWidth = 256;
935 int nCanvasHeight = 256;
936 std::vector<unsigned char> aBuffer(nCanvasWidth * nCanvasHeight * 4);
937 ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA);
938 pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), Fraction(1.0), Point(), aBuffer.data());
939 pModelObj->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, /*nTilePosY=*/0, /*nTileWidth=*/3840, /*nTileHeight=*/3840);
940 Scheduler::ProcessEventsToIdle();
942 // insert an image in view and see if both views are invalidated
943 aView.m_bInvalidateTiles = false;
944 uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
945 { "FileName", uno::Any(createFileURL(u"smile.png")) }
946 }));
947 dispatchCommand(mxComponent, u".uno:InsertGraphic"_ustr, aArgs);
948 CPPUNIT_ASSERT(aView.m_bInvalidateTiles);
950 // undo image insertion in view and see if both views are invalidated
951 aView.m_bInvalidateTiles = false;
952 uno::Sequence<beans::PropertyValue> aArgs2;
953 dispatchCommand(mxComponent, u".uno:Undo"_ustr, aArgs2);
954 CPPUNIT_ASSERT(aView.m_bInvalidateTiles);
957 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDocumentSizeWithTwoViews)
959 // Open a document that has the cursor far away & paint a tile
960 ScModelObj* pModelObj = createDoc("cursor-away.ods");
962 // Set the visible area, and press page down
963 pModelObj->setClientVisibleArea(tools::Rectangle(750, 1861, 20583, 6997));
964 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEDOWN);
965 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEDOWN);
966 Scheduler::ProcessEventsToIdle();
968 int nCanvasWidth = 256;
969 int nCanvasHeight = 256;
970 std::vector<unsigned char> aBuffer1(nCanvasWidth * nCanvasHeight * 4);
971 ScopedVclPtrInstance<VirtualDevice> pDevice1(DeviceFormat::WITHOUT_ALPHA);
972 pDevice1->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), Fraction(1.0), Point(), aBuffer1.data());
973 pModelObj->paintTile(*pDevice1, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, /*nTilePosY=*/291840, /*nTileWidth=*/3840, /*nTileHeight=*/3840);
974 Scheduler::ProcessEventsToIdle();
976 // Create a new view
977 int nView1 = SfxLokHelper::getView();
978 SfxLokHelper::createView();
980 std::vector<unsigned char> aBuffer2(nCanvasWidth * nCanvasHeight * 4);
981 ScopedVclPtrInstance<VirtualDevice> pDevice2(DeviceFormat::WITHOUT_ALPHA);
982 pDevice2->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), Fraction(1.0), Point(), aBuffer2.data());
983 pModelObj->paintTile(*pDevice2, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, /*nTilePosY=*/291840, /*nTileWidth=*/3840, /*nTileHeight=*/3840);
984 Scheduler::ProcessEventsToIdle();
986 // Check that the tiles actually have the same content
987 for (size_t i = 0; i < aBuffer1.size(); ++i)
988 CPPUNIT_ASSERT_EQUAL(aBuffer1[i], aBuffer2[i]);
990 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
991 SfxLokHelper::setView(nView1);
992 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
995 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDisableUndoRepair)
997 ScModelObj* pModelObj = createDoc("cursor-away.ods");
998 CPPUNIT_ASSERT(pModelObj);
1000 // view #1
1001 int nView1 = SfxLokHelper::getView();
1002 SfxViewShell* pView1 = SfxViewShell::Current();
1004 // view #2
1005 SfxLokHelper::createView();
1006 int nView2 = SfxLokHelper::getView();
1007 SfxViewShell* pView2 = SfxViewShell::Current();
1008 CPPUNIT_ASSERT(pView1 != pView2);
1010 // both views have UNDO disabled
1012 SfxItemSet aSet1(pView1->GetPool(), svl::Items<SID_UNDO, SID_UNDO>);
1013 SfxItemSet aSet2(pView2->GetPool(), svl::Items<SID_UNDO, SID_UNDO>);
1014 pView1->GetSlotState(SID_UNDO, nullptr, &aSet1);
1015 pView2->GetSlotState(SID_UNDO, nullptr, &aSet2);
1016 CPPUNIT_ASSERT_EQUAL(SfxItemState::DISABLED, aSet1.GetItemState(SID_UNDO));
1017 CPPUNIT_ASSERT_EQUAL(SfxItemState::DISABLED, aSet2.GetItemState(SID_UNDO));
1020 // text edit a cell in view #1
1021 SfxLokHelper::setView(nView1);
1022 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'h', 0);
1023 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'h', 0);
1024 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
1025 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
1026 Scheduler::ProcessEventsToIdle();
1027 // view1 has UNDO enabled, view2 is in UNDO-repair
1029 SfxItemSet aSet1(pView1->GetPool(), svl::Items<SID_UNDO, SID_UNDO>);
1030 SfxItemSet aSet2(pView2->GetPool(), svl::Items<SID_UNDO, SID_UNDO>);
1031 pView1->GetSlotState(SID_UNDO, nullptr, &aSet1);
1032 pView2->GetSlotState(SID_UNDO, nullptr, &aSet2);
1033 CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aSet1.GetItemState(SID_UNDO));
1034 CPPUNIT_ASSERT(dynamic_cast< const SfxStringItem* >(aSet1.GetItem(SID_UNDO)));
1035 CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aSet2.GetItemState(SID_UNDO));
1036 CPPUNIT_ASSERT(dynamic_cast< const SfxUInt32Item* >(aSet2.GetItem(SID_UNDO)));
1037 const SfxUInt32Item* pUInt32Item = dynamic_cast<const SfxUInt32Item*>(aSet2.GetItem(SID_UNDO));
1038 CPPUNIT_ASSERT(pUInt32Item);
1039 CPPUNIT_ASSERT_EQUAL(static_cast< sal_uInt32 >(SID_REPAIRPACKAGE), pUInt32Item->GetValue());
1042 // text edit a cell in view #2
1043 SfxLokHelper::setView(nView2);
1044 pModelObj->setPart(1);
1045 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'c', 0);
1046 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'c', 0);
1047 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
1048 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
1049 // both views have UNDO enabled
1050 Scheduler::ProcessEventsToIdle();
1052 SfxItemSet aSet1(pView1->GetPool(), svl::Items<SID_UNDO, SID_UNDO>);
1053 SfxItemSet aSet2(pView2->GetPool(), svl::Items<SID_UNDO, SID_UNDO>);
1054 pView1->GetSlotState(SID_UNDO, nullptr, &aSet1);
1055 pView2->GetSlotState(SID_UNDO, nullptr, &aSet2);
1056 CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aSet1.GetItemState(SID_UNDO));
1057 CPPUNIT_ASSERT(dynamic_cast< const SfxStringItem* >(aSet1.GetItem(SID_UNDO)));
1058 CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aSet2.GetItemState(SID_UNDO));
1059 CPPUNIT_ASSERT(dynamic_cast< const SfxStringItem* >(aSet2.GetItem(SID_UNDO)));
1062 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
1063 SfxLokHelper::setView(nView1);
1064 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
1067 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDocumentRepair)
1069 // Create two views.
1070 ScModelObj* pModelObj = createDoc("cursor-away.ods");
1071 CPPUNIT_ASSERT(pModelObj);
1073 // view #1
1074 SfxViewShell* pView1 = SfxViewShell::Current();
1076 // view #2
1077 int nView1 = SfxLokHelper::getView();
1078 SfxLokHelper::createView();
1079 SfxViewShell* pView2 = SfxViewShell::Current();
1080 int nView2 = SfxLokHelper::getView();
1081 CPPUNIT_ASSERT(pView1 != pView2);
1083 std::unique_ptr<SfxBoolItem> pItem1;
1084 std::unique_ptr<SfxBoolItem> pItem2;
1085 pView1->GetViewFrame().GetBindings().QueryState(SID_DOC_REPAIR, pItem1);
1086 pView2->GetViewFrame().GetBindings().QueryState(SID_DOC_REPAIR, pItem2);
1087 CPPUNIT_ASSERT(pItem1);
1088 CPPUNIT_ASSERT(pItem2);
1089 CPPUNIT_ASSERT_EQUAL(false, pItem1->GetValue());
1090 CPPUNIT_ASSERT_EQUAL(false, pItem2->GetValue());
1093 // Insert a character in the second view.
1094 SfxLokHelper::setView(nView2);
1095 pModelObj->setPart(1);
1096 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'c', 0);
1097 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'c', 0);
1098 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
1099 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
1100 Scheduler::ProcessEventsToIdle();
1102 std::unique_ptr<SfxBoolItem> pItem1;
1103 std::unique_ptr<SfxBoolItem> pItem2;
1104 pView1->GetViewFrame().GetBindings().QueryState(SID_DOC_REPAIR, pItem1);
1105 pView2->GetViewFrame().GetBindings().QueryState(SID_DOC_REPAIR, pItem2);
1106 CPPUNIT_ASSERT(pItem1);
1107 CPPUNIT_ASSERT(pItem2);
1108 CPPUNIT_ASSERT_EQUAL(true, pItem1->GetValue());
1109 CPPUNIT_ASSERT_EQUAL(true, pItem2->GetValue());
1112 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
1113 SfxLokHelper::setView(nView1);
1114 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
1117 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testLanguageStatus)
1119 ScModelObj* pModelObj = createDoc("small.ods");
1120 CPPUNIT_ASSERT(pModelObj);
1121 ScDocShell* pDocSh = dynamic_cast< ScDocShell* >( pModelObj->GetEmbeddedObject() );
1122 CPPUNIT_ASSERT(pDocSh);
1124 // view #1
1125 SfxViewShell* pView1 = SfxViewShell::Current();
1127 // view #2
1128 int nView1 = SfxLokHelper::getView();
1129 SfxLokHelper::createView();
1130 SfxViewShell* pView2 = SfxViewShell::Current();
1131 CPPUNIT_ASSERT(pView1 != pView2);
1133 std::unique_ptr<SfxPoolItem> xItem1;
1134 std::unique_ptr<SfxPoolItem> xItem2;
1135 pView1->GetViewFrame().GetBindings().QueryState(SID_LANGUAGE_STATUS, xItem1);
1136 pView2->GetViewFrame().GetBindings().QueryState(SID_LANGUAGE_STATUS, xItem2);
1137 const SfxStringItem* pItem1 = dynamic_cast<const SfxStringItem*>(xItem1.get());
1138 const SfxStringItem* pItem2 = dynamic_cast<const SfxStringItem*>(xItem2.get());
1139 CPPUNIT_ASSERT(pItem1);
1140 CPPUNIT_ASSERT(pItem2);
1141 CPPUNIT_ASSERT(!pItem1->GetValue().isEmpty());
1142 CPPUNIT_ASSERT(!pItem2->GetValue().isEmpty());
1146 SfxStringItem aLangString(SID_LANGUAGE_STATUS, u"Default_Spanish (Bolivia)"_ustr);
1147 pView1->GetViewFrame().GetDispatcher()->ExecuteList(SID_LANGUAGE_STATUS,
1148 SfxCallMode::SYNCHRON, { &aLangString });
1152 std::unique_ptr<SfxPoolItem> xItem1;
1153 std::unique_ptr<SfxPoolItem> xItem2;
1154 pView1->GetViewFrame().GetBindings().QueryState(SID_LANGUAGE_STATUS, xItem1);
1155 pView2->GetViewFrame().GetBindings().QueryState(SID_LANGUAGE_STATUS, xItem2);
1156 const SfxStringItem* pItem1 = dynamic_cast<const SfxStringItem*>(xItem1.get());
1157 const SfxStringItem* pItem2 = dynamic_cast<const SfxStringItem*>(xItem2.get());
1158 CPPUNIT_ASSERT(pItem1);
1159 CPPUNIT_ASSERT(pItem2);
1160 static constexpr OUString aLangBolivia(u"Spanish (Bolivia);es-BO"_ustr);
1161 CPPUNIT_ASSERT_EQUAL(aLangBolivia, pItem1->GetValue());
1162 CPPUNIT_ASSERT_EQUAL(aLangBolivia, pItem2->GetValue());
1165 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
1166 SfxLokHelper::setView(nView1);
1167 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
1170 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testMultiViewCopyPaste)
1172 ScModelObj* pModelObj = createDoc("empty.ods");
1173 ScDocument* pDoc = pModelObj->GetDocument();
1174 CPPUNIT_ASSERT(pDoc);
1176 pDoc->SetString(ScAddress(0, 0, 0), u"TestCopy1"_ustr);
1177 pDoc->SetString(ScAddress(1, 0, 0), u"TestCopy2"_ustr);
1179 // view #1
1180 ScTabViewShell* pView1 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
1181 CPPUNIT_ASSERT(pView1);
1182 // emulate clipboard
1183 pView1->GetViewData().GetActiveWin()->SetClipboard(css::datatransfer::clipboard::LokClipboard::create(comphelper::getProcessComponentContext()));
1185 // view #2
1186 int nView1 = SfxLokHelper::getView();
1187 SfxLokHelper::createView();
1188 ScTabViewShell* pView2 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
1189 // emulate clipboard
1190 pView2->GetViewData().GetActiveWin()->SetClipboard(css::datatransfer::clipboard::LokClipboard::create(comphelper::getProcessComponentContext()));
1191 CPPUNIT_ASSERT(pView2);
1192 CPPUNIT_ASSERT(pView1 != pView2);
1193 CPPUNIT_ASSERT(pView1->GetViewData().GetActiveWin()->GetClipboard() != pView2->GetViewData().GetActiveWin()->GetClipboard());
1195 // copy text view 1
1196 pView1->SetCursor(0, 0);
1197 pView1->GetViewFrame().GetBindings().Execute(SID_COPY);
1199 // copy text view 2
1200 pView2->SetCursor(1, 0);
1201 pView2->GetViewFrame().GetBindings().Execute(SID_COPY);
1203 // paste text view 1
1204 pView1->SetCursor(0, 1);
1205 pView1->GetViewFrame().GetBindings().Execute(SID_PASTE);
1207 // paste text view 2
1208 pView2->SetCursor(1, 1);
1209 pView2->GetViewFrame().GetBindings().Execute(SID_PASTE);
1211 CPPUNIT_ASSERT_EQUAL(u"TestCopy1"_ustr, pDoc->GetString(ScAddress(0, 1, 0)));
1212 CPPUNIT_ASSERT_EQUAL(u"TestCopy2"_ustr, pDoc->GetString(ScAddress(1, 1, 0)));
1214 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
1215 SfxLokHelper::setView(nView1);
1216 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
1219 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testIMESupport)
1221 ScModelObj* pModelObj = createDoc("empty.ods");
1222 VclPtr<vcl::Window> pDocWindow = pModelObj->getDocWindow();
1223 ScDocument* pDoc = pModelObj->GetDocument();
1225 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
1226 CPPUNIT_ASSERT(pView);
1228 pView->SetCursor(0, 0);
1229 // sequence of chinese IME compositions when 'nihao' is typed in an IME
1230 const std::vector<OString> aUtf8Inputs{ "年"_ostr, "你"_ostr, "你好"_ostr, "你哈"_ostr, "你好"_ostr, "你好"_ostr };
1231 std::vector<OUString> aInputs;
1232 std::transform(aUtf8Inputs.begin(), aUtf8Inputs.end(),
1233 std::back_inserter(aInputs), [](OString aInput) {
1234 return OUString::fromUtf8(aInput);
1236 for (const auto& aInput: aInputs)
1238 pDocWindow->PostExtTextInputEvent(VclEventId::ExtTextInput, aInput);
1240 pDocWindow->PostExtTextInputEvent(VclEventId::EndExtTextInput, u""_ustr);
1242 // commit the string to the cell
1243 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
1244 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
1245 Scheduler::ProcessEventsToIdle();
1247 CPPUNIT_ASSERT_EQUAL(aInputs[aInputs.size() - 1], pDoc->GetString(ScAddress(0, 0, 0)));
1250 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testFilterDlg)
1252 createDoc("empty.ods");
1254 // view #1
1255 SfxViewShell* pView1 = SfxViewShell::Current();
1256 int nView1 = SfxLokHelper::getView();
1258 // view #2
1259 SfxLokHelper::createView();
1260 SfxViewShell* pView2 = SfxViewShell::Current();
1261 CPPUNIT_ASSERT(pView1 != pView2);
1263 pView2->GetViewFrame().GetDispatcher()->Execute(SID_FILTER,
1264 SfxCallMode::SLOT|SfxCallMode::RECORD);
1267 Scheduler::ProcessEventsToIdle();
1268 SfxChildWindow* pRefWindow = pView2->GetViewFrame().GetChildWindow(SID_FILTER);
1269 CPPUNIT_ASSERT(pRefWindow);
1271 // switch to view 1
1272 SfxLokHelper::setView(nView1);
1273 CPPUNIT_ASSERT_EQUAL(true, pView2->GetViewFrame().GetDispatcher()->IsLocked());
1274 CPPUNIT_ASSERT_EQUAL(false, pView1->GetViewFrame().GetDispatcher()->IsLocked());
1276 pRefWindow->GetController()->response(RET_CANCEL);
1278 CPPUNIT_ASSERT_EQUAL(false, pView2->GetViewFrame().GetDispatcher()->IsLocked());
1279 CPPUNIT_ASSERT_EQUAL(false, pView1->GetViewFrame().GetDispatcher()->IsLocked());
1281 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
1282 SfxLokHelper::setView(nView1);
1283 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
1286 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testFunctionDlg)
1288 createDoc("empty.ods");
1290 // view #1
1291 SfxViewShell* pView1 = SfxViewShell::Current();
1292 int nView1 = SfxLokHelper::getView();
1294 pView1->GetViewFrame().GetDispatcher()->Execute(SID_OPENDLG_FUNCTION,
1295 SfxCallMode::SLOT|SfxCallMode::RECORD);
1297 Scheduler::ProcessEventsToIdle();
1298 SfxChildWindow* pRefWindow = pView1->GetViewFrame().GetChildWindow(SID_OPENDLG_FUNCTION);
1299 CPPUNIT_ASSERT(pRefWindow);
1301 // view #2
1302 int nView2 = SfxLokHelper::createView();
1303 SfxViewShell* pView2 = SfxViewShell::Current();
1304 CPPUNIT_ASSERT(pView1 != pView2);
1306 // check locking
1307 CPPUNIT_ASSERT_EQUAL(true, pView1->GetViewFrame().GetDispatcher()->IsLocked());
1308 CPPUNIT_ASSERT_EQUAL(false, pView2->GetViewFrame().GetDispatcher()->IsLocked());
1310 SfxLokHelper::setView(nView1);
1311 pRefWindow->GetController()->response(RET_CANCEL);
1313 CPPUNIT_ASSERT_EQUAL(false, pView1->GetViewFrame().GetDispatcher()->IsLocked());
1314 CPPUNIT_ASSERT_EQUAL(false, pView2->GetViewFrame().GetDispatcher()->IsLocked());
1316 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
1317 SfxLokHelper::setView(nView2);
1318 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
1321 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSpellOnlineParameter)
1323 createDoc("empty.ods");
1325 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
1326 CPPUNIT_ASSERT(pView);
1328 bool bSet = pView->IsAutoSpell();
1330 uno::Sequence<beans::PropertyValue> params =
1332 comphelper::makePropertyValue(u"Enable"_ustr, uno::Any(!bSet)),
1334 dispatchCommand(mxComponent, u".uno:SpellOnline"_ustr, params);
1335 CPPUNIT_ASSERT_EQUAL(!bSet, pView->IsAutoSpell());
1337 // set the same state as now and we don't expect any change (no-toggle)
1338 params =
1340 comphelper::makePropertyValue(u"Enable"_ustr, uno::Any(!bSet)),
1342 dispatchCommand(mxComponent, u".uno:SpellOnline"_ustr, params);
1343 CPPUNIT_ASSERT_EQUAL(!bSet, pView->IsAutoSpell());
1346 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testVbaRangeCopyPaste)
1348 ScModelObj* pModelObj = createDoc("RangeCopyPaste.ods");
1349 ScDocShell* pDocShell = dynamic_cast< ScDocShell* >( pModelObj->GetEmbeddedObject() );
1350 CPPUNIT_ASSERT(pDocShell);
1352 uno::Any aRet;
1353 uno::Sequence< uno::Any > aOutParam;
1354 uno::Sequence< uno::Any > aParams;
1355 uno::Sequence< sal_Int16 > aOutParamIndex;
1357 SfxObjectShell::CallXScript(
1358 mxComponent,
1359 u"vnd.sun.Star.script:Standard.Module1.Test_RangeCopyPaste?language=Basic&location=document"_ustr,
1360 aParams, aRet, aOutParamIndex, aOutParam);
1362 CPPUNIT_ASSERT(!pDocShell->GetClipData().is());
1365 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testInvalidationLoop)
1367 // Load the document with a form control.
1368 createDoc("invalidation-loop.fods");
1369 // Without the accompanying fix in place, this test would have never returned due to an infinite
1370 // invalidation loop between ScGridWindow::Paint() and vcl::Window::ImplPosSizeWindow().
1371 Scheduler::ProcessEventsToIdle();
1374 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testPageDownInvalidation)
1376 ScModelObj* pModelObj = createDoc("empty.ods");
1377 ScViewData* pViewData = ScDocShell::GetViewData();
1378 CPPUNIT_ASSERT(pViewData);
1380 int nView1 = SfxLokHelper::getView();
1381 ViewCallback aView1;
1382 CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
1384 SfxLokHelper::setView(nView1);
1385 aView1.m_bInvalidateTiles = false;
1386 aView1.m_aInvalidations.clear();
1387 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, awt::Key::PAGEDOWN, 0);
1388 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, awt::Key::PAGEDOWN, 0);
1389 Scheduler::ProcessEventsToIdle();
1390 CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
1391 CPPUNIT_ASSERT_EQUAL(size_t(3), aView1.m_aInvalidations.size());
1392 CPPUNIT_ASSERT_EQUAL(tools::Rectangle(15, 15, 1230, 225), aView1.m_aInvalidations[0]);
1395 static Bitmap getTile(ScModelObj* pModelObj, int nTilePosX, int nTilePosY, tools::Long nTileWidth, tools::Long nTileHeight)
1397 size_t nCanvasSize = 1024;
1398 size_t nTileSize = 256;
1399 std::vector<unsigned char> aPixmap(nCanvasSize * nCanvasSize * 4, 0);
1400 ScopedVclPtrInstance<VirtualDevice> xDevice(DeviceFormat::WITHOUT_ALPHA);
1401 xDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
1402 xDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasSize, nCanvasSize),
1403 Fraction(1.0), Point(), aPixmap.data());
1404 pModelObj->paintTile(*xDevice, nCanvasSize, nCanvasSize, nTilePosX, nTilePosY, nTileWidth, nTileHeight);
1405 xDevice->EnableMapMode(false);
1406 return xDevice->GetBitmap(Point(0, 0), Size(nTileSize, nTileSize));
1409 namespace
1411 void lcl_typeCharsInCell(const std::string& aStr, SCCOL nCol, SCROW nRow, ScTabViewShell* pView,
1412 ScModelObj* pModelObj, bool bInEdit = false, bool bCommit = true)
1414 if (!bInEdit)
1415 pView->SetCursor(nCol, nRow);
1417 for (const char& cChar : aStr)
1419 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, cChar, 0);
1420 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, cChar, 0);
1421 Scheduler::ProcessEventsToIdle();
1424 if (bCommit)
1426 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
1427 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
1428 Scheduler::ProcessEventsToIdle();
1431 } //namespace
1433 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSheetChangeNoInvalidation)
1435 const bool oldPartInInvalidation = comphelper::LibreOfficeKit::isPartInInvalidation();
1436 comphelper::LibreOfficeKit::setPartInInvalidation(true);
1438 ScModelObj* pModelObj = createDoc("two_sheets.ods");
1439 ScViewData* pViewData = ScDocShell::GetViewData();
1440 CPPUNIT_ASSERT(pViewData);
1442 // Set View to initial 100%
1443 pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 28050, 10605));
1444 pModelObj->setClientZoom(256, 256, 1920, 1920);
1446 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
1447 CPPUNIT_ASSERT(pView);
1449 int nView1 = SfxLokHelper::getView();
1450 ViewCallback aView1;
1451 CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
1453 SfxLokHelper::setView(nView1);
1455 aView1.ClearAllInvalids();
1457 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEDOWN | KEY_MOD1);
1458 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEDOWN | KEY_MOD1);
1459 Scheduler::ProcessEventsToIdle();
1460 // switching sheets should trigger no unnecessary invalidations
1461 CPPUNIT_ASSERT(!aView1.m_bInvalidateTiles);
1463 // Get the known columns/rows of this sheet 2 now we have switched to it so
1464 // it knows what range to broadcast invalidations for if it knows cells need
1465 // to be redrawn.
1466 tools::JsonWriter aJsonWriter1;
1467 pModelObj->getRowColumnHeaders(tools::Rectangle(0, 15, 19650, 5400), aJsonWriter1);
1468 aJsonWriter1.finishAndGetAsOString();
1469 Scheduler::ProcessEventsToIdle();
1470 aView1.ClearAllInvalids();
1472 // switching back should also trigger no unnecessary invalidations
1473 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEUP | KEY_MOD1);
1474 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEUP | KEY_MOD1);
1475 Scheduler::ProcessEventsToIdle();
1476 CPPUNIT_ASSERT(!aView1.m_bInvalidateTiles);
1478 // The 2nd sheet has formulas that depend on B1 in the first sheet. So if
1479 // we change B1 there should be an invalidation in the second sheet for the
1480 // range that depends on it. Because this is a single user document with no
1481 // active view on the 2nd sheet this will happen on switching back to sheet 2
1482 lcl_typeCharsInCell("101", 1, 0, pView, pModelObj); // Type '101' in B1
1483 aView1.ClearAllInvalids();
1485 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEDOWN | KEY_MOD1);
1486 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEDOWN | KEY_MOD1);
1487 Scheduler::ProcessEventsToIdle();
1488 CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
1489 aView1.ClearAllInvalids();
1491 // Paint it to make it valid again
1492 getTile(pModelObj, 0, 0, 3840, 3840);
1494 // switching back to sheet 1 should trigger no unnecessary invalidations
1495 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEUP | KEY_MOD1);
1496 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEUP | KEY_MOD1);
1497 Scheduler::ProcessEventsToIdle();
1498 CPPUNIT_ASSERT(!aView1.m_bInvalidateTiles);
1500 // switching to sheet 2 should trigger no unnecessary invalidations this time
1501 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEDOWN | KEY_MOD1);
1502 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEDOWN | KEY_MOD1);
1503 Scheduler::ProcessEventsToIdle();
1504 CPPUNIT_ASSERT(!aView1.m_bInvalidateTiles);
1506 comphelper::LibreOfficeKit::setPartInInvalidation(oldPartInInvalidation);
1509 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testInsertDeletePageInvalidation)
1511 ScModelObj* pModelObj = createDoc("insert_delete_sheet.ods");
1512 // the document has 1 sheet
1513 CPPUNIT_ASSERT_EQUAL(1, pModelObj->getParts());
1514 ScViewData* pViewData = ScDocShell::GetViewData();
1515 CPPUNIT_ASSERT(pViewData);
1517 int nView1 = SfxLokHelper::getView();
1518 ViewCallback aView1;
1519 CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
1521 SfxLokHelper::setView(nView1);
1522 aView1.m_bInvalidateTiles = false;
1523 aView1.m_aInvalidations.clear();
1525 uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
1526 { "Name", uno::Any(u""_ustr) },
1527 { "Index", uno::Any(sal_Int32(1)) }
1528 }));
1529 dispatchCommand(mxComponent, u".uno:Insert"_ustr, aArgs);
1530 CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
1531 CPPUNIT_ASSERT_EQUAL(size_t(2), aView1.m_aInvalidations.size());
1532 CPPUNIT_ASSERT_EQUAL(tools::Rectangle(0, 0, 1000000000, 1000000000), aView1.m_aInvalidations[0]);
1533 CPPUNIT_ASSERT_EQUAL(2, pModelObj->getParts());
1535 // Delete sheet
1536 aView1.m_bInvalidateTiles = false;
1537 aView1.m_aInvalidations.clear();
1538 uno::Sequence<beans::PropertyValue> aArgs2( comphelper::InitPropertySequence({
1539 { "Index", uno::Any(sal_Int32(1)) }
1540 }));
1541 dispatchCommand(mxComponent, u".uno:Remove"_ustr, aArgs2);
1542 CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
1543 CPPUNIT_ASSERT_EQUAL(size_t(1), aView1.m_aInvalidations.size());
1544 CPPUNIT_ASSERT_EQUAL(tools::Rectangle(0, 0, 1000000000, 1000000000), aView1.m_aInvalidations[0]);
1545 CPPUNIT_ASSERT_EQUAL(1, pModelObj->getParts());
1548 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testGetRowColumnHeadersInvalidation)
1550 // NOTE NOTE NOTE
1551 // If you run this test in isolation using CPPUNIT_TEST_NAME=, it will fail because the invalidations
1552 // will be different.
1554 ScModelObj* pModelObj = createDoc("empty.ods");
1555 ScViewData* pViewData = ScDocShell::GetViewData();
1556 CPPUNIT_ASSERT(pViewData);
1558 int nView1 = SfxLokHelper::getView();
1559 ViewCallback aView1;
1560 CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
1562 SfxLokHelper::setView(nView1);
1563 aView1.m_bInvalidateTiles = false;
1564 aView1.m_aInvalidations.clear();
1565 tools::JsonWriter aJsonWriter1;
1566 pModelObj->getRowColumnHeaders(tools::Rectangle(0, 15, 19650, 5400), aJsonWriter1);
1567 aJsonWriter1.finishAndGetAsOString();
1568 Scheduler::ProcessEventsToIdle();
1569 CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
1570 CPPUNIT_ASSERT_EQUAL(size_t(1), aView1.m_aInvalidations.size());
1571 CPPUNIT_ASSERT_EQUAL(tools::Rectangle(Point(26775, 0), Size(22950, 13005)), aView1.m_aInvalidations[0]);
1573 // Extend area top-to-bottom
1574 aView1.m_bInvalidateTiles = false;
1575 aView1.m_aInvalidations.clear();
1576 tools::JsonWriter aJsonWriter2;
1577 pModelObj->getRowColumnHeaders(tools::Rectangle(0, 5400, 19650, 9800), aJsonWriter2);
1578 aJsonWriter2.finishAndGetAsOString();
1579 Scheduler::ProcessEventsToIdle();
1580 CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
1581 CPPUNIT_ASSERT_EQUAL(size_t(1), aView1.m_aInvalidations.size());
1582 CPPUNIT_ASSERT_EQUAL(tools::Rectangle(Point(0, 13005), Size(49725, 6375)), aView1.m_aInvalidations[0]);
1584 // Extend area left-to-right
1585 aView1.m_bInvalidateTiles = false;
1586 aView1.m_aInvalidations.clear();
1587 tools::JsonWriter aJsonWriter3;
1588 pModelObj->getRowColumnHeaders(tools::Rectangle(5400, 5400, 25050, 9800), aJsonWriter3);
1589 aJsonWriter3.finishAndGetAsOString();
1590 Scheduler::ProcessEventsToIdle();
1591 CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
1592 CPPUNIT_ASSERT_EQUAL(size_t(1), aView1.m_aInvalidations.size());
1593 CPPUNIT_ASSERT_EQUAL(tools::Rectangle(Point(49725, 0), Size(25500, 19380)), aView1.m_aInvalidations[0]);
1596 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testJumpHorizontallyInvalidation)
1598 ScModelObj* pModelObj = createDoc("empty.ods");
1599 ScViewData* pViewData = ScDocShell::GetViewData();
1600 CPPUNIT_ASSERT(pViewData);
1602 int nView1 = SfxLokHelper::getView();
1603 ViewCallback aView1;
1604 CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
1606 SfxLokHelper::setView(nView1);
1607 aView1.m_bInvalidateTiles = false;
1608 aView1.m_aInvalidations.clear();
1609 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEDOWN | KEY_MOD2);
1610 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEDOWN | KEY_MOD2);
1611 Scheduler::ProcessEventsToIdle();
1612 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEDOWN | KEY_MOD2);
1613 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEDOWN | KEY_MOD2);
1614 Scheduler::ProcessEventsToIdle();
1615 CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
1616 CPPUNIT_ASSERT_EQUAL(size_t(1), aView1.m_aInvalidations.size());
1617 CPPUNIT_ASSERT_EQUAL(tools::Rectangle(26775, 0, 39525, 13005), aView1.m_aInvalidations[0]);
1620 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testJumpToLastRowInvalidation)
1622 ScModelObj* pModelObj = createDoc("empty.ods");
1623 ScViewData* pViewData = ScDocShell::GetViewData();
1624 CPPUNIT_ASSERT(pViewData);
1626 int nView1 = SfxLokHelper::getView();
1627 ViewCallback aView1;
1628 CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
1630 SfxLokHelper::setView(nView1);
1631 aView1.m_bInvalidateTiles = false;
1632 aView1.m_aInvalidations.clear();
1633 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DOWN | KEY_MOD1);
1634 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DOWN | KEY_MOD1);
1635 Scheduler::ProcessEventsToIdle();
1636 CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
1637 CPPUNIT_ASSERT_EQUAL(size_t(1), aView1.m_aInvalidations.size());
1638 // 261375 because we limit how far we jump into empty space in online, 267386880 if we don't limit
1639 CPPUNIT_ASSERT_EQUAL(tools::Rectangle(0, 13005, 26775, 261375), aView1.m_aInvalidations[0]);
1642 // We need to ensure that views are not perterbed by rendering (!?) hmm ...
1643 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testRowColumnHeaders)
1645 ScModelObj* pModelObj = createDoc("empty.ods");
1646 ScViewData* pViewData = ScDocShell::GetViewData();
1647 CPPUNIT_ASSERT(pViewData);
1649 // view #1
1650 ViewCallback aView1;
1651 int nView1 = SfxLokHelper::getView();
1652 CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
1654 // view #2
1655 SfxLokHelper::createView();
1656 int nView2 = SfxLokHelper::getView();
1657 ViewCallback aView2;
1658 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
1660 // ViewRowColumnHeaders test
1661 SfxLokHelper::setView(nView1);
1662 tools::JsonWriter aJsonWriter1;
1663 pModelObj->getRowColumnHeaders(tools::Rectangle(65,723,10410,4695), aJsonWriter1);
1664 OString aHeaders1 = aJsonWriter1.finishAndGetAsOString();
1666 SfxLokHelper::setView(nView2);
1667 // 50% zoom
1668 pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 22474, 47333));
1669 pModelObj->setClientZoom(256, 256, 6636, 6636);
1670 tools::JsonWriter aJsonWriter2;
1671 pModelObj->getRowColumnHeaders(tools::Rectangle(65,723,10410,4695), aJsonWriter2);
1672 OString aHeaders2 = aJsonWriter2.finishAndGetAsOString();
1674 // Check vs. view #1
1675 SfxLokHelper::setView(nView1);
1676 tools::JsonWriter aJsonWriter3;
1677 pModelObj->getRowColumnHeaders(tools::Rectangle(65,723,10410,4695), aJsonWriter3);
1678 OString aHeaders1_2 = aJsonWriter3.finishAndGetAsOString();
1679 CPPUNIT_ASSERT_EQUAL(aHeaders1, aHeaders1_2);
1681 // Check vs. view #2
1682 SfxLokHelper::setView(nView2);
1683 tools::JsonWriter aJsonWriter4;
1684 pModelObj->getRowColumnHeaders(tools::Rectangle(65,723,10410,4695), aJsonWriter4);
1685 OString aHeaders2_2 = aJsonWriter4.finishAndGetAsOString();
1686 CPPUNIT_ASSERT_EQUAL(aHeaders2, aHeaders2_2);
1688 SfxLokHelper::setView(nView1);
1689 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
1690 SfxLokHelper::setView(nView2);
1691 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
1694 namespace
1696 // Helper structs for setup and testing of ScModelObj::getSheetGeometryData()
1697 struct SpanEntry
1699 size_t nVal;
1700 SCCOLROW nEnd;
1703 struct SheetDimData
1705 typedef std::vector<SpanEntry> SpanList;
1706 SpanList aSizes;
1707 SpanList aHidden;
1708 SpanList aFiltered;
1709 // TODO: Add group info too to test.
1711 void setDataToDoc(ScDocument* pDoc, bool bCol) const
1713 SCCOLROW nStart = 0;
1714 // sizes
1715 for (const auto& rSpan : aSizes)
1717 if (bCol)
1719 for (SCCOLROW nIdx = nStart; nIdx <= rSpan.nEnd; ++nIdx)
1720 pDoc->SetColWidthOnly(nIdx, 0, rSpan.nVal);
1722 else
1723 pDoc->SetRowHeightOnly(nStart, rSpan.nEnd, 0, rSpan.nVal);
1725 nStart = rSpan.nEnd + 1;
1728 nStart = 0;
1729 // hidden
1730 for (const auto& rSpan : aHidden)
1732 if (bCol)
1733 pDoc->SetColHidden(nStart, rSpan.nEnd, 0, !!rSpan.nVal);
1734 else
1735 pDoc->SetRowHidden(nStart, rSpan.nEnd, 0, !!rSpan.nVal);
1737 nStart = rSpan.nEnd + 1;
1740 // There is no ScDocument interface to set ScTable::mpFilteredCols
1741 // It seems ScTable::mpFilteredCols is not really used !?
1742 if (bCol)
1743 return;
1745 nStart = 0;
1746 // filtered
1747 for (const auto& rSpan : aFiltered)
1749 pDoc->SetRowFiltered(nStart, rSpan.nEnd, 0, !!rSpan.nVal);
1750 nStart = rSpan.nEnd + 1;
1754 void testPropertyTree(const boost::property_tree::ptree& rTree, bool bCol) const
1756 struct SpanListWithKey
1758 OString aKey;
1759 const SpanList& rSpanList;
1762 const SpanListWithKey aPairList[] = {
1763 { "sizes"_ostr, aSizes },
1764 { "hidden"_ostr, aHidden },
1765 { "filtered"_ostr, aFiltered }
1768 for (const auto& rEntry : aPairList)
1770 // There is no ScDocument interface to set ScTable::mpFilteredCols
1771 // It seems ScTable::mpFilteredCols is not really used !?
1772 if (bCol && rEntry.aKey == "filtered")
1773 continue;
1775 bool bBooleanValue = rEntry.aKey != "sizes";
1776 OString aExpectedEncoding;
1777 bool bFirst = true;
1778 for (const auto& rSpan : rEntry.rSpanList)
1780 size_t nVal = rSpan.nVal;
1781 if (bBooleanValue && bFirst)
1782 nVal = static_cast<size_t>(!!nVal);
1783 if (!bBooleanValue || bFirst)
1784 aExpectedEncoding += OString::number(nVal) + ":";
1785 aExpectedEncoding += OString::number(rSpan.nEnd) + " ";
1786 bFirst = false;
1789 // Get the tree's value for the property key ("sizes"/"hidden"/"filtered").
1790 OString aTreeValue(rTree.get<std::string>(rEntry.aKey.getStr()));
1792 CPPUNIT_ASSERT_EQUAL(aExpectedEncoding, aTreeValue);
1797 class SheetGeometryData
1799 SheetDimData aCols;
1800 SheetDimData aRows;
1802 public:
1804 SheetGeometryData(const SheetDimData& rCols, const SheetDimData& rRows) :
1805 aCols(rCols), aRows(rRows)
1808 void setDataToDoc(ScDocument* pDoc) const
1810 aCols.setDataToDoc(pDoc, true);
1811 aRows.setDataToDoc(pDoc, false);
1814 void parseTest(const OString& rJSON) const
1816 // Assumes all flags passed to getSheetGeometryData() are true.
1817 boost::property_tree::ptree aTree;
1818 std::stringstream aStream((std::string(rJSON)));
1819 boost::property_tree::read_json(aStream, aTree);
1821 CPPUNIT_ASSERT_EQUAL(".uno:SheetGeometryData"_ostr, OString(aTree.get<std::string>("commandName")));
1823 aCols.testPropertyTree(aTree.get_child("columns"), true);
1824 aRows.testPropertyTree(aTree.get_child("rows"), false);
1827 } //namespace
1829 // getSheetGeometryData() should return the exact same message
1830 // irrespective of client zoom and view-area. Switching views
1831 // should also not alter it.
1832 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSheetGeometryDataInvariance)
1834 ScModelObj* pModelObj = createDoc("empty.ods");
1835 ScDocument* pDoc = pModelObj->GetDocument();
1836 const SheetGeometryData aSGData(
1837 // cols
1839 // width spans
1841 { STD_COL_WIDTH, 20 },
1842 { 2*STD_COL_WIDTH, 26 },
1843 { STD_COL_WIDTH, pDoc->MaxCol() }
1846 // hidden spans
1848 { 0, 5 },
1849 { 1, 12 },
1850 { 0, pDoc->MaxCol() }
1853 // filtered spans
1855 { 0, 50 },
1856 { 1, 59 },
1857 { 0, pDoc->MaxCol() }
1861 // rows
1863 // height spans
1865 { 300, 50 },
1866 { 600, 65 },
1867 { 300, pDoc->MaxRow() }
1870 // hidden spans
1872 { 1, 100 },
1873 { 0, 500 },
1874 { 1, 578 },
1875 { 0, pDoc->MaxRow() }
1878 // filtered spans
1880 { 0, 150 },
1881 { 1, 159 },
1882 { 0, pDoc->MaxRow() }
1887 ScViewData* pViewData = ScDocShell::GetViewData();
1888 CPPUNIT_ASSERT(pViewData);
1890 // view #1
1891 ViewCallback aView1;
1892 int nView1 = SfxLokHelper::getView();
1894 // view #2
1895 SfxLokHelper::createView();
1896 int nView2 = SfxLokHelper::getView();
1897 ViewCallback aView2;
1898 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
1900 // Try with the default empty document once (nIdx = 0) and then with sheet geometry settings (nIdx = 1)
1901 for (size_t nIdx = 0; nIdx < 2; ++nIdx)
1903 if (nIdx)
1904 aSGData.setDataToDoc(pDoc);
1906 SfxLokHelper::setView(nView1);
1907 OString aGeomStr1 = pModelObj->getSheetGeometryData(/*bColumns*/ true, /*bRows*/ true, /*bSizes*/ true,
1908 /*bHidden*/ true, /*bFiltered*/ true, /*bGroups*/ true);
1910 SfxLokHelper::setView(nView2);
1911 pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 22474, 47333));
1912 pModelObj->setClientZoom(256, 256, 6636, 6636);
1913 OString aGeomStr2 = pModelObj->getSheetGeometryData(/*bColumns*/ true, /*bRows*/ true, /*bSizes*/ true,
1914 /*bHidden*/ true, /*bFiltered*/ true, /*bGroups*/ true);
1916 // Check vs. view #1
1917 SfxLokHelper::setView(nView1);
1918 OString aGeomStr1_2 = pModelObj->getSheetGeometryData(/*bColumns*/ true, /*bRows*/ true, /*bSizes*/ true,
1919 /*bHidden*/ true, /*bFiltered*/ true, /*bGroups*/ true);
1920 CPPUNIT_ASSERT_EQUAL(aGeomStr1, aGeomStr1_2);
1922 // Check vs. view #2
1923 SfxLokHelper::setView(nView2);
1924 OString aGeomStr2_2 = pModelObj->getSheetGeometryData(/*bColumns*/ true, /*bRows*/ true, /*bSizes*/ true,
1925 /*bHidden*/ true, /*bFiltered*/ true, /*bGroups*/ true);
1926 CPPUNIT_ASSERT_EQUAL(aGeomStr2, aGeomStr2_2);
1929 SfxLokHelper::setView(nView1);
1930 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
1931 SfxLokHelper::setView(nView2);
1932 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
1935 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSheetGeometryDataCorrectness)
1937 ScModelObj* pModelObj = createDoc("empty.ods");
1938 ScDocument* pDoc = pModelObj->GetDocument();
1939 const SheetGeometryData aDefaultSGData(
1940 // cols
1942 // width spans
1943 { { STD_COL_WIDTH, pDoc->MaxCol() } },
1944 // hidden spans
1945 { { 0, pDoc->MaxCol() } },
1946 // filtered spans
1947 { { 0, pDoc->MaxCol() } }
1949 // rows
1951 // height spans
1952 { { ScGlobal::nStdRowHeight, pDoc->MaxRow() } },
1953 // hidden spans
1954 { { 0, pDoc->MaxRow() } },
1955 // filtered spans
1956 { { 0, pDoc->MaxRow() } }
1960 const SheetGeometryData aSGData(
1961 // cols
1963 // width spans
1965 { STD_COL_WIDTH, 20 },
1966 { 2*STD_COL_WIDTH, 26 },
1967 { STD_COL_WIDTH, pDoc->MaxCol() }
1970 // hidden spans
1972 { 0, 5 },
1973 { 1, 12 },
1974 { 0, pDoc->MaxCol() }
1977 // filtered spans
1979 { 0, 50 },
1980 { 1, 59 },
1981 { 0, pDoc->MaxCol() }
1985 // rows
1987 // height spans
1989 { 300, 50 },
1990 { 600, 65 },
1991 { 300, pDoc->MaxRow() }
1994 // hidden spans
1996 { 1, 100 },
1997 { 0, 500 },
1998 { 1, 578 },
1999 { 0, pDoc->MaxRow() }
2002 // filtered spans
2004 { 0, 150 },
2005 { 1, 159 },
2006 { 0, pDoc->MaxRow() }
2011 ScViewData* pViewData = ScDocShell::GetViewData();
2012 CPPUNIT_ASSERT(pViewData);
2014 // view #1
2015 ViewCallback aView1;
2017 // with the default empty sheet and test the JSON encoding.
2018 OString aGeomDefaultStr = pModelObj->getSheetGeometryData(/*bColumns*/ true, /*bRows*/ true, /*bSizes*/ true,
2019 /*bHidden*/ true, /*bFiltered*/ true, /*bGroups*/ true);
2020 aDefaultSGData.parseTest(aGeomDefaultStr);
2022 // Apply geometry settings to the sheet and then test the resulting JSON encoding.
2023 aSGData.setDataToDoc(pDoc);
2024 OString aGeomStr = pModelObj->getSheetGeometryData(/*bColumns*/ true, /*bRows*/ true, /*bSizes*/ true,
2025 /*bHidden*/ true, /*bFiltered*/ true, /*bGroups*/ true);
2026 aSGData.parseTest(aGeomStr);
2028 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
2031 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDeleteCellMultilineContent)
2033 ScModelObj* pModelObj = createDoc("multiline.ods");
2034 CPPUNIT_ASSERT(pModelObj);
2035 ScViewData* pViewData = ScDocShell::GetViewData();
2036 CPPUNIT_ASSERT(pViewData);
2037 ScDocShell* pDocSh = dynamic_cast< ScDocShell* >( pModelObj->GetEmbeddedObject() );
2038 CPPUNIT_ASSERT(pDocSh);
2040 // view #1
2041 ViewCallback aView1;
2042 CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
2044 aView1.m_sInvalidateHeader = ""_ostr;
2045 ScDocument& rDoc = pDocSh->GetDocument();
2046 sal_uInt16 nRow1Height = rDoc.GetRowHeight(static_cast<SCROW>(0), static_cast<SCTAB>(0), false);
2048 // delete multiline cell content in view #1
2049 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DOWN);
2050 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DOWN);
2051 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DELETE);
2052 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DELETE);
2053 Scheduler::ProcessEventsToIdle();
2055 // check if the row header has been invalidated and if the involved row is of the expected height
2056 CPPUNIT_ASSERT_EQUAL("row"_ostr, aView1.m_sInvalidateHeader);
2057 sal_uInt16 nRow2Height = rDoc.GetRowHeight(static_cast<SCROW>(0), static_cast<SCTAB>(0), false);
2058 CPPUNIT_ASSERT_EQUAL(nRow1Height, nRow2Height);
2059 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
2062 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testPasteIntoWrapTextCell)
2064 comphelper::LibreOfficeKit::setCompatFlag(
2065 comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
2067 ScModelObj* pModelObj = createDoc("empty.ods");
2068 CPPUNIT_ASSERT(pModelObj);
2069 ScDocument* pDoc = pModelObj->GetDocument();
2071 // Set Wrap text in A3
2072 pDoc->ApplyAttr(0, 2, 0, ScLineBreakCell(true));
2073 const ScLineBreakCell* pItem = pDoc->GetAttr(0, 2, 0, ATTR_LINEBREAK);
2074 CPPUNIT_ASSERT(pItem->GetValue());
2076 ScViewData* pViewData = ScDocShell::GetViewData();
2077 CPPUNIT_ASSERT(pViewData);
2079 ViewCallback aView;
2080 CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
2082 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
2083 CPPUNIT_ASSERT(pView);
2085 // create source text in A1
2086 OUString sCopyContent(u"Very long text to copy"_ustr);
2087 pDoc->SetString(0, 0, 0, sCopyContent);
2089 // copy A1
2090 pView->SetCursor(0, 0);
2091 Scheduler::ProcessEventsToIdle();
2092 pView->GetViewFrame().GetBindings().Execute(SID_COPY);
2093 Scheduler::ProcessEventsToIdle();
2095 // verify clipboard
2096 uno::Reference<datatransfer::clipboard::XClipboard> xClipboard1 = pView->GetViewData().GetActiveWin()->GetClipboard();
2097 uno::Reference< datatransfer::XTransferable > xDataObj =
2098 xClipboard1->getContents();
2099 datatransfer::DataFlavor aFlavor;
2100 SotExchange::GetFormatDataFlavor(SotClipboardFormatId::STRING, aFlavor);
2101 uno::Any aData = xDataObj->getTransferData(aFlavor);
2102 OUString aTmpText;
2103 aData >>= aTmpText;
2104 CPPUNIT_ASSERT_EQUAL(sCopyContent, aTmpText.trim());
2106 // Go to A2 and paste.
2107 pView->SetCursor(0, 1);
2108 Scheduler::ProcessEventsToIdle();
2109 aView.m_sInvalidateSheetGeometry = ""_ostr;
2110 pView->GetViewFrame().GetBindings().Execute(SID_PASTE);
2111 Scheduler::ProcessEventsToIdle();
2113 CPPUNIT_ASSERT_EQUAL(sCopyContent, pDoc->GetString(0, 1, 0));
2114 CPPUNIT_ASSERT_EQUAL("rows sizes"_ostr, aView.m_sInvalidateSheetGeometry);
2116 // create new source text in A2
2117 OUString sCopyContent2(u"Very long text to copy 2"_ustr);
2118 pDoc->SetString(0, 1, 0, sCopyContent2);
2119 Scheduler::ProcessEventsToIdle();
2121 // cut from A2
2122 pView->GetViewFrame().GetBindings().Execute(SID_CUT);
2123 Scheduler::ProcessEventsToIdle();
2125 // verify clipboard
2126 uno::Reference<datatransfer::clipboard::XClipboard> xClipboard2
2127 = pView->GetViewData().GetActiveWin()->GetClipboard();
2128 xDataObj = xClipboard2->getContents();
2129 SotExchange::GetFormatDataFlavor(SotClipboardFormatId::STRING, aFlavor);
2130 aData = xDataObj->getTransferData(aFlavor);
2131 aData >>= aTmpText;
2132 CPPUNIT_ASSERT_EQUAL(xClipboard1, xClipboard2);
2133 CPPUNIT_ASSERT_EQUAL(sCopyContent2, aTmpText.trim());
2135 // Go to A3 and paste.
2136 pView->SetCursor(0, 2);
2137 Scheduler::ProcessEventsToIdle();
2138 aView.m_sInvalidateSheetGeometry = ""_ostr;
2139 pView->GetViewFrame().GetBindings().Execute(SID_PASTE);
2140 Scheduler::ProcessEventsToIdle();
2142 // SG invalidations for all
2143 CPPUNIT_ASSERT_EQUAL(sCopyContent2, pDoc->GetString(0, 1, 0));
2144 CPPUNIT_ASSERT_EQUAL("all"_ostr, aView.m_sInvalidateSheetGeometry);
2146 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
2149 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSortAscendingDescending)
2151 comphelper::LibreOfficeKit::setCompatFlag(
2152 comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
2153 ScModelObj* pModelObj = createDoc("sort-range.ods");
2154 ScDocument* pDoc = pModelObj->GetDocument();
2156 ViewCallback aView;
2158 // select the values in the first column
2159 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, 551, 129, 1, MOUSE_LEFT, 0);
2160 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEMOVE, 820, 1336, 1, MOUSE_LEFT, 0);
2161 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, 820, 1359, 1, MOUSE_LEFT, 0);
2162 Scheduler::ProcessEventsToIdle();
2163 aView.m_sInvalidateSheetGeometry = ""_ostr;
2165 // sort ascending
2166 uno::Sequence<beans::PropertyValue> aArgs;
2167 dispatchCommand(mxComponent, u".uno:SortAscending"_ustr, aArgs);
2169 // check it's sorted
2170 for (SCROW r = 0; r < 6; ++r)
2172 CPPUNIT_ASSERT_EQUAL(double(r + 1), pDoc->GetValue(ScAddress(0, r, 0)));
2175 Scheduler::ProcessEventsToIdle();
2176 CPPUNIT_ASSERT_EQUAL("rows"_ostr, aView.m_sInvalidateSheetGeometry);
2178 aView.m_sInvalidateSheetGeometry = ""_ostr;
2179 // sort descending
2180 dispatchCommand(mxComponent, u".uno:SortDescending"_ustr, aArgs);
2182 // check it's sorted
2183 for (SCROW r = 0; r < 6; ++r)
2185 CPPUNIT_ASSERT_EQUAL(double(6 - r), pDoc->GetValue(ScAddress(0, r, 0)));
2188 // nothing else was sorted
2189 CPPUNIT_ASSERT_EQUAL(double(1), pDoc->GetValue(ScAddress(1, 0, 0)));
2190 CPPUNIT_ASSERT_EQUAL(double(3), pDoc->GetValue(ScAddress(1, 1, 0)));
2191 CPPUNIT_ASSERT_EQUAL(double(2), pDoc->GetValue(ScAddress(1, 2, 0)));
2193 Scheduler::ProcessEventsToIdle();
2194 CPPUNIT_ASSERT_EQUAL("rows"_ostr, aView.m_sInvalidateSheetGeometry);
2197 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testAutoInputStringBlock)
2199 ScModelObj* pModelObj = createDoc("empty.ods");
2200 CPPUNIT_ASSERT(pModelObj);
2201 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
2202 CPPUNIT_ASSERT(pView);
2203 ScDocument* pDoc = pModelObj->GetDocument();
2205 pDoc->SetString(ScAddress(0, 3, 0), u"ABC"_ustr); // A4
2206 pDoc->SetString(ScAddress(0, 4, 0), u"BAC"_ustr); // A5
2207 ScFieldEditEngine& rEE = pDoc->GetEditEngine();
2208 rEE.SetText(u"XYZ"_ustr);
2209 pDoc->SetEditText(ScAddress(0, 5, 0), rEE.CreateTextObject()); // A6
2210 pDoc->SetValue(ScAddress(0, 6, 0), 123);
2211 pDoc->SetString(ScAddress(0, 7, 0), u"ZZZ"_ustr); // A8
2213 ScAddress aA1(0, 0, 0);
2214 lcl_typeCharsInCell("X", aA1.Col(), aA1.Row(), pView, pModelObj); // Type 'X' in A1
2215 CPPUNIT_ASSERT_EQUAL_MESSAGE("A1 should autocomplete", u"XYZ"_ustr, pDoc->GetString(aA1));
2217 ScAddress aA3(0, 2, 0); // Adjacent to the string "superblock" A4:A8
2218 lcl_typeCharsInCell("X", aA3.Col(), aA3.Row(), pView, pModelObj); // Type 'X' in A3
2219 CPPUNIT_ASSERT_EQUAL_MESSAGE("A3 should autocomplete", u"XYZ"_ustr, pDoc->GetString(aA3));
2221 ScAddress aA9(0, 8, 0); // Adjacent to the string "superblock" A4:A8
2222 lcl_typeCharsInCell("X", aA9.Col(), aA9.Row(), pView, pModelObj); // Type 'X' in A9
2223 CPPUNIT_ASSERT_EQUAL_MESSAGE("A9 should autocomplete", u"XYZ"_ustr, pDoc->GetString(aA9));
2225 ScAddress aA11(0, 10, 0);
2226 lcl_typeCharsInCell("X", aA11.Col(), aA11.Row(), pView, pModelObj); // Type 'X' in A11
2227 CPPUNIT_ASSERT_EQUAL_MESSAGE("A11 should autocomplete", u"XYZ"_ustr, pDoc->GetString(aA11));
2230 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testAutoInputExactMatch)
2232 ScModelObj* pModelObj = createDoc("empty.ods");
2233 CPPUNIT_ASSERT(pModelObj);
2234 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
2235 CPPUNIT_ASSERT(pView);
2236 ScDocument* pDoc = pModelObj->GetDocument();
2238 pDoc->SetString(ScAddress(0, 1, 0), u"Simple"_ustr); // A2
2239 pDoc->SetString(ScAddress(0, 2, 0), u"Simple"_ustr); // A3
2240 pDoc->SetString(ScAddress(0, 3, 0), u"Sing"_ustr); // A4
2241 ScFieldEditEngine& rEE = pDoc->GetEditEngine();
2242 rEE.SetText(u"Case"_ustr);
2243 pDoc->SetEditText(ScAddress(0, 4, 0), rEE.CreateTextObject()); // A5
2244 pDoc->SetString(ScAddress(0, 5, 0), u"Time"_ustr); // A6
2245 pDoc->SetString(ScAddress(0, 6, 0), u"Castle"_ustr); // A7
2247 ScAddress aA8(0, 7, 0);
2248 lcl_typeCharsInCell("S", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "S" in A8
2249 // Should show the partial completion "i".
2250 CPPUNIT_ASSERT_EQUAL_MESSAGE("1: A8 should have partial completion Si", u"Si"_ustr, pDoc->GetString(aA8));
2252 lcl_typeCharsInCell("Si", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "Si" in A8
2253 // Should not show any suggestions.
2254 CPPUNIT_ASSERT_EQUAL_MESSAGE("2: A8 should not show suggestions", u"Si"_ustr, pDoc->GetString(aA8));
2256 lcl_typeCharsInCell("Sim", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "Sim" in A8
2257 // Should autocomplete to "Simple" which is the only match.
2258 CPPUNIT_ASSERT_EQUAL_MESSAGE("3: A8 should autocomplete", u"Simple"_ustr, pDoc->GetString(aA8));
2260 lcl_typeCharsInCell("Sin", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "Sin" in A8
2261 // Should autocomplete to "Sing" which is the only match.
2262 CPPUNIT_ASSERT_EQUAL_MESSAGE("4: A8 should autocomplete", u"Sing"_ustr, pDoc->GetString(aA8));
2264 lcl_typeCharsInCell("C", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "C" in A8
2265 // Should show the partial completion "as".
2266 CPPUNIT_ASSERT_EQUAL_MESSAGE("5: A8 should have partial completion Cas", u"Cas"_ustr, pDoc->GetString(aA8));
2268 lcl_typeCharsInCell("Cast", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "Cast" in A8
2269 // Should autocomplete to "Castle" which is the only match.
2270 CPPUNIT_ASSERT_EQUAL_MESSAGE("6: A8 should autocomplete", u"Castle"_ustr, pDoc->GetString(aA8));
2272 lcl_typeCharsInCell("T", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "T" in A8
2273 // Should autocomplete to "Time" which is the only match.
2274 CPPUNIT_ASSERT_EQUAL_MESSAGE("7: A8 should autocomplete", u"Time"_ustr, pDoc->GetString(aA8));
2277 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testEditCursorBounds)
2279 comphelper::LibreOfficeKit::setCompatFlag(
2280 comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
2281 ScModelObj* pModelObj = createDoc("empty.ods");
2282 ScDocument* pDoc = pModelObj->GetDocument();
2284 ViewCallback aView;
2285 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
2286 CPPUNIT_ASSERT(pView);
2287 comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(true);
2289 // ~170% zoom.
2290 pModelObj->setClientZoom(256, 256, 2222, 2222);
2291 pModelObj->setClientVisibleArea(tools::Rectangle(7725, 379832, 16240, 6449));
2292 Scheduler::ProcessEventsToIdle();
2294 constexpr SCCOL nCol = 5;
2295 constexpr SCROW nRow = 2048;
2296 pDoc->SetValue(ScAddress(nCol, nRow, 0), 123);
2298 aView.m_bOwnCursorInvalidated = false;
2299 // Obtain the cell bounds via cursor.
2300 pView->SetCursor(nCol, nRow);
2301 Scheduler::ProcessEventsToIdle();
2303 CPPUNIT_ASSERT(aView.m_bOwnCursorInvalidated);
2304 CPPUNIT_ASSERT(!aView.m_aCellCursorBounds.IsEmpty());
2305 tools::Rectangle aCellBounds(aView.m_aCellCursorBounds);
2307 aView.m_aInvalidateCursorResult.clear();
2308 // Enter edit mode in the same cell.
2309 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::F2);
2310 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::F2);
2311 Scheduler::ProcessEventsToIdle();
2313 CPPUNIT_ASSERT(!aView.m_aInvalidateCursorResult.empty());
2314 CPPUNIT_ASSERT_MESSAGE("Edit cursor must be in cell bounds!",
2315 aCellBounds.Contains(aView.m_aInvalidateCursorResult.getBounds()));
2317 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
2320 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testTextSelectionBounds)
2322 comphelper::LibreOfficeKit::setCompatFlag(
2323 comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
2324 ScModelObj* pModelObj = createDoc("empty.ods");
2325 ScDocument* pDoc = pModelObj->GetDocument();
2327 ViewCallback aView;
2328 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
2329 CPPUNIT_ASSERT(pView);
2330 comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(true);
2332 // ~170% zoom.
2333 pModelObj->setClientZoom(256, 256, 2222, 2222);
2334 pModelObj->setClientVisibleArea(tools::Rectangle(7725, 379832, 16240, 6449));
2335 Scheduler::ProcessEventsToIdle();
2337 constexpr SCCOL nCol = 5;
2338 constexpr SCROW nRow = 2048;
2339 pDoc->SetValue(ScAddress(nCol, nRow, 0), 123);
2341 aView.m_bOwnCursorInvalidated = false;
2342 // Obtain the cell bounds via cursor.
2343 pView->SetCursor(nCol, nRow);
2344 Scheduler::ProcessEventsToIdle();
2346 CPPUNIT_ASSERT(aView.m_bOwnCursorInvalidated);
2347 CPPUNIT_ASSERT(!aView.m_aCellCursorBounds.IsEmpty());
2348 tools::Rectangle aCellBounds(aView.m_aCellCursorBounds);
2350 aView.m_aTextSelectionResult.clear();
2351 // Enter edit mode in the same cell and select all text.
2352 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::F2);
2353 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::F2);
2354 Scheduler::ProcessEventsToIdle();
2356 // CTRL + A
2357 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_MOD1 | awt::Key::A);
2358 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_MOD1 | awt::Key::A);
2359 Scheduler::ProcessEventsToIdle();
2361 CPPUNIT_ASSERT(!aView.m_aTextSelectionResult.empty());
2362 CPPUNIT_ASSERT_MESSAGE("Text selections must be in cell bounds!",
2363 !aCellBounds.Intersection(aView.m_aTextSelectionResult.getBounds(0)).IsEmpty());
2365 SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
2368 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSheetViewDataCrash)
2370 ScModelObj* pModelObj = createDoc("empty.ods");
2372 // view #1
2373 int nView1 = SfxLokHelper::getView();
2374 SfxLokHelper::setView(nView1);
2376 // Imitate online while creating a new sheet on empty.ods.
2377 uno::Sequence<beans::PropertyValue> aArgs(
2378 comphelper::InitPropertySequence({
2379 { "Name", uno::Any(u"NewSheet"_ustr) },
2380 { "Index", uno::Any(sal_Int32(2)) }
2381 }));
2382 dispatchCommand(mxComponent, u".uno:Insert"_ustr, aArgs);
2383 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEDOWN | KEY_MOD1);
2384 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEDOWN | KEY_MOD1);
2385 Scheduler::ProcessEventsToIdle();
2386 ScTabViewShell* pView1 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
2387 CPPUNIT_ASSERT(pView1);
2389 // view #2
2390 SfxLokHelper::createView();
2391 ScTabViewShell* pView2 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
2392 CPPUNIT_ASSERT(pView2);
2393 Scheduler::ProcessEventsToIdle();
2395 SfxLokHelper::setView(nView1);
2396 // Delete a range.
2397 pView1->SetCursor(1, 1);
2398 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DOWN | KEY_SHIFT);
2399 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DOWN | KEY_SHIFT);
2400 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DELETE);
2401 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DELETE);
2402 // It will crash at this point without the fix.
2403 Scheduler::ProcessEventsToIdle();
2406 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testTextBoxInsert)
2408 createDoc("empty.ods");
2409 ViewCallback aView1;
2411 // insert textbox
2412 uno::Sequence<beans::PropertyValue> aArgs(
2413 comphelper::InitPropertySequence({
2414 { "CreateDirectly", uno::Any(true) }
2415 }));
2416 dispatchCommand(mxComponent, u".uno:DrawText"_ustr, aArgs);
2418 // check if we have textbox selected
2419 CPPUNIT_ASSERT(!aView1.m_ShapeSelection.isEmpty());
2420 CPPUNIT_ASSERT(aView1.m_ShapeSelection != "EMPTY");
2422 Scheduler::ProcessEventsToIdle();
2425 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testCommentCellCopyPaste)
2427 // Comments callback are emitted only if tiled annotations are off
2428 comphelper::LibreOfficeKit::setTiledAnnotations(false);
2430 // FIXME: Hack because previous tests do not destroy ScDocument(with annotations) on exit (?).
2431 ScPostIt::mnLastPostItId = 1;
2434 ScModelObj* pModelObj = createDoc("empty.ods");
2435 ViewCallback aView;
2436 int nView = SfxLokHelper::getView();
2438 SfxLokHelper::setView(nView);
2440 ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
2441 CPPUNIT_ASSERT(pTabViewShell);
2443 lcl_typeCharsInCell("ABC", 0, 0, pTabViewShell, pModelObj); // Type "ABC" in A1
2445 pTabViewShell->SetCursor(1, 1);
2447 // Add a new comment
2448 uno::Sequence<beans::PropertyValue> aArgs(comphelper::InitPropertySequence(
2450 {"Text", uno::Any(u"LOK Comment Cell B2"_ustr)},
2451 {"Author", uno::Any(u"LOK Client"_ustr)},
2452 }));
2453 dispatchCommand(mxComponent, u".uno:InsertAnnotation"_ustr, aArgs);
2455 // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action
2456 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView.m_aCommentCallbackResult.get<std::string>("action"));
2457 CPPUNIT_ASSERT_EQUAL(std::string("1"), aView.m_aCommentCallbackResult.get<std::string>("id"));
2458 CPPUNIT_ASSERT_EQUAL(std::string("0"), aView.m_aCommentCallbackResult.get<std::string>("tab"));
2459 CPPUNIT_ASSERT_EQUAL(std::string("LOK Client"), aView.m_aCommentCallbackResult.get<std::string>("author"));
2460 CPPUNIT_ASSERT_EQUAL(std::string("LOK Comment Cell B2"), aView.m_aCommentCallbackResult.get<std::string>("text"));
2462 uno::Sequence<beans::PropertyValue> aCopyPasteArgs;
2464 // We need separate tests for single cell copy-paste and cell-range copy-paste
2465 // since they hit different code paths in ScColumn methods.
2467 // Single cell(with comment) copy paste test
2469 dispatchCommand(mxComponent, u".uno:Copy"_ustr, aCopyPasteArgs);
2471 pTabViewShell->SetCursor(1, 49);
2472 Scheduler::ProcessEventsToIdle();
2473 dispatchCommand(mxComponent, u".uno:Paste"_ustr, aCopyPasteArgs); // Paste to cell B50
2475 // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action
2476 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView.m_aCommentCallbackResult.get<std::string>("action"));
2477 // Without the fix the id will be "1".
2478 CPPUNIT_ASSERT_EQUAL(std::string("2"), aView.m_aCommentCallbackResult.get<std::string>("id"));
2479 CPPUNIT_ASSERT_EQUAL(std::string("0"), aView.m_aCommentCallbackResult.get<std::string>("tab"));
2480 CPPUNIT_ASSERT_EQUAL(std::string("LOK Client"), aView.m_aCommentCallbackResult.get<std::string>("author"));
2481 CPPUNIT_ASSERT_EQUAL(std::string("LOK Comment Cell B2"), aView.m_aCommentCallbackResult.get<std::string>("text"));
2484 // Cell range (with a comment) copy paste test
2486 // Select range A1:C3
2487 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_HOME | KEY_MOD1);
2488 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_HOME | KEY_MOD1);
2489 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN | KEY_SHIFT);
2490 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN | KEY_SHIFT);
2491 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN | KEY_SHIFT);
2492 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN | KEY_SHIFT);
2493 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT | KEY_SHIFT);
2494 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT | KEY_SHIFT);
2495 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT | KEY_SHIFT);
2496 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT | KEY_SHIFT);
2497 Scheduler::ProcessEventsToIdle();
2499 dispatchCommand(mxComponent, u".uno:Copy"_ustr, aCopyPasteArgs);
2501 pTabViewShell->SetCursor(3, 49);
2502 Scheduler::ProcessEventsToIdle();
2503 dispatchCommand(mxComponent, u".uno:Paste"_ustr, aCopyPasteArgs); // Paste to cell D50
2505 // We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action
2506 CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView.m_aCommentCallbackResult.get<std::string>("action"));
2507 // Without the fix the id will be "1".
2508 CPPUNIT_ASSERT_EQUAL(std::string("3"), aView.m_aCommentCallbackResult.get<std::string>("id"));
2509 CPPUNIT_ASSERT_EQUAL(std::string("0"), aView.m_aCommentCallbackResult.get<std::string>("tab"));
2510 CPPUNIT_ASSERT_EQUAL(std::string("LOK Client"), aView.m_aCommentCallbackResult.get<std::string>("author"));
2511 CPPUNIT_ASSERT_EQUAL(std::string("LOK Comment Cell B2"), aView.m_aCommentCallbackResult.get<std::string>("text"));
2514 comphelper::LibreOfficeKit::setTiledAnnotations(true);
2517 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testInvalidEntrySave)
2519 loadFromFile(u"validity.xlsx");
2521 // .uno:Save modifies the original file, make a copy first
2522 saveAndReload(u"Calc Office Open XML"_ustr);
2523 ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
2524 CPPUNIT_ASSERT(pModelObj);
2525 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
2526 const ScDocument* pDoc = pModelObj->GetDocument();
2527 ViewCallback aView;
2528 int nView = SfxLokHelper::getView();
2530 SfxLokHelper::setView(nView);
2532 ScDocShell* pDocSh = dynamic_cast< ScDocShell* >( pModelObj->GetEmbeddedObject() );
2533 ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
2534 CPPUNIT_ASSERT(pTabViewShell);
2536 // Type partial date "7/8" of "7/8/2013" that
2537 // the validation cell at A8 can accept
2538 lcl_typeCharsInCell("7/8", 0, 7, pTabViewShell, pModelObj,
2539 false /* bInEdit */, false /* bCommit */); // Type "7/8" in A8
2541 uno::Sequence<beans::PropertyValue> aArgs;
2542 dispatchCommand(mxComponent, u".uno:Save"_ustr, aArgs);
2544 CPPUNIT_ASSERT_MESSAGE("Should not be marked modified after save", !pDocSh->IsModified());
2546 // Complete the date in A8 by appending "/2013" and commit.
2547 lcl_typeCharsInCell("/2013", 0, 7, pTabViewShell, pModelObj,
2548 true /* bInEdit */, true /* bCommit */);
2550 // This would hang if the date entered "7/8/2013" is not acceptable.
2551 Scheduler::ProcessEventsToIdle();
2553 // Ensure that the correct date is recorded in the document.
2554 CPPUNIT_ASSERT_EQUAL(double(41463), pDoc->GetValue(ScAddress(0, 7, 0)));
2557 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testUndoReordering)
2559 ScModelObj* pModelObj = createDoc("small.ods");
2560 CPPUNIT_ASSERT(pModelObj);
2561 ScDocument* pDoc = pModelObj->GetDocument();
2562 CPPUNIT_ASSERT(pDoc);
2563 ScUndoManager* pUndoManager = pDoc->GetUndoManager();
2564 CPPUNIT_ASSERT(pUndoManager);
2566 // view #1
2567 int nView1 = SfxLokHelper::getView();
2568 ViewCallback aView1;
2570 // view #2
2571 SfxLokHelper::createView();
2572 int nView2 = SfxLokHelper::getView();
2573 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
2574 ViewCallback aView2;
2576 // text edit a cell in view #1
2577 SfxLokHelper::setView(nView1);
2578 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
2579 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
2580 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
2581 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
2582 Scheduler::ProcessEventsToIdle();
2584 // check that undo action count is not 0
2585 CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
2587 // text edit a different cell in view #2
2588 SfxLokHelper::setView(nView2);
2589 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
2590 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
2591 Scheduler::ProcessEventsToIdle();
2592 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
2593 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
2594 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
2595 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
2596 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
2597 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
2598 Scheduler::ProcessEventsToIdle();
2600 // check that undo action count is not 1
2601 CPPUNIT_ASSERT_EQUAL(std::size_t(2), pUndoManager->GetUndoActionCount());
2603 // try to execute undo in view #1
2604 SfxLokHelper::setView(nView1);
2605 dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});
2606 // check that undo has been executed on view #1
2607 CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
2609 // try to execute undo in view #2
2610 SfxLokHelper::setView(nView2);
2611 dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});
2612 // check that undo has been executed on view #2
2613 CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetUndoActionCount());
2616 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testUndoReorderingRedo)
2618 ScModelObj* pModelObj = createDoc("empty.ods");
2619 CPPUNIT_ASSERT(pModelObj);
2620 ScDocument* pDoc = pModelObj->GetDocument();
2621 CPPUNIT_ASSERT(pDoc);
2622 ScUndoManager* pUndoManager = pDoc->GetUndoManager();
2623 CPPUNIT_ASSERT(pUndoManager);
2624 CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetUndoActionCount());
2626 // view #1
2627 int nView1 = SfxLokHelper::getView();
2628 SfxViewShell* pView1 = SfxViewShell::Current();
2629 ViewCallback aView1;
2631 // view #2
2632 SfxLokHelper::createView();
2633 int nView2 = SfxLokHelper::getView();
2634 SfxViewShell* pView2 = SfxViewShell::Current();
2635 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
2636 ViewCallback aView2;
2638 // text edit a cell in view #1
2639 SfxLokHelper::setView(nView1);
2640 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
2641 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
2642 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
2643 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
2644 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
2645 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
2646 Scheduler::ProcessEventsToIdle();
2647 CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
2649 // text edit another cell in view #1
2650 SfxLokHelper::setView(nView1);
2651 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'y', 0);
2652 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'y', 0);
2653 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'y', 0);
2654 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'y', 0);
2655 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
2656 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
2657 Scheduler::ProcessEventsToIdle();
2658 CPPUNIT_ASSERT_EQUAL(std::size_t(2), pUndoManager->GetUndoActionCount());
2659 CPPUNIT_ASSERT_EQUAL(u"xx"_ustr, pDoc->GetString(ScAddress(0, 0, 0)));
2660 CPPUNIT_ASSERT_EQUAL(u"yy"_ustr, pDoc->GetString(ScAddress(0, 1, 0)));
2662 // text edit a different cell in view #2
2663 SfxLokHelper::setView(nView2);
2664 ScTabViewShell* pViewShell2 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
2665 pViewShell2->SetCursor(0, 2);
2666 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'C', 0);
2667 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'C', 0);
2668 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'C', 0);
2669 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'C', 0);
2670 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
2671 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
2672 Scheduler::ProcessEventsToIdle();
2673 CPPUNIT_ASSERT_EQUAL(std::size_t(3), pUndoManager->GetUndoActionCount());
2674 CPPUNIT_ASSERT_EQUAL(u"xx"_ustr, pDoc->GetString(ScAddress(0, 0, 0)));
2675 CPPUNIT_ASSERT_EQUAL(u"yy"_ustr, pDoc->GetString(ScAddress(0, 1, 0)));
2676 CPPUNIT_ASSERT_EQUAL(u"CC"_ustr, pDoc->GetString(ScAddress(0, 2, 0)));
2678 // View 1 presses undo, and the second cell is erased
2679 SfxLokHelper::setView(nView1);
2680 dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});
2681 CPPUNIT_ASSERT_EQUAL(std::size_t(2), pUndoManager->GetUndoActionCount());
2682 CPPUNIT_ASSERT_EQUAL(u"xx"_ustr, pDoc->GetString(ScAddress(0, 0, 0)));
2683 CPPUNIT_ASSERT_EQUAL(u""_ustr, pDoc->GetString(ScAddress(0, 1, 0)));
2684 CPPUNIT_ASSERT_EQUAL(u"CC"_ustr, pDoc->GetString(ScAddress(0, 2, 0)));
2686 // Verify that the UNDO buttons/actions are still enabled
2688 SfxItemSet aSet1(pView1->GetPool(), svl::Items<SID_UNDO, SID_UNDO>);
2689 SfxItemSet aSet2(pView2->GetPool(), svl::Items<SID_UNDO, SID_UNDO>);
2690 pView1->GetSlotState(SID_UNDO, nullptr, &aSet1);
2691 pView2->GetSlotState(SID_UNDO, nullptr, &aSet2);
2692 CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aSet1.GetItemState(SID_UNDO));
2693 CPPUNIT_ASSERT(dynamic_cast< const SfxStringItem* >(aSet1.GetItem(SID_UNDO)));
2694 CPPUNIT_ASSERT_EQUAL(SfxItemState::SET, aSet2.GetItemState(SID_UNDO));
2695 CPPUNIT_ASSERT(dynamic_cast< const SfxStringItem* >(aSet2.GetItem(SID_UNDO)));
2698 // View 1 presses undo again, and the first cell is erased
2699 dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});
2700 CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
2701 CPPUNIT_ASSERT_EQUAL(u""_ustr, pDoc->GetString(ScAddress(0, 0, 0)));
2702 CPPUNIT_ASSERT_EQUAL(u""_ustr, pDoc->GetString(ScAddress(0, 1, 0)));
2703 CPPUNIT_ASSERT_EQUAL(u"CC"_ustr, pDoc->GetString(ScAddress(0, 2, 0)));
2706 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testUndoReorderingMulti)
2708 ScModelObj* pModelObj = createDoc("empty.ods");
2709 CPPUNIT_ASSERT(pModelObj);
2710 ScDocument* pDoc = pModelObj->GetDocument();
2711 CPPUNIT_ASSERT(pDoc);
2712 ScUndoManager* pUndoManager = pDoc->GetUndoManager();
2713 CPPUNIT_ASSERT(pUndoManager);
2714 CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetUndoActionCount());
2716 // view #1
2717 int nView1 = SfxLokHelper::getView();
2718 ViewCallback aView1;
2720 // view #2
2721 SfxLokHelper::createView();
2722 int nView2 = SfxLokHelper::getView();
2723 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
2724 ViewCallback aView2;
2726 // text edit a cell in view #1
2727 SfxLokHelper::setView(nView1);
2728 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
2729 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
2730 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
2731 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
2732 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
2733 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
2734 Scheduler::ProcessEventsToIdle();
2735 CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
2737 // text edit a different cell in view #2
2738 SfxLokHelper::setView(nView2);
2739 ScTabViewShell* pView2 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
2740 pView2->SetCursor(0, 2);
2741 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'C', 0);
2742 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'C', 0);
2743 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'C', 0);
2744 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'C', 0);
2745 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
2746 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
2747 Scheduler::ProcessEventsToIdle();
2748 CPPUNIT_ASSERT_EQUAL(std::size_t(2), pUndoManager->GetUndoActionCount());
2749 CPPUNIT_ASSERT_EQUAL(u"xx"_ustr, pDoc->GetString(ScAddress(0, 0, 0)));
2750 CPPUNIT_ASSERT_EQUAL(u"CC"_ustr, pDoc->GetString(ScAddress(0, 2, 0)));
2752 // and another cell in view #2
2753 pView2->SetCursor(0, 3);
2754 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'D', 0);
2755 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'D', 0);
2756 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'D', 0);
2757 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'D', 0);
2758 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
2759 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
2760 Scheduler::ProcessEventsToIdle();
2761 CPPUNIT_ASSERT_EQUAL(std::size_t(3), pUndoManager->GetUndoActionCount());
2762 CPPUNIT_ASSERT_EQUAL(u"xx"_ustr, pDoc->GetString(ScAddress(0, 0, 0)));
2763 CPPUNIT_ASSERT_EQUAL(u"CC"_ustr, pDoc->GetString(ScAddress(0, 2, 0)));
2764 CPPUNIT_ASSERT_EQUAL(u"DD"_ustr, pDoc->GetString(ScAddress(0, 3, 0)));
2766 // View 1 presses undo
2767 SfxLokHelper::setView(nView1);
2768 dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});
2769 CPPUNIT_ASSERT_EQUAL(std::size_t(2), pUndoManager->GetUndoActionCount());
2770 CPPUNIT_ASSERT_EQUAL(u""_ustr, pDoc->GetString(ScAddress(0, 0, 0)));
2771 CPPUNIT_ASSERT_EQUAL(u"CC"_ustr, pDoc->GetString(ScAddress(0, 2, 0)));
2772 CPPUNIT_ASSERT_EQUAL(u"DD"_ustr, pDoc->GetString(ScAddress(0, 3, 0)));
2775 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testGetViewRenderState)
2777 // Add a pair of schemes, last added is the default
2778 svtools::EditableColorConfig aColorConfig;
2779 aColorConfig.AddScheme(u"Dark"_ustr);
2780 aColorConfig.AddScheme(u"Light"_ustr);
2782 ScModelObj* pModelObj = createDoc("empty.ods");
2783 int nFirstViewId = SfxLokHelper::getView();
2784 ViewCallback aView1;
2786 CPPUNIT_ASSERT_EQUAL("S;Default"_ostr, pModelObj->getViewRenderState());
2787 // Create a second view
2788 SfxLokHelper::createView();
2789 ViewCallback aView2;
2790 CPPUNIT_ASSERT_EQUAL("S;Default"_ostr, pModelObj->getViewRenderState());
2791 // Set second view to dark scheme
2793 uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence(
2795 { "NewTheme", uno::Any(u"Dark"_ustr) },
2798 dispatchCommand(mxComponent, u".uno:ChangeTheme"_ustr, aPropertyValues);
2800 CPPUNIT_ASSERT_EQUAL("S;Dark"_ostr, pModelObj->getViewRenderState());
2802 // Switch back to first view and make sure it's the same
2803 SfxLokHelper::setView(nFirstViewId);
2804 CPPUNIT_ASSERT_EQUAL("S;Default"_ostr, pModelObj->getViewRenderState());
2808 * testInvalidateOnTextEditWithDifferentZoomLevels
2809 * steps:
2810 * set view 1 zoom to the passed zoom level
2811 * in view 1 type a char at the passed cell address
2812 * store invalidation rectangle
2813 * exit from in place editing (press esc)
2814 * create view 2 (keep 100% zoom)
2815 * go to the same cell address used in view 1
2816 * type a char into the cell
2817 * get invalidation rectangle for view 1
2818 * check if the invalidation rectangle is equal to the one stored previously
2820 class testInvalidateOnTextEditWithDifferentZoomLevels : public ScTiledRenderingTest
2822 public:
2823 void TestBody(const ColRowZoom& rData);
2824 CPPUNIT_TEST_SUITE(testInvalidateOnTextEditWithDifferentZoomLevels);
2825 CPPUNIT_TEST_PARAMETERIZED(TestBody,
2826 std::initializer_list<ColRowZoom>
2828 // zoom level 120%
2829 {0, 999, 1}, {99, 0, 1},
2830 // zoom level 40%
2831 {0, 999, -5}, {99, 0, -5}
2833 CPPUNIT_TEST_SUITE_END();
2835 CPPUNIT_TEST_SUITE_REGISTRATION(testInvalidateOnTextEditWithDifferentZoomLevels);
2837 void testInvalidateOnTextEditWithDifferentZoomLevels::TestBody(const ColRowZoom& rData)
2839 ScModelObj* pModelObj = createDoc("empty.ods");
2840 CPPUNIT_ASSERT(pModelObj);
2841 ScDocument* pDoc = pModelObj->GetDocument();
2842 CPPUNIT_ASSERT(pDoc);
2843 OUString sZoomUnoCmd = u".uno:ZoomPlus"_ustr;
2844 int nZoomLevel = rData.zoom;
2845 if (nZoomLevel < 0)
2847 nZoomLevel = -nZoomLevel;
2848 sZoomUnoCmd = ".uno:ZoomMinus";
2850 // view #1
2851 ViewCallback aView1;
2852 // set zoom level
2853 for (int i = 0; i < nZoomLevel; ++i)
2854 dispatchCommand(mxComponent, sZoomUnoCmd, {});
2855 Scheduler::ProcessEventsToIdle();
2856 auto* pTabViewShell1 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
2857 CPPUNIT_ASSERT(pTabViewShell1);
2858 // enable in place editing in view 1
2859 auto& rInvalidations = aView1.m_aInvalidations;
2860 pTabViewShell1->SetCursor(rData.col, rData.row);
2861 Scheduler::ProcessEventsToIdle();
2862 aView1.m_bInvalidateTiles = false;
2863 rInvalidations.clear();
2864 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
2865 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
2866 Scheduler::ProcessEventsToIdle();
2867 CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
2868 CPPUNIT_ASSERT(!rInvalidations.empty());
2869 tools::Rectangle aInvRect1 = rInvalidations[0];
2870 // end editing
2871 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::ESCAPE);
2872 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::ESCAPE);
2873 Scheduler::ProcessEventsToIdle();
2874 // view #2
2875 SfxLokHelper::createView();
2876 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
2877 ViewCallback aView2;
2878 Scheduler::ProcessEventsToIdle();
2879 auto* pTabViewShell2 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
2880 CPPUNIT_ASSERT(pTabViewShell2);
2881 pTabViewShell2->SetCursor(rData.col, rData.row);
2882 Scheduler::ProcessEventsToIdle();
2883 // text edit in view #2
2884 aView1.m_bInvalidateTiles = false;
2885 rInvalidations.clear();
2886 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
2887 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
2888 Scheduler::ProcessEventsToIdle();
2889 CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
2890 CPPUNIT_ASSERT(!rInvalidations.empty());
2891 tools::Rectangle aInvRect2 = rInvalidations[0];
2892 CPPUNIT_ASSERT_EQUAL_MESSAGE("Invalidation rectangle is wrong.", aInvRect1, aInvRect2);
2895 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testOpenURL)
2897 // Given a document that has 2 views:
2898 createDoc("empty.ods");
2899 int nView1 = SfxLokHelper::getView();
2900 ViewCallback aView1;
2901 SfxLokHelper::createView();
2902 ViewCallback aView2;
2904 // When clicking on a link in view 2, but switching to view 1 before processing async events:
2905 ScGlobal::OpenURL(/*aUrl=*/u"http://www.example.com/"_ustr, /*aTarget=*/u""_ustr,
2906 /*bIgnoreSettings=*/true);
2907 SfxLokHelper::setView(nView1);
2908 Scheduler::ProcessEventsToIdle();
2910 // Then make sure view 2 gets the callback, not view 1:
2911 // Without the accompanying fix in place, this test would have failed, view 1 got the hyperlink
2912 // callback.
2913 CPPUNIT_ASSERT(aView1.m_aHyperlinkClicked.isEmpty());
2914 CPPUNIT_ASSERT(!aView2.m_aHyperlinkClicked.isEmpty());
2917 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testInvalidateForSplitPanes)
2919 comphelper::LibreOfficeKit::setCompatFlag(
2920 comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
2922 ScModelObj* pModelObj = createDoc("split.ods");
2923 CPPUNIT_ASSERT(pModelObj);
2924 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
2925 CPPUNIT_ASSERT(pView);
2927 // view
2928 ViewCallback aView;
2930 // move way over to the right where BP:20 exists, enough so that rows A and B
2931 // would scroll off the page and not be visible, if they were not frozen
2932 pModelObj->setClientVisibleArea(tools::Rectangle(73050, 0, 94019, 7034));
2933 Scheduler::ProcessEventsToIdle();
2935 ScAddress aBP20(67, 19, 0); // BP:20
2937 pView->SetCursor(aBP20.Col(), aBP20.Row());
2938 Scheduler::ProcessEventsToIdle();
2940 aView.m_bInvalidateTiles = false;
2941 aView.m_aInvalidations.clear();
2943 lcl_typeCharsInCell("X", aBP20.Col(), aBP20.Row(), pView, pModelObj); // Type 'X' in A1
2945 CPPUNIT_ASSERT(aView.m_bInvalidateTiles);
2947 // missing before fix
2948 tools::Rectangle aTopLeftPane(0, 500, 3817, 742);
2949 bool bFoundTopLeftPane =
2950 std::find(aView.m_aInvalidations.begin(), aView.m_aInvalidations.end(), aTopLeftPane) != aView.m_aInvalidations.end();
2951 CPPUNIT_ASSERT_MESSAGE("The cell visible in the top left pane should be redrawn", bFoundTopLeftPane);
2953 // missing before fix
2954 tools::Rectangle aBottomLeftPane(0, 500, 3817, 3242);
2955 bool bFoundBottomLeftPane =
2956 std::find(aView.m_aInvalidations.begin(), aView.m_aInvalidations.end(), aBottomLeftPane) != aView.m_aInvalidations.end();
2957 CPPUNIT_ASSERT_MESSAGE("The cell visible in the bottom left pane should be redrawn", bFoundBottomLeftPane);
2960 // Saving shouldn't trigger an invalidation
2961 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testNoInvalidateOnSave)
2963 comphelper::LibreOfficeKit::setCompatFlag(
2964 comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
2966 loadFromFile(u"invalidate-on-save.ods");
2968 // .uno:Save modifies the original file, make a copy first
2969 saveAndReload(u"calc8"_ustr);
2970 ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
2971 CPPUNIT_ASSERT(pModelObj);
2972 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
2974 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
2975 CPPUNIT_ASSERT(pView);
2977 Scheduler::ProcessEventsToIdle();
2979 // track invalidations
2980 ViewCallback aView;
2982 uno::Sequence<beans::PropertyValue> aArgs;
2983 dispatchCommand(mxComponent, u".uno:Save"_ustr, aArgs);
2985 Scheduler::ProcessEventsToIdle();
2987 CPPUNIT_ASSERT(!aView.m_bInvalidateTiles);
2990 void ScTiledRenderingTest::checkSampleInvalidation(const ViewCallback& rView, bool bFullRow)
2992 // we expect invalidations, but that isn't really important
2993 CPPUNIT_ASSERT(rView.m_bInvalidateTiles);
2994 tools::Rectangle aInvalidation;
2995 for (const auto& rRect : rView.m_aInvalidations)
2996 aInvalidation.Union(rRect);
2997 if (!bFullRow)
2999 // What matters is that we expect that the invalidation does not extend all the
3000 // way to the max right of the sheet.
3001 // Here we originally got 32212306 and now ~5056 for a single cell case
3002 CPPUNIT_ASSERT_LESSEQUAL(tools::Long(8000), aInvalidation.GetWidth());
3004 else
3006 // We expect RTL to continue to invalidate the entire row
3007 // from 0 to end of sheet (see ScDocShell::PostPaint, 'Extend to whole rows'),
3008 // which is different to the adjusted LTR case which
3009 // invalidated the row from left of edited cell to right of end
3010 // of sheet.
3011 CPPUNIT_ASSERT_LESSEQUAL(tools::Long(0), aInvalidation.Left());
3012 CPPUNIT_ASSERT_EQUAL(tools::Long(32212230), aInvalidation.Right());
3016 void ScTiledRenderingTest::cellInvalidationHelper(ScModelObj* pModelObj, ScTabViewShell* pView, const ScAddress& rAdr,
3017 bool bAddText, bool bFullRow)
3019 // view
3020 ViewCallback aView;
3022 if (bAddText)
3024 // Type "Hello World" in D8, process events to idle and don't commit yet
3025 lcl_typeCharsInCell("Hello World", rAdr.Col(), rAdr.Row(), pView, pModelObj, false, false);
3027 aView.m_bInvalidateTiles = false;
3028 aView.m_aInvalidations.clear();
3030 // commit text and process events to idle
3031 lcl_typeCharsInCell("", rAdr.Col(), rAdr.Row(), pView, pModelObj, true, true);
3033 else // DeleteText
3035 pView->SetCursor(rAdr.Col(), rAdr.Row());
3036 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DELETE);
3037 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DELETE);
3038 Scheduler::ProcessEventsToIdle();
3041 checkSampleInvalidation(aView, bFullRow);
3044 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testCellMinimalInvalidations)
3046 ScAddress aA8(0, 7, 0);
3047 ScAddress aD4(3, 7, 0);
3048 ScAddress aD13(3, 12, 0);
3049 ScAddress aD17(3, 16, 0);
3051 ScModelObj* pModelObj = createDoc("cell-invalidations.ods");
3052 CPPUNIT_ASSERT(pModelObj);
3053 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
3054 CPPUNIT_ASSERT(pView);
3056 // Changed: Minimized invalidations (bFullRow: false)
3058 // Common case, LTR, default cell formatting
3059 cellInvalidationHelper(pModelObj, pView, aA8, true, false);
3060 cellInvalidationHelper(pModelObj, pView, aD4, true, false);
3061 // Left-aligned merged cells
3062 cellInvalidationHelper(pModelObj, pView, aD17, true, false);
3063 // Delete single cell text case
3064 cellInvalidationHelper(pModelObj, pView, aA8, false, false);
3065 // Paste into a single cell
3067 pView->SetCursor(aD4.Col(), aD4.Row());
3068 uno::Sequence<beans::PropertyValue> aArgs;
3069 dispatchCommand(mxComponent, u".uno:Copy"_ustr, aArgs);
3070 pView->SetCursor(aA8.Col(), aA8.Row());
3071 Scheduler::ProcessEventsToIdle();
3073 ViewCallback aView;
3074 dispatchCommand(mxComponent, u".uno:Paste"_ustr, aArgs);
3075 Scheduler::ProcessEventsToIdle();
3077 checkSampleInvalidation(aView, false);
3080 // Unchanged: Non-minimized invalidations (bFullRow: true)
3082 // Centered merged cells;
3083 cellInvalidationHelper(pModelObj, pView, aD13, true, true);
3085 // switch to RTL sheet
3086 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEDOWN | KEY_MOD1);
3087 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEDOWN | KEY_MOD1);
3088 Scheduler::ProcessEventsToIdle();
3090 cellInvalidationHelper(pModelObj, pView, aA8, true, true);
3091 cellInvalidationHelper(pModelObj, pView, aD4, true, true);
3092 // Delete Text
3093 cellInvalidationHelper(pModelObj, pView, aA8, false, true);
3096 // That we don't end up with two views on different zooms that invalidate different
3097 // rectangles, each should invalidate the same rectangle
3098 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testCellInvalidationDocWithExistingZoom)
3100 ScAddress aB7(1, 6, 0);
3101 ScopedVclPtrInstance<VirtualDevice> xDevice(DeviceFormat::WITHOUT_ALPHA);
3103 ScModelObj* pModelObj = createDoc("cell-invalidations-200zoom-settings.ods");
3104 CPPUNIT_ASSERT(pModelObj);
3105 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
3106 CPPUNIT_ASSERT(pView);
3108 // Set View #1 to initial 100% and generate a paint
3109 pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 19845, 6405));
3110 pModelObj->setClientZoom(256, 256, 1536, 1536);
3111 pModelObj->paintTile(*xDevice, 3328, 512, 0, 0, 19968, 3072);
3113 Scheduler::ProcessEventsToIdle();
3115 int nView1 = SfxLokHelper::getView();
3116 // register to track View #1 invalidations
3117 ViewCallback aView1;
3119 // Create a View #2
3120 SfxLokHelper::createView();
3121 int nView2 = SfxLokHelper::getView();
3122 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
3123 // register to track View #1 invalidations
3124 ViewCallback aView2;
3126 // Set View #2 to initial 100% and generate a paint
3127 pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 19845, 6405));
3128 pModelObj->setClientZoom(256, 256, 1536, 1536);
3129 pModelObj->paintTile(*xDevice, 3328, 512, 0, 0, 19968, 3072);
3131 // Set View #1 to 50% zoom and generate a paint
3132 SfxLokHelper::setView(nView1);
3133 pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 41150, 13250));
3134 pModelObj->setClientZoom(256, 256, 3185, 3185);
3135 pModelObj->paintTile(*xDevice, 3328, 512, 0, 0, 41405, 6370);
3137 Scheduler::ProcessEventsToIdle();
3139 // Set View #2 to 200% zoom and generate a paint
3140 SfxLokHelper::setView(nView2);
3141 pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 9574, 3090));
3142 pModelObj->setClientZoom(256, 256, 741, 741);
3143 pModelObj->paintTile(*xDevice, 3328, 512, 0, 0, 19968, 3072);
3145 Scheduler::ProcessEventsToIdle();
3146 aView1.m_bInvalidateTiles = false;
3147 aView1.m_aInvalidations.clear();
3148 aView2.m_bInvalidateTiles = false;
3149 aView2.m_aInvalidations.clear();
3151 ScTabViewShell* pView2 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
3152 CPPUNIT_ASSERT(pView2);
3153 pView2->SetCursor(aB7.Col(), aB7.Row());
3155 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DELETE);
3156 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DELETE);
3157 Scheduler::ProcessEventsToIdle();
3159 // The problem tested for here is with two views at different zooms then a
3160 // single cell invalidation resulted in the same rectangle reported as two
3161 // different invalidations rectangles of different scales. While we should
3162 // get the same invalidation rectangle reported.
3164 // (B7 is a good choice to use in the real world to see the effect, to both
3165 // avoid getting the two rects combined into one bigger one, or to have the
3166 // two separated by so much space the 2nd is off-screen and not seen
3167 CPPUNIT_ASSERT_EQUAL(size_t(1), aView1.m_aInvalidations.size());
3168 CPPUNIT_ASSERT_EQUAL(size_t(1), aView2.m_aInvalidations.size());
3170 // That they don't exactly match doesn't matter, we're not checking rounding issues,
3171 // what matters is that they are not utterly different rectangles
3172 // Without fix result is originally:
3173 // Comparing invalidation rectangles Width expected 6214742 actual 26716502 Tolerance 50
3174 CPPUNIT_ASSERT_RECTANGLE_EQUAL_WITH_TOLERANCE(aView2.m_aInvalidations[0],
3175 aView1.m_aInvalidations[0],
3176 50);
3179 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testInputHandlerSyncedZoom)
3181 ScModelObj* pModelObj = createDoc("cell-edit-300zoom-settings.ods");
3183 // Set View #1 to initial 150%
3184 pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 17933, 4853));
3185 // Before the fix, this zoom would leave the EditEngine reference device
3186 // at the zoom level stored in the document, so normal rendering and
3187 // editing rendering happened with different MapModes
3188 pModelObj->setClientZoom(256, 256, 1333, 1333);
3190 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
3191 CPPUNIT_ASSERT(pView);
3192 pView->SetCursor(0, 4); // A5
3194 Scheduler::ProcessEventsToIdle();
3196 // Activate edit mode in that A5 cell
3197 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::F2);
3198 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::F2);
3199 Scheduler::ProcessEventsToIdle();
3201 const ScViewData* pViewData1 = ScDocShell::GetViewData();
3202 CPPUNIT_ASSERT(pViewData1);
3204 // Get that active EditView
3205 EditView* pEditView1 = pViewData1->GetEditView(SC_SPLIT_BOTTOMLEFT);
3206 CPPUNIT_ASSERT(pEditView1);
3207 EditEngine& rEditEngine1 = pEditView1->getEditEngine();
3208 // These must match, if they don't then text will have a different width in edit and view modes
3209 CPPUNIT_ASSERT_EQUAL_MESSAGE("EditEngine Ref Dev Zoom and ViewData Zoom should match",
3210 pViewData1->GetZoomX(), rEditEngine1.GetRefMapMode().GetScaleX());
3211 CPPUNIT_ASSERT_EQUAL_MESSAGE("EditEngine Ref Dev Zoom and ViewData Zoom should match",
3212 pViewData1->GetZoomY(), rEditEngine1.GetRefMapMode().GetScaleY());
3214 // Create a View #2
3215 SfxLokHelper::createView();
3216 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
3218 // Set View #2 to the same zoom as View #1
3219 pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 17933, 4853));
3220 pModelObj->setClientZoom(256, 256, 1333, 1333);
3222 ScTabViewShell* pView2 = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
3223 CPPUNIT_ASSERT(pView2);
3224 pView2->SetCursor(0, 5); // A6
3226 Scheduler::ProcessEventsToIdle();
3228 // Activate edit mode in that A6 cell
3229 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::F2);
3230 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::F2);
3231 Scheduler::ProcessEventsToIdle();
3233 const ScViewData* pViewData2 = ScDocShell::GetViewData();
3234 CPPUNIT_ASSERT(pViewData2);
3236 // Get the View #2 EditView
3237 EditView* pEditView2 = pViewData2->GetEditView(SC_SPLIT_BOTTOMLEFT);
3238 CPPUNIT_ASSERT(pEditView2);
3239 EditEngine& rEditEngine2 = pEditView2->getEditEngine();
3240 CPPUNIT_ASSERT(&rEditEngine1 != &rEditEngine2);
3241 // Before the fix, these had different settings, resulting in the text
3242 // dancing for the second user as they toggle in and out of edit mode, but
3243 // each user should have the same settings.
3244 CPPUNIT_ASSERT_EQUAL(rEditEngine1.GetControlWord(), rEditEngine2.GetControlWord());
3247 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testStatusBarLocale)
3249 // Given 2 views, the second's locale is set to German:
3250 createDoc("empty.ods");
3251 int nView1 = SfxLokHelper::getView();
3252 ViewCallback aView1;
3253 SfxLokHelper::createView();
3254 ViewCallback aView2;
3255 SfxViewShell* pView2 = SfxViewShell::Current();
3256 pView2->SetLOKLocale(u"de-DE"_ustr);
3258 SfxViewFrame& rFrame = pView2->GetViewFrame();
3259 SfxSlotPool& rSlotPool = SfxSlotPool::GetSlotPool(&rFrame);
3260 uno::Reference<util::XURLTransformer> xParser(util::URLTransformer::create(m_xContext));
3261 util::URL aCommandURL;
3262 aCommandURL.Complete = ".uno:RowColSelCount";
3263 xParser->parseStrict(aCommandURL);
3264 const SfxSlot* pSlot = rSlotPool.GetUnoSlot(aCommandURL.Path);
3265 rFrame.GetBindings().GetDispatch(pSlot, aCommandURL, false);
3267 aView2.m_aStateChanges.clear();
3269 // When creating a cell selection in the 2nd view and processing jobs with the 1st view set to
3270 // active:
3271 comphelper::dispatchCommand(u".uno:GoDownSel"_ustr, {});
3272 SfxLokHelper::setView(nView1);
3273 pView2->GetViewFrame().GetBindings().GetTimer().Invoke();
3274 // Once more to hit the pImpl->bMsgDirty = false case in SfxBindings::NextJob_Impl().
3275 pView2->GetViewFrame().GetBindings().GetTimer().Invoke();
3277 // Then make sure that the locale is taken into account while producing the state changed
3278 // callback:
3279 auto it = aView2.m_aStateChanges.find(".uno:RowColSelCount");
3280 CPPUNIT_ASSERT(it != aView2.m_aStateChanges.end());
3281 std::string aLocale = it->second.get<std::string>("locale");
3282 // Without the accompanying fix in place, this test would have failed with:
3283 // - Expected: de-DE
3284 // - Actual : en-US
3285 // i.e. the 2nd view got its callback with the locale of the first view, which is buggy.
3286 CPPUNIT_ASSERT_EQUAL(std::string("de-DE"), aLocale);
3289 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testLongFirstColumnMouseClick)
3291 // Document has a long first column. We want to mouse-click on the column and
3292 // check the selection changed to this column.
3294 // The issue we want to reproduce is that the click on a cell in the first column that is
3295 // very long (longer than ~800px default size of GridWindow) triggers a code-path where the cell
3296 // selected is the neighbouring cell even when we clicked on the area of the first cell.
3298 comphelper::LibreOfficeKit::setCompatFlag(
3299 comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
3301 ScModelObj* pModelObj = createDoc("DocumentWithLongFirstColumn.ods");
3302 CPPUNIT_ASSERT(pModelObj);
3303 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
3305 // Fetch current view data
3306 ScViewData* pViewData = ScDocShell::GetViewData();
3307 CPPUNIT_ASSERT(pViewData);
3308 double nPPTX = pViewData->GetPPTX();
3309 double nPPTY = pViewData->GetPPTX();
3311 // Set click position
3313 // Left side of the first cell
3314 int leftCellSideX = 1 / nPPTX; // convert pixels to logical units
3316 // Right side of the first cell. First cell is long so click somewhere more than 800px (default of GridWindow size).
3317 int rightCellSideX = 1000 / nPPTX; // convert pixels to logical units
3319 // Vertical position - doesn't matter - select the first row
3320 int y = 1 / nPPTY;
3322 // Setup view #1
3323 ViewCallback aView1;
3324 // Set client rect to 2000 x 2000 pixels
3325 pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 2000 / nPPTX, 2000 / nPPTY));
3326 Scheduler::ProcessEventsToIdle();
3328 // Click at on the left side of A1 cell
3329 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, leftCellSideX, y, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
3330 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, leftCellSideX, y, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
3331 Scheduler::ProcessEventsToIdle();
3333 // Check the A1 cell is selected in view #1
3334 CPPUNIT_ASSERT_EQUAL(SCCOL(0), ScDocShell::GetViewData()->GetCurX());
3335 CPPUNIT_ASSERT_EQUAL(SCROW(0), ScDocShell::GetViewData()->GetCurY());
3337 // Click at on the right side of A1 cell
3338 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, rightCellSideX, y, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
3339 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, rightCellSideX, y, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
3340 Scheduler::ProcessEventsToIdle();
3342 // Check the A1 cell is selected in view #1
3343 CPPUNIT_ASSERT_EQUAL(SCCOL(0), ScDocShell::GetViewData()->GetCurX());
3344 CPPUNIT_ASSERT_EQUAL(SCROW(0), ScDocShell::GetViewData()->GetCurY());
3346 // Try to check the same scenario in a new view
3348 // Setup view #2
3349 SfxLokHelper::createView();
3350 int nView2 = SfxLokHelper::getView();
3351 ViewCallback aView2;
3352 // Set client rect to 2000 x 2000 pixels
3353 pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 2000 / nPPTX, 2000 / nPPTY));
3355 // Let's make sure we are in view #2
3356 SfxLokHelper::setView(nView2);
3357 Scheduler::ProcessEventsToIdle();
3359 // Click at on the left side of A1 cell
3360 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, leftCellSideX, y, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
3361 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, leftCellSideX, y, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
3362 Scheduler::ProcessEventsToIdle();
3364 // Check the A1 cell is selected in view #2
3365 CPPUNIT_ASSERT_EQUAL(SCCOL(0), ScDocShell::GetViewData()->GetCurX());
3366 CPPUNIT_ASSERT_EQUAL(SCROW(0), ScDocShell::GetViewData()->GetCurY());
3368 // Click at on the right side of A1 cell
3369 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, rightCellSideX, y, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
3370 pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, rightCellSideX, y, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
3371 Scheduler::ProcessEventsToIdle();
3373 // Check the A1 cell is selected in view #2
3374 CPPUNIT_ASSERT_EQUAL(SCCOL(0), ScDocShell::GetViewData()->GetCurX());
3375 CPPUNIT_ASSERT_EQUAL(SCROW(0), ScDocShell::GetViewData()->GetCurY());
3378 // if we extend the tiled area to the right and bottom we want two resulting area
3379 // that don't overlap. If they overlap that typically creates an unnecessary full
3380 // screen invalidation.
3381 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testExtendedAreasDontOverlap)
3383 comphelper::LibreOfficeKit::setCompatFlag(
3384 comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
3386 ScModelObj* pModelObj = createDoc("empty.ods");
3387 CPPUNIT_ASSERT(pModelObj);
3388 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
3389 CPPUNIT_ASSERT(pView);
3391 // Set an arbitrary initial size smaller than the final size
3392 pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 1000, 1000));
3394 Scheduler::ProcessEventsToIdle();
3396 // register to track View #1 invalidations
3397 ViewCallback aView1;
3399 // extend to the right and bottom
3400 pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 39750, 12780));
3402 Scheduler::ProcessEventsToIdle();
3404 // we should get two rectangles for the two new areas
3405 CPPUNIT_ASSERT_EQUAL(size_t(2), aView1.m_aInvalidations.size());
3407 // And those should not overlap, otherwise they would merge to form
3408 // a mega rectangle, which defeats the purpose of creating two rects
3409 // in the first place.
3410 CPPUNIT_ASSERT_MESSAGE("Invalidations should not overlap",
3411 !aView1.m_aInvalidations[0].Overlaps(aView1.m_aInvalidations[1]));
3413 // But they should be adjacent
3414 CPPUNIT_ASSERT_EQUAL(aView1.m_aInvalidations[0].Top() +
3415 aView1.m_aInvalidations[0].GetSize().Height(),
3416 aView1.m_aInvalidations[1].Top());
3419 // Ensure that editing a shape not in the topleft tile has its text shown inside the shape
3420 // center while editing
3421 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testEditShapeText)
3423 ScModelObj* pModelObj = createDoc("edit-shape-text.ods");
3425 // Set View to initial 100%
3426 pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 28050, 10605));
3427 pModelObj->setClientZoom(256, 256, 1920, 1920);
3429 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
3430 CPPUNIT_ASSERT(pView);
3432 const bool bShapeSelected = pView->SelectObject(u"Shape 1");
3433 CPPUNIT_ASSERT(bShapeSelected);
3435 CPPUNIT_ASSERT(ScDocShell::GetViewData()->GetScDrawView()->GetMarkedObjectList().GetMarkCount() != 0);
3437 Scheduler::ProcessEventsToIdle();
3439 // Enter editing mode, shape start with no text
3440 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::F2);
3441 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::F2);
3443 Scheduler::ProcessEventsToIdle();
3445 // Grab a snapshot of the center of the shape
3446 Bitmap aBitmapBefore = getTile(pModelObj, 4096, 3584, 15360, 7680);
3448 // reuse this to type into the active shape edit
3449 lcl_typeCharsInCell("MMMMMMM", 0, 0, pView, pModelObj, true, false);
3451 // Grab a new snapshot of the center of the shape
3452 Bitmap aBitmapAfter = getTile(pModelObj, 4096, 3584, 15360, 7680);
3454 // Without the fix, the text is not inside this tile and the before and
3455 // after are the same.
3456 CPPUNIT_ASSERT_MESSAGE("Text is not visible", aBitmapBefore != aBitmapAfter);
3459 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testNumberFormatLocaleMultiUser)
3462 // setup core language to FR as it will be the first session
3463 SvtSysLocaleOptions aLocalOptions;
3464 aLocalOptions.SetLocaleConfigString(u"fr-FR"_ustr);
3465 aLocalOptions.SetUILocaleConfigString(u"fr-FR"_ustr);
3466 aLocalOptions.Commit();
3468 loadFromFile(u"numlocale.xlsx");
3470 ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
3471 CPPUNIT_ASSERT(pModelObj);
3473 pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
3474 ScDocument* pDoc = pModelObj->GetDocument();
3476 int nViewFR = SfxLokHelper::getView();
3477 ViewCallback aView1;
3478 SfxViewShell* pViewFR = SfxViewShell::Current();
3479 pViewFR->SetLOKLocale(u"fr-FR"_ustr);
3481 // modify G12 with FR and use French keywords in the format
3482 SfxLokHelper::setView(nViewFR);
3484 sal_Int32 nCheckPos;
3485 SvNumFormatType nType;
3486 sal_uInt32 nFormat;
3487 OUString aNumberFormat(u"JJ/MM/AAAA"_ustr);
3488 SvNumberFormatter* pFormatter = pDoc->GetFormatTable();
3489 pFormatter->PutEntry(aNumberFormat, nCheckPos, nType, nFormat);
3490 ScAddress aCellPos1(/*nColP=*/6, /*nRowP=*/11, /*nTabP=*/0);
3491 pDoc->SetNumberFormat(aCellPos1, nFormat);
3493 Scheduler::ProcessEventsToIdle();
3497 // now setup DE language in core
3498 SvtSysLocaleOptions aLocalOptions;
3499 aLocalOptions.SetLocaleConfigString(u"de-DE"_ustr);
3500 aLocalOptions.SetUILocaleConfigString(u"de-DE"_ustr);
3501 aLocalOptions.Commit();
3503 // save and reopen
3504 // .uno:Save modifies the original file, make a copy first
3505 saveAndReload(u"Calc MS Excel 2007 VBA XML"_ustr);
3507 ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
3508 CPPUNIT_ASSERT(pModelObj);
3510 ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
3511 CPPUNIT_ASSERT(pView);
3513 Scheduler::ProcessEventsToIdle();
3515 uno::Sequence<beans::PropertyValue> aArgs;
3516 dispatchCommand(mxComponent, u".uno:Save"_ustr, aArgs);
3518 Scheduler::ProcessEventsToIdle();
3520 ScDocument* pDoc = pModelObj->GetDocument();
3522 // verify that format is correct (German), doesn't have any "string" inside
3523 sal_uInt32 nNumberFormat = pDoc->GetNumberFormat(/*col=*/6, /*row=*/11, /*tab=*/0);
3524 const SvNumberformat* pNumberFormat = pDoc->GetFormatTable()->GetEntry(nNumberFormat);
3525 CPPUNIT_ASSERT_EQUAL(u"TT.MM.JJ"_ustr, pNumberFormat->GetFormatstring());
3529 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testLeftOverflowEdit)
3531 comphelper::LibreOfficeKit::setCompatFlag(comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
3532 ScModelObj* pModelObj = createDoc("right-aligned-with-overflow.ods");
3533 ViewCallback aView;
3535 // Go to Cell B5000
3536 uno::Sequence<beans::PropertyValue> aPropertyValues = {
3537 comphelper::makePropertyValue(u"ToPoint"_ustr, u"$B$5000"_ustr),
3539 dispatchCommand(mxComponent, u".uno:GoToCell"_ustr, aPropertyValues);
3541 // Enter edit mode and select all text.
3542 aView.m_aTextSelectionResult.clear();
3543 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::F2);
3544 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::F2);
3545 Scheduler::ProcessEventsToIdle();
3546 // CTRL + A
3547 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_MOD1 | awt::Key::A);
3548 pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_MOD1 | awt::Key::A);
3549 Scheduler::ProcessEventsToIdle();
3551 // Without the accompanying fix this would fail with
3552 // - Expected: 20
3553 // - Actual : 1300
3554 CPPUNIT_ASSERT_EQUAL(tools::Long(20), aView.m_aTextSelectionResult.m_aRefPoint.getX());
3557 CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testFreezeRowOrColumn)
3559 createDoc("empty.ods");
3560 ViewCallback aView;
3561 SfxViewShell* pView = SfxViewShell::Current();
3563 // Freeze panes on a column and receive the proper state back
3564 aView.m_aStateChanges.clear();
3565 uno::Sequence<beans::PropertyValue> aPropertyValues = {
3566 comphelper::makePropertyValue("Index", uno::Any(static_cast<sal_Int32>(8))),
3568 comphelper::dispatchCommand(".uno:FreezePanesColumn", aPropertyValues);
3569 Scheduler::ProcessEventsToIdle();
3570 pView->GetViewFrame().GetBindings().GetTimer().Invoke();
3571 pView->GetViewFrame().GetBindings().GetTimer().Invoke();
3572 auto it = aView.m_aStateChanges.find(".uno:FreezePanesColumn");
3573 CPPUNIT_ASSERT(it != aView.m_aStateChanges.end());
3574 std::string values = it->second.get<std::string>("state");
3575 std::string index = values.substr(0, values.find(' '));
3576 // Without the accompanying fix in place, this test would have failed with:
3577 // - Expected: 8
3578 // - Actual : 1
3579 CPPUNIT_ASSERT_EQUAL(std::string("8"), index);
3581 // Freeze panes on a row and receive the proper state back
3582 aView.m_aStateChanges.clear();
3583 comphelper::dispatchCommand(".uno:FreezePanesRow", aPropertyValues);
3584 Scheduler::ProcessEventsToIdle();
3585 pView->GetViewFrame().GetBindings().GetTimer().Invoke();
3586 pView->GetViewFrame().GetBindings().GetTimer().Invoke();
3587 it = aView.m_aStateChanges.find(".uno:FreezePanesRow");
3588 CPPUNIT_ASSERT(it != aView.m_aStateChanges.end());
3589 values = it->second.get<std::string>("state");
3590 index = values.substr(0, values.find(' '));
3591 // Without the accompanying fix in place, this test would have failed with:
3592 // - Expected: 8
3593 // - Actual : 1
3594 CPPUNIT_ASSERT_EQUAL(std::string("8"), index);
3597 CPPUNIT_PLUGIN_IMPLEMENT();
3599 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */