/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// Select the 5th row with no modifier
uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
{ "Row", uno::Any(sal_Int32(5 - 1)) },
{ "Modifier", uno::Any(sal_uInt16(0)) }
}));
dispatchCommand(mxComponent, u".uno:SelectRow"_ustr, aArgs);
// Check if it is selected
OString aResult = apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr);
OString aExpected("1\t2\t3\t4\t5\t6\t7\t8\t9\t10\t11\t12\t13\t14\t15\t16\t17\t18\t19\t20\t21\n"_ostr);
CPPUNIT_ASSERT_EQUAL(aExpected, aResult);
// Check if all the rows from 5th to 10th get selected
aResult = apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr);
aExpected = "1\t2\t3\t4\t5\t6\t7\t8\t9\t10\t11\t12\t13\t14\t15\t16\t17\t18\t19\t20\t21\n2\t3\t4\t5\t6\t7\t8\t9\t10\t11\t12\t13\t14\t15\t16\t17\t18\t19\t20\t21\t22\n3\t4\t5\t6\t7\t8\t9\t10\t11\t12\t13\t14\t15\t16\t17\t18\t19\t20\t21\t22\t23\n4\t5\t6\t7\t8\t9\t10\t11\t12\t13\t14\t15\t16\t17\t18\t19\t20\t21\t22\t23\t24\n5\t6\t7\t8\t9\t10\t11\t12\t13\t14\t15\t16\t17\t18\t19\t20\t21\t22\t23\t24\t25\n6\t7\t8\t9\t10\t11\t12\t13\t14\t15\t16\t17\t18\t19\t20\t21\t22\t23\t24\t25\t26\n"_ostr;
CPPUNIT_ASSERT_EQUAL(aExpected, aResult);
// When we copy this, we don't get anything useful, but we must not crash // (used to happen)
aResult = apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr);
CPPUNIT_ASSERT_EQUAL("9"_ostr, aResult);
// TODO check that we really selected what we wanted here
// When we copy this, we don't get anything useful, but we must not crash // (used to happen)
aResult = apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr);
CPPUNIT_ASSERT_EQUAL("1"_ostr, aResult);
// TODO check that we really selected what we wanted here
// Test for deselection of already selected rows // First Deselect Row 13 because copy doesn't work for multiple selections
aArgs = comphelper::InitPropertySequence({ { "Row", uno::Any(static_cast<sal_Int32>(13 - 1)) },
{ "Modifier", uno::Any(KEY_MOD1) } });
dispatchCommand(mxComponent, u".uno:SelectRow"_ustr, aArgs);
// should be an empty string
CPPUNIT_ASSERT_EQUAL(OString(), apitest::helper::transferable::getTextSelection(pModelObj->getSelection(), "text/plain;charset=utf-8"_ostr));
}
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testViewCursors)
{
ScModelObj* pModelObj = createDoc("select-row-cols.ods");
ScTestViewCallback aView1;
SfxLokHelper::createView();
pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
ScTestViewCallback aView2(/*bDeleteListenerOnDestruct*/false); // This was false, the new view did not get the view (cell) cursor of the old view.
CPPUNIT_ASSERT(aView2.m_bViewCursorInvalidated);
CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DOWN);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DOWN);
Scheduler::ProcessEventsToIdle();
SfxLokHelper::destroyView(SfxLokHelper::getView());
CPPUNIT_ASSERT(aView1.m_bViewCursorInvalidated);
}
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testTextViewSelection)
{ // Create two views, and leave the second one current.
ScModelObj* pModelObj = createDoc("select-row-cols.ods");
ScTestViewCallback aView1;
SfxLokHelper::createView();
pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
ScTestViewCallback aView2;
// Create a selection on two cells in the second view, that's a text selection in LOK terms.
aView1.m_bTextViewSelectionInvalidated = false;
dispatchCommand(mxComponent, u".uno:GoRightSel"_ustr, {}); // Make sure the first view got its notification.
CPPUNIT_ASSERT(aView1.m_bTextViewSelectionInvalidated);
}
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDocumentSizeChanged)
{ // Load a document that doesn't have much content.
createDoc("small.ods");
setupLibreOfficeKitViewCallback(SfxViewShell::Current());
// Go to the A30 cell -- that will extend the document size.
uno::Sequence<beans::PropertyValue> aPropertyValues =
{
comphelper::makePropertyValue(u"ToPoint"_ustr, u"$A$30"_ustr),
};
dispatchCommand(mxComponent, u".uno:GoToCell"_ustr, aPropertyValues); // Assert that the size in the payload is not 0.
CPPUNIT_ASSERT(m_aDocumentSize.getWidth() > 0);
CPPUNIT_ASSERT(m_aDocumentSize.getHeight() > 0);
}
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testViewLock)
{ // Load a document that has a shape and create two views.
ScModelObj* pModelObj = createDoc("shape.ods");
ScTestViewCallback aView1;
SfxLokHelper::createView();
pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
ScTestViewCallback aView2;
// Begin text edit in the second view and assert that the first gets a lock // notification. const ScViewData* pViewData = ScDocShell::GetViewData();
CPPUNIT_ASSERT(pViewData);
ScTabViewShell* pViewShell = pViewData->GetViewShell();
CPPUNIT_ASSERT(pViewShell);
SdrModel* pDrawModel = pViewData->GetDocument().GetDrawLayer();
SdrPage* pDrawPage = pDrawModel->GetPage(0);
SdrObject* pObject = pDrawPage->GetObj(0);
SdrView* pView = pViewShell->GetScDrawView();
aView1.m_bViewLock = false;
pView->SdrBeginTextEdit(pObject);
CPPUNIT_ASSERT(aView1.m_bViewLock);
// End text edit in the second view, and assert that the lock is removed in // the first view.
pView->SdrEndTextEdit();
CPPUNIT_ASSERT(!aView1.m_bViewLock);
}
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testUndoShells)
{
ScModelObj* pModelObj = createDoc("small.ods"); // Clear the currently selected cell.
dispatchCommand(mxComponent, u".uno:ClearContents"_ustr, {});
auto pDocShell = dynamic_cast<ScDocShell*>(pModelObj->GetEmbeddedObject());
CPPUNIT_ASSERT(pDocShell);
ScDocument& rDoc = pDocShell->GetDocument();
ScUndoManager* pUndoManager = rDoc.GetUndoManager();
CPPUNIT_ASSERT(pUndoManager);
CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), pUndoManager->GetUndoActionCount());
sal_Int32 nView1 = SfxLokHelper::getView(); // This was -1: ScSimpleUndo did not remember what view shell created it.
CPPUNIT_ASSERT_EQUAL(ViewShellId(nView1), pUndoManager->GetUndoAction()->GetViewShellId());
}
// text edit a cell in view #1
SfxLokHelper::setView(nView1);
aView2.m_bInvalidateTiles = false;
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
Scheduler::ProcessEventsToIdle();
CPPUNIT_ASSERT(lcl_hasEditView(*pViewData));
CPPUNIT_ASSERT(aView2.m_bInvalidateTiles);
// text edit a cell in view #1 until // we can be sure we are out of the initial tile for (int i = 0; i < 40; ++i)
{
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
}
Scheduler::ProcessEventsToIdle();
// text edit a cell in view #1 inside the new tile and // check that view #2 receive a tile invalidate message
aView2.m_bInvalidateTiles = false;
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
Scheduler::ProcessEventsToIdle();
CPPUNIT_ASSERT(aView2.m_bInvalidateTiles);
// text edit a cell in view #1
SfxLokHelper::setView(nView1);
aView3.m_bInvalidateTiles = false;
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'y', 0);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'y', 0);
Scheduler::ProcessEventsToIdle();
CPPUNIT_ASSERT(aView3.m_bInvalidateTiles);
}
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testCreateViewGraphicSelection)
{ // Load a document that has a shape and create two views.
ScModelObj* pModelObj = createDoc("shape.ods");
ScTestViewCallback aView1;
// Mark the graphic in the first view. const ScViewData* pViewData = ScDocShell::GetViewData();
CPPUNIT_ASSERT(pViewData);
ScTabViewShell* pViewShell = pViewData->GetViewShell();
CPPUNIT_ASSERT(pViewShell);
SdrModel* pDrawModel = pViewData->GetDocument().GetDrawLayer();
SdrPage* pDrawPage = pDrawModel->GetPage(0);
SdrObject* pObject = pDrawPage->GetObj(0);
SdrView* pView = pViewShell->GetScDrawView();
aView1.m_bGraphicSelection = false;
aView1.m_bGraphicViewSelection = false;
pView->MarkObj(pObject, pView->GetSdrPageView());
CPPUNIT_ASSERT(aView1.m_bGraphicSelection);
// Create a second view. int nView1 = SfxLokHelper::getView();
SfxLokHelper::createView();
pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
ScTestViewCallback aView2;
CPPUNIT_ASSERT(aView2.m_bGraphicViewSelection);
CPPUNIT_ASSERT(aView1.m_bGraphicViewSelection);
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testGraphicInvalidate)
{ // Load a document that has a shape and create two views.
ScModelObj* pModelObj = createDoc("shape.ods");
ScTestViewCallback aView;
// move on the right for (int i = 0; i < 200; ++i)
{
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_RIGHT);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_RIGHT);
}
Scheduler::ProcessEventsToIdle();
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testCommentCallback)
{ // Comments callback are emitted only if tiled annotations are off
comphelper::LibreOfficeKit::setTiledAnnotations(false);
// FIXME: Hack because previous tests do not destroy ScDocument(with annotations) on exit (?).
ScPostIt::mnLastPostItId = 1;
// Edit a comment // Select some random cell, we should be able to edit the cell note without // selecting the cell if (pTabViewShell)
pTabViewShell->SetCursor(3, 100);
aArgs = comphelper::InitPropertySequence(
{
{"Id", uno::Any(OUString::createFromAscii(aCommentId))},
{"Text", uno::Any(u"Edited comment"_ustr)},
{"Author", uno::Any(u"LOK User2"_ustr)},
});
dispatchCommand(mxComponent, u".uno:EditAnnotation"_ustr, aArgs);
// We received a LOK_CALLBACK_COMMENT callback with comment 'Modify' action
CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
CPPUNIT_ASSERT_EQUAL(std::string("Modify"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
CPPUNIT_ASSERT_EQUAL(aCommentId, aView1.m_aCommentCallbackResult.get<std::string>("id"));
CPPUNIT_ASSERT_EQUAL(aCommentId, aView2.m_aCommentCallbackResult.get<std::string>("id"));
CPPUNIT_ASSERT_EQUAL(std::string("LOK User2"), aView1.m_aCommentCallbackResult.get<std::string>("author"));
CPPUNIT_ASSERT_EQUAL(std::string("LOK User2"), aView2.m_aCommentCallbackResult.get<std::string>("author"));
CPPUNIT_ASSERT_EQUAL(std::string("Edited comment"), aView1.m_aCommentCallbackResult.get<std::string>("text"));
CPPUNIT_ASSERT_EQUAL(std::string("Edited comment"), aView2.m_aCommentCallbackResult.get<std::string>("text"));
CPPUNIT_ASSERT_EQUAL(std::string("3 3 3 3"), aView1.m_aCommentCallbackResult.get<std::string>("cellRange"));
CPPUNIT_ASSERT_EQUAL(std::string("3 3 3 3"), aView2.m_aCommentCallbackResult.get<std::string>("cellRange"));
// Delete the comment if (pTabViewShell)
pTabViewShell->SetCursor(4, 43);
aArgs = comphelper::InitPropertySequence(
{
{"Id", uno::Any(OUString::createFromAscii(aCommentId))}
});
dispatchCommand(mxComponent, u".uno:DeleteNote"_ustr, aArgs);
// We received a LOK_CALLBACK_COMMENT callback with comment 'Remove' action
CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView1.m_aCommentCallbackResult.get<std::string>("action"));
CPPUNIT_ASSERT_EQUAL(std::string("Remove"), aView2.m_aCommentCallbackResult.get<std::string>("action"));
CPPUNIT_ASSERT_EQUAL(aCommentId, aView1.m_aCommentCallbackResult.get<std::string>("id"));
CPPUNIT_ASSERT_EQUAL(aCommentId, aView2.m_aCommentCallbackResult.get<std::string>("id"));
}
comphelper::LibreOfficeKit::setTiledAnnotations(true);
}
// text edit a cell in view #1
SfxLokHelper::setView(nView1);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
Scheduler::ProcessEventsToIdle();
// check that undo action count in not 0
CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
// try to execute undo in view #2
SfxLokHelper::setView(nView2);
dispatchCommand(mxComponent, u".uno:Undo"_ustr, {}); // check that undo has not been executed on view #2
CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
// try to execute undo in view #1
SfxLokHelper::setView(nView1);
dispatchCommand(mxComponent, u".uno:Undo"_ustr, {}); // check that undo has been executed on view #1
CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetUndoActionCount());
// check that redo action count in not 0
CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetRedoActionCount());
// try to execute redo in view #2
SfxLokHelper::setView(nView2);
dispatchCommand(mxComponent, u".uno:Redo"_ustr, {}); // check that redo has not been executed on view #2
CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetRedoActionCount());
// try to execute redo in view #1
SfxLokHelper::setView(nView1);
dispatchCommand(mxComponent, u".uno:Redo"_ustr, {}); // check that redo has been executed on view #1
CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetRedoActionCount());
}
// text edit a cell in view #1
SfxLokHelper::setView(nView1);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
Scheduler::ProcessEventsToIdle();
// check that undo action count in not 0
CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
// try to execute undo in view #2
SfxLokHelper::setView(nView2);
dispatchCommand(mxComponent, u".uno:Undo"_ustr, {}); // check that undo has not been executed on view #2
CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
// try to execute undo in view #2 in repair mode
SfxLokHelper::setView(nView2);
uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
{
{"Repair", uno::Any(true)}
}));
dispatchCommand(mxComponent, u".uno:Undo"_ustr, aPropertyValues); // check that undo has been executed on view #2 in repair mode
CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetUndoActionCount());
}
// we need to paint a tile in the view for triggering the tile invalidation solution int nCanvasWidth = 256; int nCanvasHeight = 256;
std::vector<unsignedchar> aBuffer(nCanvasWidth * nCanvasHeight * 4);
ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA);
pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(Size(nCanvasWidth, nCanvasHeight), Fraction(1.0), Point(), aBuffer.data());
pModelObj->paintTile(*pDevice, nCanvasWidth, nCanvasHeight, /*nTilePosX=*/0, /*nTilePosY=*/0, /*nTileWidth=*/3840, /*nTileHeight=*/3840);
Scheduler::ProcessEventsToIdle();
// insert an image in view and see if both views are invalidated
aView.m_bInvalidateTiles = false;
uno::Sequence<beans::PropertyValue> aArgs( comphelper::InitPropertySequence({
{ "FileName", uno::Any(createFileURL(u"smile.png")) }
}));
dispatchCommand(mxComponent, u".uno:InsertGraphic"_ustr, aArgs);
CPPUNIT_ASSERT(aView.m_bInvalidateTiles);
// undo image insertion in view and see if both views are invalidated
aView.m_bInvalidateTiles = false;
uno::Sequence<beans::PropertyValue> aArgs2;
dispatchCommand(mxComponent, u".uno:Undo"_ustr, aArgs2);
CPPUNIT_ASSERT(aView.m_bInvalidateTiles);
}
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDocumentSizeWithTwoViews)
{ // Open a document that has the cursor far away & paint a tile
ScModelObj* pModelObj = createDoc("cursor-away.ods");
// Set the visible area, and press page down
pModelObj->setClientVisibleArea(tools::Rectangle(750, 1861, 20583, 6997));
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEDOWN);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEDOWN);
Scheduler::ProcessEventsToIdle();
pView->SetCursor(0, 0); // sequence of chinese IME compositions when 'nihao' is typed in an IME const std::vector<OString> aUtf8Inputs{ "年"_ostr, "你"_ostr, "你好"_ostr, "你哈"_ostr, "你好"_ostr, "你好"_ostr };
std::vector<OUString> aInputs;
std::transform(aUtf8Inputs.begin(), aUtf8Inputs.end(),
std::back_inserter(aInputs), [](OString aInput) { return OUString::fromUtf8(aInput);
}); for (constauto& aInput: aInputs)
{
pDocWindow->PostExtTextInputEvent(VclEventId::ExtTextInput, aInput);
}
pDocWindow->PostExtTextInputEvent(VclEventId::EndExtTextInput, u""_ustr);
// commit the string to the cell
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
Scheduler::ProcessEventsToIdle();
// set the same state as now and we don't expect any change (no-toggle)
params =
{
comphelper::makePropertyValue(u"Enable"_ustr, uno::Any(!bSet)),
};
dispatchCommand(mxComponent, u".uno:SpellOnline"_ustr, params);
CPPUNIT_ASSERT_EQUAL(!bSet, pView->IsAutoSpell());
}
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testInvalidationLoop)
{ // Load the document with a form control.
createDoc("invalidation-loop.fods"); // Without the accompanying fix in place, this test would have never returned due to an infinite // invalidation loop between ScGridWindow::Paint() and vcl::Window::ImplPosSizeWindow().
Scheduler::ProcessEventsToIdle();
}
int nView1 = SfxLokHelper::getView();
ScTestViewCallback aView1;
CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
SfxLokHelper::setView(nView1);
aView1.ClearAllInvalids();
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEDOWN | KEY_MOD1);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEDOWN | KEY_MOD1);
Scheduler::ProcessEventsToIdle(); // switching sheets should trigger no unnecessary invalidations
CPPUNIT_ASSERT(!aView1.m_bInvalidateTiles);
// Get the known columns/rows of this sheet 2 now we have switched to it so // it knows what range to broadcast invalidations for if it knows cells need // to be redrawn.
tools::JsonWriter aJsonWriter1;
pModelObj->getRowColumnHeaders(tools::Rectangle(0, 15, 19650, 5400), aJsonWriter1);
aJsonWriter1.finishAndGetAsOString();
Scheduler::ProcessEventsToIdle();
aView1.ClearAllInvalids();
// switching back should also trigger no unnecessary invalidations
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEUP | KEY_MOD1);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEUP | KEY_MOD1);
Scheduler::ProcessEventsToIdle();
CPPUNIT_ASSERT(!aView1.m_bInvalidateTiles);
// The 2nd sheet has formulas that depend on B1 in the first sheet. So if // we change B1 there should be an invalidation in the second sheet for the // range that depends on it. Because this is a single user document with no // active view on the 2nd sheet this will happen on switching back to sheet 2
typeCharsInCell("101", 1, 0, pView, pModelObj); // Type '101' in B1
aView1.ClearAllInvalids();
// Paint it to make it valid again
getTile(pModelObj, 0, 0, 3840, 3840);
// switching back to sheet 1 should trigger no unnecessary invalidations
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEUP | KEY_MOD1);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEUP | KEY_MOD1);
Scheduler::ProcessEventsToIdle();
CPPUNIT_ASSERT(!aView1.m_bInvalidateTiles);
// switching to sheet 2 should trigger no unnecessary invalidations this time
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::PAGEDOWN | KEY_MOD1);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::PAGEDOWN | KEY_MOD1);
Scheduler::ProcessEventsToIdle();
CPPUNIT_ASSERT(!aView1.m_bInvalidateTiles);
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testGetRowColumnHeadersInvalidation)
{ // NOTE NOTE NOTE // If you run this test in isolation using CPPUNIT_TEST_NAME=, it will fail because the invalidations // will be different.
int nView1 = SfxLokHelper::getView();
ScTestViewCallback aView1;
CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
SfxLokHelper::setView(nView1);
aView1.m_bInvalidateTiles = false;
aView1.m_aInvalidations.clear();
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DOWN | KEY_MOD1);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DOWN | KEY_MOD1);
Scheduler::ProcessEventsToIdle();
CPPUNIT_ASSERT(aView1.m_bInvalidateTiles);
CPPUNIT_ASSERT_EQUAL(size_t(1), aView1.m_aInvalidations.size()); // 261375 because we limit how far we jump into empty space in online, 267386880 if we don't limit
CPPUNIT_ASSERT_EQUAL(tools::Rectangle(0, 13005, 26775, 261375), aView1.m_aInvalidations[0]);
}
// We need to ensure that views are not perterbed by rendering (!?) hmm ...
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testRowColumnHeaders)
{
ScModelObj* pModelObj = createDoc("empty.ods");
ScViewData* pViewData = ScDocShell::GetViewData();
CPPUNIT_ASSERT(pViewData);
// view #1
ScTestViewCallback aView1; int nView1 = SfxLokHelper::getView();
CPPUNIT_ASSERT(!lcl_hasEditView(*pViewData));
namespace
{ // Helper structs for setup and testing of ScModelObj::getSheetGeometryData() struct SpanEntry
{
size_t nVal;
SCCOLROW nEnd;
};
struct SheetDimData
{ typedef std::vector<SpanEntry> SpanList;
SpanList aSizes;
SpanList aHidden;
SpanList aFiltered; // TODO: Add group info too to test.
for (constauto& rEntry : aPairList)
{ // There is no ScDocument interface to set ScTable::mpFilteredCols // It seems ScTable::mpFilteredCols is not really used !? if (bCol && rEntry.aKey == "filtered") continue;
// Try with the default empty document once (nIdx = 0) and then with sheet geometry settings (nIdx = 1) for (size_t nIdx = 0; nIdx < 2; ++nIdx)
{ if (nIdx)
aSGData.setDataToDoc(pDoc);
// check if the row header has been invalidated and if the involved row is of the expected height
CPPUNIT_ASSERT_EQUAL("row"_ostr, aView1.m_sInvalidateHeader);
sal_uInt16 nRow2Height = rDoc.GetRowHeight(static_cast<SCROW>(0), static_cast<SCTAB>(0), false);
CPPUNIT_ASSERT_EQUAL(nRow1Height, nRow2Height);
SfxViewShell::Current()->setLibreOfficeKitViewCallback(nullptr);
}
// Go to A2 and paste.
pView->SetCursor(0, 1);
Scheduler::ProcessEventsToIdle();
aView.m_sInvalidateSheetGeometry = ""_ostr;
pView->GetViewFrame().GetBindings().Execute(SID_PASTE);
Scheduler::ProcessEventsToIdle();
// create new source text in A2
OUString sCopyContent2(u"Very long text to copy 2"_ustr);
pDoc->SetString(0, 1, 0, sCopyContent2);
Scheduler::ProcessEventsToIdle();
// cut from A2
pView->GetViewFrame().GetBindings().Execute(SID_CUT);
Scheduler::ProcessEventsToIdle();
// Go to A3 and paste.
pView->SetCursor(0, 2);
Scheduler::ProcessEventsToIdle();
aView.m_sInvalidateSheetGeometry = ""_ostr;
pView->GetViewFrame().GetBindings().Execute(SID_PASTE);
Scheduler::ProcessEventsToIdle();
// SG invalidations for all
CPPUNIT_ASSERT_EQUAL(sCopyContent2, pDoc->GetString(0, 1, 0));
CPPUNIT_ASSERT_EQUAL("all"_ostr, aView.m_sInvalidateSheetGeometry);
ScAddress aA8(0, 7, 0);
typeCharsInCell("S", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "S" in A8 // Should show the partial completion "i".
CPPUNIT_ASSERT_EQUAL_MESSAGE("1: A8 should have partial completion Si", u"Si"_ustr, pDoc->GetString(aA8));
typeCharsInCell("Si", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "Si" in A8 // Should not show any suggestions.
CPPUNIT_ASSERT_EQUAL_MESSAGE("2: A8 should not show suggestions", u"Si"_ustr, pDoc->GetString(aA8));
typeCharsInCell("Sim", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "Sim" in A8 // Should autocomplete to "Simple" which is the only match.
CPPUNIT_ASSERT_EQUAL_MESSAGE("3: A8 should autocomplete", u"Simple"_ustr, pDoc->GetString(aA8));
typeCharsInCell("Sin", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "Sin" in A8 // Should autocomplete to "Sing" which is the only match.
CPPUNIT_ASSERT_EQUAL_MESSAGE("4: A8 should autocomplete", u"Sing"_ustr, pDoc->GetString(aA8));
typeCharsInCell("C", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "C" in A8 // Should show the partial completion "as".
CPPUNIT_ASSERT_EQUAL_MESSAGE("5: A8 should have partial completion Cas", u"Cas"_ustr, pDoc->GetString(aA8));
typeCharsInCell("Cast", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "Cast" in A8 // Should autocomplete to "Castle" which is the only match.
CPPUNIT_ASSERT_EQUAL_MESSAGE("6: A8 should autocomplete", u"Castle"_ustr, pDoc->GetString(aA8));
typeCharsInCell("T", aA8.Col(), aA8.Row(), pView, pModelObj); // Type "T" in A8 // Should autocomplete to "Time" which is the only match.
CPPUNIT_ASSERT_EQUAL_MESSAGE("7: A8 should autocomplete", u"Time"_ustr, pDoc->GetString(aA8));
}
aView.m_aInvalidateCursorResult.clear(); // Enter edit mode in the same cell.
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::F2);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::F2);
Scheduler::ProcessEventsToIdle();
CPPUNIT_ASSERT(!aView.m_aInvalidateCursorResult.empty());
CPPUNIT_ASSERT_MESSAGE("Edit cursor must be in cell bounds!",
aCellBounds.Contains(aView.m_aInvalidateCursorResult.getBounds()));
aView.m_aTextSelectionResult.clear(); // Enter edit mode in the same cell and select all text.
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::F2);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::F2);
Scheduler::ProcessEventsToIdle();
CPPUNIT_ASSERT(!aView.m_aTextSelectionResult.empty());
CPPUNIT_ASSERT_MESSAGE("Text selections must be in cell bounds!",
!aCellBounds.Intersection(aView.m_aTextSelectionResult.getBounds(0)).IsEmpty());
SfxLokHelper::setView(nView1); // Delete a range.
pView1->SetCursor(1, 1);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DOWN | KEY_SHIFT);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DOWN | KEY_SHIFT);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::DELETE);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::DELETE); // It will crash at this point without the fix.
Scheduler::ProcessEventsToIdle();
}
// check if we have textbox selected
CPPUNIT_ASSERT(!aView1.m_ShapeSelection.isEmpty());
CPPUNIT_ASSERT(aView1.m_ShapeSelection != "EMPTY");
Scheduler::ProcessEventsToIdle();
}
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testCommentCellCopyPaste)
{ // Comments callback are emitted only if tiled annotations are off
comphelper::LibreOfficeKit::setTiledAnnotations(false);
// FIXME: Hack because previous tests do not destroy ScDocument(with annotations) on exit (?).
ScPostIt::mnLastPostItId = 1;
// We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action
CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView.m_aCommentCallbackResult.get<std::string>("action")); // Without the fix the id will be "1".
CPPUNIT_ASSERT_EQUAL(std::string("2"), aView.m_aCommentCallbackResult.get<std::string>("id"));
CPPUNIT_ASSERT_EQUAL(std::string("0"), aView.m_aCommentCallbackResult.get<std::string>("tab"));
CPPUNIT_ASSERT_EQUAL(std::string("LOK Client"), aView.m_aCommentCallbackResult.get<std::string>("author"));
CPPUNIT_ASSERT_EQUAL(std::string("LOK Comment Cell B2"), aView.m_aCommentCallbackResult.get<std::string>("text"));
}
// We received a LOK_CALLBACK_COMMENT callback with comment 'Add' action
CPPUNIT_ASSERT_EQUAL(std::string("Add"), aView.m_aCommentCallbackResult.get<std::string>("action")); // Without the fix the id will be "1".
CPPUNIT_ASSERT_EQUAL(std::string("3"), aView.m_aCommentCallbackResult.get<std::string>("id"));
CPPUNIT_ASSERT_EQUAL(std::string("0"), aView.m_aCommentCallbackResult.get<std::string>("tab"));
CPPUNIT_ASSERT_EQUAL(std::string("LOK Client"), aView.m_aCommentCallbackResult.get<std::string>("author"));
CPPUNIT_ASSERT_EQUAL(std::string("LOK Comment Cell B2"), aView.m_aCommentCallbackResult.get<std::string>("text"));
}
}
comphelper::LibreOfficeKit::setTiledAnnotations(true);
}
// .uno:Save modifies the original file, make a copy first
saveAndReload(u"Calc Office Open XML"_ustr);
ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
CPPUNIT_ASSERT(pModelObj);
pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>()); const ScDocument* pDoc = pModelObj->GetDocument();
ScTestViewCallback aView; int nView = SfxLokHelper::getView();
// Type partial date "7/8" of "7/8/2013" that // the validation cell at A8 can accept
typeCharsInCell("7/8", 0, 7, pTabViewShell, pModelObj, false/* bInEdit */, false /* bCommit */); // Type "7/8" in A8
CPPUNIT_ASSERT_MESSAGE("Should not be marked modified after save", !pDocSh->IsModified());
// Complete the date in A8 by appending "/2013" and commit.
typeCharsInCell("/2013", 0, 7, pTabViewShell, pModelObj, true/* bInEdit */, true /* bCommit */);
// This would hang if the date entered "7/8/2013" is not acceptable.
Scheduler::ProcessEventsToIdle();
// Ensure that the correct date is recorded in the document.
CPPUNIT_ASSERT_EQUAL(double(41463), pDoc->GetValue(ScAddress(0, 7, 0)));
}
// text edit a cell in view #1
SfxLokHelper::setView(nView1);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
Scheduler::ProcessEventsToIdle();
// check that undo action count is not 0
CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
// text edit a different cell in view #2
SfxLokHelper::setView(nView2);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
Scheduler::ProcessEventsToIdle();
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_DOWN);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_DOWN);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::RETURN);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::RETURN);
Scheduler::ProcessEventsToIdle();
// check that undo action count is not 1
CPPUNIT_ASSERT_EQUAL(std::size_t(2), pUndoManager->GetUndoActionCount());
// try to execute undo in view #1
SfxLokHelper::setView(nView1);
dispatchCommand(mxComponent, u".uno:Undo"_ustr, {}); // check that undo has been executed on view #1
CPPUNIT_ASSERT_EQUAL(std::size_t(1), pUndoManager->GetUndoActionCount());
// try to execute undo in view #2
SfxLokHelper::setView(nView2);
dispatchCommand(mxComponent, u".uno:Undo"_ustr, {}); // check that undo has been executed on view #2
CPPUNIT_ASSERT_EQUAL(std::size_t(0), pUndoManager->GetUndoActionCount());
}
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testGetViewRenderState)
{ // Add a pair of schemes, last added is the default
svtools::EditableColorConfig aColorConfig;
aColorConfig.AddScheme(u"Dark"_ustr);
aColorConfig.AddScheme(u"Light"_ustr);
ScModelObj* pModelObj = createDoc("empty.ods"); int nFirstViewId = SfxLokHelper::getView();
ScTestViewCallback aView1;
CPPUNIT_ASSERT_EQUAL("S;Default"_ostr, pModelObj->getViewRenderState()); // Create a second view
SfxLokHelper::createView();
ScTestViewCallback aView2;
CPPUNIT_ASSERT_EQUAL("S;Default"_ostr, pModelObj->getViewRenderState()); // Set second view to dark scheme
{
uno::Sequence<beans::PropertyValue> aPropertyValues = comphelper::InitPropertySequence(
{
{ "NewTheme", uno::Any(u"Dark"_ustr) },
}
);
dispatchCommand(mxComponent, u".uno:ChangeTheme"_ustr, aPropertyValues);
}
CPPUNIT_ASSERT_EQUAL("S;Dark"_ostr, pModelObj->getViewRenderState());
// Switch back to first view and make sure it's the same
SfxLokHelper::setView(nFirstViewId);
CPPUNIT_ASSERT_EQUAL("S;Default"_ostr, pModelObj->getViewRenderState());
}
/* * testInvalidateOnTextEditWithDifferentZoomLevels * steps: * set view 1 zoom to the passed zoom level * in view 1 type a char at the passed cell address * store invalidation rectangle * exit from in place editing (press esc) * create view 2 (keep 100% zoom) * go to the same cell address used in view 1 * type a char into the cell * get invalidation rectangle for view 1 * check if the invalidation rectangle is equal to the one stored previously
*/ class testInvalidateOnTextEditWithDifferentZoomLevels : public ScTiledRenderingTest
{ public: void TestBody(const ColRowZoom& rData);
CPPUNIT_TEST_SUITE(testInvalidateOnTextEditWithDifferentZoomLevels);
CPPUNIT_TEST_PARAMETERIZED(TestBody,
std::initializer_list<ColRowZoom>
{ // zoom level 120%
{0, 999, 1}, {99, 0, 1}, // zoom level 40%
{0, 999, -5}, {99, 0, -5}
});
CPPUNIT_TEST_SUITE_END();
};
CPPUNIT_TEST_SUITE_REGISTRATION(testInvalidateOnTextEditWithDifferentZoomLevels);
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testOpenURL)
{ // Given a document that has 2 views:
createDoc("empty.ods"); int nView1 = SfxLokHelper::getView();
ScTestViewCallback aView1;
SfxLokHelper::createView();
ScTestViewCallback aView2;
// When clicking on a link in view 2, but switching to view 1 before processing async events:
ScGlobal::OpenURL(/*aUrl=*/u"http://www.example.com/"_ustr, /*aTarget=*/u""_ustr, /*bIgnoreSettings=*/true);
SfxLokHelper::setView(nView1);
Scheduler::ProcessEventsToIdle();
// Then make sure view 2 gets the callback, not view 1: // Without the accompanying fix in place, this test would have failed, view 1 got the hyperlink // callback.
CPPUNIT_ASSERT(aView1.m_aHyperlinkClicked.isEmpty());
CPPUNIT_ASSERT(!aView2.m_aHyperlinkClicked.isEmpty());
}
// move way over to the right where BP:20 exists, enough so that rows A and B // would scroll off the page and not be visible, if they were not frozen
pModelObj->setClientVisibleArea(tools::Rectangle(73050, 0, 94019, 7034));
Scheduler::ProcessEventsToIdle();
typeCharsInCell("X", aBP20.Col(), aBP20.Row(), pView, pModelObj); // Type 'X' in A1
CPPUNIT_ASSERT(aView.m_bInvalidateTiles);
// missing before fix
tools::Rectangle aTopLeftPane(0, 500, 3817, 742); bool bFoundTopLeftPane =
std::find(aView.m_aInvalidations.begin(), aView.m_aInvalidations.end(), aTopLeftPane) != aView.m_aInvalidations.end();
CPPUNIT_ASSERT_MESSAGE("The cell visible in the top left pane should be redrawn", bFoundTopLeftPane);
// missing before fix
tools::Rectangle aBottomLeftPane(0, 500, 3817, 3242); bool bFoundBottomLeftPane =
std::find(aView.m_aInvalidations.begin(), aView.m_aInvalidations.end(), aBottomLeftPane) != aView.m_aInvalidations.end();
CPPUNIT_ASSERT_MESSAGE("The cell visible in the bottom left pane should be redrawn", bFoundBottomLeftPane);
}
// .uno:Save modifies the original file, make a copy first
saveAndReload(u"calc8"_ustr);
ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
CPPUNIT_ASSERT(pModelObj);
pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
// That we don't end up with two views on different zooms that invalidate different // rectangles, each should invalidate the same rectangle
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testCellInvalidationDocWithExistingZoom)
{
ScAddress aB7(1, 6, 0);
ScopedVclPtrInstance<VirtualDevice> xDevice(DeviceFormat::WITHOUT_ALPHA);
// The problem tested for here is with two views at different zooms then a // single cell invalidation resulted in the same rectangle reported as two // different invalidations rectangles of different scales. While we should // get the same invalidation rectangle reported. // // (B7 is a good choice to use in the real world to see the effect, to both // avoid getting the two rects combined into one bigger one, or to have the // two separated by so much space the 2nd is off-screen and not seen
CPPUNIT_ASSERT_EQUAL(size_t(1), aView1.m_aInvalidations.size());
CPPUNIT_ASSERT_EQUAL(size_t(1), aView2.m_aInvalidations.size());
// That they don't exactly match doesn't matter, we're not checking rounding issues, // what matters is that they are not utterly different rectangles // Without fix result is originally: // Comparing invalidation rectangles Width expected 6214742 actual 26716502 Tolerance 50
CPPUNIT_ASSERT_RECTANGLE_EQUAL_WITH_TOLERANCE(aView2.m_aInvalidations[0],
aView1.m_aInvalidations[0],
50);
}
// Set View #1 to initial 150%
pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 17933, 4853)); // Before the fix, this zoom would leave the EditEngine reference device // at the zoom level stored in the document, so normal rendering and // editing rendering happened with different MapModes
pModelObj->setClientZoom(256, 256, 1333, 1333);
// Get that active EditView
EditView* pEditView1 = pViewData1->GetEditView(SC_SPLIT_BOTTOMLEFT);
CPPUNIT_ASSERT(pEditView1);
EditEngine& rEditEngine1 = pEditView1->getEditEngine(); // These must match, if they don't then text will have a different width in edit and view modes
CPPUNIT_ASSERT_EQUAL_MESSAGE("EditEngine Ref Dev Zoom and ViewData Zoom should match",
pViewData1->GetZoomX(), rEditEngine1.GetRefMapMode().GetScaleX());
CPPUNIT_ASSERT_EQUAL_MESSAGE("EditEngine Ref Dev Zoom and ViewData Zoom should match",
pViewData1->GetZoomY(), rEditEngine1.GetRefMapMode().GetScaleY());
// Create a View #2
SfxLokHelper::createView();
pModelObj->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
// Set View #2 to the same zoom as View #1
pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 17933, 4853));
pModelObj->setClientZoom(256, 256, 1333, 1333);
// Get the View #2 EditView
EditView* pEditView2 = pViewData2->GetEditView(SC_SPLIT_BOTTOMLEFT);
CPPUNIT_ASSERT(pEditView2);
EditEngine& rEditEngine2 = pEditView2->getEditEngine();
CPPUNIT_ASSERT(&rEditEngine1 != &rEditEngine2); // Before the fix, these had different settings, resulting in the text // dancing for the second user as they toggle in and out of edit mode, but // each user should have the same settings.
CPPUNIT_ASSERT_EQUAL(rEditEngine1.GetControlWord(), rEditEngine2.GetControlWord());
}
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testStatusBarLocale)
{ // Given 2 views, the second's locale is set to German:
createDoc("empty.ods"); int nView1 = SfxLokHelper::getView();
ScTestViewCallback aView1;
SfxLokHelper::createView();
ScTestViewCallback aView2;
SfxViewShell* pView2 = SfxViewShell::Current();
pView2->SetLOKLocale(u"de-DE"_ustr);
{
SfxViewFrame& rFrame = pView2->GetViewFrame();
SfxSlotPool& rSlotPool = SfxSlotPool::GetSlotPool(&rFrame);
uno::Reference<util::XURLTransformer> xParser(util::URLTransformer::create(m_xContext));
util::URL aCommandURL;
aCommandURL.Complete = ".uno:RowColSelCount";
xParser->parseStrict(aCommandURL); const SfxSlot* pSlot = rSlotPool.GetUnoSlot(aCommandURL.Path);
rFrame.GetBindings().GetDispatch(pSlot, aCommandURL, false);
}
aView2.m_aStateChanges.clear();
// When creating a cell selection in the 2nd view and processing jobs with the 1st view set to // active:
comphelper::dispatchCommand(u".uno:GoDownSel"_ustr, {});
SfxLokHelper::setView(nView1);
pView2->GetViewFrame().GetBindings().GetTimer().Invoke(); // Once more to hit the pImpl->bMsgDirty = false case in SfxBindings::NextJob_Impl().
pView2->GetViewFrame().GetBindings().GetTimer().Invoke();
// Then make sure that the locale is taken into account while producing the state changed // callback: auto it = aView2.m_aStateChanges.find(".uno:RowColSelCount");
CPPUNIT_ASSERT(it != aView2.m_aStateChanges.end());
std::string aLocale = it->second.get<std::string>("locale"); // Without the accompanying fix in place, this test would have failed with: // - Expected: de-DE // - Actual : en-US // i.e. the 2nd view got its callback with the locale of the first view, which is buggy.
CPPUNIT_ASSERT_EQUAL(std::string("de-DE"), aLocale);
}
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testLongFirstColumnMouseClick)
{ // Document has a long first column. We want to mouse-click on the column and // check the selection changed to this column.
// The issue we want to reproduce is that the click on a cell in the first column that is // very long (longer than ~800px default size of GridWindow) triggers a code-path where the cell // selected is the neighbouring cell even when we clicked on the area of the first cell.
// Fetch current view data
ScViewData* pViewData = ScDocShell::GetViewData();
CPPUNIT_ASSERT(pViewData); double nPPTX = pViewData->GetPPTX(); double nPPTY = pViewData->GetPPTX();
// Set click position
// Left side of the first cell int leftCellSideX = 1 / nPPTX; // convert pixels to logical units
// Right side of the first cell. First cell is long so click somewhere more than 800px (default of GridWindow size). int rightCellSideX = 1000 / nPPTX; // convert pixels to logical units
// Vertical position - doesn't matter - select the first row int y = 1 / nPPTY;
// Setup view #1
ScTestViewCallback aView1; // Set client rect to 2000 x 2000 pixels
pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 2000 / nPPTX, 2000 / nPPTY));
Scheduler::ProcessEventsToIdle();
// Click at on the left side of A1 cell
pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, leftCellSideX, y, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, leftCellSideX, y, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
Scheduler::ProcessEventsToIdle();
// Check the A1 cell is selected in view #1
CPPUNIT_ASSERT_EQUAL(SCCOL(0), ScDocShell::GetViewData()->GetCurX());
CPPUNIT_ASSERT_EQUAL(SCROW(0), ScDocShell::GetViewData()->GetCurY());
// Click at on the right side of A1 cell
pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, rightCellSideX, y, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, rightCellSideX, y, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
Scheduler::ProcessEventsToIdle();
// Check the A1 cell is selected in view #1
CPPUNIT_ASSERT_EQUAL(SCCOL(0), ScDocShell::GetViewData()->GetCurX());
CPPUNIT_ASSERT_EQUAL(SCROW(0), ScDocShell::GetViewData()->GetCurY());
// Try to check the same scenario in a new view
// Setup view #2
SfxLokHelper::createView(); int nView2 = SfxLokHelper::getView();
ScTestViewCallback aView2; // Set client rect to 2000 x 2000 pixels
pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 2000 / nPPTX, 2000 / nPPTY));
// Let's make sure we are in view #2
SfxLokHelper::setView(nView2);
Scheduler::ProcessEventsToIdle();
// Click at on the left side of A1 cell
pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, leftCellSideX, y, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, leftCellSideX, y, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
Scheduler::ProcessEventsToIdle();
// Check the A1 cell is selected in view #2
CPPUNIT_ASSERT_EQUAL(SCCOL(0), ScDocShell::GetViewData()->GetCurX());
CPPUNIT_ASSERT_EQUAL(SCROW(0), ScDocShell::GetViewData()->GetCurY());
// Click at on the right side of A1 cell
pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONDOWN, rightCellSideX, y, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
pModelObj->postMouseEvent(LOK_MOUSEEVENT_MOUSEBUTTONUP, rightCellSideX, y, /*count=*/ 1, /*buttons=*/ 1, /*modifier=*/0);
Scheduler::ProcessEventsToIdle();
// Check the A1 cell is selected in view #2
CPPUNIT_ASSERT_EQUAL(SCCOL(0), ScDocShell::GetViewData()->GetCurX());
CPPUNIT_ASSERT_EQUAL(SCROW(0), ScDocShell::GetViewData()->GetCurY());
}
// if we extend the tiled area to the right and bottom we want two resulting area // that don't overlap. If they overlap that typically creates an unnecessary full // screen invalidation.
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testExtendedAreasDontOverlap)
{
comphelper::LibreOfficeKit::setCompatFlag(
comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs);
// Set an arbitrary initial size smaller than the final size
pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 1000, 1000));
Scheduler::ProcessEventsToIdle();
// register to track View #1 invalidations
ScTestViewCallback aView1;
// extend to the right and bottom
pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 39750, 12780));
Scheduler::ProcessEventsToIdle();
// we should get two rectangles for the two new areas
CPPUNIT_ASSERT_EQUAL(size_t(2), aView1.m_aInvalidations.size());
// And those should not overlap, otherwise they would merge to form // a mega rectangle, which defeats the purpose of creating two rects // in the first place.
CPPUNIT_ASSERT_MESSAGE("Invalidations should not overlap",
!aView1.m_aInvalidations[0].Overlaps(aView1.m_aInvalidations[1]));
// But they should be adjacent
CPPUNIT_ASSERT_EQUAL(aView1.m_aInvalidations[0].Top() +
aView1.m_aInvalidations[0].GetSize().Height(),
aView1.m_aInvalidations[1].Top());
}
// Ensure that editing a shape not in the topleft tile has its text shown inside the shape // center while editing
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testEditShapeText)
{
ScModelObj* pModelObj = createDoc("edit-shape-text.ods");
// Set View to initial 100%
pModelObj->setClientVisibleArea(tools::Rectangle(0, 0, 28050, 10605));
pModelObj->setClientZoom(256, 256, 1920, 1920);
// Enter editing mode, shape start with no text
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::F2);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::F2);
Scheduler::ProcessEventsToIdle();
// Grab a snapshot of the center of the shape
Bitmap aBitmapBefore = getTile(pModelObj, 4096, 3584, 15360, 7680);
// reuse this to type into the active shape edit
typeCharsInCell("MMMMMMM", 0, 0, pView, pModelObj, true, false);
// Grab a new snapshot of the center of the shape
Bitmap aBitmapAfter = getTile(pModelObj, 4096, 3584, 15360, 7680);
// Without the fix, the text is not inside this tile and the before and // after are the same.
CPPUNIT_ASSERT_MESSAGE("Text is not visible", aBitmapBefore != aBitmapAfter);
}
CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testNumberFormatLocaleMultiUser)
{
{ // setup core language to FR as it will be the first session
SvtSysLocaleOptions aLocalOptions;
aLocalOptions.SetLocaleConfigString(u"fr-FR"_ustr);
aLocalOptions.SetUILocaleConfigString(u"fr-FR"_ustr);
aLocalOptions.Commit();
{ // now setup DE language in core
SvtSysLocaleOptions aLocalOptions;
aLocalOptions.SetLocaleConfigString(u"de-DE"_ustr);
aLocalOptions.SetUILocaleConfigString(u"de-DE"_ustr);
aLocalOptions.Commit();
// save and reopen // .uno:Save modifies the original file, make a copy first
saveAndReload(u"Calc MS Excel 2007 VBA XML"_ustr);
// Go to Cell B5000
uno::Sequence<beans::PropertyValue> aPropertyValues = {
comphelper::makePropertyValue(u"ToPoint"_ustr, u"$B$5000"_ustr),
};
dispatchCommand(mxComponent, u".uno:GoToCell"_ustr, aPropertyValues);
// Enter edit mode and select all text.
aView.m_aTextSelectionResult.clear();
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::F2);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::F2);
Scheduler::ProcessEventsToIdle(); // CTRL + A
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_MOD1 | awt::Key::A);
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, KEY_MOD1 | awt::Key::A);
Scheduler::ProcessEventsToIdle();
// Without the accompanying fix this would fail with // - Expected: 20 // - Actual : 1300
CPPUNIT_ASSERT_EQUAL(tools::Long(20), aView.m_aTextSelectionResult.m_aRefPoint.getX());
}
// Freeze panes on a column and receive the proper state back
aView.m_aStateChanges.clear();
uno::Sequence<beans::PropertyValue> aPropertyValues = {
comphelper::makePropertyValue("Index", uno::Any(static_cast<sal_Int32>(8))),
};
comphelper::dispatchCommand(".uno:FreezePanesColumn", aPropertyValues);
Scheduler::ProcessEventsToIdle();
pView->GetViewFrame().GetBindings().GetTimer().Invoke();
pView->GetViewFrame().GetBindings().GetTimer().Invoke(); auto it = aView.m_aStateChanges.find(".uno:FreezePanesColumn");
CPPUNIT_ASSERT(it != aView.m_aStateChanges.end());
std::string values = it->second.get<std::string>("state");
std::string index = values.substr(0, values.find(' ')); // Without the accompanying fix in place, this test would have failed with: // - Expected: 8 // - Actual : 1
CPPUNIT_ASSERT_EQUAL(std::string("8"), index);
// Freeze panes on a row and receive the proper state back
aView.m_aStateChanges.clear();
comphelper::dispatchCommand(".uno:FreezePanesRow", aPropertyValues);
Scheduler::ProcessEventsToIdle();
pView->GetViewFrame().GetBindings().GetTimer().Invoke();
pView->GetViewFrame().GetBindings().GetTimer().Invoke();
it = aView.m_aStateChanges.find(".uno:FreezePanesRow");
CPPUNIT_ASSERT(it != aView.m_aStateChanges.end());
values = it->second.get<std::string>("state");
index = values.substr(0, values.find(' ')); // Without the accompanying fix in place, this test would have failed with: // - Expected: 8 // - Actual : 1
CPPUNIT_ASSERT_EQUAL(std::string("8"), index);
}
// copy text view 1
pView->SetCursor(0, 0); // Go to A1.
Scheduler::ProcessEventsToIdle();
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'B', 0); // Type B.
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'B', 0);
Scheduler::ProcessEventsToIdle();
pView->SetCursor(0, 0); // Go to A1.
Scheduler::ProcessEventsToIdle();
pView->GetViewFrame().GetBindings().Execute(SID_COPY); // Copy B.
Scheduler::ProcessEventsToIdle();
pView->SetCursor(0, 1); // Go to A2.
Scheduler::ProcessEventsToIdle();
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'B', 0); // Type B.
pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'B', 0);
Scheduler::ProcessEventsToIdle();
uno::Sequence<beans::PropertyValue> aArgs;
dispatchCommand(mxComponent, u".uno:Paste"_ustr, aArgs); // Paste B.
Scheduler::ProcessEventsToIdle();
// Text cursor should still be visible.
CPPUNIT_ASSERT_EQUAL(true, aView.m_textCursorVisible);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.