/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the Collabora Office project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <sctiledrenderingtest.hxx>

#include <com/sun/star/datatransfer/XTransferable2.hpp>
#include <com/sun/star/document/UpdateDocMode.hpp>

#include <comphelper/propertyvalue.hxx>
#include <comphelper/propertysequence.hxx>
#include <comphelper/kit.hxx>
#include <sfx2/kit/helper.hxx>
#include <vcl/BitmapReadAccess.hxx>
#include <vcl/scheduler.hxx>
#include <COKit/COKit.hxx>

#include <sctestviewcallback.hxx>
#include <docuno.hxx>
#include <scmod.hxx>
#include <sfx2/linkmgr.hxx>
#include <tabvwsh.hxx>
#include <viewdata.hxx>
#include <postit.hxx>
#include <editeng/editview.hxx>
#include <o3tl/unit_conversion.hxx>
#include <vcl/virdev.hxx>

using namespace com::sun::star;

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSidebarLocale)
{
    ScModelObj* pModelObj = createDoc("chart.ods");
    int nView1 = KitHelper::getCurrentView();
    ScTestViewCallback aView1;
    SfxViewShell* pView1 = SfxViewShell::Current();
    pView1->SetKitLocale(u"en-US"_ustr);
    KitHelper::createView();
    ScTestViewCallback aView2;
    SfxViewShell* pView2 = SfxViewShell::Current();
    pView2->SetKitLocale(u"de-DE"_ustr);
    TestKitCallbackWrapper::InitializeSidebar();
    Scheduler::ProcessEventsToIdle();
    aView2.m_aStateChanges.clear();

    pModelObj->postMouseEvent(COKitMouseEventType::BUTTONDOWN, /*x=*/1, /*y=*/1, /*count=*/2,
                              /*buttons=*/1, /*modifier=*/0);
    pModelObj->postMouseEvent(COKitMouseEventType::BUTTONUP, /*x=*/1, /*y=*/1, /*count=*/2,
                              /*buttons=*/1, /*modifier=*/0);
    KitHelper::setView(nView1);
    Scheduler::ProcessEventsToIdle();

    auto it = aView2.m_aStateChanges.find(".uno:SidebarDeck.PropertyDeck");
    CPPUNIT_ASSERT(it != aView2.m_aStateChanges.end());
    std::string aLocale = it->second.get<std::string>("locale");
    CPPUNIT_ASSERT_EQUAL(std::string("de-DE"), aLocale);
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testCopyMultiSelection)
{
    // Given a document with A1 and A3 as selected cells:
    ScModelObj* pModelObj = createDoc("multi-selection.ods");
    ScTestViewCallback aView1;
    // Get the center of A3:
    cpo::uno::Sequence<beans::PropertyValue> aPropertyValues = {
        comphelper::makePropertyValue(u"ToPoint"_ustr, u"$A$3"_ustr),
    };
    dispatchCommand(mxComponent, u".uno:GoToCell"_ustr, aPropertyValues);
    Point aPoint = aView1.m_aCellCursorBounds.Center();
    // Go to A1:
    aPropertyValues = {
        comphelper::makePropertyValue(u"ToPoint"_ustr, u"$A$1"_ustr),
    };
    dispatchCommand(mxComponent, u".uno:GoToCell"_ustr, aPropertyValues);
    // Ctrl-click on A3:
    int nCtrl = KEY_MOD1;
    pModelObj->postMouseEvent(COKitMouseEventType::BUTTONDOWN, aPoint.getX(), aPoint.getY(), 1,
                              MOUSE_LEFT, nCtrl);

    // When getting the selection:
    uno::Reference<datatransfer::XTransferable> xTransferable = pModelObj->getSelection();

    // Make sure we get A1+A3 instead of an error:
    CPPUNIT_ASSERT(xTransferable.is());

    // Also make sure that just 2 cells is classified as a simple selection:
    uno::Reference<datatransfer::XTransferable2> xTransferable2(xTransferable, uno::UNO_QUERY);
    CPPUNIT_ASSERT(xTransferable2.is());
    // Without the fix, the text selection was complex.
    CPPUNIT_ASSERT(!xTransferable2->isComplex());
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testCursorJumpOnFailedSearch)
{
    createDoc("empty.ods");
    ScTestViewCallback aView;

    // Go to lower cell
    cpo::uno::Sequence<beans::PropertyValue> aPropertyValues = {
        comphelper::makePropertyValue(u"ToPoint"_ustr, u"$C$3"_ustr),
    };
    dispatchCommand(mxComponent, u".uno:GoToCell"_ustr, aPropertyValues);

    tools::Rectangle aInitialCursor = aView.m_aCellCursorBounds;

    // Search for a non-existing string using the start point parameters
    aPropertyValues = comphelper::InitPropertySequence(
        { { "SearchItem.SearchString", cpo::uno::Any(u"No-existing"_ustr) },
          { "SearchItem.Backward", cpo::uno::Any(false) },
          { "SearchItem.SearchStartPointX", cpo::uno::Any(static_cast<sal_Int32>(100)) },
          { "SearchItem.SearchStartPointY", cpo::uno::Any(static_cast<sal_Int32>(100)) } });
    dispatchCommand(mxComponent, u".uno:ExecuteSearch"_ustr, aPropertyValues);

    tools::Rectangle aFinalCursor = aView.m_aCellCursorBounds;

    // Without the fix, the cursor jumps even when no match is found
    CPPUNIT_ASSERT_EQUAL(aInitialCursor, aFinalCursor);
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testLocaleFormulaSeparator)
{
    ScModelObj* pModelObj = createDoc("empty.ods");
    ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
    ScDocument* pDoc = pModelObj->GetDocument();

    ScAddress addr(2, 0, 0);
    typeCharsInCell("=subtotal(9,A1:A8", addr.Col(), addr.Row(), pView, pModelObj, false, true);
    // Without the fix it would fail with
    // - Expected: 0
    // - Actual  : Err:508
    CPPUNIT_ASSERT_EQUAL(u"0"_ustr, pDoc->GetString(addr));
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDecimalSeparatorInfo)
{
    createDoc("decimal-separator.ods");

    ScTestViewCallback aView1;

    // Go to cell A1.
    cpo::uno::Sequence<beans::PropertyValue> aPropertyValues
        = { comphelper::makePropertyValue(u"ToPoint"_ustr, u"$A$1"_ustr) };
    dispatchCommand(mxComponent, u".uno:GoToCell"_ustr, aPropertyValues);

    // Cell A1 has language set to English. Decimal separator should be ".".
    CPPUNIT_ASSERT_EQUAL(std::string("."), aView1.decimalSeparator);

    // Go to cell B1.
    aPropertyValues = { comphelper::makePropertyValue(u"ToPoint"_ustr, u"B$1"_ustr) };
    dispatchCommand(mxComponent, u".uno:GoToCell"_ustr, aPropertyValues);

    // Cell B1 has language set to Turkish. Decimal separator should be ",".
    CPPUNIT_ASSERT_EQUAL(std::string(","), aView1.decimalSeparator);
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testCool11739LocaleDialogFieldUnit)
{
    createDoc("empty.ods");
    SfxViewShell* pView1 = SfxViewShell::Current();
    pView1->SetKitLocale(u"fr-FR"_ustr);

    ScModule* pMod = ScModule::get();
    FieldUnit eMetric = pMod->GetMetric();

    // Without the fix, it fails with
    // - Expected: 2
    // - Actual  : 8
    // where 2 is FieldUnit::CM and 8 is FieldUnit::INCH
    CPPUNIT_ASSERT_EQUAL(FieldUnit::CM, eMetric);
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSplitPanes)
{
    createDoc("split-panes.ods");

    save(TestFilter::ODS);

    xmlDocUniquePtr pSettings = parseExport(u"settings.xml"_ustr);
    CPPUNIT_ASSERT(pSettings);

    // Without the fix in place, this test would have failed with
    // - Expected: 0
    // - Actual  : 2
    assertXPathContent(pSettings,
                       "/office:document-settings/office:settings/config:config-item-set[1]/"
                       "config:config-item-map-indexed/config:config-item-map-entry/"
                       "config:config-item-map-named/config:config-item-map-entry/"
                       "config:config-item[@config:name='VerticalSplitMode']",
                       u"0");
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testSplitPanesXLSX)
{
    createDoc("split-panes.xlsx");

    save(TestFilter::XLSX);

    xmlDocUniquePtr pSheet = parseExport(u"xl/worksheets/sheet1.xml"_ustr);
    CPPUNIT_ASSERT(pSheet);

    // Without the fix in place, this test would have failed with
    // - Expected: topRight
    // - Actual  : bottomRight
    // which also results in invalid XLSX
    assertXPath(pSheet, "/x:worksheet/x:sheetViews/x:sheetView/x:pane", "activePane", u"topRight");
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testTdf167042)
{
    ScModelObj* pModelObj = createDoc("tdf167042.ods");
    ScDocument* pDoc = pModelObj->GetDocument();
    ScTestViewCallback aView1;

    cpo::uno::Sequence<beans::PropertyValue> aPropertyValues
        = { comphelper::makePropertyValue(u"ToPoint"_ustr, u"$A$1"_ustr) };
    dispatchCommand(mxComponent, u".uno:GoToCell"_ustr, aPropertyValues);

    Point aPoint = aView1.m_aCellCursorBounds.Center();

    aPropertyValues = { comphelper::makePropertyValue(u"ToPoint"_ustr, u"$B$1"_ustr) };
    dispatchCommand(mxComponent, u".uno:GoToCell"_ustr, aPropertyValues);

    // Check that we have the comment on A1
    CPPUNIT_ASSERT_MESSAGE("There should be a note on A1", pDoc->HasNote(ScAddress(0, 0, 0)));
    ScPostIt* pNote = pDoc->GetNote(ScAddress(0, 0, 0));
    CPPUNIT_ASSERT(pNote);
    CPPUNIT_ASSERT_EQUAL(u"test1"_ustr, pNote->GetText());

    cpo::uno::Sequence aArgs{ comphelper::makePropertyValue(u"PersistentCopy"_ustr, false) };
    dispatchCommand(mxComponent, u".uno:FormatPaintbrush"_ustr, aArgs);

    pModelObj->postMouseEvent(COKitMouseEventType::BUTTONDOWN, aPoint.getX(), aPoint.getY(), 1,
                              MOUSE_LEFT, 0);
    pModelObj->postMouseEvent(COKitMouseEventType::BUTTONUP, aPoint.getX(), aPoint.getY(), 1,
                              MOUSE_LEFT, 0);

    // Check that FormatPaintbrush worked
    vcl::Font aFont;
    pDoc->GetPattern(0, 0, 0)->fillFontOnly(aFont);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("font should be bold A1", WEIGHT_BOLD,
                                 aFont.GetWeightMaybeAskConfig());

    // Check that we still have the comment on A1 after FormatPaintbrush
    pNote = pDoc->GetNote(ScAddress(0, 0, 0));
    CPPUNIT_ASSERT(pNote);
    CPPUNIT_ASSERT_EQUAL(u"test1"_ustr, pNote->GetText());

    dispatchCommand(mxComponent, u".uno:Undo"_ustr, {});

    // Check that we still have the comment on A1 after Undo
    pNote = pDoc->GetNote(ScAddress(0, 0, 0));
    CPPUNIT_ASSERT(pNote);
    // Without the fix in place, this test would have failed with
    // - Expected : test1
    // - Actual :
    CPPUNIT_ASSERT_EQUAL(u"test1"_ustr, pNote->GetText());
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testBreakPageView)
{
    // Give a file in page break view mode
    ScModelObj* pModelObj = createDoc("page-view.xlsx");

    // Paint a tile not at the top or left
    size_t nCanvasSize = 1024;
    size_t nTileSize = 256;
    std::vector<unsigned char> aPixmap(nCanvasSize * nCanvasSize * 4, 0);
    ScopedVclPtrInstance<VirtualDevice> xDevice(DeviceFormat::WITHOUT_ALPHA);
    xDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
    xDevice->SetOutputSizePixelScaleOffsetAndKitBuffer(Size(nCanvasSize, nCanvasSize), 1.0, Point(),
                                                       aPixmap.data());
    pModelObj->paintTile(*xDevice, nCanvasSize, nCanvasSize, 3840, 3840, 3840, 3840);
    xDevice->EnableMapMode(false);
    Bitmap aBitmap = xDevice->GetBitmap(Point(0, 0), Size(nTileSize, nTileSize));

    // Top left pixel should be white, not blue
    BitmapScopedReadAccess pAccess(aBitmap);
    Color aColor(pAccess->GetPixel(0, 0));
    // Without the fix in place, this test fails with
    // - Expected: rgba[ffffffff]
    // - Actual  : rgba[000080ff]
    CPPUNIT_ASSERT_EQUAL(Color(255, 255, 255), aColor);
}

