Bump version to 24.04.3.4
[LibreOffice.git] / sfx2 / source / view / lokhelper.cxx
blob99f39fbf4e8b7f8ad959348599c9ca91e8e55c4a
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <sal/config.h>
12 #include <string>
13 #include <string_view>
14 #include <list>
16 #include <sfx2/lokcomponenthelpers.hxx>
17 #include <sfx2/lokhelper.hxx>
19 #include <com/sun/star/frame/Desktop.hpp>
20 #include <com/sun/star/ui/ContextChangeEventObject.hpp>
22 #include <comphelper/processfactory.hxx>
23 #include <o3tl/string_view.hxx>
24 #include <rtl/strbuf.hxx>
25 #include <vcl/lok.hxx>
26 #include <vcl/svapp.hxx>
27 #include <vcl/commandevent.hxx>
28 #include <vcl/window.hxx>
29 #include <sal/log.hxx>
30 #include <sfx2/app.hxx>
31 #include <sfx2/msg.hxx>
32 #include <sfx2/viewsh.hxx>
33 #include <sfx2/request.hxx>
34 #include <sfx2/sfxsids.hrc>
35 #include <sfx2/viewfrm.hxx>
36 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
37 #include <comphelper/lok.hxx>
38 #include <sfx2/msgpool.hxx>
39 #include <comphelper/scopeguard.hxx>
40 #include <tools/json_writer.hxx>
42 #include <boost/property_tree/json_parser.hpp>
44 using namespace com::sun::star;
46 namespace {
47 bool g_bSettingView(false);
49 /// Used to disable callbacks.
50 /// Needed to avoid recursion when switching views,
51 /// which can cause clients to invoke LOKit API and
52 /// implicitly set the view, which might cause an
53 /// infinite recursion if not detected and prevented.
54 class DisableCallbacks
56 public:
57 DisableCallbacks()
59 assert(m_nDisabled >= 0 && "Expected non-negative DisabledCallbacks state when disabling.");
60 ++m_nDisabled;
63 ~DisableCallbacks()
65 assert(m_nDisabled > 0 && "Expected positive DisabledCallbacks state when re-enabling.");
66 --m_nDisabled;
69 static inline bool disabled()
71 return !comphelper::LibreOfficeKit::isActive() || m_nDisabled != 0;
74 private:
75 static int m_nDisabled;
78 int DisableCallbacks::m_nDisabled = 0;
81 namespace
83 LanguageTag g_defaultLanguageTag("en-US", true);
84 LanguageTag g_loadLanguageTag("en-US", true); //< The language used to load.
85 LOKDeviceFormFactor g_deviceFormFactor = LOKDeviceFormFactor::UNKNOWN;
86 bool g_isDefaultTimezoneSet = false;
87 OUString g_DefaultTimezone;
88 const std::size_t g_logNotifierCacheMaxSize = 50;
89 ::std::list<::std::string> g_logNotifierCache;
92 int SfxLokHelper::createView(SfxViewFrame& rViewFrame, ViewShellDocId docId)
94 assert(docId >= ViewShellDocId(0) && "Cannot createView for invalid (negative) DocId.");
96 SfxViewShell::SetCurrentDocId(docId);
97 SfxRequest aRequest(rViewFrame, SID_NEWWINDOW);
98 rViewFrame.ExecView_Impl(aRequest);
99 SfxViewShell* pViewShell = SfxViewShell::Current();
100 if (pViewShell == nullptr)
101 return -1;
103 assert(pViewShell->GetDocId() == docId && "DocId must be already set!");
104 return static_cast<sal_Int32>(pViewShell->GetViewShellId());
107 int SfxLokHelper::createView()
109 // Assumes a single document, or at least that the
110 // current view belongs to the document on which the
111 // view will be created.
112 SfxViewShell* pViewShell = SfxViewShell::Current();
113 if (pViewShell == nullptr)
114 return -1;
116 return createView(pViewShell->GetViewFrame(), pViewShell->GetDocId());
119 std::unordered_map<OUString, css::uno::Reference<com::sun::star::ui::XAcceleratorConfiguration>>& SfxLokHelper::getAcceleratorConfs()
121 return SfxApplication::GetOrCreate()->GetAcceleratorConfs_Impl();
124 int SfxLokHelper::createView(int nDocId)
126 const SfxApplication* pApp = SfxApplication::Get();
127 if (pApp == nullptr)
128 return -1;
130 // Find a shell with the given DocId.
131 const ViewShellDocId docId(nDocId);
132 for (const SfxViewShell* pViewShell : pApp->GetViewShells_Impl())
134 if (pViewShell->GetDocId() == docId)
135 return createView(pViewShell->GetViewFrame(), docId);
138 // No frame with nDocId found.
139 return -1;
142 void SfxLokHelper::setEditMode(int nMode, vcl::ITiledRenderable* pDoc)
144 DisableCallbacks dc;
145 pDoc->setEditMode(nMode);
148 void SfxLokHelper::destroyView(int nId)
150 const SfxApplication* pApp = SfxApplication::Get();
151 if (pApp == nullptr)
152 return;
154 const ViewShellId nViewShellId(nId);
155 std::vector<SfxViewShell*>& rViewArr = pApp->GetViewShells_Impl();
157 for (SfxViewShell* pViewShell : rViewArr)
159 if (pViewShell->GetViewShellId() == nViewShellId)
161 pViewShell->SetLOKAccessibilityState(false);
162 SfxViewFrame& rViewFrame = pViewShell->GetViewFrame();
163 SfxRequest aRequest(rViewFrame, SID_CLOSEWIN);
164 rViewFrame.Exec_Impl(aRequest);
165 break;
170 bool SfxLokHelper::isSettingView()
172 return g_bSettingView;
175 void SfxLokHelper::setView(int nId)
177 g_bSettingView = true;
178 comphelper::ScopeGuard g([] { g_bSettingView = false; });
180 SfxApplication* pApp = SfxApplication::Get();
181 if (pApp == nullptr)
182 return;
184 const ViewShellId nViewShellId(nId);
185 std::vector<SfxViewShell*>& rViewArr = pApp->GetViewShells_Impl();
187 const auto itViewShell = std::find_if(rViewArr.begin(), rViewArr.end(), [nViewShellId](SfxViewShell* pViewShell){ return pViewShell->GetViewShellId() == nViewShellId; });
188 if (itViewShell == rViewArr.end())
189 return;
191 const SfxViewShell* pViewShell = *itViewShell;
192 DisableCallbacks dc;
194 bool bIsCurrShell = (pViewShell == SfxViewShell::Current());
195 if (bIsCurrShell && comphelper::LibreOfficeKit::getLanguageTag().getBcp47() == pViewShell->GetLOKLanguageTag().getBcp47())
196 return;
198 if (bIsCurrShell)
200 // If we wanted to set the SfxViewShell that is actually set, we could skip it.
201 // But it looks like that the language can go wrong, so we have to fix that.
202 // This can happen, when someone sets the language or SfxViewShell::Current() separately.
203 SAL_WARN("lok", "LANGUAGE mismatch at setView! ... old (wrong) lang:"
204 << comphelper::LibreOfficeKit::getLanguageTag().getBcp47()
205 << " new lang:" << pViewShell->GetLOKLanguageTag().getBcp47());
208 // update the current LOK language and locale for the dialog tunneling
209 comphelper::LibreOfficeKit::setLanguageTag(pViewShell->GetLOKLanguageTag());
210 comphelper::LibreOfficeKit::setLocale(pViewShell->GetLOKLocale());
212 if (bIsCurrShell)
213 return;
215 SfxViewFrame& rViewFrame = pViewShell->GetViewFrame();
216 rViewFrame.MakeActive_Impl(false);
218 // Make comphelper::dispatchCommand() find the correct frame.
219 uno::Reference<frame::XFrame> xFrame = rViewFrame.GetFrame().GetFrameInterface();
220 uno::Reference<frame::XDesktop2> xDesktop = frame::Desktop::create(comphelper::getProcessComponentContext());
221 xDesktop->setActiveFrame(xFrame);
224 SfxViewShell* SfxLokHelper::getViewOfId(int nId)
226 SfxApplication* pApp = SfxApplication::Get();
227 if (pApp == nullptr)
228 return nullptr;
230 const ViewShellId nViewShellId(nId);
231 std::vector<SfxViewShell*>& rViewArr = pApp->GetViewShells_Impl();
232 for (SfxViewShell* pViewShell : rViewArr)
234 if (pViewShell->GetViewShellId() == nViewShellId)
235 return pViewShell;
238 return nullptr;
241 int SfxLokHelper::getView(const SfxViewShell* pViewShell)
243 if (!pViewShell)
244 pViewShell = SfxViewShell::Current();
245 // Still no valid view shell? Then no idea.
246 if (!pViewShell)
247 return -1;
249 return static_cast<sal_Int32>(pViewShell->GetViewShellId());
252 std::size_t SfxLokHelper::getViewsCount(int nDocId)
254 assert(nDocId != -1 && "Cannot getViewsCount for invalid DocId -1");
256 SfxApplication* pApp = SfxApplication::Get();
257 if (!pApp)
258 return 0;
260 const ViewShellDocId nCurrentDocId(nDocId);
261 std::size_t n = 0;
262 SfxViewShell* pViewShell = SfxViewShell::GetFirst();
263 while (pViewShell)
265 if (pViewShell->GetDocId() == nCurrentDocId)
266 n++;
267 pViewShell = SfxViewShell::GetNext(*pViewShell);
270 return n;
273 bool SfxLokHelper::getViewIds(int nDocId, int* pArray, size_t nSize)
275 assert(nDocId != -1 && "Cannot getViewsIds for invalid DocId -1");
277 SfxApplication* pApp = SfxApplication::Get();
278 if (!pApp)
279 return false;
281 const ViewShellDocId nCurrentDocId(nDocId);
282 std::size_t n = 0;
283 SfxViewShell* pViewShell = SfxViewShell::GetFirst();
284 while (pViewShell)
286 if (pViewShell->GetDocId() == nCurrentDocId)
288 if (n == nSize)
289 return false;
291 pArray[n] = static_cast<sal_Int32>(pViewShell->GetViewShellId());
292 n++;
295 pViewShell = SfxViewShell::GetNext(*pViewShell);
298 return true;
301 int SfxLokHelper::getDocumentIdOfView(int nViewId)
303 SfxViewShell* pViewShell = SfxViewShell::GetFirst();
304 while (pViewShell)
306 if (pViewShell->GetViewShellId() == ViewShellId(nViewId))
307 return static_cast<int>(pViewShell->GetDocId());
308 pViewShell = SfxViewShell::GetNext(*pViewShell);
310 return -1;
313 const LanguageTag & SfxLokHelper::getDefaultLanguage()
315 return g_defaultLanguageTag;
318 void SfxLokHelper::setDefaultLanguage(const OUString& rBcp47LanguageTag)
320 g_defaultLanguageTag = LanguageTag(rBcp47LanguageTag, true);
323 const LanguageTag& SfxLokHelper::getLoadLanguage() { return g_loadLanguageTag; }
325 void SfxLokHelper::setLoadLanguage(const OUString& rBcp47LanguageTag)
327 g_loadLanguageTag = LanguageTag(rBcp47LanguageTag, true);
330 void SfxLokHelper::setViewLanguage(int nId, const OUString& rBcp47LanguageTag)
332 std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
334 for (SfxViewShell* pViewShell : rViewArr)
336 if (pViewShell->GetViewShellId() == ViewShellId(nId))
338 pViewShell->SetLOKLanguageTag(rBcp47LanguageTag);
339 return;
344 void SfxLokHelper::setViewReadOnly(int nId, bool readOnly)
346 std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
348 for (SfxViewShell* pViewShell : rViewArr)
350 if (pViewShell && pViewShell->GetViewShellId() == ViewShellId(nId))
352 LOK_INFO("lok.readonlyview", "SfxLokHelper::setViewReadOnly: view id: " << nId << ", readOnly: " << readOnly);
353 pViewShell->SetLokReadOnlyView(readOnly);
354 return;
359 void SfxLokHelper::setAllowChangeComments(int nId, bool allow)
361 std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
363 for (SfxViewShell* pViewShell : rViewArr)
365 if (pViewShell && pViewShell->GetViewShellId() == ViewShellId(nId))
367 LOK_INFO("lok.readonlyview", "SfxLokHelper::setAllowChangeComments: view id: " << nId << ", allow: " << allow);
368 pViewShell->SetAllowChangeComments(allow);
369 return;
374 void SfxLokHelper::setAccessibilityState(int nId, bool nEnabled)
376 std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
378 for (SfxViewShell* pViewShell : rViewArr)
380 if (pViewShell && pViewShell->GetViewShellId() == ViewShellId(nId))
382 LOK_INFO("lok.a11y", "SfxLokHelper::setAccessibilityState: view id: " << nId << ", nEnabled: " << nEnabled);
383 pViewShell->SetLOKAccessibilityState(nEnabled);
384 return;
389 void SfxLokHelper::setViewLocale(int nId, const OUString& rBcp47LanguageTag)
391 std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
393 for (SfxViewShell* pViewShell : rViewArr)
395 if (pViewShell->GetViewShellId() == ViewShellId(nId))
397 pViewShell->SetLOKLocale(rBcp47LanguageTag);
398 return;
403 LOKDeviceFormFactor SfxLokHelper::getDeviceFormFactor()
405 return g_deviceFormFactor;
408 void SfxLokHelper::setDeviceFormFactor(std::u16string_view rDeviceFormFactor)
410 if (rDeviceFormFactor == u"desktop")
411 g_deviceFormFactor = LOKDeviceFormFactor::DESKTOP;
412 else if (rDeviceFormFactor == u"tablet")
413 g_deviceFormFactor = LOKDeviceFormFactor::TABLET;
414 else if (rDeviceFormFactor == u"mobile")
415 g_deviceFormFactor = LOKDeviceFormFactor::MOBILE;
416 else
417 g_deviceFormFactor = LOKDeviceFormFactor::UNKNOWN;
420 void SfxLokHelper::setDefaultTimezone(bool isSet, const OUString& rTimezone)
422 g_isDefaultTimezoneSet = isSet;
423 g_DefaultTimezone = rTimezone;
426 std::pair<bool, OUString> SfxLokHelper::getDefaultTimezone()
428 return { g_isDefaultTimezoneSet, g_DefaultTimezone };
431 void SfxLokHelper::setViewTimezone(int nId, bool isSet, const OUString& rTimezone)
433 std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
435 for (SfxViewShell* pViewShell : rViewArr)
437 if (pViewShell->GetViewShellId() == ViewShellId(nId))
439 pViewShell->SetLOKTimezone(isSet, rTimezone);
440 return;
445 std::pair<bool, OUString> SfxLokHelper::getViewTimezone(int nId)
447 std::vector<SfxViewShell*>& rViewArr = SfxGetpApp()->GetViewShells_Impl();
449 for (SfxViewShell* pViewShell : rViewArr)
451 if (pViewShell->GetViewShellId() == ViewShellId(nId))
453 return pViewShell->GetLOKTimezone();
457 return {};
461 * Used for putting a whole JSON string into a string value
462 * e.g { key: "{JSON}" }
464 static OString lcl_sanitizeJSONAsValue(const OString &rStr)
466 if (rStr.getLength() < 1)
467 return rStr;
468 // FIXME: need an optimized 'escape' method for O[U]String.
469 OStringBuffer aBuf(rStr.getLength() + 8);
470 for (sal_Int32 i = 0; i < rStr.getLength(); ++i)
472 if (rStr[i] == '"' || rStr[i] == '\\')
473 aBuf.append('\\');
475 if (rStr[i] != '\n')
476 aBuf.append(rStr[i]);
478 return aBuf.makeStringAndClear();
481 static OString lcl_generateJSON(const SfxViewShell* pView, const boost::property_tree::ptree& rTree)
483 assert(pView != nullptr && "pView must be valid");
484 boost::property_tree::ptree aMessageProps = rTree;
485 aMessageProps.put("viewId", SfxLokHelper::getView(pView));
486 aMessageProps.put("part", pView->getPart());
487 aMessageProps.put("mode", pView->getEditMode());
488 std::stringstream aStream;
489 boost::property_tree::write_json(aStream, aMessageProps, false /* pretty */);
490 return OString(o3tl::trim(aStream.str()));
493 static inline OString lcl_generateJSON(const SfxViewShell* pView, int nViewId, std::string_view rKey,
494 const OString& rPayload)
496 assert(pView != nullptr && "pView must be valid");
497 return OString::Concat("{ \"viewId\": \"") + OString::number(nViewId)
498 + "\", \"part\": \"" + OString::number(pView->getPart()) + "\", \"mode\": \""
499 + OString::number(pView->getEditMode()) + "\", \"" + rKey + "\": \""
500 + lcl_sanitizeJSONAsValue(rPayload) + "\" }";
503 static inline OString lcl_generateJSON(const SfxViewShell* pView, std::string_view rKey,
504 const OString& rPayload)
506 return lcl_generateJSON(pView, SfxLokHelper::getView(pView), rKey, rPayload);
509 void SfxLokHelper::notifyOtherView(const SfxViewShell* pThisView, SfxViewShell const* pOtherView,
510 int nType, std::string_view rKey, const OString& rPayload)
512 assert(pThisView != nullptr && "pThisView must be valid");
513 if (DisableCallbacks::disabled())
514 return;
516 const OString aPayload = lcl_generateJSON(pThisView, rKey, rPayload);
517 const int viewId = SfxLokHelper::getView(pThisView);
518 pOtherView->libreOfficeKitViewCallbackWithViewId(nType, aPayload, viewId);
521 void SfxLokHelper::notifyOtherView(const SfxViewShell* pThisView, SfxViewShell const* pOtherView,
522 int nType, const boost::property_tree::ptree& rTree)
524 assert(pThisView != nullptr && "pThisView must be valid");
525 if (DisableCallbacks::disabled() || !pOtherView)
526 return;
528 const int viewId = SfxLokHelper::getView(pThisView);
529 pOtherView->libreOfficeKitViewCallbackWithViewId(nType, lcl_generateJSON(pThisView, rTree), viewId);
532 void SfxLokHelper::notifyOtherViews(const SfxViewShell* pThisView, int nType, std::string_view rKey,
533 const OString& rPayload)
535 assert(pThisView != nullptr && "pThisView must be valid");
536 if (DisableCallbacks::disabled())
537 return;
539 // Cache the payload so we only have to generate it once, at most.
540 OString aPayload;
541 int viewId = -1;
543 const ViewShellDocId nCurrentDocId = pThisView->GetDocId();
544 SfxViewShell* pViewShell = SfxViewShell::GetFirst();
545 while (pViewShell)
547 if (pViewShell != pThisView && nCurrentDocId == pViewShell->GetDocId())
549 // Payload is only dependent on pThisView.
550 if (aPayload.isEmpty())
552 aPayload = lcl_generateJSON(pThisView, rKey, rPayload);
553 viewId = SfxLokHelper::getView(pThisView);
556 pViewShell->libreOfficeKitViewCallbackWithViewId(nType, aPayload, viewId);
559 pViewShell = SfxViewShell::GetNext(*pViewShell);
563 void SfxLokHelper::notifyOtherViews(const SfxViewShell* pThisView, int nType,
564 const boost::property_tree::ptree& rTree)
566 assert(pThisView != nullptr && "pThisView must be valid");
567 if (!pThisView || DisableCallbacks::disabled())
568 return;
570 // Cache the payload so we only have to generate it once, at most.
571 OString aPayload;
572 int viewId = -1;
574 const ViewShellDocId nCurrentDocId = pThisView->GetDocId();
575 SfxViewShell* pViewShell = SfxViewShell::GetFirst();
576 while (pViewShell)
578 if (pViewShell != pThisView && nCurrentDocId == pViewShell->GetDocId())
580 // Payload is only dependent on pThisView.
581 if (aPayload.isEmpty())
583 aPayload = lcl_generateJSON(pThisView, rTree);
584 viewId = SfxLokHelper::getView(pThisView);
587 pViewShell->libreOfficeKitViewCallbackWithViewId(nType, aPayload, viewId);
590 pViewShell = SfxViewShell::GetNext(*pViewShell);
594 OString SfxLokHelper::makePayloadJSON(const SfxViewShell* pThisView, int nViewId, std::string_view rKey, const OString& rPayload)
596 return lcl_generateJSON(pThisView, nViewId, rKey, rPayload);
599 namespace {
600 OUString lcl_getNameForSlot(const SfxViewShell* pShell, sal_uInt16 nWhich)
602 if (pShell && pShell->GetFrame())
604 const SfxSlot* pSlot = SfxSlotPool::GetSlotPool(pShell->GetFrame()).GetSlot(nWhich);
605 if (pSlot)
607 if (!pSlot->GetUnoName().isEmpty())
609 return pSlot->GetCommand();
614 return "";
618 void SfxLokHelper::sendUnoStatus(const SfxViewShell* pShell, const SfxPoolItem* pItem)
620 if (!pShell || !pItem || IsInvalidItem(pItem) || DisableCallbacks::disabled())
621 return;
623 boost::property_tree::ptree aItem = pItem->dumpAsJSON();
625 if (aItem.count("state"))
627 OUString sCommand = lcl_getNameForSlot(pShell, pItem->Which());
628 if (!sCommand.isEmpty())
629 aItem.put("commandName", sCommand);
631 std::stringstream aStream;
632 boost::property_tree::write_json(aStream, aItem);
633 pShell->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED, OString(aStream.str()));
637 void SfxLokHelper::notifyViewRenderState(const SfxViewShell* pShell, vcl::ITiledRenderable* pDoc)
639 pShell->libreOfficeKitViewCallback(LOK_CALLBACK_VIEW_RENDER_STATE, pDoc->getViewRenderState());
642 void SfxLokHelper::notifyWindow(const SfxViewShell* pThisView,
643 vcl::LOKWindowId nLOKWindowId,
644 std::u16string_view rAction,
645 const std::vector<vcl::LOKPayloadItem>& rPayload)
647 assert(pThisView != nullptr && "pThisView must be valid");
649 if (nLOKWindowId == 0 || DisableCallbacks::disabled())
650 return;
652 OStringBuffer aPayload =
653 "{ \"id\": \"" + OString::number(nLOKWindowId) + "\""
654 ", \"action\": \"" + OUStringToOString(rAction, RTL_TEXTENCODING_UTF8) + "\"";
656 for (const auto& rItem: rPayload)
658 if (!rItem.first.isEmpty() && !rItem.second.isEmpty())
660 auto aFirst = rItem.first.replaceAll("\""_ostr, "\\\""_ostr);
661 auto aSecond = rItem.second.replaceAll("\""_ostr, "\\\""_ostr);
662 aPayload.append(", \"" + aFirst + "\": \"" + aSecond + "\"");
665 aPayload.append('}');
667 const OString s = aPayload.makeStringAndClear();
668 pThisView->libreOfficeKitViewCallback(LOK_CALLBACK_WINDOW, s);
671 void SfxLokHelper::notifyInvalidation(SfxViewShell const* pThisView, tools::Rectangle const* pRect)
673 // -1 means all parts
674 const int nPart = comphelper::LibreOfficeKit::isPartInInvalidation() ? pThisView->getPart() : INT_MIN;
675 SfxLokHelper::notifyInvalidation(pThisView, nPart, pRect);
678 void SfxLokHelper::notifyInvalidation(SfxViewShell const* pThisView, const int nInPart, tools::Rectangle const* pRect)
680 if (DisableCallbacks::disabled())
681 return;
683 // -1 means all parts
684 const int nPart = comphelper::LibreOfficeKit::isPartInInvalidation() ? nInPart : INT_MIN;
685 const int nMode = pThisView->getEditMode();
686 pThisView->libreOfficeKitViewInvalidateTilesCallback(pRect, nPart, nMode);
689 void SfxLokHelper::notifyDocumentSizeChanged(SfxViewShell const* pThisView, const OString& rPayload, vcl::ITiledRenderable* pDoc, bool bInvalidateAll)
691 if (!pDoc || pDoc->isDisposed() || DisableCallbacks::disabled())
692 return;
694 if (bInvalidateAll)
696 for (int i = 0; i < pDoc->getParts(); ++i)
698 tools::Rectangle aRectangle(0, 0, 1000000000, 1000000000);
699 const int nMode = pThisView->getEditMode();
700 pThisView->libreOfficeKitViewInvalidateTilesCallback(&aRectangle, i, nMode);
703 pThisView->libreOfficeKitViewCallback(LOK_CALLBACK_DOCUMENT_SIZE_CHANGED, rPayload);
706 void SfxLokHelper::notifyDocumentSizeChangedAllViews(vcl::ITiledRenderable* pDoc, bool bInvalidateAll)
708 if (DisableCallbacks::disabled())
709 return;
711 // FIXME: Do we know whether it is the views for the document that is in the "current" view that has changed?
712 const SfxViewShell* const pCurrentViewShell = SfxViewShell::Current();
713 SfxViewShell* pViewShell = SfxViewShell::GetFirst();
714 while (pViewShell)
716 // FIXME: What if SfxViewShell::Current() returned null?
717 // Should we then do this for all views of all open documents
718 // or not?
719 if (pCurrentViewShell == nullptr || pViewShell->GetDocId() == pCurrentViewShell-> GetDocId())
721 SfxLokHelper::notifyDocumentSizeChanged(pViewShell, ""_ostr, pDoc, bInvalidateAll);
722 bInvalidateAll = false; // we direct invalidations to all views anyway.
724 pViewShell = SfxViewShell::GetNext(*pViewShell);
728 void SfxLokHelper::notifyPartSizeChangedAllViews(vcl::ITiledRenderable* pDoc, int nPart)
730 if (DisableCallbacks::disabled())
731 return;
733 SfxViewShell* pViewShell = SfxViewShell::GetFirst();
734 while (pViewShell)
736 if (// FIXME should really filter on pViewShell->GetDocId() too
737 pViewShell->getPart() == nPart)
738 SfxLokHelper::notifyDocumentSizeChanged(pViewShell, ""_ostr, pDoc, false);
739 pViewShell = SfxViewShell::GetNext(*pViewShell);
743 OString SfxLokHelper::makeVisCursorInvalidation(int nViewId, const OString& rRectangle,
744 bool bMispelledWord, const OString& rHyperlink)
746 if (comphelper::LibreOfficeKit::isViewIdForVisCursorInvalidation())
748 OString sHyperlink = rHyperlink.isEmpty() ? "{}"_ostr : rHyperlink;
749 return OString::Concat("{ \"viewId\": \"") + OString::number(nViewId) +
750 "\", \"rectangle\": \"" + rRectangle +
751 "\", \"mispelledWord\": \"" + OString::number(bMispelledWord ? 1 : 0) +
752 "\", \"hyperlink\": " + sHyperlink + " }";
754 else
756 return rRectangle;
760 void SfxLokHelper::notifyAllViews(int nType, const OString& rPayload)
762 if (DisableCallbacks::disabled())
763 return;
765 const auto payload = rPayload.getStr();
766 const SfxViewShell* const pCurrentViewShell = SfxViewShell::Current();
767 if (!pCurrentViewShell)
768 return;
769 SfxViewShell* pViewShell = SfxViewShell::GetFirst();
770 while (pViewShell)
772 if (pViewShell->GetDocId() == pCurrentViewShell->GetDocId())
773 pViewShell->libreOfficeKitViewCallback(nType, payload);
774 pViewShell = SfxViewShell::GetNext(*pViewShell);
778 void SfxLokHelper::notifyContextChange(const css::ui::ContextChangeEventObject& rEvent)
780 if (DisableCallbacks::disabled())
781 return;
783 SfxViewShell* pViewShell = SfxViewShell::Get({ rEvent.Source, css::uno::UNO_QUERY });
784 if (!pViewShell)
785 return;
787 OUString aBuffer =
788 rEvent.ApplicationName.replace(' ', '_') +
789 " " +
790 rEvent.ContextName.replace(' ', '_');
791 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CONTEXT_CHANGED, aBuffer.toUtf8());
794 void SfxLokHelper::notifyLog(const std::ostringstream& stream)
796 if (DisableCallbacks::disabled())
797 return;
799 SfxViewShell* pViewShell = SfxViewShell::Current();
800 if (!pViewShell)
801 return;
802 if (pViewShell->getLibreOfficeKitViewCallback())
804 if (!g_logNotifierCache.empty())
806 for (const auto& msg : g_logNotifierCache)
808 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CORE_LOG, msg.c_str());
810 g_logNotifierCache.clear();
812 pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CORE_LOG, stream.str().c_str());
814 else
816 while (g_logNotifierCache.size() >= g_logNotifierCacheMaxSize)
817 g_logNotifierCache.pop_front();
818 g_logNotifierCache.push_back(stream.str());
822 void SfxLokHelper::notifyUpdate(SfxViewShell const* pThisView, int nType)
824 if (DisableCallbacks::disabled())
825 return;
827 pThisView->libreOfficeKitViewUpdatedCallback(nType);
830 void SfxLokHelper::notifyUpdatePerViewId(SfxViewShell const* pThisView, int nType)
832 notifyUpdatePerViewId(pThisView, pThisView, pThisView, nType);
835 void SfxLokHelper::notifyUpdatePerViewId(SfxViewShell const* pTargetShell, SfxViewShell const* pViewShell,
836 SfxViewShell const* pSourceShell, int nType)
838 if (DisableCallbacks::disabled())
839 return;
841 int viewId = SfxLokHelper::getView(pViewShell);
842 int sourceViewId = SfxLokHelper::getView(pSourceShell);
843 pTargetShell->libreOfficeKitViewUpdatedCallbackPerViewId(nType, viewId, sourceViewId);
846 void SfxLokHelper::notifyOtherViewsUpdatePerViewId(SfxViewShell const* pThisView, int nType)
848 assert(pThisView != nullptr && "pThisView must be valid");
849 if (DisableCallbacks::disabled())
850 return;
852 int viewId = SfxLokHelper::getView(pThisView);
853 const ViewShellDocId nCurrentDocId = pThisView->GetDocId();
854 SfxViewShell* pViewShell = SfxViewShell::GetFirst();
855 while (pViewShell)
857 if (pViewShell != pThisView && nCurrentDocId == pViewShell->GetDocId())
858 pViewShell->libreOfficeKitViewUpdatedCallbackPerViewId(nType, viewId, viewId);
860 pViewShell = SfxViewShell::GetNext(*pViewShell);
864 namespace
866 struct LOKAsyncEventData
868 int mnView; // Window is not enough.
869 VclPtr<vcl::Window> mpWindow;
870 VclEventId mnEvent;
871 MouseEvent maMouseEvent;
872 KeyEvent maKeyEvent;
873 OUString maText;
876 void LOKPostAsyncEvent(void* pEv, void*)
878 std::unique_ptr<LOKAsyncEventData> pLOKEv(static_cast<LOKAsyncEventData*>(pEv));
879 if (pLOKEv->mpWindow->isDisposed())
880 return;
882 int nView = SfxLokHelper::getView(nullptr);
883 if (nView != pLOKEv->mnView)
885 SAL_INFO("sfx.view", "LOK - view mismatch " << nView << " vs. " << pLOKEv->mnView);
886 SfxLokHelper::setView(pLOKEv->mnView);
889 if (!pLOKEv->mpWindow->HasChildPathFocus(true))
891 SAL_INFO("sfx.view", "LOK - focus mismatch, switching focus");
892 pLOKEv->mpWindow->GrabFocus();
895 VclPtr<vcl::Window> pFocusWindow = pLOKEv->mpWindow->GetFocusedWindow();
896 if (!pFocusWindow)
897 pFocusWindow = pLOKEv->mpWindow;
899 if (pLOKEv->mpWindow->isDisposed())
900 return;
902 switch (pLOKEv->mnEvent)
904 case VclEventId::WindowKeyInput:
906 sal_uInt16 nRepeat = pLOKEv->maKeyEvent.GetRepeat();
907 KeyEvent singlePress(pLOKEv->maKeyEvent.GetCharCode(),
908 pLOKEv->maKeyEvent.GetKeyCode());
909 for (sal_uInt16 i = 0; i <= nRepeat; ++i)
910 if (!pFocusWindow->isDisposed())
911 pFocusWindow->KeyInput(singlePress);
912 break;
914 case VclEventId::WindowKeyUp:
915 if (!pFocusWindow->isDisposed())
916 pFocusWindow->KeyUp(pLOKEv->maKeyEvent);
917 break;
918 case VclEventId::WindowMouseButtonDown:
919 pLOKEv->mpWindow->SetLastMousePos(pLOKEv->maMouseEvent.GetPosPixel());
920 pLOKEv->mpWindow->MouseButtonDown(pLOKEv->maMouseEvent);
921 // Invoke the context menu
922 if (pLOKEv->maMouseEvent.GetButtons() & MOUSE_RIGHT)
924 const CommandEvent aCEvt(pLOKEv->maMouseEvent.GetPosPixel(), CommandEventId::ContextMenu, true, nullptr);
925 pLOKEv->mpWindow->Command(aCEvt);
927 break;
928 case VclEventId::WindowMouseButtonUp:
929 pLOKEv->mpWindow->SetLastMousePos(pLOKEv->maMouseEvent.GetPosPixel());
930 pLOKEv->mpWindow->MouseButtonUp(pLOKEv->maMouseEvent);
932 // sometimes MouseButtonDown captures mouse and starts tracking, and VCL
933 // will not take care of releasing that with tiled rendering
934 if (pLOKEv->mpWindow->IsTracking())
935 pLOKEv->mpWindow->EndTracking();
937 break;
938 case VclEventId::WindowMouseMove:
939 pLOKEv->mpWindow->SetLastMousePos(pLOKEv->maMouseEvent.GetPosPixel());
940 pLOKEv->mpWindow->MouseMove(pLOKEv->maMouseEvent);
941 pLOKEv->mpWindow->RequestHelp(HelpEvent{
942 pLOKEv->mpWindow->OutputToScreenPixel(pLOKEv->maMouseEvent.GetPosPixel()),
943 HelpEventMode::QUICK }); // If needed, HelpEventMode should be taken from a config
944 break;
945 case VclEventId::ExtTextInput:
946 case VclEventId::EndExtTextInput:
947 pLOKEv->mpWindow->PostExtTextInputEvent(pLOKEv->mnEvent, pLOKEv->maText);
948 break;
949 default:
950 assert(false);
951 break;
955 void postEventAsync(LOKAsyncEventData *pEvent)
957 if (!pEvent->mpWindow || pEvent->mpWindow->isDisposed())
959 SAL_WARN("vcl", "Async event post - but no valid window as destination " << pEvent->mpWindow.get());
960 delete pEvent;
961 return;
964 pEvent->mnView = SfxLokHelper::getView(nullptr);
965 if (vcl::lok::isUnipoll())
967 if (!Application::IsMainThread())
968 SAL_WARN("lok", "Posting event directly but not called from main thread!");
969 LOKPostAsyncEvent(pEvent, nullptr);
971 else
972 Application::PostUserEvent(Link<void*, void>(pEvent, LOKPostAsyncEvent));
976 void SfxLokHelper::postKeyEventAsync(const VclPtr<vcl::Window> &xWindow,
977 int nType, int nCharCode, int nKeyCode, int nRepeat)
979 LOKAsyncEventData* pLOKEv = new LOKAsyncEventData;
980 switch (nType)
982 case LOK_KEYEVENT_KEYINPUT:
983 pLOKEv->mnEvent = VclEventId::WindowKeyInput;
984 break;
985 case LOK_KEYEVENT_KEYUP:
986 pLOKEv->mnEvent = VclEventId::WindowKeyUp;
987 break;
988 default:
989 assert(false);
991 pLOKEv->maKeyEvent = KeyEvent(nCharCode, nKeyCode, nRepeat);
992 pLOKEv->mpWindow = xWindow;
993 postEventAsync(pLOKEv);
996 void SfxLokHelper::setBlockedCommandList(int nViewId, const char* blockedCommandList)
998 SfxViewShell* pViewShell = SfxLokHelper::getViewOfId(nViewId);
1000 if(pViewShell)
1002 pViewShell->setBlockedCommandList(blockedCommandList);
1006 void SfxLokHelper::postExtTextEventAsync(const VclPtr<vcl::Window> &xWindow,
1007 int nType, const OUString &rText)
1009 LOKAsyncEventData* pLOKEv = new LOKAsyncEventData;
1010 switch (nType)
1012 case LOK_EXT_TEXTINPUT:
1013 pLOKEv->mnEvent = VclEventId::ExtTextInput;
1014 pLOKEv->maText = rText;
1015 break;
1016 case LOK_EXT_TEXTINPUT_END:
1017 pLOKEv->mnEvent = VclEventId::EndExtTextInput;
1018 pLOKEv->maText = "";
1019 break;
1020 default:
1021 assert(false);
1023 pLOKEv->mpWindow = xWindow;
1024 postEventAsync(pLOKEv);
1027 void SfxLokHelper::postMouseEventAsync(const VclPtr<vcl::Window> &xWindow, LokMouseEventData const & rLokMouseEventData)
1029 LOKAsyncEventData* pLOKEv = new LOKAsyncEventData;
1030 switch (rLokMouseEventData.mnType)
1032 case LOK_MOUSEEVENT_MOUSEBUTTONDOWN:
1033 pLOKEv->mnEvent = VclEventId::WindowMouseButtonDown;
1034 break;
1035 case LOK_MOUSEEVENT_MOUSEBUTTONUP:
1036 pLOKEv->mnEvent = VclEventId::WindowMouseButtonUp;
1037 break;
1038 case LOK_MOUSEEVENT_MOUSEMOVE:
1039 pLOKEv->mnEvent = VclEventId::WindowMouseMove;
1040 break;
1041 default:
1042 assert(false);
1045 // no reason - just always true so far.
1046 assert (rLokMouseEventData.meModifiers == MouseEventModifiers::SIMPLECLICK);
1048 pLOKEv->maMouseEvent = MouseEvent(rLokMouseEventData.maPosition, rLokMouseEventData.mnCount,
1049 rLokMouseEventData.meModifiers, rLokMouseEventData.mnButtons,
1050 rLokMouseEventData.mnModifier);
1051 if (rLokMouseEventData.maLogicPosition)
1053 pLOKEv->maMouseEvent.setLogicPosition(*rLokMouseEventData.maLogicPosition);
1055 pLOKEv->mpWindow = xWindow;
1056 postEventAsync(pLOKEv);
1059 void SfxLokHelper::dumpState(rtl::OStringBuffer &rState)
1061 SfxViewShell* pShell = SfxViewShell::Current();
1062 sal_Int32 nDocId = pShell ? static_cast<sal_Int32>(pShell->GetDocId().get()) : -1;
1064 rState.append("\n\tDocId:\t");
1065 rState.append(nDocId);
1067 if (nDocId < 0)
1068 return;
1070 rState.append("\n\tViewCount:\t");
1071 rState.append(static_cast<sal_Int32>(getViewsCount(nDocId)));
1073 const SfxViewShell* const pCurrentViewShell = SfxViewShell::Current();
1074 SfxViewShell* pViewShell = SfxViewShell::GetFirst();
1075 while (pViewShell)
1077 if (pCurrentViewShell == nullptr || pViewShell->GetDocId() == pCurrentViewShell-> GetDocId())
1078 pViewShell->dumpLibreOfficeKitViewState(rState);
1080 pViewShell = SfxViewShell::GetNext(*pViewShell);
1084 bool SfxLokHelper::testInPlaceComponentMouseEventHit(SfxViewShell* pViewShell, int nType, int nX,
1085 int nY, int nCount, int nButtons,
1086 int nModifier, double fScaleX, double fScaleY,
1087 bool bNegativeX)
1089 // In LOK RTL mode draw/svx operates in negative X coordinates
1090 // But the coordinates from client is always positive, so negate nX.
1091 if (bNegativeX)
1092 nX = -nX;
1094 // check if the user hit a chart/math object which is being edited by this view
1095 if (LokChartHelper aChartHelper(pViewShell, bNegativeX);
1096 aChartHelper.postMouseEvent(nType, nX, nY, nCount, nButtons, nModifier, fScaleX, fScaleY))
1097 return true;
1099 if (LokStarMathHelper aMathHelper(pViewShell);
1100 aMathHelper.postMouseEvent(nType, nX, nY, nCount, nButtons, nModifier, fScaleX, fScaleY))
1101 return true;
1103 // check if the user hit a chart which is being edited by someone else
1104 // and, if so, skip current mouse event
1105 if (nType != LOK_MOUSEEVENT_MOUSEMOVE)
1107 if (LokChartHelper::HitAny({nX, nY}, bNegativeX))
1108 return true;
1111 return false;
1114 VclPtr<vcl::Window> SfxLokHelper::getInPlaceDocWindow(SfxViewShell* pViewShell)
1116 if (VclPtr<vcl::Window> pWindow = LokChartHelper(pViewShell).GetWindow())
1117 return pWindow;
1118 if (VclPtr<vcl::Window> pWindow = LokStarMathHelper(pViewShell).GetWidgetWindow())
1119 return pWindow;
1120 return {};
1123 void SfxLokHelper::sendNetworkAccessError(std::string_view rAction)
1125 tools::JsonWriter aWriter;
1126 aWriter.put("code", static_cast<sal_uInt32>(
1127 ErrCode(ErrCodeArea::Inet, sal_uInt16(ErrCodeClass::Access))));
1128 aWriter.put("kind", "network");
1129 aWriter.put("cmd", rAction);
1131 SfxViewShell* pViewShell = SfxViewShell::Current();
1132 if (pViewShell)
1134 pViewShell->libreOfficeKitViewCallback(
1135 LOK_CALLBACK_ERROR, aWriter.finishAndGetAsOString());
1139 SfxLokLanguageGuard::SfxLokLanguageGuard(SfxViewShell* pNewShell)
1140 : m_bSetLanguage(false)
1141 , m_pOldShell(nullptr)
1142 , m_pNewShell(pNewShell)
1144 m_pOldShell = SfxViewShell::Current();
1145 if (!comphelper::LibreOfficeKit::isActive() || !m_pNewShell || m_pNewShell == m_pOldShell)
1147 return;
1150 // The current view ID is not the one that belongs to this frame, update
1151 // language/locale.
1152 comphelper::LibreOfficeKit::setLanguageTag(m_pNewShell->GetLOKLanguageTag());
1153 comphelper::LibreOfficeKit::setLocale(m_pNewShell->GetLOKLocale());
1154 m_bSetLanguage = true;
1157 SfxLokLanguageGuard::~SfxLokLanguageGuard()
1159 if (!m_bSetLanguage || !m_pOldShell)
1161 return;
1164 comphelper::LibreOfficeKit::setLanguageTag(m_pOldShell->GetLOKLanguageTag());
1165 comphelper::LibreOfficeKit::setLocale(m_pOldShell->GetLOKLocale());
1168 LOKEditViewHistory::EditViewHistoryMap LOKEditViewHistory::maEditViewHistory;
1171 void LOKEditViewHistory::Update(bool bRemove)
1173 if (!comphelper::LibreOfficeKit::isActive())
1174 return;
1176 SfxViewShell* pViewShell = SfxViewShell::Current();
1177 if (pViewShell)
1179 int nDocId = pViewShell->GetDocId().get();
1180 if (maEditViewHistory.find(nDocId) != maEditViewHistory.end())
1181 maEditViewHistory[nDocId].remove(pViewShell);
1182 if (!bRemove)
1184 maEditViewHistory[nDocId].push_back(pViewShell);
1185 if (maEditViewHistory[nDocId].size() > 10)
1186 maEditViewHistory[nDocId].pop_front();
1191 ViewShellList LOKEditViewHistory::GetHistoryForDoc(ViewShellDocId aDocId)
1193 int nDocId = aDocId.get();
1194 ViewShellList aResult;
1195 if (maEditViewHistory.find(nDocId) != maEditViewHistory.end())
1196 aResult = maEditViewHistory.at(nDocId);
1197 return aResult;
1200 ViewShellList LOKEditViewHistory::GetSortedViewsForDoc(ViewShellDocId aDocId)
1202 ViewShellList aEditViewHistoryForDoc = LOKEditViewHistory::GetHistoryForDoc(aDocId);
1203 // all views where document is loaded
1204 ViewShellList aCurrentDocViewList;
1205 // active views that are listed in the edit history
1206 ViewShellList aEditedViewList;
1208 // Populate aCurrentDocViewList and aEditedViewList
1209 SfxViewShell* pViewShell = SfxViewShell::GetFirst();
1210 while (pViewShell)
1212 if (pViewShell->GetDocId() == aDocId)
1214 if (aEditViewHistoryForDoc.empty() ||
1215 std::find(aEditViewHistoryForDoc.begin(), aEditViewHistoryForDoc.end(),
1216 pViewShell) == aEditViewHistoryForDoc.end())
1218 // append views not listed in the edit history;
1219 // the edit history is limited to 10 views,
1220 // so it could miss some view where in place editing is occurring
1221 aCurrentDocViewList.push_back(pViewShell);
1223 else
1225 // view is listed in the edit history
1226 aEditedViewList.push_back(pViewShell);
1229 pViewShell = SfxViewShell::GetNext(*pViewShell);
1232 // in case some no more active view needs to be removed from the history
1233 aEditViewHistoryForDoc.remove_if(
1234 [&aEditedViewList](SfxViewShell* pHistoryItem) {
1235 return std::find(aEditedViewList.begin(), aEditedViewList.end(), pHistoryItem) == aEditedViewList.end();
1238 // place views belonging to the edit history at the end
1239 aCurrentDocViewList.splice(aCurrentDocViewList.end(), aEditViewHistoryForDoc);
1241 return aCurrentDocViewList;
1244 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */