1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
10 #include <sal/config.h>
13 #include <string_view>
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
;
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
59 assert(m_nDisabled
>= 0 && "Expected non-negative DisabledCallbacks state when disabling.");
65 assert(m_nDisabled
> 0 && "Expected positive DisabledCallbacks state when re-enabling.");
69 static inline bool disabled()
71 return !comphelper::LibreOfficeKit::isActive() || m_nDisabled
!= 0;
75 static int m_nDisabled
;
78 int DisableCallbacks::m_nDisabled
= 0;
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)
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)
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();
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.
142 void SfxLokHelper::setEditMode(int nMode
, vcl::ITiledRenderable
* pDoc
)
145 pDoc
->setEditMode(nMode
);
148 void SfxLokHelper::destroyView(int nId
)
150 const SfxApplication
* pApp
= SfxApplication::Get();
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
);
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();
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())
191 const SfxViewShell
* pViewShell
= *itViewShell
;
194 bool bIsCurrShell
= (pViewShell
== SfxViewShell::Current());
195 if (bIsCurrShell
&& comphelper::LibreOfficeKit::getLanguageTag().getBcp47() == pViewShell
->GetLOKLanguageTag().getBcp47())
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());
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();
230 const ViewShellId
nViewShellId(nId
);
231 std::vector
<SfxViewShell
*>& rViewArr
= pApp
->GetViewShells_Impl();
232 for (SfxViewShell
* pViewShell
: rViewArr
)
234 if (pViewShell
->GetViewShellId() == nViewShellId
)
241 int SfxLokHelper::getView(const SfxViewShell
* pViewShell
)
244 pViewShell
= SfxViewShell::Current();
245 // Still no valid view shell? Then no idea.
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();
260 const ViewShellDocId
nCurrentDocId(nDocId
);
262 SfxViewShell
* pViewShell
= SfxViewShell::GetFirst();
265 if (pViewShell
->GetDocId() == nCurrentDocId
)
267 pViewShell
= SfxViewShell::GetNext(*pViewShell
);
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();
281 const ViewShellDocId
nCurrentDocId(nDocId
);
283 SfxViewShell
* pViewShell
= SfxViewShell::GetFirst();
286 if (pViewShell
->GetDocId() == nCurrentDocId
)
291 pArray
[n
] = static_cast<sal_Int32
>(pViewShell
->GetViewShellId());
295 pViewShell
= SfxViewShell::GetNext(*pViewShell
);
301 int SfxLokHelper::getDocumentIdOfView(int nViewId
)
303 SfxViewShell
* pViewShell
= SfxViewShell::GetFirst();
306 if (pViewShell
->GetViewShellId() == ViewShellId(nViewId
))
307 return static_cast<int>(pViewShell
->GetDocId());
308 pViewShell
= SfxViewShell::GetNext(*pViewShell
);
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
);
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
);
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
);
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
);
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
);
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
;
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
);
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();
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)
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
] == '\\')
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())
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
)
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())
539 // Cache the payload so we only have to generate it once, at most.
543 const ViewShellDocId nCurrentDocId
= pThisView
->GetDocId();
544 SfxViewShell
* pViewShell
= SfxViewShell::GetFirst();
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())
570 // Cache the payload so we only have to generate it once, at most.
574 const ViewShellDocId nCurrentDocId
= pThisView
->GetDocId();
575 SfxViewShell
* pViewShell
= SfxViewShell::GetFirst();
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
);
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
);
607 if (!pSlot
->GetUnoName().isEmpty())
609 return pSlot
->GetCommand();
618 void SfxLokHelper::sendUnoStatus(const SfxViewShell
* pShell
, const SfxPoolItem
* pItem
)
620 if (!pShell
|| !pItem
|| IsInvalidItem(pItem
) || DisableCallbacks::disabled())
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())
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())
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())
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())
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();
716 // FIXME: What if SfxViewShell::Current() returned null?
717 // Should we then do this for all views of all open documents
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())
733 SfxViewShell
* pViewShell
= SfxViewShell::GetFirst();
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
+ " }";
760 void SfxLokHelper::notifyAllViews(int nType
, const OString
& rPayload
)
762 if (DisableCallbacks::disabled())
765 const auto payload
= rPayload
.getStr();
766 const SfxViewShell
* const pCurrentViewShell
= SfxViewShell::Current();
767 if (!pCurrentViewShell
)
769 SfxViewShell
* pViewShell
= SfxViewShell::GetFirst();
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())
783 SfxViewShell
* pViewShell
= SfxViewShell::Get({ rEvent
.Source
, css::uno::UNO_QUERY
});
788 rEvent
.ApplicationName
.replace(' ', '_') +
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())
799 SfxViewShell
* pViewShell
= SfxViewShell::Current();
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());
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())
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())
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())
852 int viewId
= SfxLokHelper::getView(pThisView
);
853 const ViewShellDocId nCurrentDocId
= pThisView
->GetDocId();
854 SfxViewShell
* pViewShell
= SfxViewShell::GetFirst();
857 if (pViewShell
!= pThisView
&& nCurrentDocId
== pViewShell
->GetDocId())
858 pViewShell
->libreOfficeKitViewUpdatedCallbackPerViewId(nType
, viewId
, viewId
);
860 pViewShell
= SfxViewShell::GetNext(*pViewShell
);
866 struct LOKAsyncEventData
868 int mnView
; // Window is not enough.
869 VclPtr
<vcl::Window
> mpWindow
;
871 MouseEvent maMouseEvent
;
876 void LOKPostAsyncEvent(void* pEv
, void*)
878 std::unique_ptr
<LOKAsyncEventData
> pLOKEv(static_cast<LOKAsyncEventData
*>(pEv
));
879 if (pLOKEv
->mpWindow
->isDisposed())
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();
897 pFocusWindow
= pLOKEv
->mpWindow
;
899 if (pLOKEv
->mpWindow
->isDisposed())
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
);
914 case VclEventId::WindowKeyUp
:
915 if (!pFocusWindow
->isDisposed())
916 pFocusWindow
->KeyUp(pLOKEv
->maKeyEvent
);
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
);
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();
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
945 case VclEventId::ExtTextInput
:
946 case VclEventId::EndExtTextInput
:
947 pLOKEv
->mpWindow
->PostExtTextInputEvent(pLOKEv
->mnEvent
, pLOKEv
->maText
);
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());
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);
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
;
982 case LOK_KEYEVENT_KEYINPUT
:
983 pLOKEv
->mnEvent
= VclEventId::WindowKeyInput
;
985 case LOK_KEYEVENT_KEYUP
:
986 pLOKEv
->mnEvent
= VclEventId::WindowKeyUp
;
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
);
1002 pViewShell
->setBlockedCommandList(blockedCommandList
);
1006 void SfxLokHelper::postExtTextEventAsync(const VclPtr
<vcl::Window
> &xWindow
,
1007 int nType
, const OUString
&rText
)
1009 LOKAsyncEventData
* pLOKEv
= new LOKAsyncEventData
;
1012 case LOK_EXT_TEXTINPUT
:
1013 pLOKEv
->mnEvent
= VclEventId::ExtTextInput
;
1014 pLOKEv
->maText
= rText
;
1016 case LOK_EXT_TEXTINPUT_END
:
1017 pLOKEv
->mnEvent
= VclEventId::EndExtTextInput
;
1018 pLOKEv
->maText
= "";
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
;
1035 case LOK_MOUSEEVENT_MOUSEBUTTONUP
:
1036 pLOKEv
->mnEvent
= VclEventId::WindowMouseButtonUp
;
1038 case LOK_MOUSEEVENT_MOUSEMOVE
:
1039 pLOKEv
->mnEvent
= VclEventId::WindowMouseMove
;
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
);
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();
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
,
1089 // In LOK RTL mode draw/svx operates in negative X coordinates
1090 // But the coordinates from client is always positive, so negate 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
))
1099 if (LokStarMathHelper
aMathHelper(pViewShell
);
1100 aMathHelper
.postMouseEvent(nType
, nX
, nY
, nCount
, nButtons
, nModifier
, fScaleX
, fScaleY
))
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
))
1114 VclPtr
<vcl::Window
> SfxLokHelper::getInPlaceDocWindow(SfxViewShell
* pViewShell
)
1116 if (VclPtr
<vcl::Window
> pWindow
= LokChartHelper(pViewShell
).GetWindow())
1118 if (VclPtr
<vcl::Window
> pWindow
= LokStarMathHelper(pViewShell
).GetWidgetWindow())
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();
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
)
1150 // The current view ID is not the one that belongs to this frame, update
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
)
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())
1176 SfxViewShell
* pViewShell
= SfxViewShell::Current();
1179 int nDocId
= pViewShell
->GetDocId().get();
1180 if (maEditViewHistory
.find(nDocId
) != maEditViewHistory
.end())
1181 maEditViewHistory
[nDocId
].remove(pViewShell
);
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
);
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();
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
);
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: */