diff options
-rw-r--r-- | desktop/inc/lib/init.hxx | 23 | ||||
-rw-r--r-- | desktop/source/lib/init.cxx | 144 | ||||
-rw-r--r-- | include/editeng/outliner.hxx | 2 | ||||
-rw-r--r-- | include/sfx2/lokcallback.hxx | 11 | ||||
-rw-r--r-- | include/sfx2/lokhelper.hxx | 18 | ||||
-rw-r--r-- | include/sfx2/viewsh.hxx | 5 | ||||
-rw-r--r-- | include/test/lokcallback.hxx | 24 | ||||
-rw-r--r-- | sc/qa/unit/tiledrendering/tiledrendering.cxx | 15 | ||||
-rw-r--r-- | sd/qa/unit/tiledrendering/tiledrendering.cxx | 15 | ||||
-rw-r--r-- | sfx2/source/view/lokhelper.cxx | 69 | ||||
-rw-r--r-- | sfx2/source/view/viewsh.cxx | 33 | ||||
-rw-r--r-- | sw/inc/view.hxx | 1 | ||||
-rw-r--r-- | sw/inc/viscrs.hxx | 3 | ||||
-rw-r--r-- | sw/qa/core/txtnode/txtnode.cxx | 2 | ||||
-rw-r--r-- | sw/qa/extras/tiledrendering/tiledrendering.cxx | 72 | ||||
-rw-r--r-- | sw/source/core/crsr/viscrs.cxx | 85 | ||||
-rw-r--r-- | sw/source/uibase/inc/wrtsh.hxx | 2 | ||||
-rw-r--r-- | sw/source/uibase/uiview/view.cxx | 7 | ||||
-rw-r--r-- | sw/source/uibase/wrtsh/wrtsh4.cxx | 13 | ||||
-rw-r--r-- | test/source/lokcallback.cxx | 121 |
20 files changed, 584 insertions, 81 deletions
diff --git a/desktop/inc/lib/init.hxx b/desktop/inc/lib/init.hxx index bc090fc208a9..01620a4f02ac 100644 --- a/desktop/inc/lib/init.hxx +++ b/desktop/inc/lib/init.hxx @@ -104,6 +104,8 @@ namespace desktop { virtual void libreOfficeKitViewCallback(int nType, const char* pPayload) override; virtual void libreOfficeKitViewCallbackWithViewId(int nType, const char* pPayload, int nViewId) override; virtual void libreOfficeKitViewInvalidateTilesCallback(const tools::Rectangle* pRect, int nPart) override; + virtual void libreOfficeKitViewUpdatedCallback(int nType) override; + virtual void libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int nViewId, int nSourceViewId) override; private: struct CallbackData @@ -171,6 +173,8 @@ namespace desktop { queue_type2::iterator toQueue2(queue_type1::iterator); queue_type2::reverse_iterator toQueue2(queue_type1::reverse_iterator); void queue(const int type, CallbackData& data); + void enqueueUpdatedTypes(); + void enqueueUpdatedType( int type, SfxViewShell* sourceViewShell, int viewId ); /** we frequently want to scan the queue, and mostly when we do so, we only care about the element type so we split the queue in 2 to make the scanning cache friendly. */ @@ -178,6 +182,25 @@ namespace desktop { queue_type2 m_queue2; std::map<int, std::string> m_states; std::unordered_map<int, std::unordered_map<int, std::string>> m_viewStates; + + // For some types only the last message matters (see isUpdatedType()) or only the last message + // per each viewId value matters (see isUpdatedTypePerViewId()), so instead of using push model + // where we'd get flooded by repeated messages (which might be costly to generate and process), + // the preferred way is that libreOfficeKitViewUpdatedCallback() + // or libreOfficeKitViewUpdatedCallbackPerViewId() get called to notify about such a message being + // needed, and we'll set a flag here to fetch the actual message before flushing. + void setUpdatedType( int nType, bool value ); + void setUpdatedTypePerViewId( int nType, int nViewId, int nSourceViewId, bool value ); + void resetUpdatedType( int nType); + void resetUpdatedTypePerViewId( int nType, int nViewId ); + std::vector<bool> m_updatedTypes; // index is type, value is if set + struct PerViewIdData + { + bool set; // value is if set + int sourceViewId; + }; + std::unordered_map<int, std::vector<PerViewIdData>> m_updatedTypesPerViewId; // key is view, index is type + LibreOfficeKitDocument* m_pDocument; int m_viewId = -1; // view id of the associated SfxViewShell LibreOfficeKitCallback m_pCallback; diff --git a/desktop/source/lib/init.cxx b/desktop/source/lib/init.cxx index ba3315306464..c38a6dd8d754 100644 --- a/desktop/source/lib/init.cxx +++ b/desktop/source/lib/init.cxx @@ -698,6 +698,23 @@ static bool lcl_isViewCallbackType(const int type) } } +static bool isUpdatedType(int /*type*/) +{ + return false; +} + +static bool isUpdatedTypePerViewId(int type) +{ + switch (type) + { + case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: + case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR: + return true; + default: + return false; + } +} + static int lcl_getViewId(const std::string& payload) { // this is a cheap way how to get the viewId from a JSON message; proper @@ -1455,6 +1472,48 @@ CallbackFlushHandler::queue_type2::reverse_iterator CallbackFlushHandler::toQueu return m_queue2.rbegin() + delta; } +void CallbackFlushHandler::setUpdatedType( int nType, bool value ) +{ + assert(isUpdatedType(nType)); + if( m_updatedTypes.size() <= o3tl::make_unsigned( nType )) + m_updatedTypes.resize( nType + 1 ); // new are default-constructed, i.e. false + m_updatedTypes[ nType ] = value; +} + +void CallbackFlushHandler::resetUpdatedType( int nType ) +{ + setUpdatedType( nType, false ); +} + +void CallbackFlushHandler::setUpdatedTypePerViewId( int nType, int nViewId, int nSourceViewId, bool value ) +{ + assert(isUpdatedTypePerViewId(nType)); + std::vector<PerViewIdData>& types = m_updatedTypesPerViewId[ nViewId ]; + if( types.size() <= o3tl::make_unsigned( nType )) + types.resize( nType + 1 ); // new are default-constructed, i.e. false + types[ nType ] = PerViewIdData{ value, nSourceViewId }; +} + +void CallbackFlushHandler::resetUpdatedTypePerViewId( int nType, int nViewId ) +{ + assert(isUpdatedTypePerViewId(nType)); + bool allViewIds = false; + // Handle specially messages that do not have viewId for backwards compatibility. + if( nType == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR && !comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation()) + allViewIds = true; + if( !allViewIds ) + { + setUpdatedTypePerViewId( nType, nViewId, -1, false ); + return; + } + for( auto& it : m_updatedTypesPerViewId ) + { + std::vector<PerViewIdData>& types = it.second; + if( types.size() >= o3tl::make_unsigned( nType )) + types[ nType ].set = false; + } +} + void CallbackFlushHandler::libreOfficeKitViewCallback(int nType, const char* pPayload) { CallbackData callbackData(pPayload); @@ -1473,6 +1532,22 @@ void CallbackFlushHandler::libreOfficeKitViewInvalidateTilesCallback(const tools queue(LOK_CALLBACK_INVALIDATE_TILES, callbackData); } +void CallbackFlushHandler::libreOfficeKitViewUpdatedCallback(int nType) +{ + assert(isUpdatedType( nType )); + std::unique_lock<std::mutex> lock(m_mutex); + SAL_INFO("lok", "Updated: [" << nType << "]"); + setUpdatedType(nType, true); +} + +void CallbackFlushHandler::libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int nViewId, int nSourceViewId) +{ + assert(isUpdatedTypePerViewId( nType )); + std::unique_lock<std::mutex> lock(m_mutex); + SAL_INFO("lok", "Updated: [" << nType << "]"); + setUpdatedTypePerViewId(nType, nViewId, nSourceViewId, true); +} + void CallbackFlushHandler::queue(const int type, const char* data) { CallbackData callbackData(data); @@ -1536,6 +1611,20 @@ void CallbackFlushHandler::queue(const int type, CallbackData& aCallbackData) std::unique_lock<std::mutex> lock(m_mutex); + // Update types should be received via the updated callbacks for performance, + // getting them as normal callbacks is technically not wrong, but probably should be avoided. + // Reset the updated flag if we get a normal message. + if(isUpdatedType(type)) + { + SAL_INFO("lok", "Received event with updated type [" << type << "] as normal callback"); + resetUpdatedType(type); + } + if(isUpdatedTypePerViewId(type)) + { + SAL_INFO("lok", "Received event with updated type [" << type << "] as normal callback"); + resetUpdatedTypePerViewId(type, aCallbackData.getViewId()); + } + // drop duplicate callbacks for the listed types switch (type) { @@ -2028,6 +2117,58 @@ bool CallbackFlushHandler::processWindowEvent(int type, CallbackData& aCallbackD return false; } +void CallbackFlushHandler::enqueueUpdatedTypes() +{ + if( m_updatedTypes.empty() && m_updatedTypesPerViewId.empty()) + return; + SfxViewShell* viewShell = SfxViewShell::GetFirst( false, + [this](const SfxViewShell* shell) { return shell->GetViewShellId().get() == m_viewId; } ); + assert(viewShell != nullptr); + for( size_t type = 0; type < m_updatedTypes.size(); ++type ) + { + if(m_updatedTypes[ type ]) + { + assert(isUpdatedType( type )); + enqueueUpdatedType( type, viewShell, m_viewId ); + } + } + for( const auto& it : m_updatedTypesPerViewId ) + { + int viewId = it.first; + const std::vector<PerViewIdData>& types = it.second; + for( size_t type = 0; type < types.size(); ++type ) + { + if(types[ type ].set) + { + assert(isUpdatedTypePerViewId( type )); + SfxViewShell* sourceViewShell = viewShell; + const int sourceViewId = types[ type ].sourceViewId; + if( sourceViewId != m_viewId ) + sourceViewShell = SfxViewShell::GetFirst( false, + [sourceViewId](const SfxViewShell* shell) { return shell->GetViewShellId().get() == sourceViewId; } ); + if(sourceViewShell == nullptr) + { + SAL_INFO("lok", "View #" << sourceViewId << " no longer found for updated event [" << type << "]"); + continue; // View removed, probably cleaning up. + } + enqueueUpdatedType( type, sourceViewShell, viewId ); + } + } + } + m_updatedTypes.clear(); + m_updatedTypesPerViewId.clear(); +} + +void CallbackFlushHandler::enqueueUpdatedType( int type, SfxViewShell* viewShell, int viewId ) +{ + OString payload = viewShell->getLOKPayload( type, viewId ); + CallbackData callbackData(payload.getStr(), viewId); + m_queue1.emplace_back(type); + m_queue2.emplace_back(callbackData); + SAL_INFO("lok", "Queued updated [" << type << "]: [" << callbackData.getPayload() + << "] to have " << m_queue1.size() << " entries."); +} + void CallbackFlushHandler::Invoke() { comphelper::ProfileZone aZone("CallbackFlushHandler::Invoke"); @@ -2045,6 +2186,9 @@ void CallbackFlushHandler::Invoke() std::scoped_lock<std::mutex> lock(m_mutex); + // Append messages for updated types, fetch them only now. + enqueueUpdatedTypes(); + SAL_INFO("lok", "Flushing " << m_queue1.size() << " elements."); auto it1 = m_queue1.begin(); auto it2 = m_queue2.begin(); diff --git a/include/editeng/outliner.hxx b/include/editeng/outliner.hxx index c269bf49046f..5003a671d35e 100644 --- a/include/editeng/outliner.hxx +++ b/include/editeng/outliner.hxx @@ -366,6 +366,8 @@ public: virtual void libreOfficeKitViewCallback(int nType, const char* pPayload) const = 0; virtual void libreOfficeKitViewCallbackWithViewId(int nType, const char* pPayload, int nViewId) const = 0; virtual void libreOfficeKitViewInvalidateTilesCallback(const tools::Rectangle* pRect, int nPart) const = 0; + virtual void libreOfficeKitViewUpdatedCallback(int nType) const = 0; + virtual void libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int nViewId, int nSourceViewId) const = 0; virtual ViewShellId GetViewShellId() const = 0; virtual ViewShellDocId GetDocId() const = 0; /// Wrapper around SfxLokHelper::notifyOtherViews(). diff --git a/include/sfx2/lokcallback.hxx b/include/sfx2/lokcallback.hxx index d01e7203205e..6f59402d0cec 100644 --- a/include/sfx2/lokcallback.hxx +++ b/include/sfx2/lokcallback.hxx @@ -11,6 +11,8 @@ #include <sal/types.h> +#include <vector> + namespace tools { class Rectangle; @@ -37,6 +39,15 @@ public: // comphelper::LibreOfficeKit::isPartInInvalidation() is not set virtual void libreOfficeKitViewInvalidateTilesCallback(const tools::Rectangle* pRect, int nPart) = 0; + // A message of the given type should be sent, for performance purpose only a notification + // is given here, details about the message should be queried from SfxViewShell when necessary. + // This is used for messages that are generated often but only the last one is needed. + virtual void libreOfficeKitViewUpdatedCallback(int nType) = 0; + // Like libreOfficeKitViewUpdatedCallback(), but a last message is needed for each nViewId value. + // SfxViewShell:getLOKPayload() will be called on nSourceViewId view. + virtual void libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int nViewId, + int nSourceViewId) + = 0; }; /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/include/sfx2/lokhelper.hxx b/include/sfx2/lokhelper.hxx index cf2c1c9c72bb..4ac50a19cc69 100644 --- a/include/sfx2/lokhelper.hxx +++ b/include/sfx2/lokhelper.hxx @@ -110,14 +110,28 @@ public: static void notifyDocumentSizeChangedAllViews(vcl::ITiledRenderable* pDoc, bool bInvalidateAll = true); /// Emits a LOK_CALLBACK_INVALIDATE_TILES, but tweaks it according to setOptionalFeatures() if needed. static void notifyInvalidation(SfxViewShell const* pThisView, tools::Rectangle const *); - /// Emits a LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, but tweaks it according to setOptionalFeatures() if needed. - static void notifyVisCursorInvalidation(OutlinerViewShell const* pThisView, const OString& rRectangle, bool bMispelledWord = false, const OString& rHyperlink = ""); /// Notifies all views with the given type and payload. static void notifyAllViews(int nType, const OString& rPayload); /// Notify about the editing context change. static void notifyContextChange(SfxViewShell const* pViewShell, const OUString& aApplication, const OUString& aContext); + // Notify about the given type needing an update. + static void notifyUpdate(SfxViewShell const* pViewShell, int nType); + // Notify about the given type needing a per-viewid update. + static void notifyUpdatePerViewId(SfxViewShell const* pViewShell, int nType); + /// Same as notifyUpdatePerViewId(), pTargetShell will be notified, relevant viewId in pViewShell, + /// pSourceView->getLOKPayload() will be called to get the data. + static void notifyUpdatePerViewId(SfxViewShell const* pTargetShell, SfxViewShell const* pViewShell, + SfxViewShell const* pSourceShell, int nType); + // Notify other views about the given type needing a per-viewid update. + static void notifyOtherViewsUpdatePerViewId(SfxViewShell const* pViewShell, int nType); + + static OString makePayloadJSON(const SfxViewShell* pThisView, int nViewId, std::string_view rKey, const OString& rPayload); + /// Makes a LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR payload, but tweaks it according to setOptionalFeatures() if needed. + static OString makeVisCursorInvalidation(int nViewId, const OString& rRectangle, + bool bMispelledWord = false, const OString& rHyperlink = ""); + /// Helper for posting async key event static void postKeyEventAsync(const VclPtr<vcl::Window> &xWindow, int nType, int nCharCode, int nKeyCode, int nRepeat = 0); diff --git a/include/sfx2/viewsh.hxx b/include/sfx2/viewsh.hxx index 89c9cd6c0c7c..e35c6848c9e1 100644 --- a/include/sfx2/viewsh.hxx +++ b/include/sfx2/viewsh.hxx @@ -343,6 +343,11 @@ public: virtual void libreOfficeKitViewInvalidateTilesCallback(const tools::Rectangle* pRect, int nPart) const override; // Performs any pending calls to libreOfficeKitViewInvalidateTilesCallback() as necessary. virtual void flushPendingLOKInvalidateTiles(); + virtual void libreOfficeKitViewUpdatedCallback(int nType) const override; + virtual void libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int nViewId, int nSourceViewId) const override; + // Returns current payload for nType, after libreOfficeKitViewUpdatedCallback() or + // libreOfficeKitViewUpdatedCallbackPerViewId() were called. + virtual OString getLOKPayload(int nType, int nViewId) const; /// Set if we are doing tiled searching. void setTiledSearching(bool bTiledSearching); diff --git a/include/test/lokcallback.hxx b/include/test/lokcallback.hxx index f7372bc7ec80..a3f383bcec5e 100644 --- a/include/test/lokcallback.hxx +++ b/include/test/lokcallback.hxx @@ -15,28 +15,48 @@ #include <sfx2/lokcallback.hxx> #include <vcl/idle.hxx> +#include <vector> + /** A helper to convert SfxLokCallbackInterface to a LIbreOfficeKitCallback for tests. It reimplements the specialized callbacks and converts them to the generic type/payload callback. */ - class OOO_DLLPUBLIC_TEST TestLokCallbackWrapper final : public SfxLokCallbackInterface, public Idle { public: TestLokCallbackWrapper(LibreOfficeKitCallback callback, void* data); + /// Discard all possibly still held events. + void clear(); + /// Set the view id of the associated SfxViewShell. + void setLOKViewId(int viewId) { m_viewId = viewId; } virtual void libreOfficeKitViewCallback(int nType, const char* pPayload) override; virtual void libreOfficeKitViewCallbackWithViewId(int nType, const char* pPayload, int nViewId) override; virtual void libreOfficeKitViewInvalidateTilesCallback(const tools::Rectangle* pRect, int nPart) override; + virtual void libreOfficeKitViewUpdatedCallback(int nType) override; + virtual void libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int nViewId, + int nSourceViewId) override; virtual void Invoke() override; private: - void callCallback(int nType, const char* pPayload); + void callCallback(int nType, const char* pPayload, int nViewId); + void startTimer(); + void flushLOKData(); + void discardUpdatedTypes(int nType, int nViewId); LibreOfficeKitCallback m_callback; void* m_data; + int m_viewId = -1; // the associated SfxViewShell + std::vector<int> m_updatedTypes; // value is type + struct PerViewIdData + { + int type; + int viewId; + int sourceViewId; + }; + std::vector<PerViewIdData> m_updatedTypesPerViewId; }; /* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */ diff --git a/sc/qa/unit/tiledrendering/tiledrendering.cxx b/sc/qa/unit/tiledrendering/tiledrendering.cxx index 351f1f12644f..d8aef459e4d5 100644 --- a/sc/qa/unit/tiledrendering/tiledrendering.cxx +++ b/sc/qa/unit/tiledrendering/tiledrendering.cxx @@ -179,6 +179,7 @@ public: private: ScModelObj* createDoc(const char* pName); + void setupLibreOfficeKitViewCallback(SfxViewShell* pViewShell); static void callback(int nType, const char* pPayload, void* pData); void callbackImpl(int nType, const char* pPayload); @@ -220,6 +221,7 @@ void ScTiledRenderingTest::tearDown() } mxComponent->dispose(); } + m_callbackWrapper.clear(); comphelper::LibreOfficeKit::setActive(false); test::BootstrapFixture::tearDown(); @@ -236,6 +238,12 @@ ScModelObj* ScTiledRenderingTest::createDoc(const char* pName) return pModelObj; } +void ScTiledRenderingTest::setupLibreOfficeKitViewCallback(SfxViewShell* pViewShell) +{ + pViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper); + m_callbackWrapper.setLOKViewId(SfxLokHelper::getView(pViewShell)); +} + void ScTiledRenderingTest::callback(int nType, const char* pPayload, void* pData) { static_cast<ScTiledRenderingTest*>(pData)->callbackImpl(nType, pPayload); @@ -398,7 +406,7 @@ void ScTiledRenderingTest::testDocumentSize() ScTabViewShell* pViewShell = pDocSh->GetBestViewShell(false); CPPUNIT_ASSERT(pViewShell); - pViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pViewShell); // check initial document size Size aDocSize = pModelObj->getDocumentSize(); @@ -598,6 +606,7 @@ public: mpViewShell = SfxViewShell::Current(); mpViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper); mnView = SfxLokHelper::getView(); + m_callbackWrapper.setLOKViewId( mnView ); if (!bDeleteListenerOnDestruct) mpViewShell = nullptr; } @@ -781,7 +790,7 @@ void ScTiledRenderingTest::testDocumentSizeChanged() // Load a document that doesn't have much content. createDoc("small.ods"); - SfxViewShell::Current()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(SfxViewShell::Current()); // Go to the A30 cell -- that will extend the document size. uno::Sequence<beans::PropertyValue> aPropertyValues = @@ -883,7 +892,7 @@ void ScTiledRenderingTest::testColRowResize() ScTabViewShell* pViewShell = pDocSh->GetBestViewShell(false); CPPUNIT_ASSERT(pViewShell); - pViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pViewShell); ScDocument& rDoc = pDocSh->GetDocument(); diff --git a/sd/qa/unit/tiledrendering/tiledrendering.cxx b/sd/qa/unit/tiledrendering/tiledrendering.cxx index e2a0e8a46501..ea7bfc5c11bb 100644 --- a/sd/qa/unit/tiledrendering/tiledrendering.cxx +++ b/sd/qa/unit/tiledrendering/tiledrendering.cxx @@ -203,6 +203,7 @@ public: private: SdXImpressDocument* createDoc(const char* pName, const uno::Sequence<beans::PropertyValue>& rArguments = uno::Sequence<beans::PropertyValue>()); + void setupLibreOfficeKitViewCallback(SfxViewShell& pViewShell); static void callback(int nType, const char* pPayload, void* pData); void callbackImpl(int nType, const char* pPayload); xmlDocUniquePtr parseXmlDump(); @@ -252,6 +253,7 @@ void SdTiledRenderingTest::tearDown() if (m_pXmlBuffer) xmlBufferFree(m_pXmlBuffer); + m_callbackWrapper.clear(); comphelper::LibreOfficeKit::setActive(false); test::BootstrapFixture::tearDown(); @@ -268,6 +270,12 @@ SdXImpressDocument* SdTiledRenderingTest::createDoc(const char* pName, const uno return pImpressDocument; } +void SdTiledRenderingTest::setupLibreOfficeKitViewCallback(SfxViewShell& pViewShell) +{ + pViewShell.setLibreOfficeKitViewCallback(&m_callbackWrapper); + m_callbackWrapper.setLOKViewId(SfxLokHelper::getView(&pViewShell)); +} + void SdTiledRenderingTest::callback(int nType, const char* pPayload, void* pData) { static_cast<SdTiledRenderingTest*>(pData)->callbackImpl(nType, pPayload); @@ -404,7 +412,7 @@ void SdTiledRenderingTest::testRegisterCallback() { SdXImpressDocument* pXImpressDocument = createDoc("dummy.odp"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); - pViewShell->GetViewShellBase().setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pViewShell->GetViewShellBase()); // Start text edit of the empty title shape. SdPage* pActualPage = pViewShell->GetActualPage(); @@ -634,7 +642,7 @@ void SdTiledRenderingTest::testInsertDeletePage() { SdXImpressDocument* pXImpressDocument = createDoc("insert-delete.odp"); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); - pViewShell->GetViewShellBase().setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pViewShell->GetViewShellBase()); SdDrawDocument* pDoc = pXImpressDocument->GetDocShell()->GetDoc(); CPPUNIT_ASSERT(pDoc); @@ -921,6 +929,7 @@ public: mpViewShell = SfxViewShell::Current(); mpViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper); mnView = SfxLokHelper::getView(); + m_callbackWrapper.setLOKViewId( mnView ); } ~ViewCallback() @@ -2534,7 +2543,7 @@ void SdTiledRenderingTest::testCutSelectionChange() CPPUNIT_ASSERT(pXImpressDocument); sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); - pViewShell->GetViewShellBase().setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pViewShell->GetViewShellBase()); Scheduler::ProcessEventsToIdle(); // Select first text object diff --git a/sfx2/source/view/lokhelper.cxx b/sfx2/source/view/lokhelper.cxx index 35b61798c272..cd93f9b4dd55 100644 --- a/sfx2/source/view/lokhelper.cxx +++ b/sfx2/source/view/lokhelper.cxx @@ -357,15 +357,21 @@ static OString lcl_generateJSON(const SfxViewShell* pView, const boost::property return OString(aString.c_str(), aString.size()).trim(); } -static inline OString lcl_generateJSON(const SfxViewShell* pView, std::string_view rKey, +static inline OString lcl_generateJSON(const SfxViewShell* pView, int nViewId, std::string_view rKey, const OString& rPayload) { assert(pView != nullptr && "pView must be valid"); - return OString::Concat("{ \"viewId\": \"") + OString::number(SfxLokHelper::getView(pView)) + return OString::Concat("{ \"viewId\": \"") + OString::number(nViewId) + "\", \"part\": \"" + OString::number(pView->getPart()) + "\", \"" + rKey + "\": \"" + lcl_sanitizeJSONAsValue(rPayload) + "\" }"; } +static inline OString lcl_generateJSON(const SfxViewShell* pView, std::string_view rKey, + const OString& rPayload) +{ + return lcl_generateJSON(pView, SfxLokHelper::getView(pView), rKey, rPayload); +} + void SfxLokHelper::notifyOtherView(const SfxViewShell* pThisView, SfxViewShell const* pOtherView, int nType, std::string_view rKey, const OString& rPayload) { @@ -451,6 +457,11 @@ void SfxLokHelper::notifyOtherViews(const SfxViewShell* pThisView, int nType, } } +OString SfxLokHelper::makePayloadJSON(const SfxViewShell* pThisView, int nViewId, std::string_view rKey, const OString& rPayload) +{ + return lcl_generateJSON(pThisView, nViewId, rKey, rPayload); +} + namespace { OUString lcl_getNameForSlot(const SfxViewShell* pShell, sal_uInt16 nWhich) { @@ -565,25 +576,20 @@ void SfxLokHelper::notifyDocumentSizeChangedAllViews(vcl::ITiledRenderable* pDoc } } -void SfxLokHelper::notifyVisCursorInvalidation(OutlinerViewShell const* pThisView, const OString& rRectangle, bool bMispelledWord, const OString& rHyperlink) +OString SfxLokHelper::makeVisCursorInvalidation(int nViewId, const OString& rRectangle, + bool bMispelledWord, const OString& rHyperlink) { - if (DisableCallbacks::disabled()) - return; - if (comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation()) { OString sHyperlink = rHyperlink.isEmpty() ? "{}" : rHyperlink; - OString sPayload = OString::Concat("{ \"viewId\": \"") + OString::number(SfxLokHelper::getView()) + + return OString::Concat("{ \"viewId\": \"") + OString::number(nViewId) + "\", \"rectangle\": \"" + rRectangle + "\", \"mispelledWord\": \"" + OString::number(bMispelledWord ? 1 : 0) + "\", \"hyperlink\": " + sHyperlink + " }"; - const int viewId = SfxLokHelper::getView(); - pThisView->libreOfficeKitViewCallbackWithViewId(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, sPayload.getStr(), viewId); } else { - OString sPayload = rRectangle; - pThisView->libreOfficeKitViewCallback(LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR, sPayload.getStr()); + return rRectangle; } } @@ -615,6 +621,47 @@ void SfxLokHelper::notifyContextChange(SfxViewShell const* pViewShell, const OUS pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CONTEXT_CHANGED, aBuffer.getStr()); } +void SfxLokHelper::notifyUpdate(SfxViewShell const* pThisView, int nType) +{ + if (DisableCallbacks::disabled()) + return; + + pThisView->libreOfficeKitViewUpdatedCallback(nType); +} + +void SfxLokHelper::notifyUpdatePerViewId(SfxViewShell const* pThisView, int nType) +{ + notifyUpdatePerViewId(pThisView, pThisView, pThisView, nType); +} + +void SfxLokHelper::notifyUpdatePerViewId(SfxViewShell const* pTargetShell, SfxViewShell const* pViewShell, + SfxViewShell const* pSourceShell, int nType) +{ + if (DisableCallbacks::disabled()) + return; + + int viewId = SfxLokHelper::getView(pViewShell); + int sourceViewId = SfxLokHelper::getView(pSourceShell); + pTargetShell->libreOfficeKitViewUpdatedCallbackPerViewId(nType, viewId, sourceViewId); +} + +void SfxLokHelper::notifyOtherViewsUpdatePerViewId(SfxViewShell const* pThisView, int nType) +{ + assert(pThisView != nullptr && "pThisView must be valid"); + if (DisableCallbacks::disabled()) + return; + + int viewId = SfxLokHelper::getView(pThisView); + const ViewShellDocId nCurrentDocId = pThisView->GetDocId(); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + if (pViewShell != pThisView && nCurrentDocId == pViewShell->GetDocId()) + pViewShell->libreOfficeKitViewUpdatedCallbackPerViewId(nType, viewId, viewId); + + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} namespace { diff --git a/sfx2/source/view/viewsh.cxx b/sfx2/source/view/viewsh.cxx index 5766fb48205c..ecfd3f97bba4 100644 --- a/sfx2/source/view/viewsh.cxx +++ b/sfx2/source/view/viewsh.cxx @@ -1522,6 +1522,32 @@ void SfxViewShell::libreOfficeKitViewCallback(int nType, const char* pPayload) c << lokCallbackTypeToString(nType) << ": [" << pPayload << ']'); } +void SfxViewShell::libreOfficeKitViewUpdatedCallback(int nType) const +{ + if (ignoreLibreOfficeKitViewCallback(nType, pImpl.get())) + return; + if (pImpl->m_pLibreOfficeKitViewCallback) + pImpl->m_pLibreOfficeKitViewCallback->libreOfficeKitViewUpdatedCallback(nType); + else + SAL_INFO( + "sfx.view", + "SfxViewShell::libreOfficeKitViewUpdatedCallback no callback set! Dropped payload of type " + << lokCallbackTypeToString(nType)); +} + +void SfxViewShell::libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int nViewId, int nSourceViewId) const +{ + if (ignoreLibreOfficeKitViewCallback(nType, pImpl.get())) + return; + if (pImpl->m_pLibreOfficeKitViewCallback) + pImpl->m_pLibreOfficeKitViewCallback->libreOfficeKitViewUpdatedCallbackPerViewId(nType, nViewId, nSourceViewId); + else + SAL_INFO( + "sfx.view", + "SfxViewShell::libreOfficeKitViewUpdatedCallbackPerViewId no callback set! Dropped payload of type " + << lokCallbackTypeToString(nType)); +} + void SfxViewShell::afterCallbackRegistered() { } @@ -1531,6 +1557,13 @@ void SfxViewShell::flushPendingLOKInvalidateTiles() // SfxViewShell itself does not delay any tile invalidations. } +OString SfxViewShell::getLOKPayload(int nType, int /*nViewId*/) const +{ + // SfxViewShell itself currently doesn't handle any updated-payload types. + SAL_WARN("sfx.view", "SfxViewShell::getLOKPayload unhandled type " << lokCallbackTypeToString(nType)); + abort(); +} + vcl::Window* SfxViewShell::GetEditWindowForActiveOLEObj() const { vcl::Window* pEditWin = nullptr; diff --git a/sw/inc/view.hxx b/sw/inc/view.hxx index 7a5423732a6e..2f6381469d34 100644 --- a/sw/inc/view.hxx +++ b/sw/inc/view.hxx @@ -693,6 +693,7 @@ public: virtual tools::Rectangle getLOKVisibleArea() const override; virtual void flushPendingLOKInvalidateTiles() override; + virtual OString getLOKPayload(int nType, int nViewId) const override; }; inline tools::Long SwView::GetXScroll() const diff --git a/sw/inc/viscrs.hxx b/sw/inc/viscrs.hxx index 9ad0753f0bc0..c8b8b356f7e3 100644 --- a/sw/inc/viscrs.hxx +++ b/sw/inc/viscrs.hxx @@ -43,6 +43,7 @@ class SW_DLLPUBLIC SwVisibleCursor /// For LibreOfficeKit only - remember what page we were at the last time. sal_uInt16 m_nPageLastTime; + SwRect m_aLastLOKRect; bool m_bIsVisible; bool m_bIsDragCursor; @@ -58,6 +59,8 @@ public: void SetDragCursor( bool bFlag = true ) { m_bIsDragCursor = bFlag; } void SetPosAndShow(SfxViewShell const * pViewShell); const vcl::Cursor& GetTextCursor() const; + + OString getLOKPayload(int nType, int nViewId) const; }; // From here classes/methods for selections. diff --git a/sw/qa/core/txtnode/txtnode.cxx b/sw/qa/core/txtnode/txtnode.cxx index 6dc5b55cc28f..72763909122f 100644 --- a/sw/qa/core/txtnode/txtnode.cxx +++ b/sw/qa/core/txtnode/txtnode.cxx @@ -14,6 +14,7 @@ #include <sfx2/viewsh.hxx> #include <vcl/gdimtf.hxx> #include <vcl/scheduler.hxx> +#include <sfx2/lokhelper.hxx> #include <test/lokcallback.hxx> #include <IDocumentStatistics.hxx> @@ -134,6 +135,7 @@ CPPUNIT_TEST_FIXTURE(SwCoreTxtnodeTest, testTitleFieldInvalidate) ViewCallback aCallback; TestLokCallbackWrapper aCallbackWrapper(&ViewCallback::callback, &aCallback); pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&aCallbackWrapper); + aCallbackWrapper.setLOKViewId(SfxLokHelper::getView(pWrtShell->GetSfxViewShell())); Scheduler::ProcessEventsToIdle(); aCallback.m_nInvalidations = 0; diff --git a/sw/qa/extras/tiledrendering/tiledrendering.cxx b/sw/qa/extras/tiledrendering/tiledrendering.cxx index 7dab608c6323..4ad4159a295c 100644 --- a/sw/qa/extras/tiledrendering/tiledrendering.cxx +++ b/sw/qa/extras/tiledrendering/tiledrendering.cxx @@ -246,6 +246,7 @@ public: private: SwXTextDocument* createDoc(const char* pName = nullptr); + void setupLibreOfficeKitViewCallback(SfxViewShell* pViewShell); static void callback(int nType, const char* pPayload, void* pData); void callbackImpl(int nType, const char* pPayload); // First invalidation. @@ -305,6 +306,7 @@ void SwTiledRenderingTest::tearDown() mxComponent->dispose(); mxComponent.clear(); } + m_callbackWrapper.clear(); comphelper::LibreOfficeKit::setActive(false); test::BootstrapFixture::tearDown(); @@ -323,6 +325,12 @@ SwXTextDocument* SwTiledRenderingTest::createDoc(const char* pName) return pTextDocument; } +void SwTiledRenderingTest::setupLibreOfficeKitViewCallback(SfxViewShell* pViewShell) +{ + pViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper); + m_callbackWrapper.setLOKViewId(SfxLokHelper::getView(pViewShell)); +} + void SwTiledRenderingTest::callback(int nType, const char* pPayload, void* pData) { static_cast<SwTiledRenderingTest*>(pData)->callbackImpl(nType, pPayload); @@ -441,7 +449,7 @@ void SwTiledRenderingTest::testRegisterCallback() { SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); // Insert a character at the beginning of the document. pWrtShell->Insert("x"); Scheduler::ProcessEventsToIdle(); @@ -625,7 +633,7 @@ void SwTiledRenderingTest::testSearch() { SwXTextDocument* pXTextDocument = createDoc("search.odt"); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); SwNodeOffset nNode = pWrtShell->getShellCursor(false)->Start()->nNode.GetNode().GetIndex(); // First hit, in the second paragraph, before the shape. @@ -690,7 +698,7 @@ void SwTiledRenderingTest::testSearchTextFrame() { SwXTextDocument* pXTextDocument = createDoc("search.odt"); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( { {"SearchItem.SearchString", uno::makeAny(OUString("TextFrame"))}, @@ -705,7 +713,7 @@ void SwTiledRenderingTest::testSearchTextFrameWrapAround() { SwXTextDocument* pXTextDocument = createDoc("search.odt"); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( { {"SearchItem.SearchString", uno::makeAny(OUString("TextFrame"))}, @@ -723,7 +731,7 @@ void SwTiledRenderingTest::testDocumentSizeChanged() // Get the current document size. SwXTextDocument* pXTextDocument = createDoc("2-pages.odt"); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); Size aSize = pXTextDocument->getDocumentSize(); // Delete the second page and see how the size changes. @@ -739,7 +747,7 @@ void SwTiledRenderingTest::testSearchAll() { SwXTextDocument* pXTextDocument = createDoc("search.odt"); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( { {"SearchItem.SearchString", uno::makeAny(OUString("shape"))}, @@ -757,7 +765,7 @@ void SwTiledRenderingTest::testSearchAllNotifications() { SwXTextDocument* pXTextDocument = createDoc("search.odt"); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); // Reset notification counter before search. m_nSelectionBeforeSearchResult = 0; uno::Sequence<beans::PropertyValue> aPropertyValues(comphelper::InitPropertySequence( @@ -784,7 +792,7 @@ void SwTiledRenderingTest::testPageDownInvalidation() })); pXTextDocument->initializeForTiledRendering(aPropertyValues); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); comphelper::dispatchCommand(".uno:PageDown", uno::Sequence<beans::PropertyValue>()); // This was 2. @@ -855,6 +863,7 @@ namespace { mpViewShell = pViewShell ? pViewShell : SfxViewShell::Current(); mpViewShell->setLibreOfficeKitViewCallback(&m_callbackWrapper); mnView = SfxLokHelper::getView(); + m_callbackWrapper.setLOKViewId( mnView ); } ~ViewCallback() @@ -1056,6 +1065,7 @@ void SwTiledRenderingTest::testViewCursors() SfxLokHelper::createView(); ViewCallback aView2; + Scheduler::ProcessEventsToIdle(); CPPUNIT_ASSERT(aView1.m_bOwnCursorInvalidated); CPPUNIT_ASSERT(aView1.m_bViewCursorInvalidated); CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated); @@ -1074,6 +1084,7 @@ void SwTiledRenderingTest::testViewCursors() pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 5, /*bBasicCall=*/false); // Create a selection on the word. pWrtShell->SelWrd(); + Scheduler::ProcessEventsToIdle(); SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); // Did we indeed manage to select the second word? CPPUNIT_ASSERT_EQUAL(OUString("bbb"), pShellCursor->GetText()); @@ -1535,7 +1546,7 @@ void SwTiledRenderingTest::testTrackChangesCallback() // Load a document. SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); // Turn on track changes and type "x". uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY); @@ -1562,7 +1573,7 @@ void SwTiledRenderingTest::testRedlineUpdateCallback() // Load a document. SwXTextDocument* pXTextDocument = createDoc("dummy.fodt"); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); // Turn on track changes, type "xx" and delete the second one. uno::Reference<beans::XPropertySet> xPropertySet(mxComponent, uno::UNO_QUERY); @@ -2351,7 +2362,7 @@ void SwTiledRenderingTest::testSplitNodeRedlineCallback() // Load a document. SwXTextDocument* pXTextDocument = createDoc("splitnode_redline_callback.fodt"); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); // 1. test case // Move cursor between the two tracked changes @@ -2409,7 +2420,7 @@ void SwTiledRenderingTest::testDeleteNodeRedlineCallback() // Load a document. SwXTextDocument* pXTextDocument = createDoc("removenode_redline_callback.fodt"); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); // 1. test case // Move cursor between the two tracked changes @@ -2474,7 +2485,6 @@ void SwTiledRenderingTest::testVisCursorInvalidation() ViewCallback aView2; Scheduler::ProcessEventsToIdle(); - // Move visible cursor in the first view SfxLokHelper::setView(nView1); Scheduler::ProcessEventsToIdle(); @@ -2496,6 +2506,7 @@ void SwTiledRenderingTest::testVisCursorInvalidation() // Insert text in the second view which moves the other view's cursor too SfxLokHelper::setView(nView2); + Scheduler::ProcessEventsToIdle(); aView1.m_bOwnCursorInvalidated = false; aView1.m_bViewCursorInvalidated = false; aView2.m_bOwnCursorInvalidated = false; @@ -2509,12 +2520,19 @@ void SwTiledRenderingTest::testVisCursorInvalidation() CPPUNIT_ASSERT(aView1.m_bOwnCursorInvalidated); CPPUNIT_ASSERT(aView2.m_bViewCursorInvalidated); CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated); + // Check that views have correct location for the other's cursor. + CPPUNIT_ASSERT_EQUAL(aView1.m_aOwnCursor, aView2.m_aViewCursor); + CPPUNIT_ASSERT_EQUAL(aView2.m_aOwnCursor, aView1.m_aViewCursor); + // Their cursors should be on the same line, first view's more to the right. + CPPUNIT_ASSERT_EQUAL(aView1.m_aOwnCursor.getY(), aView2.m_aOwnCursor.getY()); + CPPUNIT_ASSERT_GREATER(aView2.m_aOwnCursor.getX(), aView1.m_aOwnCursor.getX()); // Do the same as before, but set the related compatibility flag first SfxLokHelper::setView(nView2); comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(true); + Scheduler::ProcessEventsToIdle(); aView1.m_bOwnCursorInvalidated = false; aView1.m_bViewCursorInvalidated = false; aView2.m_bOwnCursorInvalidated = false; @@ -2530,6 +2548,11 @@ void SwTiledRenderingTest::testVisCursorInvalidation() CPPUNIT_ASSERT(aView2.m_bViewCursorInvalidated); CPPUNIT_ASSERT(aView2.m_bOwnCursorInvalidated); CPPUNIT_ASSERT_EQUAL(nView2, aView2.m_nOwnCursorInvalidatedBy); + CPPUNIT_ASSERT_EQUAL(aView1.m_aOwnCursor, aView2.m_aViewCursor); + CPPUNIT_ASSERT_EQUAL(aView2.m_aOwnCursor, aView1.m_aViewCursor); + // Their cursors should be on the same line, first view's more to the right. + CPPUNIT_ASSERT_EQUAL(aView1.m_aOwnCursor.getY(), aView2.m_aOwnCursor.getY()); + CPPUNIT_ASSERT_GREATER(aView2.m_aOwnCursor.getX(), aView1.m_aOwnCursor.getX()); comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(false); } @@ -2786,7 +2809,7 @@ void SwTiledRenderingTest::testRedlineNotificationDuringSave() // It's an empty document, just settings.xml and content.xml are custom. SwXTextDocument* pXTextDocument = createDoc("redline-notification-during-save.odt"); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); // Save the document. utl::MediaDescriptor aMediaDescriptor; @@ -2802,7 +2825,8 @@ void SwTiledRenderingTest::testHyperlink() comphelper::LibreOfficeKit::setViewIdForVisCursorInvalidation(true); SwXTextDocument* pXTextDocument = createDoc("hyperlink.odt"); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); + m_callbackWrapper.setLOKViewId(SfxLokHelper::getView(pWrtShell->GetSfxViewShell())); SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); Point aStart = pShellCursor->GetSttPos(); @@ -2829,7 +2853,7 @@ void SwTiledRenderingTest::testDropDownFormFieldButton() pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000)); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); // Move the cursor to trigger displaying of the field button. pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false); @@ -2902,7 +2926,7 @@ void SwTiledRenderingTest::testDropDownFormFieldButtonEditing() pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000)); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); // Move the cursor to trigger displaying of the field button. pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false); @@ -2959,7 +2983,7 @@ void SwTiledRenderingTest::testDropDownFormFieldButtonNoSelection() pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000)); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); // Move the cursor to trigger displaying of the field button. pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false); @@ -3012,7 +3036,7 @@ void SwTiledRenderingTest::testMoveShapeHandle() SwXTextDocument* pXTextDocument = createDoc("shape.fodt"); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); SdrPage* pPage = pWrtShell->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()->GetPage(0); SdrObject* pObject = pPage->GetObj(0); pWrtShell->SelectObj(Point(), 0, pObject); @@ -3045,7 +3069,7 @@ void SwTiledRenderingTest::testDropDownFormFieldButtonNoItem() pXTextDocument->setClientVisibleArea(tools::Rectangle(0, 0, 10000, 4000)); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); // Move the cursor to trigger displaying of the field button. pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false); @@ -3082,7 +3106,7 @@ void SwTiledRenderingTest::testTablePaintInvalidate() // Load a document with a table in it. SwXTextDocument* pXTextDocument = createDoc("table-paint-invalidate.odt"); SwWrtShell* pWrtShell = pXTextDocument->GetDocShell()->GetWrtShell(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); // Enter the table. pWrtShell->Down(/*bSelect=*/false); Scheduler::ProcessEventsToIdle(); @@ -3180,7 +3204,7 @@ void SwTiledRenderingTest::testBulletDeleteInvalidation() pWrtShell->GetLayout()->PaintSwFrame(*pWrtShell->GetOut(), pWrtShell->GetLayout()->getFrameArea()); Scheduler::ProcessEventsToIdle(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); m_aInvalidations = tools::Rectangle(); // When pressing backspace in the last paragraph. @@ -3210,7 +3234,7 @@ void SwTiledRenderingTest::testBulletNoNumInvalidation() pWrtShell->GetLayout()->PaintSwFrame(*pWrtShell->GetOut(), pWrtShell->GetLayout()->getFrameArea()); Scheduler::ProcessEventsToIdle(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); m_aInvalidations = tools::Rectangle(); // When pressing backspace in the last paragraph to turn bullets off. @@ -3247,7 +3271,7 @@ void SwTiledRenderingTest::testBulletMultiDeleteInvalidation() pWrtShell->GetLayout()->PaintSwFrame(*pWrtShell->GetOut(), pWrtShell->GetLayout()->getFrameArea()); Scheduler::ProcessEventsToIdle(); - pWrtShell->GetSfxViewShell()->setLibreOfficeKitViewCallback(&m_callbackWrapper); + setupLibreOfficeKitViewCallback(pWrtShell->GetSfxViewShell()); m_aInvalidations = tools::Rectangle(); // When selecting and deleting several bullets: select till the end of the 2nd para and delete. diff --git a/sw/source/core/crsr/viscrs.cxx b/sw/source/core/crsr/viscrs.cxx index c756d3da3586..2c77e6d4dd87 100644 --- a/sw/source/core/crsr/viscrs.cxx +++ b/sw/source/core/crsr/viscrs.cxx @@ -225,12 +225,64 @@ void SwVisibleCursor::SetPosAndShow(SfxViewShell const * pViewShell) m_pCursorShell->GetSfxViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, aPayload.getStr()); } + // This may get called often, so instead of sending data on each update, just notify + // that there's been an update, and the other side will pull the data using + // getLOKPayload() when it decides to. + m_aLastLOKRect = aRect; + if (pViewShell) + { + if (pViewShell == m_pCursorShell->GetSfxViewShell()) + { + SfxLokHelper::notifyUpdatePerViewId(pViewShell, LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR); + } + else + { + SfxLokHelper::notifyUpdatePerViewId(pViewShell, m_pCursorShell->GetSfxViewShell(), pViewShell, + LOK_CALLBACK_INVALIDATE_VIEW_CURSOR); + } + } + else + { + SfxLokHelper::notifyUpdatePerViewId(m_pCursorShell->GetSfxViewShell(), SfxViewShell::Current(), + m_pCursorShell->GetSfxViewShell(), LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR); + SfxLokHelper::notifyOtherViewsUpdatePerViewId(m_pCursorShell->GetSfxViewShell(), LOK_CALLBACK_INVALIDATE_VIEW_CURSOR); + } + } + + if ( m_pCursorShell->IsCursorReadonly() && !m_pCursorShell->GetViewOptions()->IsSelectionInReadonly() ) + return; + + if ( m_pCursorShell->GetDrawView() ) + const_cast<SwDrawView*>(static_cast<const SwDrawView*>(m_pCursorShell->GetDrawView()))->SetAnimationEnabled( + !m_pCursorShell->IsSelection() ); + + sal_uInt16 nStyle = m_bIsDragCursor ? CURSOR_SHADOW : 0; + if( nStyle != m_aTextCursor.GetStyle() ) + { + m_aTextCursor.SetStyle( nStyle ); + m_aTextCursor.SetWindow( m_bIsDragCursor ? m_pCursorShell->GetWin() : nullptr ); + } + + m_aTextCursor.Show(); +} + +OString SwVisibleCursor::getLOKPayload(int nType, int nViewId) const +{ + assert(nType == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR || nType == LOK_CALLBACK_INVALIDATE_VIEW_CURSOR); + if (comphelper::LibreOfficeKit::isActive()) + { + SwRect aRect = m_aLastLOKRect; + // notify about the cursor position & size tools::Rectangle aSVRect(aRect.Pos().getX(), aRect.Pos().getY(), aRect.Pos().getX() + aRect.SSize().Width(), aRect.Pos().getY() + aRect.SSize().Height()); OString sRect = aSVRect.toString(); + if(nType == LOK_CALLBACK_INVALIDATE_VIEW_CURSOR) + return SfxLokHelper::makePayloadJSON(m_pCursorShell->GetSfxViewShell(), nViewId, "rectangle", sRect); + // is cursor at a misspelled word ? bool bIsWrong = false; + SwView* pView = dynamic_cast<SwView*>(m_pCursorShell->GetSfxViewShell()); if (pView && pView->GetWrtShellPtr()) { const SwViewOption* pVOpt = pView->GetWrtShell().GetViewOptions(); @@ -283,37 +335,10 @@ void SwVisibleCursor::SetPosAndShow(SfxViewShell const * pViewShell) } } - if (pViewShell) - { - if (pViewShell == m_pCursorShell->GetSfxViewShell()) - { - SfxLokHelper::notifyVisCursorInvalidation(pViewShell, sRect, bIsWrong, sHyperlink); - } - else - SfxLokHelper::notifyOtherView(m_pCursorShell->GetSfxViewShell(), pViewShell, LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, "rectangle", sRect); - } - else - { - SfxLokHelper::notifyVisCursorInvalidation(m_pCursorShell->GetSfxViewShell(), sRect, bIsWrong, sHyperlink); - SfxLokHelper::notifyOtherViews(m_pCursorShell->GetSfxViewShell(), LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, "rectangle", sRect); - } + return SfxLokHelper::makeVisCursorInvalidation(nViewId, sRect, bIsWrong, sHyperlink); } - - if ( m_pCursorShell->IsCursorReadonly() && !m_pCursorShell->GetViewOptions()->IsSelectionInReadonly() ) - return; - - if ( m_pCursorShell->GetDrawView() ) - const_cast<SwDrawView*>(static_cast<const SwDrawView*>(m_pCursorShell->GetDrawView()))->SetAnimationEnabled( - !m_pCursorShell->IsSelection() ); - - sal_uInt16 nStyle = m_bIsDragCursor ? CURSOR_SHADOW : 0; - if( nStyle != m_aTextCursor.GetStyle() ) - { - m_aTextCursor.SetStyle( nStyle ); - m_aTextCursor.SetWindow( m_bIsDragCursor ? m_pCursorShell->GetWin() : nullptr ); - } - - m_aTextCursor.Show(); + else + abort(); } const vcl::Cursor& SwVisibleCursor::GetTextCursor() const diff --git a/sw/source/uibase/inc/wrtsh.hxx b/sw/source/uibase/inc/wrtsh.hxx index 8e2573916e8b..a47e68cd2045 100644 --- a/sw/source/uibase/inc/wrtsh.hxx +++ b/sw/source/uibase/inc/wrtsh.hxx @@ -499,6 +499,8 @@ typedef bool (SwWrtShell::*FNSimpleMove)(); void InvalidateOutlineContentVisibility(); bool GetAttrOutlineContentVisible(const size_t nPos); + OString getLOKPayload(int nType, int nViewId) const; + private: SAL_DLLPRIVATE void OpenMark(); diff --git a/sw/source/uibase/uiview/view.cxx b/sw/source/uibase/uiview/view.cxx index 9a4b83a28566..7755afb107ea 100644 --- a/sw/source/uibase/uiview/view.cxx +++ b/sw/source/uibase/uiview/view.cxx @@ -1912,6 +1912,13 @@ void SwView::flushPendingLOKInvalidateTiles() pSh->FlushPendingLOKInvalidateTiles(); } +OString SwView::getLOKPayload(int nType, int nViewId) const +{ + SwWrtShell* pSh = GetWrtShellPtr(); + assert(pSh); + return pSh->getLOKPayload(nType, nViewId); +} + OUString SwView::GetDataSourceName() const { uno::Reference<lang::XMultiServiceFactory> xFactory(GetDocShell()->GetModel(), uno::UNO_QUERY); diff --git a/sw/source/uibase/wrtsh/wrtsh4.cxx b/sw/source/uibase/wrtsh/wrtsh4.cxx index 8009ce98037b..b80b41b41e54 100644 --- a/sw/source/uibase/wrtsh/wrtsh4.cxx +++ b/sw/source/uibase/wrtsh/wrtsh4.cxx @@ -19,6 +19,8 @@ #include <wrtsh.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> + // Private methods, which move the cursor over search. // The removal of the selection must be made on the level above. @@ -232,4 +234,15 @@ bool SwWrtShell::BwdPara_() return bRet; } +OString SwWrtShell::getLOKPayload(int nType, int nViewId) const +{ + switch(nType) + { + case LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR: + case LOK_CALLBACK_INVALIDATE_VIEW_CURSOR: + return GetVisibleCursor()->getLOKPayload(nType, nViewId); + } + abort(); +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/test/source/lokcallback.cxx b/test/source/lokcallback.cxx index 13d381f0b46a..6a39b9064470 100644 --- a/test/source/lokcallback.cxx +++ b/test/source/lokcallback.cxx @@ -25,22 +25,37 @@ TestLokCallbackWrapper::TestLokCallbackWrapper(LibreOfficeKitCallback callback, SetPriority(TaskPriority::LOWEST); } -inline void TestLokCallbackWrapper::callCallback(int nType, const char* pPayload) +void TestLokCallbackWrapper::clear() +{ + m_viewId = -1; + m_updatedTypes.clear(); + m_updatedTypesPerViewId.clear(); +} + +inline void TestLokCallbackWrapper::startTimer() { - m_callback(nType, pPayload, m_data); if (!IsActive()) Start(); } +constexpr int NO_VIEWID = -1; + +inline void TestLokCallbackWrapper::callCallback(int nType, const char* pPayload, int nViewId) +{ + discardUpdatedTypes(nType, nViewId); + m_callback(nType, pPayload, m_data); + startTimer(); +} + void TestLokCallbackWrapper::libreOfficeKitViewCallback(int nType, const char* pPayload) { - callCallback(nType, pPayload); + callCallback(nType, pPayload, NO_VIEWID); } void TestLokCallbackWrapper::libreOfficeKitViewCallbackWithViewId(int nType, const char* pPayload, - int /*nViewId*/) + int nViewId) { - callCallback(nType, pPayload); // the view id is also included in payload + callCallback(nType, pPayload, nViewId); } void TestLokCallbackWrapper::libreOfficeKitViewInvalidateTilesCallback( @@ -56,7 +71,100 @@ void TestLokCallbackWrapper::libreOfficeKitViewInvalidateTilesCallback( buf.append(", "); buf.append(static_cast<sal_Int32>(nPart)); } - callCallback(LOK_CALLBACK_INVALIDATE_TILES, buf.makeStringAndClear().getStr()); + callCallback(LOK_CALLBACK_INVALIDATE_TILES, buf.makeStringAndClear().getStr(), NO_VIEWID); +} + +// TODO This is probably a pointless code duplication with CallbackFlushHandler, +// and using this in unittests also means that CallbackFlushHandler does not get +// tested as thoroughly as it could. On the other hand, this class is simpler, +// so debugging those unittests should also be simpler. The proper solution +// is presumably this class using CallbackFlushHandler internally by default, +// but having an option to use this simpler code when needed. + +void TestLokCallbackWrapper::libreOfficeKitViewUpdatedCallback(int nType) +{ + if (std::find(m_updatedTypes.begin(), m_updatedTypes.end(), nType) == m_updatedTypes.end()) + { + m_updatedTypes.push_back(nType); + startTimer(); + } +} + +void TestLokCallbackWrapper::libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int nViewId, + int nSourceViewId) +{ + const PerViewIdData data{ nType, nViewId, nSourceViewId }; + auto& l = m_updatedTypesPerViewId; + // The source view doesn't matter for uniqueness, just keep the latest one. + auto it = std::find_if(l.begin(), l.end(), [data](const PerViewIdData& other) { + return data.type == other.type && data.viewId == other.viewId; + }); + if (it != l.end()) + *it = data; + else + l.push_back(data); + startTimer(); +} + +void TestLokCallbackWrapper::discardUpdatedTypes(int nType, int nViewId) +{ + // If a callback is called directly with an event, drop the updated flag for it, since + // the direct event replaces it. + for (auto it = m_updatedTypes.begin(); it != m_updatedTypes.end();) + { + if (*it == nType) + it = m_updatedTypes.erase(it); + else + ++it; + } + // If we do not have a specific view id, drop flag for all views. + bool allViewIds = false; + if (nViewId < 0) + allViewIds = true; + if (nType == LOK_CALLBACK_INVALIDATE_VISIBLE_CURSOR + && !comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation()) + allViewIds = true; + for (auto it = m_updatedTypesPerViewId.begin(); it != m_updatedTypesPerViewId.end();) + { + if (it->type == nType && (allViewIds || it->viewId == nViewId)) + it = m_updatedTypesPerViewId.erase(it); + else + ++it; + } +} + +void TestLokCallbackWrapper::flushLOKData() +{ + if (m_updatedTypes.empty() && m_updatedTypesPerViewId.empty()) + return; + // Ask for payloads of all the pending types that need updating, and call the generic callback with that data. + assert(m_viewId >= 0); + SfxViewShell* viewShell = SfxViewShell::GetFirst(false, [this](const SfxViewShell* shell) { + return shell->GetViewShellId().get() == m_viewId; + }); + assert(viewShell != nullptr); + // First move data to local structures, so that notifyFromLOKCallback() doesn't modify it. + std::vector<int> updatedTypes; + std::swap(updatedTypes, m_updatedTypes); + std::vector<PerViewIdData> updatedTypesPerViewId; + std::swap(updatedTypesPerViewId, m_updatedTypesPerViewId); + + for (int type : updatedTypes) + { + OString payload = viewShell->getLOKPayload(type, m_viewId); + if (!payload.isEmpty()) + libreOfficeKitViewCallback(type, payload.getStr()); + } + for (const PerViewIdData& data : updatedTypesPerViewId) + { + viewShell = SfxViewShell::GetFirst(false, [data](const SfxViewShell* shell) { + return shell->GetViewShellId().get() == data.sourceViewId; + }); + assert(viewShell != nullptr); + OString payload = viewShell->getLOKPayload(data.type, data.viewId); + if (!payload.isEmpty()) + libreOfficeKitViewCallbackWithViewId(data.type, payload.getStr(), data.viewId); + } } void TestLokCallbackWrapper::Invoke() @@ -67,6 +175,7 @@ void TestLokCallbackWrapper::Invoke() { viewShell->flushPendingLOKInvalidateTiles(); } + flushLOKData(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |