diff options
Diffstat (limited to 'desktop/source/lib/init.cxx')
-rw-r--r-- | desktop/source/lib/init.cxx | 917 |
1 files changed, 833 insertions, 84 deletions
diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx index 5b4434b75e22..aa717dfe17cf 100644 --- a/desktop/source/lib/init.cxx +++ b/desktop/source/lib/init.cxx @@ -7,6 +7,8 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "sfx2/lokhelper.hxx" +#include <config_buildconfig.h> #include <config_features.h> #include <stdio.h> @@ -24,6 +26,10 @@ #include <postmac.h> #endif +#ifdef LINUX +#include <fcntl.h> +#endif + #ifdef ANDROID #include <osl/detail/android-bootstrap.h> #endif @@ -51,9 +57,11 @@ #include <rtl/strbuf.hxx> #include <rtl/uri.hxx> #include <svl/zforlist.hxx> +#include <linguistic/misc.hxx> #include <cppuhelper/bootstrap.hxx> #include <comphelper/base64.hxx> #include <comphelper/dispatchcommand.hxx> +#include <comphelper/propertysequence.hxx> #include <comphelper/lok.hxx> #include <comphelper/processfactory.hxx> #include <comphelper/string.hxx> @@ -62,11 +70,13 @@ #include <comphelper/propertyvalue.hxx> #include <comphelper/scopeguard.hxx> #include <comphelper/threadpool.hxx> +#include <comphelper/servicehelper.hxx> #include <comphelper/sequenceashashmap.hxx> #include <com/sun/star/document/MacroExecMode.hpp> #include <com/sun/star/beans/XPropertySet.hpp> #include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/document/XDocumentLanguages.hpp> #include <com/sun/star/frame/Desktop.hpp> #include <com/sun/star/frame/DispatchResultEvent.hpp> #include <com/sun/star/frame/DispatchResultState.hpp> @@ -92,8 +102,10 @@ #include <com/sun/star/xml/crypto/XCertificateCreator.hpp> #include <com/sun/star/security/XCertificate.hpp> +#include <com/sun/star/linguistic2/LanguageGuessing.hpp> #include <com/sun/star/linguistic2/LinguServiceManager.hpp> #include <com/sun/star/linguistic2/XSpellChecker.hpp> +#include <com/sun/star/linguistic2/XProofreader.hpp> #include <com/sun/star/i18n/LocaleCalendar2.hpp> #include <com/sun/star/i18n/ScriptType.hpp> #include <com/sun/star/lang/DisposedException.hpp> @@ -109,7 +121,7 @@ #include <sfx2/viewfrm.hxx> #include <sfx2/msgpool.hxx> #include <sfx2/dispatch.hxx> -#include <sfx2/lokcharthelper.hxx> +#include <sfx2/lokcomponenthelpers.hxx> #include <sfx2/DocumentSigner.hxx> #include <sfx2/sidebar/SidebarDockingWindow.hxx> #include <sfx2/sidebar/SidebarController.hxx> @@ -128,6 +140,7 @@ #include <svtools/ctrltool.hxx> #include <svtools/langtab.hxx> #include <svtools/languagetoolcfg.hxx> +#include <svtools/deeplcfg.hxx> #include <vcl/fontcharmap.hxx> #include <vcl/graphicfilter.hxx> #ifdef IOS @@ -137,6 +150,9 @@ #include <vcl/ImageTree.hxx> #include <vcl/ITiledRenderable.hxx> #include <vcl/dialoghelper.hxx> +#ifdef _WIN32 +#include <vcl/BitmapReadAccess.hxx> +#endif #include <unicode/uchar.h> #include <unotools/securityoptions.hxx> #include <unotools/configmgr.hxx> @@ -161,6 +177,7 @@ // Needed for getUndoManager() #include <com/sun/star/document/XUndoManager.hpp> #include <com/sun/star/document/XUndoManagerSupplier.hpp> +#include <com/sun/star/document/XLinkTargetSupplier.hpp> #include <editeng/sizeitem.hxx> #include <svx/rulritem.hxx> #include <svx/pageitem.hxx> @@ -383,6 +400,97 @@ std::vector<beans::PropertyValue> desktop::jsonToPropertyValuesVector(const char return aArguments; } +static bool extractLinks(const uno::Reference< container::XNameAccess >& xLinks, bool subcontent, OUStringBuffer& jsonText) +{ + const uno::Sequence< OUString > aNames( xLinks->getElementNames() ); + + const sal_uLong nLinks = aNames.getLength(); + const OUString* pNames = aNames.getConstArray(); + const OUString aProp_LinkDisplayName( "LinkDisplayName" ); + const OUString aProp_LinkTarget( "com.sun.star.document.LinkTarget" ); + bool bIsTarget = false; + for( sal_uLong i = 0; i < nLinks; i++ ) + { + uno::Any aAny; + OUString aLink( *pNames++ ); + + bool bError = false; + try + { + aAny = xLinks->getByName( aLink ); + } + catch(const uno::Exception&) + { + // if the name of the target was invalid (like empty headings) + // no object can be provided + bError = true; + } + if(bError) + continue; + + uno::Reference< beans::XPropertySet > xTarget; + if( aAny >>= xTarget ) + { + try + { + // get name to display + aAny = xTarget->getPropertyValue( aProp_LinkDisplayName ); + OUString aDisplayName; + aAny >>= aDisplayName; + OUString aStrDisplayname ( aDisplayName ); + + if (subcontent) + { + jsonText.append("\""); + jsonText.append(aStrDisplayname); + jsonText.append("\": \""); + jsonText.append(aLink); + jsonText.append("\""); + if (i < nLinks-1) + { + jsonText.append(", "); + } + } + else + { + uno::Reference< lang::XServiceInfo > xSI( xTarget, uno::UNO_QUERY ); + bIsTarget = xSI->supportsService( aProp_LinkTarget ); + if (i != 0) + { + if (!bIsTarget) + jsonText.append("}"); + if (i < nLinks) + { + jsonText.append(", "); + } + } + jsonText.append("\""); + jsonText.append(aStrDisplayname); + jsonText.append("\": "); + + if (bIsTarget) + { + jsonText.append("true"); + continue; + } + jsonText.append("{"); + } + + uno::Reference< document::XLinkTargetSupplier > xLTS( xTarget, uno::UNO_QUERY ); + if( xLTS.is() ) + { + extractLinks(xLTS->getLinks(), true, jsonText); + } + } + catch(...) + { + SAL_WARN("lok", "extractLinks: Exception"); + } + } + } + return bIsTarget; +} + static void unoAnyToJson(tools::JsonWriter& rJson, const char * pNodeName, const uno::Any& anyItem) { auto aNode = rJson.startNode(pNodeName); @@ -421,12 +529,26 @@ RectangleAndPart RectangleAndPart::Create(const std::string& rPayload) { aRet.m_aRectangle = tools::Rectangle(0, 0, SfxLokHelper::MaxTwips, SfxLokHelper::MaxTwips); if (comphelper::LibreOfficeKit::isPartInInvalidation()) - aRet.m_nPart = std::stol(rPayload.substr(6)); + { + int nSeparatorPos = rPayload.find(',', 6); + bool bHasMode = nSeparatorPos > 0; + if (bHasMode) + { + aRet.m_nPart = std::stol(rPayload.substr(6, nSeparatorPos - 6)); + assert(rPayload.length() > o3tl::make_unsigned(nSeparatorPos)); + aRet.m_nMode = std::stol(rPayload.substr(nSeparatorPos + 1)); + } + else + { + aRet.m_nPart = std::stol(rPayload.substr(6)); + aRet.m_nMode = 0; + } + } return aRet; } - // Read '<left>, <top>, <width>, <height>[, <part>]'. C++ streams are simpler but slower. + // Read '<left>, <top>, <width>, <height>[, <part>, <mode>]'. C++ streams are simpler but slower. const char* pos = rPayload.c_str(); const char* end = rPayload.c_str() + rPayload.size(); tools::Long nLeft = rtl_str_toInt64_WithLength(pos, 10, end - pos); @@ -446,6 +568,7 @@ RectangleAndPart RectangleAndPart::Create(const std::string& rPayload) assert(pos < end); tools::Long nHeight = rtl_str_toInt64_WithLength(pos, 10, end - pos); tools::Long nPart = INT_MIN; + tools::Long nMode = 0; if (comphelper::LibreOfficeKit::isPartInInvalidation()) { while( *pos != ',' ) @@ -453,10 +576,20 @@ RectangleAndPart RectangleAndPart::Create(const std::string& rPayload) ++pos; assert(pos < end); nPart = rtl_str_toInt64_WithLength(pos, 10, end - pos); + + while( *pos && *pos != ',' ) + ++pos; + if (*pos) + { + ++pos; + assert(pos < end); + nMode = rtl_str_toInt64_WithLength(pos, 10, end - pos); + } } aRet.m_aRectangle = SanitizedRectangle(nLeft, nTop, nWidth, nHeight); aRet.m_nPart = nPart; + aRet.m_nMode = nMode; return aRet; } @@ -878,13 +1011,21 @@ void setupSidebar(std::u16string_view sidebarDeckId = u"") if (!pDockingWin) return; + pViewFrame->ShowChildWindow( SID_SIDEBAR ); + + const rtl::Reference<sfx2::sidebar::SidebarController>& xController + = pDockingWin->GetOrCreateSidebarController(); + + xController->FadeIn(); + xController->RequestOpenDeck(); + if (!sidebarDeckId.empty()) { - pDockingWin->GetSidebarController()->SwitchToDeck(sidebarDeckId); + xController->SwitchToDeck(sidebarDeckId); } else { - pDockingWin->GetSidebarController()->SwitchToDefaultDeck(); + xController->SwitchToDefaultDeck(); } pDockingWin->SyncUpdate(); @@ -963,6 +1104,7 @@ static void doc_selectPart(LibreOfficeKitDocument* pThis, int nPart, int nSelect static void doc_moveSelectedParts(LibreOfficeKitDocument* pThis, int nPosition, bool bDuplicate); static char* doc_getPartName(LibreOfficeKitDocument* pThis, int nPart); static void doc_setPartMode(LibreOfficeKitDocument* pThis, int nPartMode); +static int doc_getEditMode(LibreOfficeKitDocument* pThis); static void doc_paintTile(LibreOfficeKitDocument* pThis, unsigned char* pBuffer, const int nCanvasWidth, const int nCanvasHeight, @@ -978,6 +1120,7 @@ static void doc_paintTileToCGContext(LibreOfficeKitDocument* pThis, static void doc_paintPartTile(LibreOfficeKitDocument* pThis, unsigned char* pBuffer, const int nPart, + const int nMode, const int nCanvasWidth, const int nCanvasHeight, const int nTilePosX, const int nTilePosY, const int nTileWidth, const int nTileHeight); @@ -985,6 +1128,10 @@ static int doc_getTileMode(LibreOfficeKitDocument* pThis); static void doc_getDocumentSize(LibreOfficeKitDocument* pThis, long* pWidth, long* pHeight); +static void doc_getDataArea(LibreOfficeKitDocument* pThis, + long nTab, + long* pCol, + long* pRow); static void doc_initializeForRendering(LibreOfficeKitDocument* pThis, const char* pArguments); @@ -1154,6 +1301,8 @@ static bool doc_renderSearchResult(LibreOfficeKitDocument* pThis, static void doc_sendContentControlEvent(LibreOfficeKitDocument* pThis, const char* pArguments); +static void doc_setViewTimezone(LibreOfficeKitDocument* pThis, int nId, const char* timezone); + } // extern "C" namespace { @@ -1230,6 +1379,7 @@ LibLODocument_Impl::LibLODocument_Impl(const uno::Reference <css::lang::XCompone m_pDocumentClass->moveSelectedParts = doc_moveSelectedParts; m_pDocumentClass->getPartName = doc_getPartName; m_pDocumentClass->setPartMode = doc_setPartMode; + m_pDocumentClass->getEditMode = doc_getEditMode; m_pDocumentClass->paintTile = doc_paintTile; #ifdef IOS m_pDocumentClass->paintTileToCGContext = doc_paintTileToCGContext; @@ -1237,6 +1387,7 @@ LibLODocument_Impl::LibLODocument_Impl(const uno::Reference <css::lang::XCompone m_pDocumentClass->paintPartTile = doc_paintPartTile; m_pDocumentClass->getTileMode = doc_getTileMode; m_pDocumentClass->getDocumentSize = doc_getDocumentSize; + m_pDocumentClass->getDataArea = doc_getDataArea; m_pDocumentClass->initializeForRendering = doc_initializeForRendering; m_pDocumentClass->registerCallback = doc_registerCallback; m_pDocumentClass->postKeyEvent = doc_postKeyEvent; @@ -1300,6 +1451,8 @@ LibLODocument_Impl::LibLODocument_Impl(const uno::Reference <css::lang::XCompone m_pDocumentClass->sendContentControlEvent = doc_sendContentControlEvent; + m_pDocumentClass->setViewTimezone = doc_setViewTimezone; + gDocumentClass = m_pDocumentClass; } pClass = m_pDocumentClass.get(); @@ -1451,9 +1604,9 @@ void CallbackFlushHandler::libreOfficeKitViewCallbackWithViewId(int nType, const queue(nType, callbackData); } -void CallbackFlushHandler::libreOfficeKitViewInvalidateTilesCallback(const tools::Rectangle* pRect, int nPart) +void CallbackFlushHandler::libreOfficeKitViewInvalidateTilesCallback(const tools::Rectangle* pRect, int nPart, int nMode) { - CallbackData callbackData(pRect, nPart); + CallbackData callbackData(pRect, nPart, nMode); queue(LOK_CALLBACK_INVALIDATE_TILES, callbackData); } @@ -1473,6 +1626,29 @@ void CallbackFlushHandler::libreOfficeKitViewUpdatedCallbackPerViewId(int nType, setUpdatedTypePerViewId(nType, nViewId, nSourceViewId, true); } +void CallbackFlushHandler::dumpState(rtl::OStringBuffer &rState) +{ + // NB. no locking + rState.append("\nView:\t"); + rState.append(static_cast<sal_Int32>(m_viewId)); + rState.append("\n\tDisableCallbacks:\t"); + rState.append(static_cast<sal_Int32>(m_nDisableCallbacks)); + rState.append("\n\tStates:\n"); + for (const auto &i : m_states) + { + rState.append("\n\t\t"); + rState.append(static_cast<sal_Int32>(i.first)); + rState.append("\t"); + rState.append(i.second); + } +} + +void CallbackFlushHandler::libreOfficeKitViewAddPendingInvalidateTiles() +{ + // Invoke() will call flushPendingLOKInvalidateTiles(), so just make sure the timer is active. + startTimer(); +} + void CallbackFlushHandler::queue(const int type, const char* data) { CallbackData callbackData(data); @@ -1514,6 +1690,7 @@ void CallbackFlushHandler::queue(const int type, CallbackData& aCallbackData) type != LOK_CALLBACK_TEXT_SELECTION && type != LOK_CALLBACK_TEXT_SELECTION_START && type != LOK_CALLBACK_TEXT_SELECTION_END && + type != LOK_CALLBACK_MEDIA_SHAPE && type != LOK_CALLBACK_REFERENCE_MARKS) { SAL_INFO("lok", "Skipping while painting [" << type << "]: [" << aCallbackData.getPayload() << "]."); @@ -1766,14 +1943,15 @@ bool CallbackFlushHandler::processInvalidateTilesEvent(int type, CallbackData& a { auto pos2 = toQueue2(pos); const RectangleAndPart& rcOld = pos2->getRectangleAndPart(); - if (rcOld.isInfinite() && (rcOld.m_nPart == -1 || rcOld.m_nPart == rcNew.m_nPart)) + if (rcOld.isInfinite() && (rcOld.m_nPart == -1 || rcOld.m_nPart == rcNew.m_nPart) && + (rcOld.m_nMode == rcNew.m_nMode)) { SAL_INFO("lok", "Skipping queue [" << type << "]: [" << aCallbackData.getPayload() << "] since all tiles need to be invalidated."); return true; } - if (rcOld.m_nPart == -1 || rcOld.m_nPart == rcNew.m_nPart) + if ((rcOld.m_nPart == -1 || rcOld.m_nPart == rcNew.m_nPart) && (rcOld.m_nMode == rcNew.m_nMode)) { // If fully overlapping. if (rcOld.m_aRectangle.Contains(rcNew.m_aRectangle)) @@ -1791,7 +1969,8 @@ bool CallbackFlushHandler::processInvalidateTilesEvent(int type, CallbackData& a << "] so removing all with part " << rcNew.m_nPart << "."); removeAll(LOK_CALLBACK_INVALIDATE_TILES, [&rcNew](const CallbackData& elemData) { // Remove exiting if new is all-encompassing, or if of the same part. - return (rcNew.m_nPart == -1 || rcNew.m_nPart == elemData.getRectangleAndPart().m_nPart); + return ((rcNew.m_nPart == -1 || rcNew.m_nPart == elemData.getRectangleAndPart().m_nPart) + && (rcNew.m_nMode == elemData.getRectangleAndPart().m_nMode)); }); } else @@ -1801,7 +1980,8 @@ bool CallbackFlushHandler::processInvalidateTilesEvent(int type, CallbackData& a SAL_INFO("lok", "Have [" << type << "]: [" << aCallbackData.getPayload() << "] so merging overlapping."); removeAll(LOK_CALLBACK_INVALIDATE_TILES,[&rcNew](const CallbackData& elemData) { const RectangleAndPart& rcOld = elemData.getRectangleAndPart(); - if (rcNew.m_nPart != -1 && rcOld.m_nPart != -1 && rcOld.m_nPart != rcNew.m_nPart) + if (rcNew.m_nPart != -1 && rcOld.m_nPart != -1 && + (rcOld.m_nPart != rcNew.m_nPart || rcOld.m_nMode != rcNew.m_nMode)) { SAL_INFO("lok", "Nothing to merge between new: " << rcNew.toString() << ", and old: " << rcOld.toString()); @@ -1813,7 +1993,7 @@ bool CallbackFlushHandler::processInvalidateTilesEvent(int type, CallbackData& a // Don't merge unless fully overlapped. SAL_INFO("lok", "New " << rcNew.toString() << " has " << rcOld.toString() << "?"); - if (rcNew.m_aRectangle.Contains(rcOld.m_aRectangle)) + if (rcNew.m_aRectangle.Contains(rcOld.m_aRectangle) && rcOld.m_nMode == rcNew.m_nMode) { SAL_INFO("lok", "New " << rcNew.toString() << " engulfs old " << rcOld.toString() << "."); @@ -1825,7 +2005,7 @@ bool CallbackFlushHandler::processInvalidateTilesEvent(int type, CallbackData& a // Don't merge unless fully overlapped. SAL_INFO("lok", "Old " << rcOld.toString() << " has " << rcNew.toString() << "?"); - if (rcOld.m_aRectangle.Contains(rcNew.m_aRectangle)) + if (rcOld.m_aRectangle.Contains(rcNew.m_aRectangle) && rcOld.m_nMode == rcNew.m_nMode) { SAL_INFO("lok", "New " << rcNew.toString() << " engulfs old " << rcOld.toString() << "."); @@ -1836,7 +2016,7 @@ bool CallbackFlushHandler::processInvalidateTilesEvent(int type, CallbackData& a { const tools::Rectangle rcOverlap = rcNew.m_aRectangle.GetIntersection(rcOld.m_aRectangle); - const bool bOverlap = !rcOverlap.IsEmpty(); + const bool bOverlap = !rcOverlap.IsEmpty() && rcOld.m_nMode == rcNew.m_nMode; SAL_INFO("lok", "Merging " << rcNew.toString() << " & " << rcOld.toString() << " => " << rcOverlap.toString() << " Overlap: " << bOverlap); @@ -2106,11 +2286,15 @@ void CallbackFlushHandler::enqueueUpdatedTypes() void CallbackFlushHandler::enqueueUpdatedType( int type, const SfxViewShell* viewShell, int viewId ) { - bool ignore = false; - OString payload = viewShell->getLOKPayload( type, viewId, &ignore ); - if(ignore) + if (type == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR) + { + if (const SfxViewShell* viewShell2 = LokStarMathHelper(viewShell).GetSmViewShell()) + viewShell = viewShell2; + } + std::optional<OString> payload = viewShell->getLOKPayload( type, viewId ); + if(!payload) return; // No actual payload to send. - CallbackData callbackData(payload.getStr(), viewId); + CallbackData callbackData(payload->getStr(), viewId); m_queue1.emplace_back(type); m_queue2.emplace_back(callbackData); SAL_INFO("lok", "Queued updated [" << type << "]: [" << callbackData.getPayload() @@ -2330,6 +2514,9 @@ static bool lo_signDocument(LibreOfficeKit* pThis, const unsigned char* pPrivateKeyBinary, const int nPrivateKeyBinarySize); +static char* lo_extractRequest(LibreOfficeKit* pThis, + const char* pFilePath); + static void lo_runLoop(LibreOfficeKit* pThis, LibreOfficeKitPollCallback pPollCallback, LibreOfficeKitWakeCallback pWakeCallback, @@ -2341,6 +2528,8 @@ static void lo_sendDialogEvent(LibreOfficeKit* pThis, static void lo_setOption(LibreOfficeKit* pThis, const char* pOption, const char* pValue); +static void lo_dumpState(LibreOfficeKit* pThis, const char* pOptions, char** pState); + LibLibreOffice_Impl::LibLibreOffice_Impl() : m_pOfficeClass( gOfficeClass.lock() ) , maThread(nullptr) @@ -2367,6 +2556,8 @@ LibLibreOffice_Impl::LibLibreOffice_Impl() m_pOfficeClass->runLoop = lo_runLoop; m_pOfficeClass->sendDialogEvent = lo_sendDialogEvent; m_pOfficeClass->setOption = lo_setOption; + m_pOfficeClass->dumpState = lo_dumpState; + m_pOfficeClass->extractRequest = lo_extractRequest; gOfficeClass = m_pOfficeClass; } @@ -2492,6 +2683,14 @@ static LibreOfficeKitDocument* lo_documentLoadWithOptions(LibreOfficeKit* pThis, if (!aLanguage.isEmpty() && isValidLangTag) { + static bool isLoading = true; + if (isLoading) + { + // Capture the language used to load the document. + SfxLokHelper::setLoadLanguage(aLanguage); + isLoading = false; + } + SfxLokHelper::setDefaultLanguage(aLanguage); // Set the LOK language tag, used for dialog tunneling. comphelper::LibreOfficeKit::setLanguageTag(LanguageTag(aLanguage)); @@ -2505,6 +2704,27 @@ static LibreOfficeKitDocument* lo_documentLoadWithOptions(LibreOfficeKit* pThis, SvNumberFormatter::resetTheCurrencyTable(); } + // Set the timezone, if not empty. + const OUString aTimezone = extractParameter(aOptions, u"Timezone"); + if (!aTimezone.isEmpty()) + { + SfxLokHelper::setDefaultTimezone(true, aTimezone); + } + else + { + // Default to the TZ envar, if set. + const char* tz = ::getenv("TZ"); + if (tz) + { + SfxLokHelper::setDefaultTimezone(true, + OStringToOUString(tz, RTL_TEXTENCODING_UTF8)); + } + else + { + SfxLokHelper::setDefaultTimezone(false, OUString()); + } + } + const OUString aDeviceFormFactor = extractParameter(aOptions, u"DeviceFormFactor"); SfxLokHelper::setDeviceFormFactor(aDeviceFormFactor); @@ -2547,10 +2767,15 @@ static LibreOfficeKitDocument* lo_documentLoadWithOptions(LibreOfficeKit* pThis, document::MacroExecMode::NEVER_EXECUTE; #endif + // set AsTemplate explicitly false to be able to load template files + // as regular files, otherwise we cannot save them; it will try + // to bring saveas dialog which cannot work with LOK case uno::Sequence<css::beans::PropertyValue> aFilterOptions{ comphelper::makePropertyValue("FilterOptions", sFilterOptions), comphelper::makePropertyValue("InteractionHandler", xInteraction), - comphelper::makePropertyValue("MacroExecutionMode", nMacroExecMode) + comphelper::makePropertyValue("MacroExecutionMode", nMacroExecMode), + comphelper::makePropertyValue("AsTemplate", false), + comphelper::makePropertyValue("Silent", !aBatch.isEmpty()) }; /* TODO @@ -2559,6 +2784,8 @@ static LibreOfficeKitDocument* lo_documentLoadWithOptions(LibreOfficeKit* pThis, aFilterOptions[3].Value <<= nUpdateDoc; */ + OutputDevice::StartTrackingFontMappingUse(); + const int nThisDocumentId = nDocumentIdCounter++; SfxViewShell::SetCurrentDocId(ViewShellDocId(nThisDocumentId)); uno::Reference<lang::XComponent> xComponent = xComponentLoader->loadComponentFromURL( @@ -2582,6 +2809,104 @@ static LibreOfficeKitDocument* lo_documentLoadWithOptions(LibreOfficeKit* pThis, int nState = doc_getSignatureState(pDocument); pLib->mpCallback(LOK_CALLBACK_SIGNATURE_STATUS, OString::number(nState).getStr(), pLib->mpCallbackData); } + + auto aFontMappingUseData = OutputDevice::FinishTrackingFontMappingUse(); + + if (aFontMappingUseData.size() > 0) + { + SAL_INFO("lok.fontsubst", "================ Original substitutions:"); + for (const auto &i : aFontMappingUseData) + { + SAL_INFO("lok.fontsubst", i.mOriginalFont); + for (const auto &j : i.mUsedFonts) + SAL_INFO("lok.fontsubst", " " << j); + } + } + + // Filter out font substitutions that actually aren't any substitutions, like "Liberation + // Serif" -> "Liberation Serif/Regular". If even one of the "substitutions" of a font is to + // the same font, don't count that as a missing font. + + aFontMappingUseData.erase + (std::remove_if(aFontMappingUseData.begin(), aFontMappingUseData.end(), + [](OutputDevice::FontMappingUseItem x) + { + // If the original font had an empty style and one of its + // replacement fonts has the same family name, we assume the font is + // present. The root problem here is that the code that collects + // font substitutions tends to get just empty styles for the font + // that is being substituted, as vcl::Font::GetStyleName() tends to + // return an empty string. (Italicness is instead indicated by what + // vcl::Font::GetItalic() returns and boldness by what + // vcl::Font::GetWeight() returns.) + + if (x.mOriginalFont.indexOf('/') == -1) + for (const auto &j : x.mUsedFonts) + if (j == x.mOriginalFont || + j.startsWith(OUStringConcatenation(x.mOriginalFont + "/"))) + return true; + + return false; + }), + aFontMappingUseData.end()); + + // Filter out substitutions where a proprietary font has been substituted by a + // metric-compatible one. Obviously this is just a heuristic and implemented only for some + // well-known cases. + + aFontMappingUseData.erase + (std::remove_if(aFontMappingUseData.begin(), aFontMappingUseData.end(), + [](OutputDevice::FontMappingUseItem x) + { + // Again, handle only cases where the original font does not include + // a style. Unclear whether there ever will be a style part included + // in the mOriginalFont. + + if (x.mOriginalFont.indexOf('/') == -1) + for (const auto &j : x.mUsedFonts) + if ((x.mOriginalFont == "Arial" && + j.startsWith("Liberation Sans/")) || + (x.mOriginalFont == "Times New Roman" && + j.startsWith("Liberation Serif/")) || + (x.mOriginalFont == "Courier New" && + j.startsWith("Liberation Mono/")) || + (x.mOriginalFont == "Arial Narrow" && + j.startsWith("Liberation Sans Narrow/")) || + (x.mOriginalFont == "Cambria" && + j.startsWith("Caladea/")) || + (x.mOriginalFont == "Calibri" && + j.startsWith("Carlito/")) || + (x.mOriginalFont == "Palatino Linotype" && + j.startsWith("P052/")) || + // Perhaps a risky heuristic? If some glyphs from Symbol + // have been mapped to ones in OpenSymbol, don't warn + // that Symbol is missing. + (x.mOriginalFont == "Symbol" && + j.startsWith("OpenSymbol/"))) + { + return true; + } + + return false; + }), + aFontMappingUseData.end()); + + if (aFontMappingUseData.size() > 0) + { + SAL_INFO("lok.fontsubst", "================ Pruned substitutions:"); + for (const auto &i : aFontMappingUseData) + { + SAL_INFO("lok.fontsubst", i.mOriginalFont); + for (const auto &j : i.mUsedFonts) + SAL_INFO("lok.fontsubst", " " << j); + } + } + + for (std::size_t i = 0; i < aFontMappingUseData.size(); ++i) + { + pDocument->maFontsMissing.insert(aFontMappingUseData[i].mOriginalFont); + } + return pDocument; } catch (const uno::Exception& exception) @@ -2750,6 +3075,69 @@ static bool lo_signDocument(LibreOfficeKit* /*pThis*/, return true; } + +static char* lo_extractRequest(LibreOfficeKit* /*pThis*/, const char* pFilePath) +{ + uno::Reference<frame::XDesktop2> xComponentLoader = frame::Desktop::create(xContext); + uno::Reference< css::lang::XComponent > xComp; + OUString aURL(getAbsoluteURL(pFilePath)); + OUString result; + if (!aURL.isEmpty()) + { + if (xComponentLoader.is()) + { + try + { + uno::Sequence<css::beans::PropertyValue> aFilterOptions(comphelper::InitPropertySequence( + { + {"Hidden", css::uno::Any(true)}, + {"ReadOnly", css::uno::Any(true)} + })); + xComp = xComponentLoader->loadComponentFromURL( aURL, "_blank", 0, aFilterOptions ); + } + catch ( const lang::IllegalArgumentException& ex ) + { + SAL_WARN("lok", "lo_extractRequest: IllegalArgumentException: " << ex.Message); + result = "{ }"; + return convertOUString(result); + } + catch (...) + { + SAL_WARN("lok", "lo_extractRequest: Exception on loadComponentFromURL, url= " << aURL); + result = "{ }"; + return convertOUString(result); + } + + if (xComp.is()) + { + uno::Reference< document::XLinkTargetSupplier > xLTS( xComp, uno::UNO_QUERY ); + + if( xLTS.is() ) + { + OUStringBuffer jsonText; + jsonText.append("{ \"Targets\": { "); + bool lastParentheses = extractLinks(xLTS->getLinks(), false, jsonText); + jsonText.append("} }"); + if (!lastParentheses) + jsonText.append(" }"); + + OUString res(jsonText.makeStringAndClear()); + return convertOUString(res); + } + xComp->dispose(); + } + else + { + result = "{ }"; + return convertOUString(result); + } + + } + } + result = "{ }"; + return convertOUString(result); +} + static void lo_registerCallback (LibreOfficeKit* pThis, LibreOfficeKitCallback pCallback, void* pData) @@ -3058,6 +3446,8 @@ static void doc_iniUnoCommands () OUString(".uno:NumberFormatCurrency"), OUString(".uno:NumberFormatPercent"), OUString(".uno:NumberFormatDecimal"), + OUString(".uno:NumberFormatIncDecimals"), + OUString(".uno:NumberFormatDecDecimals"), OUString(".uno:NumberFormatDate"), OUString(".uno:EditHeaderAndFooter"), OUString(".uno:FrameLineColor"), @@ -3138,6 +3528,7 @@ static void doc_iniUnoCommands () OUString(".uno:InsertAuthoritiesEntry"), OUString(".uno:InsertMultiIndex"), OUString(".uno:InsertField"), + OUString(".uno:PageNumberWizard"), OUString(".uno:InsertPageNumberField"), OUString(".uno:InsertPageCountField"), OUString(".uno:InsertDateField"), @@ -3176,7 +3567,13 @@ static void doc_iniUnoCommands () OUString(".uno:UngroupSparklines"), OUString(".uno:FormatSparklineMenu"), OUString(".uno:Protect"), - OUString(".uno:UnsetCellsReadOnly") + OUString(".uno:UnsetCellsReadOnly"), + OUString(".uno:ContentControlProperties"), + OUString(".uno:InsertCheckboxContentControl"), + OUString(".uno:InsertContentControl"), + OUString(".uno:InsertDateContentControl"), + OUString(".uno:InsertDropdownContentControl"), + OUString(".uno:InsertPictureContentControl") }; util::URL aCommandURL; @@ -3198,6 +3595,20 @@ static void doc_iniUnoCommands () return; } + uno::Reference<xml::crypto::XSEInitializer> xSEInitializer = xml::crypto::SEInitializer::create(xContext); + if (!xSEInitializer.is()) + { + SAL_WARN("lok", "iniUnoCommands: XSEInitializer is not available"); + return; + } + + uno::Reference<xml::crypto::XXMLSecurityContext> xSecurityContext = + xSEInitializer->createSecurityContext(OUString()); + if (!xSecurityContext.is()) + { + SAL_WARN("lok", "iniUnoCommands: failed to create security context"); + } + SfxSlotPool& rSlotPool = SfxSlotPool::GetSlotPool(pViewFrame); uno::Reference<util::XURLTransformer> xParser(util::URLTransformer::create(xContext)); @@ -3449,6 +3860,23 @@ static void doc_setPartMode(LibreOfficeKitDocument* pThis, } } +static int doc_getEditMode(LibreOfficeKitDocument* pThis) +{ + comphelper::ProfileZone aZone("doc_getEditMode"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg("Document doesn't support tiled rendering"); + return 0; + } + + return pDoc->getEditMode(); +} + static void doc_paintTile(LibreOfficeKitDocument* pThis, unsigned char* pBuffer, const int nCanvasWidth, const int nCanvasHeight, @@ -3471,7 +3899,7 @@ static void doc_paintTile(LibreOfficeKitDocument* pThis, return; } -#if defined(UNX) && !defined(MACOSX) +#if defined(UNX) && !defined(MACOSX) || defined(_WIN32) // Painting of zoomed or HiDPI spreadsheets is special, we actually draw everything at 100%, // and only set cairo's (or CoreGraphic's, in the iOS case) scale factor accordingly, so that @@ -3508,6 +3936,36 @@ static void doc_paintTile(LibreOfficeKitDocument* pThis, pDevice->DrawRect(aRect); pDevice->Pop(); } + +#ifdef _WIN32 + // pBuffer was not used there + pDevice->EnableMapMode(false); + BitmapEx aBmpEx = pDevice->GetBitmapEx({ 0, 0 }, { nCanvasWidth, nCanvasHeight }); + Bitmap aBmp = aBmpEx.GetBitmap(); + Bitmap aAlpha = aBmpEx.GetAlpha(); + Bitmap::ScopedReadAccess sraBmp(aBmp); + Bitmap::ScopedReadAccess sraAlpha(aAlpha); + + assert(sraBmp->Height() == nCanvasHeight); + assert(sraBmp->Width() == nCanvasWidth); + assert(!sraAlpha || sraBmp->Height() == sraAlpha->Height()); + assert(!sraAlpha || sraBmp->Width() == sraAlpha->Width()); + auto p = pBuffer; + for (tools::Long y = 0; y < sraBmp->Height(); ++y) + { + Scanline dataBmp = sraBmp->GetScanline(y); + Scanline dataAlpha = sraAlpha ? sraAlpha->GetScanline(y) : nullptr; + for (tools::Long x = 0; x < sraBmp->Width(); ++x) + { + BitmapColor color = sraBmp->GetPixelFromData(dataBmp, x); + sal_uInt8 alpha = dataAlpha ? sraAlpha->GetPixelFromData(dataAlpha, x).GetBlue() : 255; + *p++ = color.GetBlue(); + *p++ = color.GetGreen(); + *p++ = color.GetRed(); + *p++ = alpha; + } + } +#endif #endif #else @@ -3548,6 +4006,7 @@ static void doc_paintTileToCGContext(LibreOfficeKitDocument* pThis, static void doc_paintPartTile(LibreOfficeKitDocument* pThis, unsigned char* pBuffer, const int nPart, + const int nMode, const int nCanvasWidth, const int nCanvasHeight, const int nTilePosX, const int nTilePosY, const int nTileWidth, const int nTileHeight) @@ -3557,7 +4016,7 @@ static void doc_paintPartTile(LibreOfficeKitDocument* pThis, SolarMutexGuard aGuard; SetLastExceptionMsg(); - SAL_INFO( "lok.tiledrendering", "paintPartTile: painting @ " << nPart << " [" + SAL_INFO( "lok.tiledrendering", "paintPartTile: painting @ " << nPart << " : " << nMode << " [" << nTileWidth << "x" << nTileHeight << "]@(" << nTilePosX << ", " << nTilePosY << ") to [" << nCanvasWidth << "x" << nCanvasHeight << "]px" ); @@ -3565,6 +4024,13 @@ static void doc_paintPartTile(LibreOfficeKitDocument* pThis, LibLODocument_Impl* pDocument = static_cast<LibLODocument_Impl*>(pThis); int nOrigViewId = doc_getView(pThis); + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg("Document doesn't support tiled rendering"); + return; + } + if (nOrigViewId < 0) { // tile painting always needs a SfxViewShell::Current(), but actually @@ -3595,14 +4061,19 @@ static void doc_paintPartTile(LibreOfficeKitDocument* pThis, { // Text documents have a single coordinate system; don't change part. int nOrigPart = 0; - const bool isText = (doc_getDocumentType(pThis) == LOK_DOCTYPE_TEXT); + const int aType = doc_getDocumentType(pThis); + const bool isText = (aType == LOK_DOCTYPE_TEXT); + const bool isCalc = (aType == LOK_DOCTYPE_SPREADSHEET); + int nOrigEditMode = 0; + bool bPaintTextEdit = true; int nViewId = nOrigViewId; - int nLastNonEditorView = nViewId; + int nLastNonEditorView = -1; + int nViewMatchingMode = -1; if (!isText) { // Check if just switching to another view is enough, that has // less side-effects. - if (nPart != doc_getPart(pThis)) + if (nPart != doc_getPart(pThis) || nMode != pDoc->getEditMode()) { SfxViewShell* pViewShell = SfxViewShell::GetFirst(); while (pViewShell) @@ -3612,23 +4083,48 @@ static void doc_paintPartTile(LibreOfficeKitDocument* pThis, if (!bIsInEdit) nLastNonEditorView = pViewShell->GetViewShellId().get(); - if (pViewShell->getPart() == nPart && !bIsInEdit) + if (pViewShell->getPart() == nPart && + pViewShell->getEditMode() == nMode && + !bIsInEdit) { nViewId = pViewShell->GetViewShellId().get(); + nViewMatchingMode = nViewId; nLastNonEditorView = nViewId; doc_setView(pThis, nViewId); break; } + else if (pViewShell->getEditMode() == nMode && !bIsInEdit) + { + nViewMatchingMode = pViewShell->GetViewShellId().get(); + } + pViewShell = SfxViewShell::GetNext(*pViewShell); } } - // if not found view with correct part - at least avoid rendering active textbox + // if not found view with correct part + // - at least avoid rendering active textbox, This is for Impress. + // - prefer view with the same mode SfxViewShell* pCurrentViewShell = SfxViewShell::Current(); - if (pCurrentViewShell && pCurrentViewShell->GetDrawView() && + if (nViewMatchingMode >= 0 && nViewMatchingMode != nViewId) + { + nViewId = nViewMatchingMode; + doc_setView(pThis, nViewId); + } + else if (!isCalc && nLastNonEditorView >= 0 && nLastNonEditorView != nViewId && + pCurrentViewShell && pCurrentViewShell->GetDrawView() && pCurrentViewShell->GetDrawView()->GetTextEditOutliner()) { - doc_setView(pThis, nLastNonEditorView); + nViewId = nLastNonEditorView; + doc_setView(pThis, nViewId); + } + + // Disable callbacks while we are painting - after setting the view + if (nViewId != nOrigViewId && nViewId >= 0) + { + const auto handlerIt = pDocument->mpCallbackFlushHandlers.find(nViewId); + if (handlerIt != pDocument->mpCallbackFlushHandlers.end()) + handlerIt->second->disableCallbacks(); } nOrigPart = doc_getPart(pThis); @@ -3636,17 +4132,44 @@ static void doc_paintPartTile(LibreOfficeKitDocument* pThis, { doc_setPartImpl(pThis, nPart, false); } + + nOrigEditMode = pDoc->getEditMode(); + if (nOrigEditMode != nMode) + { + SfxLokHelper::setEditMode(nMode, pDoc); + } + + bPaintTextEdit = (nPart == nOrigPart && nMode == nOrigEditMode); + pDoc->setPaintTextEdit(bPaintTextEdit); } doc_paintTile(pThis, pBuffer, nCanvasWidth, nCanvasHeight, nTilePosX, nTilePosY, nTileWidth, nTileHeight); - if (!isText && nPart != nOrigPart) - { - doc_setPartImpl(pThis, nOrigPart, false); - } - if (!isText && nViewId != nOrigViewId) + if (!isText) { - doc_setView(pThis, nOrigViewId); + pDoc->setPaintTextEdit(true); + + if (nMode != nOrigEditMode) + { + SfxLokHelper::setEditMode(nOrigEditMode, pDoc); + } + + if (nPart != nOrigPart) + { + doc_setPartImpl(pThis, nOrigPart, false); + } + + if (nViewId != nOrigViewId) + { + if (nViewId >= 0) + { + const auto handlerIt = pDocument->mpCallbackFlushHandlers.find(nViewId); + if (handlerIt != pDocument->mpCallbackFlushHandlers.end()) + handlerIt->second->enableCallbacks(); + } + + doc_setView(pThis, nOrigViewId); + } } } catch (const std::exception&) @@ -3690,6 +4213,29 @@ static void doc_getDocumentSize(LibreOfficeKitDocument* pThis, } } +static void doc_getDataArea(LibreOfficeKitDocument* pThis, + long nTab, + long* pCol, + long* pRow) +{ + comphelper::ProfileZone aZone("doc_getDataArea"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (pDoc) + { + Size aDocumentSize = pDoc->getDataArea(nTab); + *pCol = aDocumentSize.Width(); + *pRow = aDocumentSize.Height(); + } + else + { + SetLastExceptionMsg("Document doesn't support tiled rendering"); + } +} + static void doc_initializeForRendering(LibreOfficeKitDocument* pThis, const char* pArguments) { @@ -3759,6 +4305,23 @@ static void doc_registerCallback(LibreOfficeKitDocument* pThis, pDocument->mpCallbackFlushHandlers[nView]->setViewId(pViewShell->GetViewShellId().get()); pViewShell->setLibreOfficeKitViewCallback(pDocument->mpCallbackFlushHandlers[nView].get()); } + + if (pDocument->maFontsMissing.size() != 0) + { + std::string sPayload = "{ \"fontsmissing\": [ "; + bool bFirst = true; + for (const auto &f : pDocument->maFontsMissing) + { + if (bFirst) + bFirst = false; + else + sPayload += ", "; + sPayload += "\"" + std::string(f.toUtf8().getStr()) + "\""; + } + sPayload += " ] }"; + pCallback(LOK_CALLBACK_FONTS_MISSING, sPayload.c_str(), pData); + pDocument->maFontsMissing.clear(); + } } else { @@ -3908,12 +4471,12 @@ static void doc_removeTextContext(LibreOfficeKitDocument* pThis, unsigned nLOKWi // backspace if (nLOKWindowId == 0) { - KeyEvent aEvt(8, 1283); + KeyEvent aEvt(8, KEY_BACKSPACE); for (int i = 0; i < nCharBefore; ++i) pWindow->KeyInput(aEvt); } else - SfxLokHelper::postKeyEventAsync(pWindow, LOK_KEYEVENT_KEYINPUT, 8, 1283, nCharBefore - 1); + SfxLokHelper::postKeyEventAsync(pWindow, LOK_KEYEVENT_KEYINPUT, 8, KEY_BACKSPACE, nCharBefore - 1); } if (nCharAfter > 0) @@ -3921,12 +4484,12 @@ static void doc_removeTextContext(LibreOfficeKitDocument* pThis, unsigned nLOKWi // delete (forward) if (nLOKWindowId == 0) { - KeyEvent aEvt(46, 1286); + KeyEvent aEvt(46, KEY_DELETE); for (int i = 0; i < nCharAfter; ++i) pWindow->KeyInput(aEvt); } else - SfxLokHelper::postKeyEventAsync(pWindow, LOK_KEYEVENT_KEYINPUT, 46, 1286, nCharAfter - 1); + SfxLokHelper::postKeyEventAsync(pWindow, LOK_KEYEVENT_KEYINPUT, 46, KEY_DELETE, nCharAfter - 1); } } @@ -4037,13 +4600,18 @@ namespace { */ class DispatchResultListener : public cppu::WeakImplHelper<css::frame::XDispatchResultListener> { - OString maCommand; ///< Command for which this is the result. - std::shared_ptr<CallbackFlushHandler> mpCallback; ///< Callback to call. + const OString maCommand; ///< Command for which this is the result. + const std::shared_ptr<CallbackFlushHandler> mpCallback; ///< Callback to call. + const std::chrono::steady_clock::time_point mSaveTime; //< The time we started saving. + const bool mbWasModified; //< Whether or not the document was modified before saving. public: - DispatchResultListener(const char* pCommand, std::shared_ptr<CallbackFlushHandler> const & pCallback) + DispatchResultListener(const char* pCommand, + std::shared_ptr<CallbackFlushHandler> const& pCallback) : maCommand(pCommand) , mpCallback(pCallback) + , mSaveTime(std::chrono::steady_clock::now()) + , mbWasModified(SfxObjectShell::Current()->IsModified()) { assert(mpCallback); } @@ -4060,6 +4628,16 @@ public: } unoAnyToJson(aJson, "result", rEvent.Result); + aJson.put("wasModified", mbWasModified); + + const auto saveTime = std::chrono::time_point_cast<std::chrono::microseconds>(mSaveTime); + aJson.put("startUnixTimeMics", + static_cast<sal_Int64>(saveTime.time_since_epoch().count())); + + const auto saveDuration = std::chrono::duration_cast<std::chrono::microseconds>( + std::chrono::steady_clock::now() - mSaveTime); + aJson.put("saveDurationMics", static_cast<sal_Int64>(saveDuration.count())); + mpCallback->queue(LOK_CALLBACK_UNO_COMMAND_RESULT, aJson.extractData()); } @@ -4149,13 +4727,62 @@ static void lo_setOption(LibreOfficeKit* /*pThis*/, const char *pOption, const c else sal_detail_set_log_selector(pCurrentSalLogOverride); } +#ifdef LINUX else if (strcmp(pOption, "addfont") == 0) { + if (memcmp(pValue, "file://", 7) == 0) + pValue += 7; + + int fd = open(pValue, O_RDONLY); + if (fd == -1) + { + std::cerr << "Could not open font file '" << pValue << "': " << strerror(errno) << std::endl; + return; + } + + OUString sMagicFileName = "file:///:FD:/" + OUString::number(fd); + OutputDevice *pDevice = Application::GetDefaultDevice(); OutputDevice::ImplClearAllFontData(false); - pDevice->AddTempDevFont(OUString::fromUtf8(pValue), ""); + pDevice->AddTempDevFont(sMagicFileName, ""); OutputDevice::ImplRefreshAllFontData(false); } +#endif +} + +static void lo_dumpState (LibreOfficeKit* pThis, const char* /* pOptions */, char** pState) +{ + if (!pState) + return; + + // NB. no SolarMutexGuard since this may be caused in some extremis / deadlock + SetLastExceptionMsg(); + + *pState = nullptr; + OStringBuffer aState(4096*256); + + LibLibreOffice_Impl* pLib = static_cast<LibLibreOffice_Impl*>(pThis); + + pLib->dumpState(aState); + + OString aStr = aState.makeStringAndClear(); + *pState = strdup(aStr.getStr()); +} + +void LibLibreOffice_Impl::dumpState(rtl::OStringBuffer &rState) +{ + rState.append("LibreOfficeKit state:"); + rState.append("\n\tLastExceptionMsg:\t"); + rState.append(rtl::OUStringToOString(maLastExceptionMsg, RTL_TEXTENCODING_UTF8)); + rState.append("\n\tUnipoll:\t"); + rState.append(vcl::lok::isUnipoll() ? "yes" : "no: events on thread"); + rState.append("\n\tOptionalFeatures:\t0x"); + rState.append(static_cast<sal_Int64>(mOptionalFeatures), 16); + rState.append("\n\tCallbackData:\t0x"); + rState.append(reinterpret_cast<sal_Int64>(mpCallback), 16); + // TODO: dump mInteractionMap + SfxLokHelper::dumpState(rState); + vcl::lok::dumpState(rState); } static void doc_postUnoCommand(LibreOfficeKitDocument* pThis, const char* pCommand, const char* pArguments, bool bNotifyWhenFinished) @@ -4344,7 +4971,13 @@ static void doc_postUnoCommand(LibreOfficeKitDocument* pThis, const char* pComma aChartDispatcher->dispatch(aCommandURL, comphelper::containerToSequence(aPropertyValuesVector)); return; } - else if (bNotifyWhenFinished && pDocument->mpCallbackFlushHandlers.count(nView)) + if (LokStarMathHelper aMathHelper(SfxViewShell::Current()); + aMathHelper.GetGraphicWindow() && aCommand != ".uno:Save") + { + aMathHelper.Dispatch(aCommand, comphelper::containerToSequence(aPropertyValuesVector)); + return; + } + if (bNotifyWhenFinished && pDocument->mpCallbackFlushHandlers.count(nView)) { bResult = comphelper::dispatchCommand(aCommand, comphelper::containerToSequence(aPropertyValuesVector), new DispatchResultListener(pCommand, pDocument->mpCallbackFlushHandlers[nView])); @@ -4510,7 +5143,7 @@ static bool encodeImageAsHTML( // Encode in base64. auto aSeq = Sequence<sal_Int8>(reinterpret_cast<const sal_Int8*>(aRet.getStr()), aRet.getLength()); - OUStringBuffer aBase64Data; + OStringBuffer aBase64Data; comphelper::Base64::encode(aBase64Data, aSeq); // Embed in HTML. @@ -4521,7 +5154,7 @@ static bool encodeImageAsHTML( + getGenerator().toUtf8() + "\"/>" "</head><body><img src=\"data:" + aMimeType + ";base64," - + aBase64Data.makeStringAndClear().toUtf8() + "\"/></body></html>"; + + aBase64Data + "\"/></body></html>"; return true; } @@ -4957,12 +5590,28 @@ static void doc_resetSelection(LibreOfficeKitDocument* pThis) pDoc->resetSelection(); } +static void addLocale(boost::property_tree::ptree& rValues, css::lang::Locale const & rLocale) +{ + boost::property_tree::ptree aChild; + OUString sLanguage; + const LanguageTag aLanguageTag( rLocale ); + sLanguage = SvtLanguageTable::GetLanguageString(aLanguageTag.getLanguageType()); + if (sLanguage.endsWith("}")) + return; + + sLanguage += ";" + aLanguageTag.getBcp47(false); + aChild.put("", sLanguage.toUtf8()); + rValues.push_back(std::make_pair("", aChild)); +} + static char* getLanguages(const char* pCommand) { css::uno::Sequence< css::lang::Locale > aLocales; + css::uno::Sequence< css::lang::Locale > aGrammarLocales; if (xContext.is()) { + // SpellChecker css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLangSrv = css::linguistic2::LinguServiceManager::create(xContext); if (xLangSrv.is()) { @@ -4970,24 +5619,26 @@ static char* getLanguages(const char* pCommand) if (xSpell.is()) aLocales = xSpell->getLocales(); } + + // LanguageTool + SvxLanguageToolOptions& rLanguageOpts = SvxLanguageToolOptions::Get(); + if (rLanguageOpts.getEnabled()) + { + uno::Reference< linguistic2::XProofreader > xGC( + xContext->getServiceManager()->createInstanceWithContext("org.openoffice.lingu.LanguageToolGrammarChecker", xContext), + uno::UNO_QUERY_THROW ); + uno::Reference< linguistic2::XSupportedLocales > xSuppLoc( xGC, uno::UNO_QUERY_THROW ); + aGrammarLocales = xSuppLoc->getLocales(); + } } boost::property_tree::ptree aTree; aTree.put("commandName", pCommand); boost::property_tree::ptree aValues; - boost::property_tree::ptree aChild; - OUString sLanguage; - for ( css::lang::Locale const & locale : std::as_const(aLocales) ) - { - const LanguageTag aLanguageTag( locale ); - sLanguage = SvtLanguageTable::GetLanguageString(aLanguageTag.getLanguageType()); - if (sLanguage.startsWith("{") && sLanguage.endsWith("}")) - continue; - - sLanguage += ";" + aLanguageTag.getBcp47(false); - aChild.put("", sLanguage.toUtf8()); - aValues.push_back(std::make_pair("", aChild)); - } + for ( css::lang::Locale const & rLocale : std::as_const(aLocales) ) + addLocale(aValues, rLocale); + for ( css::lang::Locale const & rLocale : std::as_const(aGrammarLocales) ) + addLocale(aValues, rLocale); aTree.add_child("commandValues", aValues); std::stringstream aStream; boost::property_tree::write_json(aStream, aTree); @@ -5315,6 +5966,13 @@ static char* doc_getCommandValues(LibreOfficeKitDocument* pThis, const char* pCo static constexpr OStringLiteral aCellCursor(".uno:CellCursor"); static constexpr OStringLiteral aFontSubset(".uno:FontSubset&name="); + ITiledRenderable* pDoc = getTiledRenderable(pThis); + if (!pDoc) + { + SetLastExceptionMsg("Document doesn't support tiled rendering"); + return nullptr; + } + if (!strcmp(pCommand, ".uno:LanguageStatus")) { return getLanguages(pCommand); @@ -5357,13 +6015,6 @@ static char* doc_getCommandValues(LibreOfficeKitDocument* pThis, const char* pCo } else if (aCommand.startsWith(aViewRowColumnHeaders)) { - ITiledRenderable* pDoc = getTiledRenderable(pThis); - if (!pDoc) - { - SetLastExceptionMsg("Document doesn't support tiled rendering"); - return nullptr; - } - tools::Rectangle aRectangle; if (aCommand.getLength() > aViewRowColumnHeaders.getLength()) { @@ -5409,13 +6060,6 @@ static char* doc_getCommandValues(LibreOfficeKitDocument* pThis, const char* pCo } else if (aCommand.startsWith(aSheetGeometryData)) { - ITiledRenderable* pDoc = getTiledRenderable(pThis); - if (!pDoc) - { - SetLastExceptionMsg("Document doesn't support tiled rendering"); - return nullptr; - } - bool bColumns = true; bool bRows = true; bool bSizes = true; @@ -5475,12 +6119,6 @@ static char* doc_getCommandValues(LibreOfficeKitDocument* pThis, const char* pCo } else if (aCommand.startsWith(aCellCursor)) { - ITiledRenderable* pDoc = getTiledRenderable(pThis); - if (!pDoc) - { - SetLastExceptionMsg("Document doesn't support tiled rendering"); - return nullptr; - } // Ignore command's deprecated parameters. tools::JsonWriter aJsonWriter; pDoc->getCellCursor(aJsonWriter); @@ -5490,6 +6128,12 @@ static char* doc_getCommandValues(LibreOfficeKitDocument* pThis, const char* pCo { return getFontSubset(std::string_view(pCommand + aFontSubset.getLength())); } + else if (pDoc->supportsCommand(INetURLObject(OUString::fromUtf8(aCommand)).GetURLPath())) + { + tools::JsonWriter aJsonWriter; + pDoc->getCommandValues(aJsonWriter, aCommand); + return aJsonWriter.extractData(); + } else { SetLastExceptionMsg("Unknown command, no values returned"); @@ -6188,6 +6832,22 @@ static void doc_sendContentControlEvent(LibreOfficeKitDocument* pThis, const cha pDoc->executeContentControlEvent(aMap); } +static void doc_setViewTimezone(SAL_UNUSED_PARAMETER LibreOfficeKitDocument* /*pThis*/, int nId, + const char* pTimezone) +{ + comphelper::ProfileZone aZone("doc_setViewTimezone"); + + SolarMutexGuard aGuard; + SetLastExceptionMsg(); + + // Leave the default if we get a null timezone. + if (pTimezone) + { + OUString sTimezone = OStringToOUString(pTimezone, RTL_TEXTENCODING_UTF8); + SfxLokHelper::setViewTimezone(nId, true, sTimezone); + } +} + static char* lo_getError (LibreOfficeKit *pThis) { comphelper::ProfileZone aZone("lo_getError"); @@ -6282,7 +6942,8 @@ static char* lo_getVersionInfo(SAL_UNUSED_PARAMETER LibreOfficeKit* /*pThis*/) "\"ProductName\": \"%PRODUCTNAME\", " "\"ProductVersion\": \"%PRODUCTVERSION\", " "\"ProductExtension\": \"%PRODUCTEXTENSION\", " - "\"BuildId\": \"%BUILDID\" " + "\"BuildId\": \"%BUILDID\", " + "\"BuildConfig\": \"" BUILDCONFIG "\" " "}")); } @@ -6400,6 +7061,8 @@ static void lo_status_indicator_callback(void *data, comphelper::LibreOfficeKit: } } +void setLanguageToolConfig(); + /// Used only by LibreOfficeKit when used by Online to pre-initialize static void preloadData() { @@ -6418,6 +7081,9 @@ static void preloadData() if(bAbort) std::cerr << "CheckExtensionDependencies failed" << std::endl; + // setup LanguageTool config before spell checking init + setLanguageToolConfig(); + // preload all available dictionaries css::uno::Reference<css::linguistic2::XLinguServiceManager> xLngSvcMgr = css::linguistic2::LinguServiceManager::create(comphelper::getProcessComponentContext()); @@ -6542,6 +7208,25 @@ static void activateNotebookbar(std::u16string_view rApp) } } +void setHelpRootURL() +{ + const char* pHelpRootURL = ::getenv("LOK_HELP_URL"); + if (pHelpRootURL) + { + OUString aHelpRootURL = OStringToOUString(pHelpRootURL, RTL_TEXTENCODING_UTF8); + try + { + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Common::Help::HelpRootURL::set(aHelpRootURL, batch); + batch->commit(); + } + catch (uno::Exception const& rException) + { + SAL_WARN("lok", "Failed to set the help root URL: " << rException.Message); + } + } +} + void setCertificateDir() { const char* pEnvVarString = ::getenv("LO_CERTIFICATE_DATABASE_PATH"); @@ -6566,19 +7251,27 @@ void setLanguageToolConfig() { const char* pEnabled = ::getenv("LANGUAGETOOL_ENABLED"); const char* pBaseUrlString = ::getenv("LANGUAGETOOL_BASEURL"); - const char* pUsername = ::getenv("LANGUAGETOOL_USERNAME"); - const char* pApikey = ::getenv("LANGUAGETOOL_APIKEY"); + if (pEnabled && pBaseUrlString) { + const char* pUsername = ::getenv("LANGUAGETOOL_USERNAME"); + const char* pApikey = ::getenv("LANGUAGETOOL_APIKEY"); + const char* pSSLVerification = ::getenv("LANGUAGETOOL_SSL_VERIFICATION"); + const char* pRestProtocol = ::getenv("LANGUAGETOOL_RESTPROTOCOL"); + OUString aEnabled = OStringToOUString(pEnabled, RTL_TEXTENCODING_UTF8); + OUString aSSLVerification = OStringToOUString(pSSLVerification, RTL_TEXTENCODING_UTF8); if (aEnabled != "true") return; OUString aBaseUrl = OStringToOUString(pBaseUrlString, RTL_TEXTENCODING_UTF8); + OUString aRestProtocol = pRestProtocol ? OStringToOUString(pRestProtocol, RTL_TEXTENCODING_UTF8) : ""; try { SvxLanguageToolOptions& rLanguageOpts = SvxLanguageToolOptions::Get(); rLanguageOpts.setBaseURL(aBaseUrl); rLanguageOpts.setEnabled(true); + rLanguageOpts.setSSLVerification(aSSLVerification == "true"); + rLanguageOpts.setRestProtocol(aRestProtocol); if (pUsername && pApikey) { OUString aUsername = OStringToOUString(pUsername, RTL_TEXTENCODING_UTF8); @@ -6586,6 +7279,31 @@ void setLanguageToolConfig() rLanguageOpts.setUsername(aUsername); rLanguageOpts.setApiKey(aApiKey); } + + css::uno::Reference<css::linguistic2::XLinguServiceManager2> xLangSrv = + css::linguistic2::LinguServiceManager::create(xContext); + if (xLangSrv.is()) + { + css::uno::Reference<css::linguistic2::XSpellChecker> xSpell = xLangSrv->getSpellChecker(); + if (xSpell.is()) + { + Sequence<OUString> aEmpty; + static constexpr OUStringLiteral cSpell(SN_SPELLCHECKER); + Sequence<css::lang::Locale> aLocales = xSpell->getLocales(); + + uno::Reference<linguistic2::XProofreader> xGC( + xContext->getServiceManager()->createInstanceWithContext("org.openoffice.lingu.LanguageToolGrammarChecker", xContext), + uno::UNO_QUERY_THROW); + uno::Reference<linguistic2::XSupportedLocales> xSuppLoc(xGC, uno::UNO_QUERY_THROW); + + for (int itLocale = 0; itLocale < aLocales.getLength(); itLocale++) + { + // turn off spell checker if LanguageTool supports the locale already + if (xSuppLoc->hasLocale(aLocales[itLocale])) + xLangSrv->setConfiguredServices(cSpell, aLocales[itLocale], aEmpty); + } + } + } } catch(uno::Exception const& rException) { @@ -6594,6 +7312,28 @@ void setLanguageToolConfig() } } +void setDeeplConfig() +{ + const char* pAPIUrlString = ::getenv("DEEPL_API_URL"); + const char* pAuthKeyString = ::getenv("DEEPL_AUTH_KEY"); + if (pAPIUrlString && pAuthKeyString) + { + OUString aAPIUrl = OStringToOUString(pAPIUrlString, RTL_TEXTENCODING_UTF8); + OUString aAuthKey = OStringToOUString(pAuthKeyString, RTL_TEXTENCODING_UTF8); + try + { + SvxDeeplOptions& rDeeplOptions = SvxDeeplOptions::Get(); + rDeeplOptions.setAPIUrl(aAPIUrl); + rDeeplOptions.setAuthKey(aAuthKey); + } + catch(uno::Exception const& rException) + { + SAL_WARN("lok", "Failed to set Deepl API settings: " << rException.Message); + } + } +} + + } static int lo_initialize(LibreOfficeKit* pThis, const char* pAppPath, const char* pUserProfileUrl) @@ -6664,8 +7404,15 @@ static int lo_initialize(LibreOfficeKit* pThis, const char* pAppPath, const char comphelper::ProfileZone aZone("lok-init"); if (eStage == PRE_INIT) + { rtl_alloc_preInit(true); + // Set the default timezone to the TZ envar, if set. + const char* tz = ::getenv("TZ"); + SfxLokHelper::setDefaultTimezone(!!tz, tz ? OStringToOUString(tz, RTL_TEXTENCODING_UTF8) + : OUString()); + } + if (eStage != SECOND_INIT) comphelper::LibreOfficeKit::setActive(); @@ -6907,8 +7654,10 @@ static int lo_initialize(LibreOfficeKit* pThis, const char* pAppPath, const char } #endif + + setHelpRootURL(); setCertificateDir(); - setLanguageToolConfig(); + setDeeplConfig(); if (bNotebookbar) { @@ -6961,7 +7710,7 @@ int lok_preinit(const char* install_path, const char* user_profile_url) } SAL_JNI_EXPORT -int lok_preinit_2(const char* install_path, const char* user_profile_url, LibLibreOffice_Impl** kit) +int lok_preinit_2(const char* install_path, const char* user_profile_url, LibreOfficeKit** kit) { lok_preinit_2_called = true; int result = lo_initialize(nullptr, install_path, user_profile_url); |