/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <config_oox.h>
#include <memory>
#include <string_view>
#include <com/sun/star/lang/XComponent.hpp>
#include <com/sun/star/text/XTextDocument.hpp>
#include <com/sun/star/awt/Key.hpp>
#include <com/sun/star/awt/XReschedule.hpp>
#include <com/sun/star/awt/Toolkit.hpp>
#include <com/sun/star/drawing/XDrawPageSupplier.hpp>
#include <com/sun/star/text/TextContentAnchorType.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/util/XCloseable.hpp>
#include <vcl/scheduler.hxx>
#include <vcl/svapp.hxx>
#include <vcl/syswin.hxx>
#include <vcl/window.hxx>
#include <vcl/ctrl.hxx>
#include <vcl/uitest/uiobject.hxx>
#include <comphelper/processfactory.hxx>
#include <rtl/math.hxx>
#include <sfx2/childwin.hxx>
#include <sfx2/lokhelper.hxx>
#include <test/unoapi_test.hxx>
#include <comphelper/lok.hxx>
#include <comphelper/propertysequence.hxx>
#include <osl/conditn.hxx>
#include <svl/srchitem.hxx>
#include <LibreOfficeKit/LibreOfficeKitEnums.h>
#include <unotools/tempfile.hxx>
#include <sfx2/viewsh.hxx>
#include <sfx2/viewfrm.hxx>
#include <sfx2/bindings.hxx>
#include <unotools/datetime.hxx>
#include <unotools/syslocaleoptions.hxx>
#include <comphelper/string.hxx>
#include <comphelper/scopeguard.hxx>
#include <cairo.h>
#include <config_fonts.h>
#include <config_mpl.h>
#include <tools/json_writer.hxx>
#include <o3tl/unit_conversion.hxx>
#include <o3tl/string_view.hxx>
#include <lib/init.hxx>
#include <svx/svxids.hrc>
#include <cppunit/TestAssert.h>
#include <vcl/BitmapTools.hxx>
#include <vcl/filter/PngImageWriter.hxx>
#include <vcl/filter/PDFiumLibrary.hxx>
#include <svtools/colorcfg.hxx>
#include <sal/types.h>
#include <test/lokcallback.hxx>
#if USE_TLS_NSS
#include <nss.h>
#endif
using namespace com::sun::star;
using namespace desktop;
static LibreOfficeKitDocumentType getDocumentTypeFromName(
const char * pName)
{
CPPUNIT_ASSERT_MESSAGE(
"Document name must be valid." , pName != nullptr);
const std::string name(pName);
CPPUNIT_ASSERT_MESSAGE(
"Document name must include extension." , name.size() > 4);
const auto it = name.rfind(
'.' );
if (it != std::string::npos)
{
const std::string ext = name.substr(it);
if (ext ==
".ods" )
return LOK_DOCTYPE_SPREADSHEET;
if (ext ==
".odp" )
return LOK_DOCTYPE_PRESENTATION;
}
CPPUNIT_ASSERT_MESSAGE(
"Document name must include extension." , it != std::string::
npos);
return LOK_DOCTYPE_TEXT;
}
class DesktopLOKTest : public UnoApiTest
{
public :
DesktopLOKTest() : UnoApiTest(u"/desktop/qa/data/" _ustr),
m_nSelectionBeforeSearchResult(0),
m_nSelectionAfterSearchResult(0),
m_bModified(false ),
m_nTrackChanges(0)
{
}
~DesktopLOKTest();
void readFileIntoByteVector(
std::u16string_view sFilename, std::vector<sal_uInt8> & rByteVector);
virtual void setUp() override
{
comphelper::LibreOfficeKit::setActive(true );
UnoApiTest::setUp();
}
virtual void tearDown() override
{
closeDoc();
// documents are already closed, no need to call UnoApiTest::tearDown
test::BootstrapFixture::tearDown();
comphelper::LibreOfficeKit::setActive(false );
}
std::unique_ptr<LibLODocument_Impl>
loadDocImpl(const char * pName, LibreOfficeKitDocumentType eType);
private :
std::unique_ptr<LibLODocument_Impl>
loadDocImpl(const char * pName);
public :
std::unique_ptr<LibLODocument_Impl>
loadDocUrlImpl(const OUString& rFileURL, LibreOfficeKitDocumentType eType);
LibLODocument_Impl* loadDocUrl(const OUString& rFileURL, LibreOfficeKitDocumentType eType);
LibLODocument_Impl* loadDoc(const char * pName, LibreOfficeKitDocumentType eType);
LibLODocument_Impl* loadDoc(const char * pName)
{
return loadDoc(pName, getDocumentTypeFromName(pName));
}
void closeDoc(std::unique_ptr<LibLODocument_Impl>& loDocument);
void closeDoc() { closeDoc(m_pDocument); }
static void callback(int nType, const char * pPayload, void * pData);
void callbackImpl(int nType, const char * pPayload);
void testGetStyles();
void testGetFonts();
void testCreateView();
void testGetFilterTypes();
void testGetPartPageRectangles();
void testSearchCalc();
void testSearchAllNotificationsCalc();
void testPaintTile();
void testSaveAs();
void testSaveAsJsonOptions();
void testSaveAsCalc();
void testPasteWriter();
void testPasteWriterJPEG();
void testUndoWriter();
void testRowColumnHeaders();
void testHiddenRowHeaders();
void testCellCursor();
void testCommandResult();
void testWriterComments();
void testSheetOperations();
void testSheetSelections();
void testSheetDragDrop();
void testContextMenuCalc();
void testContextMenuWriter();
void testContextMenuImpress();
void testNotificationCompression();
void testTileInvalidationCompression();
void testPartInInvalidation();
void testBinaryCallback();
void testOmitInvalidate();
void test2ViewsOmitInvalidate();
void testPaintTileOmitInvalidate();
void testCreateViewOmitInvalidate();
void testInput();
void testRedlineWriter();
void testRedlineCalc();
void testPaintPartTile();
void testPaintPartTileDifferentSchemes();
#if HAVE_MORE_FONTS
void testGetFontSubset();
#endif
void testCommentsWriter();
void testCommentsCalc();
void testCommentsImpress();
void testCommentsCallbacksWriter();
void testCommentsAddEditDeleteDraw();
void testCommentsInReadOnlyMode();
void testCalcValidityDropdown();
void testCalcValidityDropdownInReadonlyMode();
void testRunMacro();
void testExtractParameter();
void testGetSignatureState_NonSigned();
void testGetSignatureState_Signed();
#if 0 // broken with system nss on RHEL 7
void testInsertCertificate_DER_ODT();
void testInsertCertificate_PEM_ODT();
void testInsertCertificate_PEM_DOCX();
#endif
void testSignDocument_PEM_PDF();
void testTextSelectionHandles();
void testComplexSelection();
void testSpellcheckerMultiView();
void testDialogPaste();
void testCalcSaveAs();
void testControlState();
void testMetricField();
void testMultiDocuments();
void testJumpCursor();
void testRenderSearchResult_WriterNode();
void testRenderSearchResult_CommonNode();
void testNoDuplicateTableSelection();
void testMultiViewTableSelection();
void testColorPaletteCallback();
void testABI();
CPPUNIT_TEST_SUITE(DesktopLOKTest);
CPPUNIT_TEST(testGetStyles);
CPPUNIT_TEST(testGetFonts);
CPPUNIT_TEST(testCreateView);
CPPUNIT_TEST(testGetFilterTypes);
CPPUNIT_TEST(testGetPartPageRectangles);
CPPUNIT_TEST(testSearchCalc);
CPPUNIT_TEST(testSearchAllNotificationsCalc);
CPPUNIT_TEST(testPaintTile);
CPPUNIT_TEST(testSaveAs);
CPPUNIT_TEST(testSaveAsJsonOptions);
CPPUNIT_TEST(testSaveAsCalc);
CPPUNIT_TEST(testPasteWriter);
CPPUNIT_TEST(testPasteWriterJPEG);
CPPUNIT_TEST(testUndoWriter);
CPPUNIT_TEST(testRowColumnHeaders);
CPPUNIT_TEST(testHiddenRowHeaders);
CPPUNIT_TEST(testCellCursor);
CPPUNIT_TEST(testCommandResult);
CPPUNIT_TEST(testWriterComments);
CPPUNIT_TEST(testSheetOperations);
CPPUNIT_TEST(testSheetSelections);
CPPUNIT_TEST(testSheetDragDrop);
CPPUNIT_TEST(testContextMenuCalc);
CPPUNIT_TEST(testContextMenuWriter);
CPPUNIT_TEST(testContextMenuImpress);
CPPUNIT_TEST(testNotificationCompression);
CPPUNIT_TEST(testTileInvalidationCompression);
CPPUNIT_TEST(testPartInInvalidation);
CPPUNIT_TEST(testBinaryCallback);
CPPUNIT_TEST(testOmitInvalidate);
CPPUNIT_TEST(test2ViewsOmitInvalidate);
CPPUNIT_TEST(testPaintTileOmitInvalidate);
CPPUNIT_TEST(testCreateViewOmitInvalidate);
CPPUNIT_TEST(testInput);
CPPUNIT_TEST(testRedlineWriter);
CPPUNIT_TEST(testRedlineCalc);
CPPUNIT_TEST(testPaintPartTile);
CPPUNIT_TEST(testPaintPartTileDifferentSchemes);
#if HAVE_MORE_FONTS
CPPUNIT_TEST(testGetFontSubset);
#endif
CPPUNIT_TEST(testCommentsWriter);
CPPUNIT_TEST(testCommentsCalc);
CPPUNIT_TEST(testCommentsImpress);
CPPUNIT_TEST(testCommentsCallbacksWriter);
CPPUNIT_TEST(testCommentsAddEditDeleteDraw);
CPPUNIT_TEST(testCommentsInReadOnlyMode);
CPPUNIT_TEST(testCalcValidityDropdown);
CPPUNIT_TEST(testCalcValidityDropdownInReadonlyMode);
CPPUNIT_TEST(testRunMacro);
CPPUNIT_TEST(testExtractParameter);
CPPUNIT_TEST(testGetSignatureState_Signed);
CPPUNIT_TEST(testGetSignatureState_NonSigned);
#if !MPL_HAVE_SUBSET
#if 0 // broken with system nss on RHEL 7
CPPUNIT_TEST(testInsertCertificate_DER_ODT);
CPPUNIT_TEST(testInsertCertificate_PEM_ODT);
CPPUNIT_TEST(testInsertCertificate_PEM_DOCX);
#endif
CPPUNIT_TEST(testSignDocument_PEM_PDF);
#endif
CPPUNIT_TEST(testTextSelectionHandles);
CPPUNIT_TEST(testComplexSelection);
CPPUNIT_TEST(testSpellcheckerMultiView);
CPPUNIT_TEST(testDialogPaste);
CPPUNIT_TEST(testCalcSaveAs);
CPPUNIT_TEST(testControlState);
CPPUNIT_TEST(testMetricField);
CPPUNIT_TEST(testMultiDocuments);
CPPUNIT_TEST(testJumpCursor);
CPPUNIT_TEST(testRenderSearchResult_WriterNode);
CPPUNIT_TEST(testRenderSearchResult_CommonNode);
CPPUNIT_TEST(testNoDuplicateTableSelection);
CPPUNIT_TEST(testMultiViewTableSelection);
CPPUNIT_TEST(testColorPaletteCallback);
CPPUNIT_TEST(testABI);
CPPUNIT_TEST_SUITE_END();
OString m_aTextSelection;
OString m_aTextSelectionStart;
OString m_aTextSelectionEnd;
std::vector<OString> m_aSearchResultSelection;
std::vector<int > m_aSearchResultPart;
int m_nSelectionBeforeSearchResult;
int m_nSelectionAfterSearchResult;
// for testCommandResult
osl::Condition m_aCommandResultCondition;
OString m_aCommandResult;
// for testModifiedStatus
osl::Condition m_aStateChangedCondition;
bool m_bModified;
int m_nTrackChanges;
// for testContextMenu{Calc, Writer}
osl::Condition m_aContextMenuCondition;
boost::property_tree::ptree m_aContextMenuResult;
std::unique_ptr<LibLODocument_Impl> m_pDocument;
};
DesktopLOKTest::~DesktopLOKTest()
{
#if USE_TLS_NSS
NSS_Shutdown();
#endif
}
static Control* GetFocusControl(vcl::Window const * pParent)
{
sal_uInt16 nChildren = pParent->GetChildCount();
for (sal_uInt16 nChild = 0; nChild < nChildren; ++nChild)
{
vcl::Window* pChild = pParent->GetChild( nChild );
Control* pCtrl = dynamic_cast <Control*>(pChild);
if (pCtrl && pCtrl->HasControlFocus())
return pCtrl;
Control* pSubCtrl = GetFocusControl( pChild );
if (pSubCtrl)
return pSubCtrl;
}
return nullptr;
}
std::unique_ptr<LibLODocument_Impl>
DesktopLOKTest::loadDocUrlImpl(const OUString& rFileURL, LibreOfficeKitDocumentType eType)
{
OUString aService;
switch (eType)
{
case LOK_DOCTYPE_TEXT:
aService = "com.sun.star.text.TextDocument" ;
break ;
case LOK_DOCTYPE_SPREADSHEET:
aService = "com.sun.star.sheet.SpreadsheetDocument" ;
break ;
case LOK_DOCTYPE_PRESENTATION:
aService = "com.sun.star.presentation.PresentationDocument" ;
break ;
default :
CPPUNIT_ASSERT(false );
break ;
}
static int nDocumentIdCounter = 0;
SfxViewShell::SetCurrentDocId(ViewShellDocId(nDocumentIdCounter));
mxComponent = loadFromDesktop(rFileURL, aService);
std::unique_ptr<LibLODocument_Impl> pDocument(new LibLODocument_Impl(mxComponent, nDocumentIdCounter));
++nDocumentIdCounter;
return pDocument;
}
std::unique_ptr<LibLODocument_Impl>
DesktopLOKTest::loadDocImpl(const char * pName, LibreOfficeKitDocumentType eType)
{
OUString aFileURL = createFileURL(OUString::createFromAscii(pName));
return loadDocUrlImpl(aFileURL, eType);
}
std::unique_ptr<LibLODocument_Impl>
DesktopLOKTest::loadDocImpl(const char * pName)
{
return loadDocImpl(pName, getDocumentTypeFromName(pName));
}
LibLODocument_Impl* DesktopLOKTest::loadDocUrl(const OUString& rFileURL, LibreOfficeKitDocumentType eType)
{
m_pDocument = loadDocUrlImpl(rFileURL, eType);
return m_pDocument.get();
}
LibLODocument_Impl* DesktopLOKTest::loadDoc(const char * pName, LibreOfficeKitDocumentType eType)
{
m_pDocument = loadDocImpl(pName, eType);
return m_pDocument.get();
}
void DesktopLOKTest::closeDoc(std::unique_ptr<LibLODocument_Impl>& pDocument)
{
if (pDocument)
{
pDocument->pClass->registerCallback(pDocument.get(), nullptr, nullptr);
pDocument.reset();
}
if (mxComponent.is())
{
css::uno::Reference<util::XCloseable> xCloseable(mxComponent, css::uno::UNO_QUERY_THROW);
xCloseable->close(false );
mxComponent.clear();
}
}
void DesktopLOKTest::callback(int nType, const char * pPayload, void * pData)
{
static_cast <DesktopLOKTest*>(pData)->callbackImpl(nType, pPayload);
}
void DesktopLOKTest::callbackImpl(int nType, const char * pPayload)
{
switch (nType)
{
case LOK_CALLBACK_TEXT_SELECTION:
{
m_aTextSelection = pPayload;
if (m_aSearchResultSelection.empty())
++m_nSelectionBeforeSearchResult;
else
++m_nSelectionAfterSearchResult;
}
break ;
case LOK_CALLBACK_TEXT_SELECTION_START:
m_aTextSelectionStart = pPayload;
break ;
case LOK_CALLBACK_TEXT_SELECTION_END:
m_aTextSelectionEnd = pPayload;
break ;
case LOK_CALLBACK_SEARCH_RESULT_SELECTION:
{
m_aSearchResultSelection.clear();
boost::property_tree::ptree aTree;
std::stringstream aStream(pPayload);
boost::property_tree::read_json(aStream, aTree);
for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("searchResultSelection" ))
{
m_aSearchResultSelection.emplace_back(rValue.second.get<std::string>("rectangles" ).c_str());
m_aSearchResultPart.push_back(std::atoi(rValue.second.get<std::string>("part" ).c_str()));
}
}
break ;
case LOK_CALLBACK_UNO_COMMAND_RESULT:
{
m_aCommandResult = pPayload;
m_aCommandResultCondition.set();
}
break ;
case LOK_CALLBACK_STATE_CHANGED:
{
OString aPayload(pPayload);
OString aPrefix(".uno:ModifiedStatus=" _ostr);
if (aPayload.startsWith(aPrefix))
{
m_bModified = aPayload.copy(aPrefix.getLength()).toBoolean();
m_aStateChangedCondition.set();
}
else if (aPayload.startsWith(".uno:TrackChanges=" ) && aPayload.endsWith("=true" ))
++m_nTrackChanges;
}
break ;
case LOK_CALLBACK_CONTEXT_MENU:
{
m_aContextMenuResult.clear();
std::stringstream aStream(pPayload);
boost::property_tree::read_json(aStream, m_aContextMenuResult);
m_aContextMenuCondition.set();
}
break ;
}
}
void DesktopLOKTest::testGetStyles()
{
LibLODocument_Impl* pDocument = loadDoc("blank_text.odt" );
boost::property_tree::ptree aTree;
char * pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:StyleApply" );
std::stringstream aStream(pJSON);
boost::property_tree::read_json(aStream, aTree);
CPPUNIT_ASSERT( !aTree.empty() );
CPPUNIT_ASSERT_EQUAL( std::string(".uno:StyleApply" ), aTree.get_child("commandName" ).get_value<std::string>() );
boost::property_tree::ptree aValues = aTree.get_child("commandValues" );
CPPUNIT_ASSERT( !aValues.empty() );
for (const auto & rPair : aValues)
{
if ( rPair.first != "ClearStyle" )
{
CPPUNIT_ASSERT( !rPair.second.empty());
}
if (rPair.first != "CharacterStyles" &&
rPair.first != "ParagraphStyles" &&
rPair.first != "FrameStyles" &&
rPair.first != "PageStyles" &&
rPair.first != "NumberingStyles" &&
rPair.first != "CellStyles" &&
rPair.first != "ShapeStyles" &&
rPair.first != "TableStyles" &&
rPair.first != "HeaderFooter" &&
rPair.first != "Commands" )
{
CPPUNIT_FAIL("Unknown style family: " + rPair.first);
}
}
free(pJSON);
}
void DesktopLOKTest::testGetFonts()
{
LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp" );
boost::property_tree::ptree aTree;
char * pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:CharFontName" );
std::stringstream aStream(pJSON);
boost::property_tree::read_json(aStream, aTree);
CPPUNIT_ASSERT( !aTree.empty() );
CPPUNIT_ASSERT_EQUAL( std::string(".uno:CharFontName" ), aTree.get_child("commandName" ).get_value<std::string>() );
boost::property_tree::ptree aValues = aTree.get_child("commandValues" );
CPPUNIT_ASSERT( !aValues.empty() );
for (const auto & rPair : aValues)
{
// check that we have font sizes available for each font
CPPUNIT_ASSERT( !rPair.second.empty());
}
free(pJSON);
}
void DesktopLOKTest::testCreateView()
{
LibLODocument_Impl* pDocument = loadDoc("blank_text.odt" );
CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument));
int nId0 = pDocument->m_pDocumentClass->getView(pDocument);
int nId1 = pDocument->m_pDocumentClass->createView(pDocument);
CPPUNIT_ASSERT_EQUAL(2, pDocument->m_pDocumentClass->getViewsCount(pDocument));
// Test getViewIds().
std::vector<int > aViewIds(2);
CPPUNIT_ASSERT(pDocument->m_pDocumentClass->getViewIds(pDocument, aViewIds.data(), aViewIds.size()));
CPPUNIT_ASSERT_EQUAL(nId0, aViewIds[0]);
CPPUNIT_ASSERT_EQUAL(nId1, aViewIds[1]);
// Make sure the created view is the active one, then switch to the old
// one.
CPPUNIT_ASSERT_EQUAL(nId1, pDocument->m_pDocumentClass->getView(pDocument));
pDocument->m_pDocumentClass->setView(pDocument, nId0);
CPPUNIT_ASSERT_EQUAL(nId0, pDocument->m_pDocumentClass->getView(pDocument));
pDocument->m_pDocumentClass->destroyView(pDocument, nId1);
CPPUNIT_ASSERT_EQUAL(1, pDocument->m_pDocumentClass->getViewsCount(pDocument));
}
void DesktopLOKTest::testGetPartPageRectangles()
{
// Test that we get as many page rectangles as expected: blank document is
// one page.
LibLODocument_Impl* pDocument = loadDoc("blank_text.odt" );
char * pRectangles = pDocument->pClass->getPartPageRectangles(pDocument);
OUString sRectangles = OUString::fromUtf8(pRectangles);
std::vector<OUString> aRectangles;
sal_Int32 nIndex = 0;
do
{
OUString aRectangle = sRectangles.getToken(0, ';' , nIndex);
if (!aRectangle.isEmpty())
aRectangles.push_back(aRectangle);
}
while (nIndex >= 0);
CPPUNIT_ASSERT_EQUAL(static_cast <size_t>(1), aRectangles.size());
free(pRectangles);
}
void DesktopLOKTest::testGetFilterTypes()
{
LibLibreOffice_Impl aOffice;
char * pJSON = aOffice.m_pOfficeClass->getFilterTypes(&aOffice);
std::stringstream aStream(pJSON);
boost::property_tree::ptree aTree;
boost::property_tree::read_json(aStream, aTree);
CPPUNIT_ASSERT(!aTree.empty());
CPPUNIT_ASSERT_EQUAL(std::string("application/vnd.oasis.opendocument.text" ), aTree.get_child("writer8" ).get_child("MediaType" ).get_value<std::string>());
free(pJSON);
}
void DesktopLOKTest::testSearchCalc()
{
LibLibreOffice_Impl aOffice;
LibLODocument_Impl* pDocument = loadDoc("search.ods" );
pDocument->pClass->initializeForRendering(pDocument, nullptr);
pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this );
uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
{
{"SearchItem.SearchString" , uno::Any(u"foo" _ustr)},
{"SearchItem.Backward" , uno::Any(false )},
{"SearchItem.Command" , uno::Any(static_cast <sal_uInt16>(SvxSearchCmd::FIND_ALL))},
}));
dispatchCommand(mxComponent, u".uno:ExecuteSearch" _ustr, aPropertyValues);
std::vector<OString> aSelections;
sal_Int32 nIndex = 0;
do
{
OString aToken = m_aTextSelection.getToken(0, ';' , nIndex);
aSelections.push_back(aToken);
} while (nIndex >= 0);
// This was 1, find-all only found one match.
CPPUNIT_ASSERT_EQUAL(static_cast <size_t>(2), aSelections.size());
// Make sure that we get exactly as many rectangle lists as matches.
CPPUNIT_ASSERT_EQUAL(static_cast <size_t>(2), m_aSearchResultSelection.size());
// Result is on the first sheet.
CPPUNIT_ASSERT_EQUAL(0, m_aSearchResultPart[0]);
}
void DesktopLOKTest::testSearchAllNotificationsCalc()
{
LibLibreOffice_Impl aOffice;
LibLODocument_Impl* pDocument = loadDoc("search.ods" );
pDocument->pClass->initializeForRendering(pDocument, nullptr);
pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this );
uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
{
{"SearchItem.SearchString" , uno::Any(u"foo" _ustr)},
{"SearchItem.Backward" , uno::Any(false )},
{"SearchItem.Command" , uno::Any(static_cast <sal_uInt16>(SvxSearchCmd::FIND_ALL))},
}));
dispatchCommand(mxComponent, u".uno:ExecuteSearch" _ustr, aPropertyValues);
// This was 1, make sure that we get no notifications about selection changes during search.
CPPUNIT_ASSERT_EQUAL(0, m_nSelectionBeforeSearchResult);
// But we do get the selection afterwards.
CPPUNIT_ASSERT(m_nSelectionAfterSearchResult > 0);
}
void DesktopLOKTest::testPaintTile()
{
LibLODocument_Impl* pDocument = loadDoc("blank_text.odt" );
int nCanvasWidth = 100;
int nCanvasHeight = 300;
sal_Int32 nStride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, nCanvasWidth);
std::vector<unsigned char > aBuffer(nStride * nCanvasHeight);
int nTilePosX = 0;
int nTilePosY = 0;
int nTileWidth = 1000;
int nTileHeight = 3000;
// This used to crash: paintTile() implementation did not handle
// nCanvasWidth != nCanvasHeight correctly, as usually both are just always
// 256.
pDocument->pClass->paintTile(pDocument, aBuffer.data(), nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight);
// This crashed in OutputDevice::DrawDeviceAlphaBitmap().
nCanvasWidth = 200;
nCanvasHeight = 200;
nTileWidth = 4000;
nTileHeight = 4000;
aBuffer.resize(nCanvasWidth * nCanvasHeight * 4);
pDocument->pClass->paintTile(pDocument, aBuffer.data(), nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight);
}
void DesktopLOKTest::testSaveAs()
{
LibLODocument_Impl* pDocument = loadDoc("blank_text.odt" );
CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "png" , nullptr));
}
void DesktopLOKTest::testSaveAsJsonOptions()
{
// Given a document with 3 pages:
LibLODocument_Impl* pDocument = loadDoc("3page.odg" );
// When exporting that document to PDF, skipping the first page:
OString aOptions("{\" PageRange\":{\" type\":\" string\",\" value\":\" 2-\"}}" _ostr);
CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "pdf" , aOptions.getStr()));
std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get();
if (!pPDFium)
return ;
// Then make sure the resulting PDF has 2 pages:
std::unique_ptr<vcl::pdf::PDFiumDocument> pPdfDocument
= parsePDFExport();
// Without the accompanying fix in place, this test would have failed with:
// - Expected: 2
// - Actual : 3
// i.e. FilterOptions was ignored.
CPPUNIT_ASSERT_EQUAL(2, pPdfDocument->getPageCount());
}
void DesktopLOKTest::testSaveAsCalc()
{
LibLODocument_Impl* pDocument = loadDoc("search.ods" );
CPPUNIT_ASSERT(pDocument->pClass->saveAs(pDocument, maTempFile.GetURL().toUtf8().getStr(), "png" , nullptr));
}
void DesktopLOKTest::testPasteWriter()
{
LibLODocument_Impl* pDocument = loadDoc("blank_text.odt" );
OString aText("hello" _ostr);
CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/plain;charset=utf-8" , aText.getStr(), aText.getLength()));
pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll" , nullptr, false );
Scheduler::ProcessEventsToIdle();
char * pText = pDocument->pClass->getTextSelection(pDocument, "text/plain;charset=utf-8" , nullptr);
CPPUNIT_ASSERT_EQUAL("hello" _ostr, OString(pText));
free(pText);
// textt/plain should be rejected.
CPPUNIT_ASSERT(!pDocument->pClass->paste(pDocument, "textt/plain;charset=utf-8" , aText.getStr(), aText.getLength()));
// Writer is expected to support text/html.
CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/html" , aText.getStr(), aText.getLength()));
// Overwrite doc contents with a HTML paste.
pDocument->pClass->postUnoCommand(pDocument, ".uno:SelectAll" , nullptr, false );
Scheduler::ProcessEventsToIdle();
OString aComment("foo baz" _ostr);
CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "text/html" , aComment.getStr(), aComment.getLength()));
// Check if we have a comment.
uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xTextDocument->getText(), uno::UNO_QUERY);
uno::Reference<container::XEnumeration> xParagraphEnumeration = xParagraphEnumerationAccess->createEnumeration();
uno::Reference<container::XEnumerationAccess> xParagraph(xParagraphEnumeration->nextElement(), uno::UNO_QUERY);
uno::Reference<container::XEnumeration> xTextPortionEnumeration = xParagraph->createEnumeration();
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortionEnumeration->nextElement(), uno::UNO_QUERY);
CPPUNIT_ASSERT_EQUAL(u"Text" _ustr, xTextPortion->getPropertyValue(u"TextPortionType" _ustr).get<OUString>());
// Without the accompanying fix in place, this test would have failed, as we had a comment
// between "foo" and "baz".
CPPUNIT_ASSERT(!xTextPortionEnumeration->hasMoreElements());
}
void DesktopLOKTest::testPasteWriterJPEG()
{
LibLODocument_Impl* pDocument = loadDoc("blank_text.odt" );
OUString aFileURL = createFileURL(u"paste.jpg" );
std::ifstream aImageStream(aFileURL.toUtf8().copy(strlen("file://")).getStr());
std::vector<char > aImageContents((std::istreambuf_iterator<char >(aImageStream)), std::istreambuf_iterator<char >());
CPPUNIT_ASSERT(pDocument->pClass->paste(pDocument, "image/jpeg" , aImageContents.data(), aImageContents.size()));
uno::Reference<drawing::XDrawPageSupplier> xDrawPageSupplier(mxComponent, uno::UNO_QUERY);
uno::Reference<drawing::XDrawPage> xDrawPage = xDrawPageSupplier->getDrawPage();
// This was 0, JPEG was not handled as a format for clipboard paste.
CPPUNIT_ASSERT_EQUAL(static_cast <sal_Int32>(1), xDrawPage->getCount());
uno::Reference<beans::XPropertySet> xShape(xDrawPage->getByIndex(0), uno::UNO_QUERY);
// This was text::TextContentAnchorType_AT_PARAGRAPH.
CPPUNIT_ASSERT_EQUAL(text::TextContentAnchorType_AS_CHARACTER, xShape->getPropertyValue(u"AnchorType" _ustr).get<text::TextContentAnchorType>());
// Delete the pasted picture, and paste again with a custom anchor type.
uno::Reference<lang::XComponent>(xShape, uno::UNO_QUERY_THROW)->dispose();
uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence(
{
{"AnchorType" , uno::Any(static_cast <sal_uInt16>(text::TextContentAnchorType_AT_CHARACTER))},
}));
dispatchCommand(mxComponent, u".uno:Paste" _ustr, aPropertyValues);
xShape.set(xDrawPage->getByIndex(0), uno::UNO_QUERY);
// This was text::TextContentAnchorType_AS_CHARACTER, AnchorType argument was ignored.
CPPUNIT_ASSERT_EQUAL(text::TextContentAnchorType_AT_CHARACTER, xShape->getPropertyValue(u"AnchorType" _ustr).get<text::TextContentAnchorType>());
}
void DesktopLOKTest::testUndoWriter()
{
// Load a Writer document and press a key.
LibLODocument_Impl* pDocument = loadDoc("blank_text.odt" );
pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't' , 0);
pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYUP, 't' , 0);
Scheduler::ProcessEventsToIdle();
// Get undo info.
boost::property_tree::ptree aTree;
char * pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:Undo" );
std::stringstream aStream(pJSON);
free(pJSON);
CPPUNIT_ASSERT(!aStream.str().empty());
boost::property_tree::read_json(aStream, aTree);
// Make sure that pressing a key creates exactly one undo action.
CPPUNIT_ASSERT_EQUAL(static_cast <size_t>(1), aTree.get_child("actions" ).size());
}
void DesktopLOKTest::testRowColumnHeaders()
{
/*
* Payload example:
*
* {
* "rows": [
* {
* "size": "254.987250637468",
* "text": "1"
* },
* {
* "size": "509.974501274936",
* "text": "2"
* }
* ],
* "columns": [
* {
* "size": "1274.93625318734",
* "text": "A"
* },
* {
* "size": "2549.87250637468",
* "text": "B"
* }
* ]
* }
*
* "size" defines the bottom/right boundary of a row/column in twips (size between 0 and boundary)
* "text" has the header label in UTF-8
*/
LibLODocument_Impl* pDocument = loadDoc("search.ods" );
pDocument->pClass->initializeForRendering(pDocument, nullptr);
tools::Long nWidth = 0;
tools::Long nHeight = 0;
pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight);
tools::Long nX = rtl::math::round(nWidth / 4.0);
tools::Long nY = rtl::math::round(nHeight / 4.0);
nWidth = rtl::math::round(nWidth / 2.0);
nHeight = rtl::math::round(nHeight / 2.0);
std::stringstream aPayload;
aPayload << ".uno:ViewRowColumnHeaders?x=" << nX << "&y=" << nY << "&width=" << nWidth << "&height=" << nHeight;
boost::property_tree::ptree aTree;
char * pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aPayload.str().c_str());
std::stringstream aStream(pJSON);
free(pJSON);
CPPUNIT_ASSERT(!aStream.str().empty());
boost::property_tree::read_json(aStream, aTree);
sal_Int32 nPrevious = 0;
bool bFirstHeader = true ;
bool bNotEnoughHeaders = true ;
for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("rows" ))
{
sal_Int32 nSize = o3tl::toInt32(rValue.second.get<std::string>("size" ));
nSize = o3tl::convert(nSize, o3tl::Length::px, o3tl::Length::twip);
OString aText(rValue.second.get<std::string>("text" ));
if (bFirstHeader)
{
CPPUNIT_ASSERT(nSize <= nY);
CPPUNIT_ASSERT_EQUAL("10" _ostr, aText);
bFirstHeader = false ;
}
else
{
CPPUNIT_ASSERT(nSize > 0);
CPPUNIT_ASSERT(nPrevious < nSize);
if (nSize > nY + nHeight)
{
bNotEnoughHeaders = false ;
break ;
}
}
nPrevious = nSize;
}
CPPUNIT_ASSERT(!bNotEnoughHeaders);
nPrevious = 0;
bFirstHeader = true ;
bNotEnoughHeaders = true ;
for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("columns" ))
{
sal_Int32 nSize = o3tl::toInt32(rValue.second.get<std::string>("size" ));
nSize = o3tl::convert(nSize, o3tl::Length::px, o3tl::Length::twip);
OString aText(rValue.second.get<std::string>("text" ));
if (bFirstHeader)
{
CPPUNIT_ASSERT(nSize <= nX);
CPPUNIT_ASSERT_EQUAL("3" _ostr, aText);
bFirstHeader = false ;
}
else
{
CPPUNIT_ASSERT(nSize > 0);
CPPUNIT_ASSERT(nPrevious < nSize);
if (nSize > nX + nWidth)
{
bNotEnoughHeaders = false ;
break ;
}
}
nPrevious = nSize;
}
CPPUNIT_ASSERT(!bNotEnoughHeaders);
}
void DesktopLOKTest::testHiddenRowHeaders()
{
LibLODocument_Impl* pDocument = loadDoc("hidden-row.ods" );
pDocument->pClass->initializeForRendering(pDocument, nullptr);
tools::Long const nX = 0;
tools::Long const nY = 0;
tools::Long nWidth = 0;
tools::Long nHeight = 0;
pDocument->m_pDocumentClass->getDocumentSize(pDocument, &nWidth, &nHeight);
std::stringstream aPayload;
aPayload << ".uno:ViewRowColumnHeaders?x=" << nX << "&y=" << nY << "&width=" << nWidth << "&height=" << nHeight;
boost::property_tree::ptree aTree;
char * pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, aPayload.str().c_str());
std::stringstream aStream(pJSON);
CPPUNIT_ASSERT(!aStream.str().empty());
boost::property_tree::read_json(aStream, aTree);
free(pJSON);
sal_Int32 nPrevious = 0;
sal_Int32 nIndex = 0;
for (const boost::property_tree::ptree::value_type& rValue : aTree.get_child("rows" ))
{
sal_Int32 nSize = o3tl::toInt32(rValue.second.get<std::string>("size" ));
if (nIndex++ == 2)
{
// nSize was 510, nPrevious was 255, i.e. hidden row wasn't reported as 0 height.
CPPUNIT_ASSERT_EQUAL(nPrevious, nSize);
break ;
}
nPrevious = nSize;
}
}
void DesktopLOKTest::testCellCursor()
{
LibLODocument_Impl* pDocument = loadDoc("search.ods" );
boost::property_tree::ptree aTree;
char * pJSON = pDocument->m_pDocumentClass->getCommandValues(pDocument, ".uno:CellCursor?tileWidth=1&tileHeight=1&outputWidth=1&outputHeight=1" );
std::stringstream aStream(pJSON);
free(pJSON);
CPPUNIT_ASSERT(!aStream.str().empty());
boost::property_tree::read_json(aStream, aTree);
OString aRectangle(aTree.get<std::string>("commandValues" ));
// cell cursor geometry + col + row
CPPUNIT_ASSERT_EQUAL("0, 0, 1274, 254, 0, 0" _ostr, aRectangle);
}
void DesktopLOKTest::testCommandResult()
{
LibLODocument_Impl* pDocument = loadDoc("blank_text.odt" );
// the postUnoCommand() is supposed to be async, let's test it safely
// [no idea if it is async in reality - most probably we are operating
// under some solar mutex or something anyway ;-) - but...]
TimeValue aTimeValue = { 2 , 0 }; // 2 seconds max
// nothing is triggered when we have no callback yet, we just time out on
// the condition var.
m_aCommandResultCondition.reset();
pDocument->pClass->postUnoCommand(pDocument, ".uno:Bold" , nullptr, true );
Scheduler::ProcessEventsToIdle();
m_aCommandResultCondition.wait(aTimeValue);
CPPUNIT_ASSERT(m_aCommandResult.isEmpty());
// but we get some real values when the callback is set up
pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this );
m_aCommandResultCondition.reset();
pDocument->pClass->postUnoCommand(pDocument, ".uno:Bold" , nullptr, true );
Scheduler::ProcessEventsToIdle();
m_aCommandResultCondition.wait(aTimeValue);
boost::property_tree::ptree aTree;
std::stringstream aStream((std::string(m_aCommandResult)));
boost::property_tree::read_json(aStream, aTree);
CPPUNIT_ASSERT_EQUAL(std::string(".uno:Bold" ), aTree.get_child("commandName" ).get_value<std::string>());
CPPUNIT_ASSERT_EQUAL(true , aTree.get_child("success" ).get_value<bool >());
}
void DesktopLOKTest::testWriterComments()
{
LibLODocument_Impl* pDocument = loadDoc("blank_text.odt" );
pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this );
uno::Reference<awt::XReschedule> xToolkit = css::awt::Toolkit::create(comphelper::getProcessComponentContext());
// Insert a comment at the beginning of the document and wait till the main
// loop grabs the focus, so characters end up in the annotation window.
TimeValue const aTimeValue = {2 , 0}; // 2 seconds max
m_aCommandResultCondition.reset();
pDocument->pClass->postUnoCommand(pDocument, ".uno:InsertAnnotation" , nullptr, true );
Scheduler::ProcessEventsToIdle();
m_aCommandResultCondition.wait(aTimeValue);
CPPUNIT_ASSERT(!m_aCommandResult.isEmpty());
xToolkit->reschedule();
// Test that we have a comment.
uno::Reference<text::XTextDocument> xTextDocument(mxComponent, uno::UNO_QUERY);
uno::Reference<container::XEnumerationAccess> xParagraphEnumerationAccess(xTextDocument->getText(), uno::UNO_QUERY);
uno::Reference<container::XEnumeration> xParagraphEnumeration = xParagraphEnumerationAccess->createEnumeration();
uno::Reference<container::XEnumerationAccess> xParagraph(xParagraphEnumeration->nextElement(), uno::UNO_QUERY);
uno::Reference<container::XEnumeration> xTextPortionEnumeration = xParagraph->createEnumeration();
uno::Reference<beans::XPropertySet> xTextPortion(xTextPortionEnumeration->nextElement(), uno::UNO_QUERY);
CPPUNIT_ASSERT_EQUAL(u"Annotation" _ustr, xTextPortion->getPropertyValue(u"TextPortionType" _ustr).get<OUString>());
// Type "test" and finish editing.
pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't' , 0);
pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 'e' , 0);
pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 's' , 0);
pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 't' , 0);
pDocument->pClass->postKeyEvent(pDocument, LOK_KEYEVENT_KEYINPUT, 0, css::awt::Key::ESCAPE);
Scheduler::ProcessEventsToIdle();
// Test that the typed characters ended up in the right window.
auto xTextField = xTextPortion->getPropertyValue(u"TextField" _ustr).get< uno::Reference<beans::XPropertySet> >();
// This was empty, typed characters ended up in the body text.
CPPUNIT_ASSERT_EQUAL(u"test" _ustr, xTextField->getPropertyValue(u"Content" _ustr).get<OUString>());
}
void DesktopLOKTest::testSheetOperations()
{
LibLODocument_Impl* pDocument = loadDoc("sheets.ods" );
// insert the last sheet
pDocument->pClass->postUnoCommand(pDocument, ".uno:Insert" ,
"{ \" Name\": { \" type\": \" string\", \" value\": \" LastSheet\" }, \" Index\": { \" type\": \" long \", \" value\": 0 } }" , false );
// insert the first sheet
pDocument->pClass->postUnoCommand(pDocument, ".uno:Insert" ,
"{ \" Name\": { \" type\": \" string\", \" value\": \" FirstSheet\" }, \" Index\": { \" type\": \" long \", \" value\": 1 } }" , false );
// rename the \"Sheet1\" (2nd now) to \"Renamed\"
pDocument->pClass->postUnoCommand(pDocument, ".uno:Name" ,
"{ \" Name\": { \" type\": \" string\", \" value\": \" Renamed\" }, \" Index\": { \" type\": \" long \", \" value\": 2 } }" , false );
// delete the \"Sheet2\" (3rd)
pDocument->pClass->postUnoCommand(pDocument, ".uno:Remove" ,
"{ \" Index\": { \" type\": \" long \", \" value\": 3 } }" , false );
Scheduler::ProcessEventsToIdle();
CPPUNIT_ASSERT_EQUAL(6, pDocument->pClass->getParts(pDocument));
std::vector<OString> aExpected = { "FirstSheet" _ostr, "Renamed" _ostr, "Sheet3" _ostr, "Sheet4" _ostr, "Sheet5" _ostr, "LastSheet" _ostr };
for (int i = 0; i < 6; ++i)
{
char * pPartName = pDocument->pClass->getPartName(pDocument, i);
CPPUNIT_ASSERT_EQUAL(aExpected[i], OString(pPartName));
free(pPartName);
}
}
void DesktopLOKTest::testSheetSelections()
{
LibLODocument_Impl* pDocument = loadDoc("sheets.ods" , LOK_DOCTYPE_SPREADSHEET);
pDocument->pClass->initializeForRendering(pDocument, nullptr);
pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this );
/*
* Check if selection data is correct
*/
// Values in twips
int row5 = 1150;
int col1 = 1100;
int const col2 = 2200;
int const col3 = 3300;
int col4 = 4400;
int col5 = 5500;
// Select row 5 from column 1 through column 5
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
col1, row5,
1, 1, 0);
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEMOVE,
col2, row5,
1, 1, 0);
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEMOVE,
col3, row5,
1, 1, 0);
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEMOVE,
col4, row5,
1, 1, 0);
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEMOVE,
col5, row5,
1, 1, 0);
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEBUTTONUP,
col5, row5,
1, 1, 0);
Scheduler::ProcessEventsToIdle();
// Copy the contents and check if matches expected data
{
char * pUsedMimeType = nullptr;
char * pCopiedContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pUsedMimeType);
std::vector<long > aExpected = {5, 6, 7, 8, 9};
std::istringstream iss(pCopiedContent);
for (const long nIndex : aExpected)
{
std::string token;
iss >> token;
CPPUNIT_ASSERT_EQUAL(nIndex, strtol(token.c_str(), nullptr, 10));
}
free(pUsedMimeType);
free(pCopiedContent);
}
/*
* Check if clicking inside the selection deselects the whole selection
*/
// Click at row5, col4
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
col4, row5,
1, 1, 0);
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEBUTTONUP,
col4, row5,
1, 1, 0);
Scheduler::ProcessEventsToIdle();
// Selected text should get deselected and copying should give us
// content of only one cell, now
{
char * pUsedMimeType = nullptr;
char * pCopiedContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pUsedMimeType);
std::vector<long > aExpected = { 8 };
std::istringstream iss(pCopiedContent);
for (const long nIndex : aExpected)
{
std::string token;
iss >> token;
CPPUNIT_ASSERT_EQUAL(nIndex, strtol(token.c_str(), nullptr, 10));
}
free(pUsedMimeType);
free(pCopiedContent);
}
}
void DesktopLOKTest::testSheetDragDrop()
{
LibLODocument_Impl* pDocument = loadDoc("sheets.ods" , LOK_DOCTYPE_SPREADSHEET);
pDocument->pClass->initializeForRendering(pDocument, nullptr);
pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this );
int row01 = 100;
int col01 = 1100;
int col02 = 2200;
int col03 = 3300;
int col05 = 5500;
int col07 = 5700;
// Select row 01 from column 01 through column 05
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
col01, row01,
1, 1, 0);
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEMOVE,
col02, row01,
1, 1, 0);
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEMOVE,
col05, row01,
1, 1, 0);
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEBUTTONUP,
col05, row01,
1, 1, 0);
Scheduler::ProcessEventsToIdle();
{
SfxViewShell* pViewShell = SfxViewShell::Current();
SfxViewFrame& rViewFrame = pViewShell->GetViewFrame();
OUString sValue;
css::uno::Any aValue;
css::util::URL aURL;
std::unique_ptr<SfxPoolItem> pState;
aURL.Protocol = ".uno:" ;
aURL.Complete = ".uno:Address" ;
aURL.Path = "Address" ;
aURL.Main = ".uno:Address" ;
rViewFrame.GetBindings().QueryState(rViewFrame.GetBindings().QuerySlotId(aURL), pState);
pState->QueryValue(aValue);
aValue >>= sValue;
CPPUNIT_ASSERT_EQUAL(u"Sheet5.A1:E1" _ustr, sValue);
}
// Check selection content
{
char * pMimeType = nullptr;
char * pContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pMimeType);
std::vector<long > aExpected = {1, 2, 3, 4, 5};
std::istringstream aContent(pContent);
std::string token;
for (const long nIndex : aExpected)
{
aContent >> token;
CPPUNIT_ASSERT_EQUAL(nIndex, strtol(token.c_str(), nullptr, 10));
}
free(pMimeType);
free(pContent);
}
// drag and drop
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
col01, row01,
1, 1, 0);
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEMOVE,
col02, row01,
1, 1, 0);
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEMOVE,
col03, row01,
1, 1, 0);
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEBUTTONUP,
col07, row01,
1, 1, 0);
Scheduler::ProcessEventsToIdle();
{
SfxViewShell* pViewShell = SfxViewShell::Current();
SfxViewFrame& rViewFrame = pViewShell->GetViewFrame();
OUString sValue;
css::uno::Any aValue;
css::util::URL aURL;
std::unique_ptr<SfxPoolItem> pState;
aURL.Protocol = ".uno:" ;
aURL.Complete = ".uno:Address" ;
aURL.Path = "Address" ;
aURL.Main = ".uno:Address" ;
rViewFrame.GetBindings().QueryState(rViewFrame.GetBindings().QuerySlotId(aURL), pState);
pState->QueryValue(aValue);
aValue >>= sValue;
CPPUNIT_ASSERT_EQUAL(u"Sheet5.D1:H1" _ustr, sValue);
}
// Check selection content
{
char * pMimeType = nullptr;
char * pContent = pDocument->pClass->getTextSelection(pDocument, nullptr, &pMimeType);
std::vector<long > aExpected = {1, 2, 3, 4, 5};
std::istringstream aContent(pContent);
std::string token;
for (const long nIndex : aExpected)
{
aContent >> token;
CPPUNIT_ASSERT_EQUAL(nIndex, strtol(token.c_str(), nullptr, 10));
}
free(pMimeType);
free(pContent);
}
}
namespace {
void verifyContextMenuStructure(boost::property_tree::ptree& aRoot)
{
for (const auto & aItemPair: aRoot)
{
// This is an array, so no key
CPPUNIT_ASSERT_EQUAL(aItemPair.first, std::string("" ));
boost::property_tree::ptree aItemValue = aItemPair.second;
boost::optional<boost::property_tree::ptree&> aText = aItemValue.get_child_optional("text" );
boost::optional<boost::property_tree::ptree&> aType = aItemValue.get_child_optional("type" );
boost::optional<boost::property_tree::ptree&> aCommand = aItemValue.get_child_optional("command" );
boost::optional<boost::property_tree::ptree&> aSubmenu = aItemValue.get_child_optional("menu" );
boost::optional<boost::property_tree::ptree&> aEnabled = aItemValue.get_child_optional("enabled" );
boost::optional<boost::property_tree::ptree&> aChecktype = aItemValue.get_child_optional("checktype" );
boost::optional<boost::property_tree::ptree&> aChecked = aItemValue.get_child_optional("checked" );
// type is omnipresent
CPPUNIT_ASSERT( aType );
// separator doesn't have any other attribs
if ( aType.get().data() == "separator" )
{
CPPUNIT_ASSERT( !aText );
CPPUNIT_ASSERT( !aCommand );
CPPUNIT_ASSERT( !aSubmenu );
CPPUNIT_ASSERT( !aEnabled );
CPPUNIT_ASSERT( !aChecktype );
CPPUNIT_ASSERT( !aChecked );
}
else if ( aType.get().data() == "command" )
{
CPPUNIT_ASSERT( aCommand );
CPPUNIT_ASSERT( aText );
}
else if ( aType.get().data() == "menu" )
{
CPPUNIT_ASSERT( aSubmenu );
CPPUNIT_ASSERT( aText );
verifyContextMenuStructure( aSubmenu.get() );
}
if ( aChecktype )
{
CPPUNIT_ASSERT( aChecktype.get().data() == "radio" ||
aChecktype.get().data() == "checkmark" ||
aChecktype.get().data() == "auto" );
CPPUNIT_ASSERT( aChecked );
CPPUNIT_ASSERT( aChecked.get().data() == "true" || aChecked.get().data() == "false" );
}
}
}
boost::optional<boost::property_tree::ptree>
getContextMenuItem(boost::property_tree::ptree& aMenu, std::string const & unoSelector)
{
boost::optional<boost::property_tree::ptree> aMenuItem;
for (const auto & aItemPair: aMenu)
{
boost::property_tree::ptree aItemValue = aItemPair.second;
boost::optional<boost::property_tree::ptree&> aCommand = aItemValue.get_child_optional("command" );
if (aCommand && aCommand.get().data() == unoSelector )
{
aMenuItem = aItemValue;
break ;
}
}
return aMenuItem;
}
} // end anonymous namespace
void DesktopLOKTest::testContextMenuCalc()
{
LibLODocument_Impl* pDocument = loadDoc("sheet_with_image.ods" , LOK_DOCTYPE_SPREADSHEET);
pDocument->pClass->initializeForRendering(pDocument, nullptr);
pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this );
// Values in twips
Point aPointOnImage(1150, 1100);
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
aPointOnImage.X(), aPointOnImage.Y(),
1, 4, 0);
Scheduler::ProcessEventsToIdle();
TimeValue const aTimeValue = {2 , 0}; // 2 seconds max
m_aContextMenuCondition.wait(aTimeValue);
CPPUNIT_ASSERT( !m_aContextMenuResult.empty() );
boost::optional<boost::property_tree::ptree&> aMenu = m_aContextMenuResult.get_child_optional("menu" );
CPPUNIT_ASSERT( aMenu );
verifyContextMenuStructure( aMenu.get() );
// tests for calc specific context menu
// Cut is enabled
{
boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut" );
CPPUNIT_ASSERT(aMenuItem);
boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled" );
CPPUNIT_ASSERT(aEnabled);
CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true" ));
}
// Copy is enabled
{
boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy" );
CPPUNIT_ASSERT(aMenuItem);
boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled" );
CPPUNIT_ASSERT(aEnabled);
CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true" ));
}
// Paste is enabled
{
boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste" );
CPPUNIT_ASSERT(aMenuItem);
boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled" );
CPPUNIT_ASSERT(aEnabled);
CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true" ));
}
// Remove hyperlink is disabled
{
boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:RemoveHyperlink" );
CPPUNIT_ASSERT(aMenuItem);
boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled" );
CPPUNIT_ASSERT(aEnabled);
CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false" ));
}
// open hyperlink is disabled
{
boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:OpenHyperlinkOnCursor" );
CPPUNIT_ASSERT(aMenuItem);
boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled" );
CPPUNIT_ASSERT(aEnabled);
CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false" ));
}
// checkbutton tests
{
boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:AnchorMenu" );
CPPUNIT_ASSERT(aMenuItem);
boost::optional<boost::property_tree::ptree&> aSubmenu = aMenuItem.get().get_child_optional("menu" );
CPPUNIT_ASSERT(aSubmenu);
boost::optional<boost::property_tree::ptree> aMenuItemToPage = getContextMenuItem(aSubmenu.get(), ".uno:SetAnchorToPage" );
CPPUNIT_ASSERT(aMenuItemToPage);
boost::optional<boost::property_tree::ptree> aMenuItemToCell = getContextMenuItem(aSubmenu.get(), ".uno:SetAnchorToCell" );
CPPUNIT_ASSERT(aMenuItemToCell);
// these are radio buttons
boost::optional<boost::property_tree::ptree&> aChecktypeToPage = aMenuItemToPage.get().get_child_optional("checktype" );
CPPUNIT_ASSERT(aChecktypeToPage);
CPPUNIT_ASSERT_EQUAL(aChecktypeToPage.get().data(), std::string("radio" ));
boost::optional<boost::property_tree::ptree&> aChecktypeToCell = aMenuItemToCell.get().get_child_optional("checktype" );
CPPUNIT_ASSERT(aChecktypeToCell);
CPPUNIT_ASSERT_EQUAL(aChecktypeToCell.get().data(), std::string("radio" ));
// ToPage is checked
boost::optional<boost::property_tree::ptree&> aCheckedToPage = aMenuItemToPage.get().get_child_optional("checked" );
CPPUNIT_ASSERT(aCheckedToPage);
CPPUNIT_ASSERT_EQUAL(aCheckedToPage.get().data(), std::string("true" ));
// ToCell is unchecked
boost::optional<boost::property_tree::ptree&> aCheckedToCell = aMenuItemToCell.get().get_child_optional("checked" );
CPPUNIT_ASSERT(aCheckedToCell);
CPPUNIT_ASSERT_EQUAL(aCheckedToCell.get().data(), std::string("false" ));
}
}
void DesktopLOKTest::testContextMenuWriter()
{
LibLODocument_Impl* pDocument = loadDoc("blank_text.odt" );
pDocument->pClass->initializeForRendering(pDocument, nullptr);
pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this );
Point aRandomPoint(1150, 1100);
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
aRandomPoint.X(), aRandomPoint.Y(),
1, 4, 0);
Scheduler::ProcessEventsToIdle();
TimeValue const aTimeValue = {2 , 0}; // 2 seconds max
m_aContextMenuCondition.wait(aTimeValue);
CPPUNIT_ASSERT( !m_aContextMenuResult.empty() );
boost::optional<boost::property_tree::ptree&> aMenu = m_aContextMenuResult.get_child_optional("menu" );
CPPUNIT_ASSERT( aMenu );
verifyContextMenuStructure( aMenu.get() );
// tests for writer specific context menu
// Cut is disabled
{
boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut" );
CPPUNIT_ASSERT(aMenuItem);
boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled" );
CPPUNIT_ASSERT(aEnabled);
CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false" ));
}
// Copy is disabled
{
boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy" );
CPPUNIT_ASSERT(aMenuItem);
boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled" );
CPPUNIT_ASSERT(aEnabled);
CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false" ));
}
// Paste is enabled
{
boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste" );
CPPUNIT_ASSERT(aMenuItem);
boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled" );
CPPUNIT_ASSERT(aEnabled);
CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true" ));
}
}
void DesktopLOKTest::testContextMenuImpress()
{
LibLODocument_Impl* pDocument = loadDoc("blank_presentation.odp" , LOK_DOCTYPE_PRESENTATION);
pDocument->pClass->initializeForRendering(pDocument, nullptr);
pDocument->pClass->registerCallback(pDocument, &DesktopLOKTest::callback, this );
// random point where we don't hit an underlying comment or text box
Point aRandomPoint(10, 1150);
pDocument->pClass->postMouseEvent(pDocument,
LOK_MOUSEEVENT_MOUSEBUTTONDOWN,
aRandomPoint.X(), aRandomPoint.Y(),
1, 4, 0);
Scheduler::ProcessEventsToIdle();
TimeValue const aTimeValue = {2 , 0}; // 2 seconds max
m_aContextMenuCondition.wait(aTimeValue);
CPPUNIT_ASSERT( !m_aContextMenuResult.empty() );
boost::optional<boost::property_tree::ptree&> aMenu = m_aContextMenuResult.get_child_optional("menu" );
CPPUNIT_ASSERT( aMenu );
verifyContextMenuStructure( aMenu.get() );
// tests for impress specific context menu
// Cut is disabled
{
boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Cut" );
CPPUNIT_ASSERT(aMenuItem);
boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled" );
CPPUNIT_ASSERT(aEnabled);
CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false" ));
}
// Copy is disabled
{
boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Copy" );
CPPUNIT_ASSERT(aMenuItem);
boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled" );
CPPUNIT_ASSERT(aEnabled);
CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false" ));
}
// Paste is enabled
{
boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:Paste" );
CPPUNIT_ASSERT(aMenuItem);
boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled" );
CPPUNIT_ASSERT(aEnabled);
CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("true" ));
}
// SaveBackground is disabled
{
boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:SaveBackground" );
CPPUNIT_ASSERT(aMenuItem);
boost::optional<boost::property_tree::ptree&> aEnabled = aMenuItem.get().get_child_optional("enabled" );
CPPUNIT_ASSERT(aEnabled);
CPPUNIT_ASSERT_EQUAL(aEnabled.get().data(), std::string("false" ));
}
// checkbutton tests
{
boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:ShowRuler" );
CPPUNIT_ASSERT(aMenuItem);
boost::optional<boost::property_tree::ptree&> aChecktype = aMenuItem.get().get_child_optional("checktype" );
CPPUNIT_ASSERT(aChecktype);
CPPUNIT_ASSERT_EQUAL(aChecktype.get().data(), std::string("checkmark" ));
boost::optional<boost::property_tree::ptree&> aChecked = aMenuItem.get().get_child_optional("checked" );
CPPUNIT_ASSERT(aChecked);
CPPUNIT_ASSERT_EQUAL(aChecked.get().data(), std::string("false" ));
}
// Checkbutton tests inside SnapLines submenu
{
boost::optional<boost::property_tree::ptree> aMenuItem = getContextMenuItem(aMenu.get(), ".uno:SnapLinesMenu" );
CPPUNIT_ASSERT(aMenuItem);
boost::optional<boost::property_tree::ptree&> aSubmenu = aMenuItem.get().get_child_optional("menu" );
CPPUNIT_ASSERT(aSubmenu);
boost::optional<boost::property_tree::ptree> aMenuItemHelpVis = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesVisible" );
CPPUNIT_ASSERT(aMenuItemHelpVis);
boost::optional<boost::property_tree::ptree> aMenuItemHelpUse = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesUse" );
CPPUNIT_ASSERT(aMenuItemHelpUse);
boost::optional<boost::property_tree::ptree> aMenuItemHelpFront = getContextMenuItem(aSubmenu.get(), ".uno:HelplinesFront" );
CPPUNIT_ASSERT(aMenuItemHelpFront);
// these are checkmarks
boost::optional<boost::property_tree::ptree&> aChecktypeHelpVis = aMenuItemHelpVis.get().get_child_optional("checktype" );
CPPUNIT_ASSERT(aChecktypeHelpVis);
CPPUNIT_ASSERT_EQUAL(aChecktypeHelpVis.get().data(), std::string("checkmark" ));
boost::optional<boost::property_tree::ptree&> aChecktypeHelpUse = aMenuItemHelpUse.get().get_child_optional("checktype" );
CPPUNIT_ASSERT(aChecktypeHelpUse);
CPPUNIT_ASSERT_EQUAL(aChecktypeHelpUse.get().data(), std::string("checkmark" ));
boost::optional<boost::property_tree::ptree&> aChecktypeHelpFront = aMenuItemHelpFront.get().get_child_optional("checktype" );
CPPUNIT_ASSERT(aChecktypeHelpFront);
CPPUNIT_ASSERT_EQUAL(aChecktypeHelpFront.get().data(), std::string("checkmark" ));
// HelplineVisible is unchecked
boost::optional<boost::property_tree::ptree&> aCheckedHelpVis = aMenuItemHelpVis.get().get_child_optional("checked" );
CPPUNIT_ASSERT(aCheckedHelpVis);
CPPUNIT_ASSERT_EQUAL(aCheckedHelpVis.get().data(), std::string("false" ));
// HelplineUse is checked
boost::optional<boost::property_tree::ptree&> aCheckedHelpUse = aMenuItemHelpUse.get().get_child_optional("checked" );
CPPUNIT_ASSERT(aCheckedHelpUse);
CPPUNIT_ASSERT_EQUAL(aCheckedHelpUse.get().data(), std::string("true" ));
// HelplineFront is checked
boost::optional<boost::property_tree::ptree&> aCheckedHelpFront = aMenuItemHelpFront.get().get_child_optional("checked" );
CPPUNIT_ASSERT(aCheckedHelpFront);
CPPUNIT_ASSERT_EQUAL(aCheckedHelpFront.get().data(), std::string("true" ));
}
}
static void callbackCompressionTest(const int type, const char * payload, void * data)
{
std::vector<std::tuple<int , std::string>>* notifs = static_cast <std::vector<std::tuple<int , std::string>>*>(data);
notifs->emplace_back(type, std::string(payload ? payload : "(nil)" ));
}
void DesktopLOKTest::testNotificationCompression()
{
LibLODocument_Impl* pDocument = loadDoc("blank_text.odt" );
std::vector<std::tuple<int , std::string>> notifs;
std::unique_ptr<CallbackFlushHandler> handler(new CallbackFlushHandler(pDocument, callbackCompressionTest, ¬ifs));
handler->setViewId(SfxLokHelper::getView());
handler->queue(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, "" _ostr); // 0
handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10" _ostr); // Superseded.
handler->queue(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, "" _ostr); // Should be dropped.
handler->queue(LOK_CALLBACK_INVALIDATE_TILES, "15, 25, 15, 10" _ostr); // 1
handler->queue(LOK_CALLBACK_TEXT_SELECTION, "15, 25, 15, 10" _ostr); // Should be dropped.
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 C=93 H=95 G=93
¤ Dauer der Verarbeitung: 0.18 Sekunden
¤
*© Formatika GbR, Deutschland