static void lcl_drag(ScModelObj* pModelObj, Point aFrom, Point aTo, int nModifier)
{
    // No modifiers in button down to start a drag
    pModelObj->postMouseEvent(COKitMouseEventType::BUTTONDOWN, aFrom.getX(), aFrom.getY(), 1,
                              MOUSE_LEFT, 0);
    Scheduler::ProcessEventsToIdle();
    // Need a clear motion so it is detected as a drag
    const int nSteps = 5;
    for (int i = 1; i <= nSteps; ++i)
    {
        pModelObj->postMouseEvent(
            COKitMouseEventType::MOVE, aFrom.getX() + (aTo.getX() - aFrom.getX()) * i / nSteps,
            aFrom.getY() + (aTo.getY() - aFrom.getY()) * i / nSteps, 1, MOUSE_LEFT, nModifier);
        Scheduler::ProcessEventsToIdle();
    }
    pModelObj->postMouseEvent(COKitMouseEventType::BUTTONUP, aTo.getX(), aTo.getY(), 1, MOUSE_LEFT,
                              nModifier);
    Scheduler::ProcessEventsToIdle();
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDragDropMove)
{
    // Document has A1=A, B1=B, A2=C, B2=D, D1=E, D2=F.
    ScModelObj* pModelObj = createDoc("cell_drag.fods");
    ScTestViewCallback aView;
    ScDocument* pDoc = pModelObj->GetDocument();

    dispatchCommand(mxComponent, u".uno:GoToCell"_ustr,
                    { comphelper::makePropertyValue(u"ToPoint"_ustr, u"$A$1"_ustr) });
    Point aFrom = aView.m_aCellCursorBounds.TopLeft() + Point(10, 10);
    dispatchCommand(mxComponent, u".uno:GoToCell"_ustr,
                    { comphelper::makePropertyValue(u"ToPoint"_ustr, u"$D$1"_ustr) });
    Point aTo = aView.m_aCellCursorBounds.TopLeft() + Point(10, 10);

    dispatchCommand(mxComponent, u".uno:GoToCell"_ustr,
                    { comphelper::makePropertyValue(u"ToPoint"_ustr, u"$A$1:$B$2"_ustr) });
    Scheduler::ProcessEventsToIdle();

    lcl_drag(pModelObj, aFrom, aTo, 0);

    CPPUNIT_ASSERT_EQUAL(u""_ustr, pDoc->GetString(ScAddress(0, 0, 0)));
    CPPUNIT_ASSERT_EQUAL(u""_ustr, pDoc->GetString(ScAddress(1, 0, 0)));
    CPPUNIT_ASSERT_EQUAL(u""_ustr, pDoc->GetString(ScAddress(0, 1, 0)));
    CPPUNIT_ASSERT_EQUAL(u""_ustr, pDoc->GetString(ScAddress(1, 1, 0)));
    CPPUNIT_ASSERT_EQUAL(u"A"_ustr, pDoc->GetString(ScAddress(3, 0, 0)));
    CPPUNIT_ASSERT_EQUAL(u"B"_ustr, pDoc->GetString(ScAddress(4, 0, 0)));
    CPPUNIT_ASSERT_EQUAL(u"C"_ustr, pDoc->GetString(ScAddress(3, 1, 0)));
    CPPUNIT_ASSERT_EQUAL(u"D"_ustr, pDoc->GetString(ScAddress(4, 1, 0)));
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDragDropCopy)
{
    ScModelObj* pModelObj = createDoc("cell_drag.fods");
    ScTestViewCallback aView;
    ScDocument* pDoc = pModelObj->GetDocument();

    dispatchCommand(mxComponent, u".uno:GoToCell"_ustr,
                    { comphelper::makePropertyValue(u"ToPoint"_ustr, u"$A$1"_ustr) });
    Point aFrom = aView.m_aCellCursorBounds.TopLeft() + Point(10, 10);
    dispatchCommand(mxComponent, u".uno:GoToCell"_ustr,
                    { comphelper::makePropertyValue(u"ToPoint"_ustr, u"$D$1"_ustr) });
    Point aTo = aView.m_aCellCursorBounds.TopLeft() + Point(10, 10);

    dispatchCommand(mxComponent, u".uno:GoToCell"_ustr,
                    { comphelper::makePropertyValue(u"ToPoint"_ustr, u"$A$1:$B$2"_ustr) });
    Scheduler::ProcessEventsToIdle();

    lcl_drag(pModelObj, aFrom, aTo, KEY_MOD1); // Ctrl

    CPPUNIT_ASSERT_EQUAL(u"A"_ustr, pDoc->GetString(ScAddress(0, 0, 0)));
    CPPUNIT_ASSERT_EQUAL(u"B"_ustr, pDoc->GetString(ScAddress(1, 0, 0)));
    CPPUNIT_ASSERT_EQUAL(u"C"_ustr, pDoc->GetString(ScAddress(0, 1, 0)));
    CPPUNIT_ASSERT_EQUAL(u"D"_ustr, pDoc->GetString(ScAddress(1, 1, 0)));
    CPPUNIT_ASSERT_EQUAL(u"A"_ustr, pDoc->GetString(ScAddress(3, 0, 0)));
    CPPUNIT_ASSERT_EQUAL(u"B"_ustr, pDoc->GetString(ScAddress(4, 0, 0)));
    CPPUNIT_ASSERT_EQUAL(u"C"_ustr, pDoc->GetString(ScAddress(3, 1, 0)));
    CPPUNIT_ASSERT_EQUAL(u"D"_ustr, pDoc->GetString(ScAddress(4, 1, 0)));
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testDragDropInsert)
{
    // Alt-drag A1:B1 to D2: insert-move into a different row.
    // F at D2 should be pushed right, and A/B placed at D2:E2.
    // Row 1 source is cleared; row 2 is not affected by the cleanup.
    ScModelObj* pModelObj = createDoc("cell_drag.fods");
    ScTestViewCallback aView;
    ScDocument* pDoc = pModelObj->GetDocument();

    dispatchCommand(mxComponent, u".uno:GoToCell"_ustr,
                    { comphelper::makePropertyValue(u"ToPoint"_ustr, u"$A$1"_ustr) });
    Point aFrom = aView.m_aCellCursorBounds.TopLeft() + Point(10, 10);
    dispatchCommand(mxComponent, u".uno:GoToCell"_ustr,
                    { comphelper::makePropertyValue(u"ToPoint"_ustr, u"$D$2"_ustr) });
    Point aTo = aView.m_aCellCursorBounds.TopLeft() + Point(10, 10);

    dispatchCommand(mxComponent, u".uno:GoToCell"_ustr,
                    { comphelper::makePropertyValue(u"ToPoint"_ustr, u"$A$1:$B$1"_ustr) });
    Scheduler::ProcessEventsToIdle();

    lcl_drag(pModelObj, aFrom, aTo, KEY_MOD2); // Alt

    CPPUNIT_ASSERT_EQUAL(u""_ustr, pDoc->GetString(ScAddress(0, 0, 0))); // A1
    CPPUNIT_ASSERT_EQUAL(u""_ustr, pDoc->GetString(ScAddress(1, 0, 0))); // B1
    CPPUNIT_ASSERT_EQUAL(u"E"_ustr, pDoc->GetString(ScAddress(3, 0, 0))); // D1
    CPPUNIT_ASSERT_EQUAL(u"A"_ustr, pDoc->GetString(ScAddress(3, 1, 0))); // D2
    CPPUNIT_ASSERT_EQUAL(u"B"_ustr, pDoc->GetString(ScAddress(4, 1, 0))); // E2
    CPPUNIT_ASSERT_EQUAL(u"F"_ustr, pDoc->GetString(ScAddress(5, 1, 0))); // F2 (was D2)
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testPageBackgroundRemoteNotFetched)
{
    // Page background image with a remote URL must not fetch
    // the URL during paint when link updates are not allowed.
    cpo::uno::Sequence<beans::PropertyValue> aParams = {
        comphelper::makePropertyValue(u"UpdateDocMode"_ustr,
                                      sal_Int16(css::document::UpdateDocMode::NO_UPDATE)),
    };
    loadWithParams(createFileURL(u"page-background-link.fods"), aParams);
    ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
    CPPUNIT_ASSERT(pModelObj);
    pModelObj->initializeForTiledRendering({});

    ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA);
    pDevice->SetOutputSizePixel(Size(1024, 768));
    pModelObj->paintTile(*pDevice, 1024, 768, 0, 0, 15360, 7680);
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testShapeBackgroundRemoteNotFetched)
{
    // Shape fill bitmap with a remote URL must not fetch
    // the URL during paint when link updates are not allowed.
    // The assert in createNewSdrFillGraphicAttribute will fire if
    // a remote fetch is attempted.
    cpo::uno::Sequence<beans::PropertyValue> aParams = {
        comphelper::makePropertyValue(u"UpdateDocMode"_ustr,
                                      sal_Int16(css::document::UpdateDocMode::NO_UPDATE)),
    };
    loadWithParams(createFileURL(u"shape-background-link.fods"), aParams);
    ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
    CPPUNIT_ASSERT(pModelObj);

    // The shape's deferred remote fill bitmap is registered as an external
    // link as the shape is imported, so it appears in Edit, Links to External
    // Files and can be updated or broken.
    const sfx2::LinkManager* pLinkMgr = pModelObj->GetDocument()->GetLinkManager();
    CPPUNIT_ASSERT(pLinkMgr);
    CPPUNIT_ASSERT_MESSAGE("shape fill bitmap link should be registered",
                           !pLinkMgr->GetLinks().empty());

    pModelObj->initializeForTiledRendering({});

    ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA);
    pDevice->SetOutputSizePixel(Size(1024, 768));
    pModelObj->paintTile(*pDevice, 1024, 768, 0, 0, 15360, 7680);
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testBulletImageRemoteNotFetched)
{
    // text:list-level-style-image with a remote URL must not fetch
    // the URL during paint when link updates are not allowed.
    // Currently the editeng rendering path silently skips unresolved
    // GraphicExternalLink graphics. If someone adds fetching here,
    // this test should catch it.
    cpo::uno::Sequence<beans::PropertyValue> aParams = {
        comphelper::makePropertyValue(u"UpdateDocMode"_ustr,
                                      sal_Int16(css::document::UpdateDocMode::NO_UPDATE)),
    };
    loadWithParams(createFileURL(u"bullet-image-link.fods"), aParams);
    ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
    CPPUNIT_ASSERT(pModelObj);
    pModelObj->initializeForTiledRendering({});

    ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA);
    pDevice->SetOutputSizePixel(Size(1024, 768));
    pModelObj->paintTile(*pDevice, 1024, 768, 0, 0, 15360, 7680);
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testFormImageRemoteNotFetched)
{
    // Form image button with a remote ImageURL must not fetch the
    // URL during import when link updates are not allowed.
    cpo::uno::Sequence<beans::PropertyValue> aParams = {
        comphelper::makePropertyValue(u"UpdateDocMode"_ustr,
                                      sal_Int16(css::document::UpdateDocMode::NO_UPDATE)),
    };
    loadWithParams(createFileURL(u"form-image-link.fods"), aParams);
    ScModelObj* pModelObj = comphelper::getFromUnoTunnel<ScModelObj>(mxComponent);
    CPPUNIT_ASSERT(pModelObj);
    pModelObj->initializeForTiledRendering({});

    ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA);
    pDevice->SetOutputSizePixel(Size(1024, 768));
    pModelObj->paintTile(*pDevice, 1024, 768, 0, 0, 15360, 7680);
}

CPPUNIT_TEST_FIXTURE(ScTiledRenderingTest, testEditTextPaintStartInTiledMode)
{
    // A short right-aligned number in a cell far from the left of the sheet is
    // edited in place. It fits its column, so the in-edit text stays right
    // anchored and the edit view's visible-document left is large. In tiled
    // rendering the paint must subtract that offset so the right-anchored text
    // lands on the cell's own tile. Skipping the subtraction (an earlier broad
    // fix did that for every kit edit) draws the text off the tile, so the cell
    // rendered blank while typing.
    comphelper::COKit::setCompatFlag(comphelper::COKit::Compat::scPrintTwipsMsgs);
    ScModelObj* pModelObj = createDoc("empty.ods");
    CPPUNIT_ASSERT(pModelObj);
    ScTabViewShell* pView = dynamic_cast<ScTabViewShell*>(SfxViewShell::Current());
    CPPUNIT_ASSERT(pView);

    // a short number in a cell far to the right (column AI, row 736)
    typeCharsInCell("12345", 34, 735, pView, pModelObj, /*bInEdit*/ false, /*bCommit*/ false);
    Scheduler::ProcessEventsToIdle();

    ScViewData& rVD = pView->GetViewData();
    EditView* pEditView = rVD.GetEditView(rVD.GetEditActivePart());
    CPPUNIT_ASSERT(pEditView);

    // a right-anchored cell this far right has a large visible-document left
    const tools::Long nVisDocLeft = pEditView->GetVisArea().Left();
    CPPUNIT_ASSERT_MESSAGE("precondition: far right-aligned cell has a non-zero "
                           "visible-document left",
                           nVisDocLeft > 0);

    // the paint start subtracts that offset, mapping the right-anchored text onto
    // the cell, rather than staying at the output area top-left
    const Point aStartPos = pEditView->CalculateTextPaintStartPosition();
    CPPUNIT_ASSERT_EQUAL(pEditView->GetOutputArea().Left() - nVisDocLeft, aStartPos.X());
}

CPPUNIT_PLUGIN_IMPLEMENT();

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
