Bump version to 24.04.3.4
[LibreOffice.git] / sfx2 / source / view / viewsh.cxx
blob0b6c07c68d5c5c432b7b5cd77c0858c1093f0292
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <config_features.h>
22 #include <boost/property_tree/json_parser.hpp>
24 #include <sal/log.hxx>
25 #include <svl/stritem.hxx>
26 #include <svl/eitem.hxx>
27 #include <svl/whiter.hxx>
28 #include <utility>
29 #include <vcl/svapp.hxx>
30 #include <vcl/toolbox.hxx>
31 #include <vcl/weld.hxx>
32 #include <svl/intitem.hxx>
33 #include <svtools/colorcfg.hxx>
34 #include <svtools/langhelp.hxx>
35 #include <com/sun/star/awt/XPopupMenu.hpp>
36 #include <com/sun/star/frame/XLayoutManager.hpp>
37 #include <com/sun/star/frame/ModuleManager.hpp>
38 #include <com/sun/star/io/IOException.hpp>
39 #include <com/sun/star/beans/XPropertySet.hpp>
40 #include <com/sun/star/embed/EmbedStates.hpp>
41 #include <com/sun/star/embed/EmbedMisc.hpp>
42 #include <com/sun/star/embed/XEmbeddedObject.hpp>
43 #include <com/sun/star/container/XContainerQuery.hpp>
44 #include <com/sun/star/frame/XStorable.hpp>
45 #include <com/sun/star/frame/XModel.hpp>
46 #include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
47 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
48 #include <com/sun/star/datatransfer/clipboard/XClipboardListener.hpp>
49 #include <com/sun/star/datatransfer/clipboard/XClipboardNotifier.hpp>
50 #include <com/sun/star/view/XRenderable.hpp>
51 #include <com/sun/star/uno/Reference.hxx>
52 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
53 #include <com/sun/star/accessibility/XAccessibleContext.hpp>
54 #include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
55 #include <com/sun/star/accessibility/XAccessibleSelection.hpp>
56 #include <com/sun/star/accessibility/AccessibleEventId.hpp>
57 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
58 #include <com/sun/star/accessibility/AccessibleRole.hpp>
59 #include <com/sun/star/accessibility/XAccessibleText.hpp>
60 #include <com/sun/star/accessibility/XAccessibleTable.hpp>
61 #include <cppuhelper/implbase.hxx>
62 #include <com/sun/star/ui/XAcceleratorConfiguration.hpp>
64 #include <cppuhelper/weakref.hxx>
66 #include <com/sun/star/accessibility/XAccessibleTextAttributes.hpp>
67 #include <com/sun/star/accessibility/AccessibleTextType.hpp>
68 #include <com/sun/star/awt/FontSlant.hpp>
70 #include <comphelper/diagnose_ex.hxx>
71 #include <editeng/unoprnms.hxx>
72 #include <tools/urlobj.hxx>
73 #include <unotools/tempfile.hxx>
74 #include <svtools/soerr.hxx>
75 #include <tools/svborder.hxx>
77 #include <framework/actiontriggerhelper.hxx>
78 #include <comphelper/lok.hxx>
79 #include <comphelper/processfactory.hxx>
80 #include <comphelper/propertyvalue.hxx>
81 #include <comphelper/sequenceashashmap.hxx>
82 #include <toolkit/helper/vclunohelper.hxx>
83 #include <vcl/settings.hxx>
84 #include <vcl/commandinfoprovider.hxx>
85 #include <LibreOfficeKit/LibreOfficeKitEnums.h>
87 #include <officecfg/Setup.hxx>
88 #include <sfx2/app.hxx>
89 #include <sfx2/flatpak.hxx>
90 #include <sfx2/viewsh.hxx>
91 #include "viewimp.hxx"
92 #include <sfx2/sfxresid.hxx>
93 #include <sfx2/request.hxx>
94 #include <sfx2/printer.hxx>
95 #include <sfx2/docfile.hxx>
96 #include <sfx2/dispatch.hxx>
97 #include <sfx2/strings.hrc>
98 #include <sfx2/sfxbasecontroller.hxx>
99 #include <sfx2/mailmodelapi.hxx>
100 #include <bluthsndapi.hxx>
101 #include <sfx2/viewfrm.hxx>
102 #include <sfx2/event.hxx>
103 #include <sfx2/ipclient.hxx>
104 #include <sfx2/sfxsids.hrc>
105 #include <sfx2/objface.hxx>
106 #include <sfx2/lokhelper.hxx>
107 #include <sfx2/lokcallback.hxx>
108 #include <openuriexternally.hxx>
109 #include <iostream>
110 #include <vector>
111 #include <list>
112 #include <libxml/xmlwriter.h>
113 #include <toolkit/awt/vclxmenu.hxx>
114 #include <unordered_map>
115 #include <unordered_set>
117 #define ShellClass_SfxViewShell
118 #include <sfxslots.hxx>
120 using namespace ::com::sun::star;
121 using namespace ::com::sun::star::uno;
122 using namespace ::com::sun::star::frame;
123 using namespace ::com::sun::star::beans;
124 using namespace ::com::sun::star::util;
125 using namespace ::cppu;
127 class SfxClipboardChangeListener : public ::cppu::WeakImplHelper<
128 datatransfer::clipboard::XClipboardListener >
130 public:
131 SfxClipboardChangeListener( SfxViewShell* pView, uno::Reference< datatransfer::clipboard::XClipboardNotifier > xClpbrdNtfr );
133 // XEventListener
134 virtual void SAL_CALL disposing( const lang::EventObject& rEventObject ) override;
136 // XClipboardListener
137 virtual void SAL_CALL changedContents( const datatransfer::clipboard::ClipboardEvent& rEventObject ) override;
139 void DisconnectViewShell() { m_pViewShell = nullptr; }
140 void ChangedContents();
142 enum AsyncExecuteCmd
144 ASYNCEXECUTE_CMD_DISPOSING,
145 ASYNCEXECUTE_CMD_CHANGEDCONTENTS
148 struct AsyncExecuteInfo
150 AsyncExecuteInfo( AsyncExecuteCmd eCmd, SfxClipboardChangeListener* pListener ) :
151 m_eCmd( eCmd ), m_xListener( pListener ) {}
153 AsyncExecuteCmd m_eCmd;
154 rtl::Reference<SfxClipboardChangeListener> m_xListener;
157 private:
158 SfxViewShell* m_pViewShell;
159 uno::Reference< datatransfer::clipboard::XClipboardNotifier > m_xClpbrdNtfr;
160 uno::Reference< lang::XComponent > m_xCtrl;
162 DECL_STATIC_LINK( SfxClipboardChangeListener, AsyncExecuteHdl_Impl, void*, void );
165 SfxClipboardChangeListener::SfxClipboardChangeListener( SfxViewShell* pView, uno::Reference< datatransfer::clipboard::XClipboardNotifier > xClpbrdNtfr )
166 : m_pViewShell( nullptr ), m_xClpbrdNtfr(std::move( xClpbrdNtfr )), m_xCtrl(pView->GetController())
168 if ( m_xCtrl.is() )
170 m_xCtrl->addEventListener( uno::Reference < lang::XEventListener > ( static_cast < lang::XEventListener* >( this ) ) );
171 m_pViewShell = pView;
173 if ( m_xClpbrdNtfr.is() )
175 m_xClpbrdNtfr->addClipboardListener( uno::Reference< datatransfer::clipboard::XClipboardListener >(
176 static_cast< datatransfer::clipboard::XClipboardListener* >( this )));
180 void SfxClipboardChangeListener::ChangedContents()
182 const SolarMutexGuard aGuard;
183 if (!m_pViewShell)
184 return;
186 SfxBindings& rBind = m_pViewShell->GetViewFrame().GetBindings();
187 rBind.Invalidate(SID_PASTE);
188 rBind.Invalidate(SID_PASTE_SPECIAL);
189 rBind.Invalidate(SID_CLIPBOARD_FORMAT_ITEMS);
191 if (comphelper::LibreOfficeKit::isActive())
193 // In the future we might send the payload as well.
194 SfxLokHelper::notifyAllViews(LOK_CALLBACK_CLIPBOARD_CHANGED, ""_ostr);
198 IMPL_STATIC_LINK( SfxClipboardChangeListener, AsyncExecuteHdl_Impl, void*, p, void )
200 AsyncExecuteInfo* pAsyncExecuteInfo = static_cast<AsyncExecuteInfo*>(p);
201 if ( pAsyncExecuteInfo )
203 if ( pAsyncExecuteInfo->m_xListener.is() )
205 if ( pAsyncExecuteInfo->m_eCmd == ASYNCEXECUTE_CMD_DISPOSING )
206 pAsyncExecuteInfo->m_xListener->DisconnectViewShell();
207 else if ( pAsyncExecuteInfo->m_eCmd == ASYNCEXECUTE_CMD_CHANGEDCONTENTS )
208 pAsyncExecuteInfo->m_xListener->ChangedContents();
211 delete pAsyncExecuteInfo;
214 void SAL_CALL SfxClipboardChangeListener::disposing( const lang::EventObject& /*rEventObject*/ )
216 // Either clipboard or ViewShell is going to be destroyed -> no interest in listening anymore
217 uno::Reference< lang::XComponent > xCtrl( m_xCtrl );
218 uno::Reference< datatransfer::clipboard::XClipboardNotifier > xNotify( m_xClpbrdNtfr );
220 uno::Reference< datatransfer::clipboard::XClipboardListener > xThis( static_cast< datatransfer::clipboard::XClipboardListener* >( this ));
221 if ( xCtrl.is() )
222 xCtrl->removeEventListener( uno::Reference < lang::XEventListener > ( static_cast < lang::XEventListener* >( this )));
223 if ( xNotify.is() )
224 xNotify->removeClipboardListener( xThis );
226 // Make asynchronous call to avoid locking SolarMutex which is the
227 // root for many deadlocks, especially in conjunction with the "Windows"
228 // based single thread apartment clipboard code!
229 AsyncExecuteInfo* pInfo = new AsyncExecuteInfo( ASYNCEXECUTE_CMD_DISPOSING, this );
230 if (!Application::PostUserEvent( LINK( nullptr, SfxClipboardChangeListener, AsyncExecuteHdl_Impl ), pInfo ))
231 delete pInfo;
234 void SAL_CALL SfxClipboardChangeListener::changedContents( const datatransfer::clipboard::ClipboardEvent& )
236 // Make asynchronous call to avoid locking SolarMutex which is the
237 // root for many deadlocks, especially in conjunction with the "Windows"
238 // based single thread apartment clipboard code!
239 AsyncExecuteInfo* pInfo = new AsyncExecuteInfo( ASYNCEXECUTE_CMD_CHANGEDCONTENTS, this );
240 if (!Application::PostUserEvent( LINK( nullptr, SfxClipboardChangeListener, AsyncExecuteHdl_Impl ), pInfo ))
241 delete pInfo;
244 namespace
246 struct TableSizeType
248 sal_Int32 nRowCount;
249 sal_Int32 nColCount;
253 typedef std::list<uno::Reference<accessibility::XAccessibleTable>> XAccessibleTableList;
255 namespace
257 constexpr
258 bool isText(sal_Int16 nRole)
260 return nRole == accessibility::AccessibleRole::DOCUMENT_TEXT;
263 constexpr
264 bool isSpreadsheet(sal_Int16 nRole)
266 return nRole == accessibility::AccessibleRole::DOCUMENT_SPREADSHEET;
269 constexpr
270 bool isPresentation(sal_Int16 nRole)
272 return nRole == accessibility::AccessibleRole::DOCUMENT_PRESENTATION;
275 constexpr
276 bool isDocument(sal_Int16 nRole)
278 return isText(nRole) || isSpreadsheet(nRole) || isPresentation(nRole);
281 bool hasState(const accessibility::AccessibleEventObject& aEvent, ::sal_Int64 nState)
283 bool res = false;
284 uno::Reference< accessibility::XAccessibleContext > xContext(aEvent.Source, uno::UNO_QUERY);
285 if (xContext.is())
287 ::sal_Int64 nStateSet = xContext->getAccessibleStateSet();
288 res = (nStateSet & nState) != 0;
290 return res;
293 bool isFocused(const accessibility::AccessibleEventObject& aEvent)
295 return hasState(aEvent, accessibility::AccessibleStateType::FOCUSED);
298 uno::Reference<accessibility::XAccessibleContext>
299 getParentContext(const uno::Reference<accessibility::XAccessibleContext>& xContext)
301 uno::Reference<accessibility::XAccessibleContext> xParentContext;
302 uno::Reference<accessibility::XAccessible> xParent = xContext->getAccessibleParent();
303 if (xParent.is())
304 xParentContext = uno::Reference<accessibility::XAccessibleContext>(xParent, uno::UNO_QUERY);
305 return xParentContext;
308 OUString selectionEventTypeToString(sal_Int16 nEventId)
310 using namespace accessibility;
311 switch(nEventId)
313 case AccessibleEventId::SELECTION_CHANGED:
314 return "create";
315 case AccessibleEventId::SELECTION_CHANGED_ADD:
316 return "add";
317 case AccessibleEventId::SELECTION_CHANGED_REMOVE:
318 return "remove";
319 default:
320 return "";
324 bool selectionHasToBeNotified(const uno::Reference<accessibility::XAccessibleContext>& xContext)
326 sal_Int16 nRole = xContext->getAccessibleRole();
327 return
328 nRole == accessibility::AccessibleRole::GRAPHIC ||
329 nRole == accessibility::AccessibleRole::EMBEDDED_OBJECT ||
330 nRole == accessibility::AccessibleRole::SHAPE;
333 bool hasToBeActiveForEditing(sal_Int16 nRole)
335 return
336 nRole == accessibility::AccessibleRole::SHAPE;
339 sal_Int16 getParentRole(const uno::Reference<accessibility::XAccessibleContext>& xContext)
341 sal_Int16 nRole = 0;
342 if (xContext.is())
344 uno::Reference<accessibility::XAccessibleContext> xParentContext = getParentContext(xContext);
345 if (xParentContext.is())
346 nRole = xParentContext->getAccessibleRole();
348 return nRole;
351 sal_Int64 getAccessibleSiblingCount(const Reference<accessibility::XAccessibleContext>& xContext)
353 if (!xContext.is())
354 return -1;
356 sal_Int64 nChildCount = 0;
357 Reference<accessibility::XAccessible> xParent = xContext->getAccessibleParent();
358 if (xParent.is())
360 Reference<accessibility::XAccessibleContext> xParentContext = xParent->getAccessibleContext();
361 if (xParentContext.is())
363 nChildCount = xParentContext->getAccessibleChildCount();
366 return nChildCount - 1;
369 // Put in rAncestorList all ancestors of xTable up to xAncestorTable or
370 // up to the first not-a-table ancestor if xAncestorTable is not an ancestor.
371 // xTable is included in the list, xAncestorTable is not included.
372 // The list is ordered from the ancient ancestor to xTable.
373 // Return true if xAncestorTable is an ancestor of xTable.
374 bool getAncestorList(XAccessibleTableList& rAncestorList,
375 const uno::Reference<accessibility::XAccessibleTable>& xTable,
376 const uno::Reference<accessibility::XAccessibleTable>& xAncestorTable = uno::Reference<accessibility::XAccessibleTable>())
378 uno::Reference<accessibility::XAccessibleTable> xCurrentTable = xTable;
379 while (xCurrentTable.is() && xCurrentTable != xAncestorTable)
381 rAncestorList.push_front(xCurrentTable);
383 uno::Reference<accessibility::XAccessibleContext> xContext(xCurrentTable, uno::UNO_QUERY);
384 xCurrentTable.clear();
385 if (xContext.is())
387 uno::Reference<accessibility::XAccessible> xParent = xContext->getAccessibleParent();
388 uno::Reference<accessibility::XAccessibleContext> xParentContext(xParent, uno::UNO_QUERY);
389 if (xParentContext.is()
390 && xParentContext->getAccessibleRole() == accessibility::AccessibleRole::TABLE_CELL)
392 uno::Reference<accessibility::XAccessible> xCellParent = xParentContext->getAccessibleParent();
393 if (xCellParent.is())
395 xCurrentTable = uno::Reference<accessibility::XAccessibleTable>(xCellParent, uno::UNO_QUERY);
401 return xCurrentTable.is() && xCurrentTable == xAncestorTable;
404 void lookForParentTable(const uno::Reference<accessibility::XAccessibleContext>& xContext,
405 uno::Reference<accessibility::XAccessibleTable>& xTable,
406 sal_Int64& nChildIndex)
408 using namespace accessibility;
409 uno::Reference<XAccessibleContext> xParentContext = getParentContext(xContext);
410 if (xParentContext.is() && xParentContext->getAccessibleRole() == AccessibleRole::TABLE_CELL)
412 uno::Reference<XAccessible> xCellParent = xParentContext->getAccessibleParent();
413 if (xCellParent.is())
415 xTable = uno::Reference<XAccessibleTable>(xCellParent, uno::UNO_QUERY);
416 if (xTable.is())
418 nChildIndex = xParentContext->getAccessibleIndexInParent();
424 OUString truncateText(OUString& sText, sal_Int32 nNewLength)
426 // truncate test to given length
427 OUString sNewText = sText.copy(0, nNewLength);
428 // try to truncate at a word
429 nNewLength = sNewText.lastIndexOf(" ");
430 if (nNewLength > 0)
431 sNewText = sNewText.copy(0, nNewLength);
432 return sNewText;
435 std::string stateSetToString(::sal_Int64 stateSet)
437 static const std::string states[35] = {
438 "ACTIVE", "ARMED", "BUSY", "CHECKED", "DEFUNC",
439 "EDITABLE", "ENABLED", "EXPANDABLE", "EXPANDED", "FOCUSABLE",
440 "FOCUSED", "HORIZONTAL", "ICONIFIED", "INDETERMINATE", "MANAGES_DESCENDANTS",
441 "MODAL", "MULTI_LINE", "MULTI_SELECTABLE", "OPAQUE", "PRESSED",
442 "RESIZABLE", "SELECTABLE", "SELECTED", "SENSITIVE", "SHOWING",
443 "SINGLE_LINE", "STALE", "TRANSIENT", "VERTICAL", "VISIBLE",
444 "MOVEABLE", "DEFAULT", "OFFSCREEN", "COLLAPSE", "CHECKABLE"
447 if (stateSet == 0)
448 return "INVALID";
449 ::sal_Int64 state = 1;
450 std::string s;
451 for (int i = 0; i < 35; ++i)
453 if (stateSet & state)
455 s += states[i];
456 s += "|";
458 state <<= 1;
460 return s;
463 void aboutView(std::string msg, const void* pInstance, const SfxViewShell* pViewShell)
465 if (!pViewShell)
466 return;
468 SAL_INFO("lok.a11y", ">>> " << msg << ": instance: " << pInstance
469 << ", VIED ID: " << pViewShell->GetViewShellId().get() << " <<<");
472 void aboutEvent(std::string msg, const accessibility::AccessibleEventObject& aEvent)
476 uno::Reference< accessibility::XAccessible > xSource(aEvent.Source, uno::UNO_QUERY);
477 if (xSource.is())
479 uno::Reference< accessibility::XAccessibleContext > xContext =
480 xSource->getAccessibleContext();
482 if (xContext.is())
484 SAL_INFO("lok.a11y", msg << ": event id: " << aEvent.EventId
485 << "\n xSource: " << xSource.get()
486 << "\n role: " << xContext->getAccessibleRole()
487 << "\n name: " << xContext->getAccessibleName()
488 << "\n index in parent: " << xContext->getAccessibleIndexInParent()
489 << "\n state set: " << stateSetToString(xContext->getAccessibleStateSet())
490 << "\n parent: " << xContext->getAccessibleParent().get()
491 << "\n child count: " << xContext->getAccessibleChildCount());
493 else
495 SAL_INFO("lok.a11y", msg << ": event id: " << aEvent.EventId
496 << ", no accessible context!");
499 else
501 SAL_INFO("lok.a11y", msg << ": event id: " << aEvent.EventId
502 << ", no accessible source!");
504 uno::Reference< accessibility::XAccessible > xOldValue;
505 aEvent.OldValue >>= xOldValue;
506 if (xOldValue.is())
508 uno::Reference< accessibility::XAccessibleContext > xContext =
509 xOldValue->getAccessibleContext();
511 if (xContext.is())
513 SAL_INFO("lok.a11y", msg << ": "
514 "\n xOldValue: " << xOldValue.get()
515 << "\n role: " << xContext->getAccessibleRole()
516 << "\n name: " << xContext->getAccessibleName()
517 << "\n index in parent: " << xContext->getAccessibleIndexInParent()
518 << "\n state set: " << stateSetToString(xContext->getAccessibleStateSet())
519 << "\n parent: " << xContext->getAccessibleParent().get()
520 << "\n child count: " << xContext->getAccessibleChildCount());
523 uno::Reference< accessibility::XAccessible > xNewValue;
524 aEvent.NewValue >>= xNewValue;
525 if (xNewValue.is())
527 uno::Reference< accessibility::XAccessibleContext > xContext =
528 xNewValue->getAccessibleContext();
530 if (xContext.is())
532 SAL_INFO("lok.a11y", msg << ": "
533 "\n xNewValue: " << xNewValue.get()
534 << "\n role: " << xContext->getAccessibleRole()
535 << "\n name: " << xContext->getAccessibleName()
536 << "\n index in parent: " << xContext->getAccessibleIndexInParent()
537 << "\n state set: " << stateSetToString(xContext->getAccessibleStateSet())
538 << "\n parent: " << xContext->getAccessibleParent().get()
539 << "\n child count: " << xContext->getAccessibleChildCount());
543 catch( const lang::IndexOutOfBoundsException& /*e*/ )
545 LOK_WARN("lok.a11y", "Focused object has invalid index in parent");
549 sal_Int32 getListPrefixSize(const uno::Reference<css::accessibility::XAccessibleText>& xAccText)
551 if (!xAccText.is())
552 return 0;
554 OUString sText = xAccText->getText();
555 sal_Int32 nLength = sText.getLength();
556 if (nLength <= 0)
557 return 0;
559 css::uno::Sequence< css::beans::PropertyValue > aRunAttributeList;
560 css::uno::Sequence< OUString > aRequestedAttributes = {UNO_NAME_NUMBERING_LEVEL, UNO_NAME_NUMBERING};
561 aRunAttributeList = xAccText->getCharacterAttributes(0, aRequestedAttributes);
563 sal_Int16 nLevel = -1;
564 bool bIsCounted = false;
565 for (const auto& attribute: aRunAttributeList)
567 if (attribute.Name.isEmpty())
568 continue;
569 if (attribute.Name == UNO_NAME_NUMBERING_LEVEL)
570 attribute.Value >>= nLevel;
571 else if (attribute.Name == UNO_NAME_NUMBERING)
572 attribute.Value >>= bIsCounted;
574 if (nLevel < 0 || !bIsCounted)
575 return 0;
577 css::accessibility::TextSegment aTextSegment =
578 xAccText->getTextAtIndex(0, css::accessibility::AccessibleTextType::ATTRIBUTE_RUN);
580 SAL_INFO("lok.a11y", "getListPrefixSize: prefix: " << aTextSegment.SegmentText << ", level: " << nLevel);
582 return aTextSegment.SegmentEnd;
585 void aboutTextFormatting(std::string msg, const uno::Reference<css::accessibility::XAccessibleText>& xAccText)
587 if (!xAccText.is())
588 return;
590 OUString sText = xAccText->getText();
591 sal_Int32 nLength = sText.getLength();
592 if (nLength <= 0)
593 return;
595 css::uno::Reference<css::accessibility::XAccessibleTextAttributes>
596 xAccTextAttr(xAccText, uno::UNO_QUERY);
597 css::uno::Sequence< OUString > aRequestedAttributes;
599 sal_Int32 nPos = 0;
600 while (nPos < nLength)
602 css::accessibility::TextSegment aTextSegment =
603 xAccText->getTextAtIndex(nPos, css::accessibility::AccessibleTextType::ATTRIBUTE_RUN);
604 SAL_INFO("lok.a11y", msg << ": "
605 "text segment: '" << aTextSegment.SegmentText
606 << "', start: " << aTextSegment.SegmentStart
607 << ", end: " << aTextSegment.SegmentEnd);
609 css::uno::Sequence< css::beans::PropertyValue > aRunAttributeList;
610 if (xAccTextAttr.is())
612 aRunAttributeList = xAccTextAttr->getRunAttributes(nPos, aRequestedAttributes);
614 else
616 aRunAttributeList = xAccText->getCharacterAttributes(nPos, aRequestedAttributes);
619 sal_Int32 nSize = aRunAttributeList.getLength();
620 SAL_INFO("lok.a11y",
621 msg << ": attribute list size: " << nSize);
622 if (nSize)
624 OUString sValue;
625 OUString sAttributes = "{ ";
626 for (const auto& attribute: aRunAttributeList)
628 if (attribute.Name.isEmpty())
629 continue;
631 if (attribute.Name == "CharHeight" || attribute.Name == "CharWeight")
633 float fValue = 0;
634 attribute.Value >>= fValue;
635 sValue = OUString::number(fValue);
637 else if (attribute.Name == "CharPosture")
639 awt::FontSlant nValue = awt::FontSlant_NONE;
640 attribute.Value >>= nValue;
641 sValue = OUString::number(static_cast<unsigned int>(nValue));
643 else if (attribute.Name == "CharUnderline")
645 sal_Int16 nValue = 0;
646 attribute.Value >>= nValue;
647 sValue = OUString::number(nValue);
649 else if (attribute.Name == "CharFontName")
651 attribute.Value >>= sValue;
653 else if (attribute.Name == "Rsid")
655 sal_uInt32 nValue = 0;
656 attribute.Value >>= nValue;
657 sValue = OUString::number(nValue);
659 else if (attribute.Name == UNO_NAME_NUMBERING_LEVEL)
661 sal_Int16 nValue(-1);
662 attribute.Value >>= nValue;
663 sValue = OUString::number(nValue);
665 else if (attribute.Name == UNO_NAME_NUMBERING)
667 bool bValue(false);
668 attribute.Value >>= bValue;
669 sValue = OUString::boolean(bValue);
671 else if (attribute.Name == UNO_NAME_NUMBERING_RULES)
673 attribute.Value >>= sValue;
676 if (!sValue.isEmpty())
678 if (sAttributes != "{ ")
679 sAttributes += ", ";
680 sAttributes += attribute.Name + ": " + sValue;
681 sValue = "";
684 sAttributes += " }";
685 SAL_INFO("lok.a11y",
686 msg << ": " << sAttributes);
688 nPos = aTextSegment.SegmentEnd + 1;
692 void aboutParagraph(std::string msg, const OUString& rsParagraphContent, sal_Int32 nCaretPosition,
693 sal_Int32 nSelectionStart, sal_Int32 nSelectionEnd, sal_Int32 nListPrefixLength,
694 bool force = false)
696 SAL_INFO("lok.a11y", msg << ": "
697 "\n text content: \"" << rsParagraphContent << "\""
698 "\n caret pos: " << nCaretPosition
699 << "\n selection: start: " << nSelectionStart << ", end: " << nSelectionEnd
700 << "\n list prefix length: " << nListPrefixLength
701 << "\n force: " << force
705 void aboutParagraph(std::string msg, const uno::Reference<css::accessibility::XAccessibleText>& xAccText,
706 bool force = false)
708 if (!xAccText.is())
709 return;
711 OUString sText = xAccText->getText();
712 sal_Int32 nCaretPosition = xAccText->getCaretPosition();
713 sal_Int32 nSelectionStart = xAccText->getSelectionStart();
714 sal_Int32 nSelectionEnd = xAccText->getSelectionEnd();
715 sal_Int32 nListPrefixLength = getListPrefixSize(xAccText);
716 aboutParagraph(msg, sText, nCaretPosition, nSelectionStart, nSelectionEnd, nListPrefixLength, force);
719 void aboutFocusedCellChanged(sal_Int32 nOutCount, const std::vector<TableSizeType>& aInList,
720 sal_Int32 nRow, sal_Int32 nCol, sal_Int32 nRowSpan, sal_Int32 nColSpan)
722 std::stringstream inListStream;
723 inListStream << "[ ";
724 for (const auto& rTableSize: aInList)
726 inListStream << "{ rowCount: " << rTableSize.nRowCount << " colCount: " << rTableSize.nColCount << " } ";
728 inListStream << "]";
730 SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyFocusedCellChanged: "
731 "\n outCount: " << nOutCount
732 << "\n inList: " << inListStream.str()
733 << "\n row: " << nRow
734 << "\n column: " << nCol
735 << "\n rowSpan: " << nRowSpan
736 << "\n colSpan: " << nColSpan
739 } // anonymous namespace
741 class LOKDocumentFocusListener :
742 public ::cppu::WeakImplHelper< accessibility::XAccessibleEventListener >
744 static constexpr sal_Int64 MAX_ATTACHABLE_CHILDREN = 100;
746 const SfxViewShell* m_pViewShell;
747 sal_Int16 m_nDocumentType;
748 std::unordered_set<uno::Reference<uno::XInterface>> m_aRefList;
749 OUString m_sFocusedParagraph;
750 sal_Int32 m_nCaretPosition;
751 sal_Int32 m_nSelectionStart;
752 sal_Int32 m_nSelectionEnd;
753 sal_Int32 m_nListPrefixLength;
754 uno::Reference<accessibility::XAccessibleTable> m_xLastTable;
755 OUString m_sSelectedText;
756 bool m_bIsEditingCell;
757 // used for text content of a shape
758 bool m_bIsEditingInSelection;
759 OUString m_sSelectedCellAddress;
760 uno::Reference<accessibility::XAccessible> m_xSelectedObject;
762 public:
763 explicit LOKDocumentFocusListener(const SfxViewShell* pViewShell);
765 /// @throws lang::IndexOutOfBoundsException
766 /// @throws uno::RuntimeException
767 void attachRecursive(
768 const uno::Reference< accessibility::XAccessible >& xAccessible
771 /// @throws lang::IndexOutOfBoundsException
772 /// @throws uno::RuntimeException
773 void attachRecursive(
774 const uno::Reference< accessibility::XAccessible >& xAccessible,
775 const uno::Reference< accessibility::XAccessibleContext >& xContext
778 /// @throws lang::IndexOutOfBoundsException
779 /// @throws uno::RuntimeException
780 void attachRecursive(
781 const uno::Reference< accessibility::XAccessible >& xAccessible,
782 const uno::Reference< accessibility::XAccessibleContext >& xContext,
783 const sal_Int64 nStateSet
786 /// @throws lang::IndexOutOfBoundsException
787 /// @throws uno::RuntimeException
788 void detachRecursive(
789 const uno::Reference< accessibility::XAccessible >& xAccessible,
790 bool bForce = false
793 /// @throws lang::IndexOutOfBoundsException
794 /// @throws uno::RuntimeException
795 void detachRecursive(
796 const uno::Reference< accessibility::XAccessibleContext >& xContext,
797 bool bForce = false
800 /// @throws lang::IndexOutOfBoundsException
801 /// @throws uno::RuntimeException
802 void detachRecursive(
803 const uno::Reference< accessibility::XAccessibleContext >& xContext,
804 const sal_Int64 nStateSet,
805 bool bForce = false
808 /// @throws lang::IndexOutOfBoundsException
809 /// @throws uno::RuntimeException
810 static uno::Reference< accessibility::XAccessible > getAccessible(const lang::EventObject& aEvent );
812 // XEventListener
813 virtual void SAL_CALL disposing( const lang::EventObject& Source ) override;
815 // XAccessibleEventListener
816 virtual void SAL_CALL notifyEvent( const accessibility::AccessibleEventObject& aEvent ) override;
818 void notifyEditingInSelectionState(bool bParagraph = true);
819 void notifyFocusedParagraphChanged(bool force = false);
820 void notifyCaretChanged();
821 void notifyTextSelectionChanged();
822 void notifyFocusedCellChanged(sal_Int32 nOutCount, const std::vector<TableSizeType>& aInList, sal_Int32 nRow, sal_Int32 nCol, sal_Int32 nRowSpan, sal_Int32 nColSpan);
823 void notifySelectionChanged(const uno::Reference<accessibility::XAccessible>& xAccObj, const OUString& sAction);
825 OUString getFocusedParagraph() const;
826 int getCaretPosition() const;
828 private:
829 void paragraphPropertiesToTree(boost::property_tree::ptree& aPayloadTree, bool force = false) const;
830 void paragraphPropertiesToJson(std::string& aPayload, bool force = false) const;
831 bool updateParagraphInfo(const uno::Reference<css::accessibility::XAccessibleText>& xAccText,
832 bool force, std::string msg = "");
833 void updateAndNotifyParagraph(const uno::Reference<css::accessibility::XAccessibleText>& xAccText,
834 bool force, std::string msg = "");
835 void resetParagraphInfo();
836 void onFocusedParagraphInWriterTable(const uno::Reference<accessibility::XAccessibleTable>& xTable,
837 sal_Int64& nChildIndex,
838 const uno::Reference<accessibility::XAccessibleText>& xAccText);
839 uno::Reference< accessibility::XAccessible >
840 getSelectedObject(const accessibility::AccessibleEventObject& aEvent) const;
841 void onShapeSelectionChanged(const Reference<accessibility::XAccessible>& xSelectedObject,
842 const OUString& sAction);
845 LOKDocumentFocusListener::LOKDocumentFocusListener(const SfxViewShell* pViewShell)
846 : m_pViewShell(pViewShell)
847 , m_nDocumentType(0)
848 , m_nCaretPosition(0)
849 , m_nSelectionStart(0)
850 , m_nSelectionEnd(0)
851 , m_nListPrefixLength(0)
852 , m_bIsEditingCell(false)
853 , m_bIsEditingInSelection(false)
857 void LOKDocumentFocusListener::paragraphPropertiesToTree(boost::property_tree::ptree& aPayloadTree, bool force) const
859 bool bLeftToRight = m_nCaretPosition == m_nSelectionEnd;
860 aPayloadTree.put("content", m_sFocusedParagraph.toUtf8().getStr());
861 aPayloadTree.put("position", m_nCaretPosition);
862 aPayloadTree.put("start", bLeftToRight ? m_nSelectionStart : m_nSelectionEnd);
863 aPayloadTree.put("end", bLeftToRight ? m_nSelectionEnd : m_nSelectionStart);
864 if (m_nListPrefixLength > 0)
865 aPayloadTree.put("listPrefixLength", m_nListPrefixLength);
866 if (force)
867 aPayloadTree.put("force", 1);
870 void LOKDocumentFocusListener::paragraphPropertiesToJson(std::string& aPayload, bool force) const
872 boost::property_tree::ptree aPayloadTree;
873 paragraphPropertiesToTree(aPayloadTree, force);
874 std::stringstream aStream;
875 boost::property_tree::write_json(aStream, aPayloadTree);
876 aPayload = aStream.str();
879 OUString LOKDocumentFocusListener::getFocusedParagraph() const
881 aboutView("LOKDocumentFocusListener::getFocusedParagraph", this, m_pViewShell);
882 aboutParagraph("LOKDocumentFocusListener::getFocusedParagraph",
883 m_sFocusedParagraph, m_nCaretPosition,
884 m_nSelectionStart, m_nSelectionEnd, m_nListPrefixLength);
886 std::string aPayload;
887 paragraphPropertiesToJson(aPayload);
888 OUString sRet = OUString::fromUtf8(aPayload);
889 return sRet;
892 int LOKDocumentFocusListener::getCaretPosition() const
894 aboutView("LOKDocumentFocusListener::getCaretPosition", this, m_pViewShell);
895 SAL_INFO("lok.a11y", "LOKDocumentFocusListener::getCaretPosition: " << m_nCaretPosition);
896 return m_nCaretPosition;
899 // notifyEditingInSelectionState
900 // Used for notifying when editing becomes active/disabled for a shape
901 // bParagraph: should we append currently focused paragraph ?
902 // The problem is that the initially focused paragraph could not be the one user has clicked on,
903 // when there are more than a single paragraph.
904 // So in some case sending the focused paragraph could be misleading.
905 void LOKDocumentFocusListener::notifyEditingInSelectionState(bool bParagraph)
907 aboutView("LOKDocumentFocusListener::notifyEditingInSelectionState", this, m_pViewShell);
909 boost::property_tree::ptree aPayloadTree;
910 bool bIsCell = !m_sSelectedCellAddress.isEmpty();
911 aPayloadTree.put("cell", bIsCell ? 1 : 0);
912 if (bIsCell)
914 aPayloadTree.put("enabled", m_bIsEditingCell ? 1 : 0);
915 if (m_bIsEditingCell)
917 aPayloadTree.put("selection", m_sSelectedCellAddress);
918 if (bParagraph)
919 aPayloadTree.put("paragraph", m_sFocusedParagraph);
922 else
924 aPayloadTree.put("enabled", m_bIsEditingInSelection ? 1 : 0);
925 if (m_bIsEditingInSelection && m_xSelectedObject.is())
927 uno::Reference<accessibility::XAccessibleContext> xContext = m_xSelectedObject->getAccessibleContext();
928 if (xContext.is())
930 OUString sSelectionDescr = xContext->getAccessibleName();
931 sSelectionDescr = sSelectionDescr.trim();
932 aPayloadTree.put("selection", sSelectionDescr);
933 if (bParagraph)
934 aPayloadTree.put("paragraph", m_sFocusedParagraph);
939 std::stringstream aStream;
940 boost::property_tree::write_json(aStream, aPayloadTree);
941 std::string aPayload = aStream.str();
942 if (m_pViewShell)
944 SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEditingInSelectionState: payload: \n" << aPayload);
945 m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_EDITING_IN_SELECTION_STATE, aPayload.c_str());
949 /// notifyFocusedParagraphChanged
951 // Notify content, caret position and text selection start/end for the focused paragraph
952 // in current view.
953 // For focused we don't mean to be necessarily the currently focused accessibility node.
954 // It's enough that the caret is present in the paragraph (position != -1).
955 // In fact each view has its own accessibility node per each text paragraph.
956 // Anyway there can be only one focused accessibility node at time.
957 // So when text changes are performed in one view, both accessibility nodes emit
958 // a text changed event, anyway only the accessibility node belonging to the view
959 // where the text change has occurred is the focused one.
961 // force: when true update the clipboard content even if client is composing.
963 // Usually when editing on the client involves composing the clipboard area updating
964 // is skipped until the composition is over.
965 // On the contrary the composition would be aborted, making dictation not possible.
966 // Anyway when the text change has been performed by another view we are in due
967 // to update the clipboard content even if the user is in the middle of a composition.
968 void LOKDocumentFocusListener::notifyFocusedParagraphChanged(bool force)
970 aboutView("LOKDocumentFocusListener::notifyFocusedParagraphChanged", this, m_pViewShell);
971 std::string aPayload;
972 paragraphPropertiesToJson(aPayload, force);
973 if (m_pViewShell)
975 aboutParagraph("LOKDocumentFocusListener::notifyFocusedParagraphChanged",
976 m_sFocusedParagraph, m_nCaretPosition,
977 m_nSelectionStart, m_nSelectionEnd, m_nListPrefixLength, force);
979 m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_FOCUS_CHANGED, aPayload.c_str());
983 void LOKDocumentFocusListener::notifyCaretChanged()
985 aboutView("LOKDocumentFocusListener::notifyCaretChanged", this, m_pViewShell);
986 boost::property_tree::ptree aPayloadTree;
987 aPayloadTree.put("position", m_nCaretPosition);
988 std::stringstream aStream;
989 boost::property_tree::write_json(aStream, aPayloadTree);
990 std::string aPayload = aStream.str();
991 if (m_pViewShell)
993 SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyCaretChanged: " << m_nCaretPosition);
994 m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_CARET_CHANGED, aPayload.c_str());
998 void LOKDocumentFocusListener::notifyTextSelectionChanged()
1000 aboutView("LOKDocumentFocusListener::notifyTextSelectionChanged", this, m_pViewShell);
1001 bool bLeftToRight = m_nCaretPosition == m_nSelectionEnd;
1002 boost::property_tree::ptree aPayloadTree;
1003 aPayloadTree.put("start", bLeftToRight ? m_nSelectionStart : m_nSelectionEnd);
1004 aPayloadTree.put("end", bLeftToRight ? m_nSelectionEnd : m_nSelectionStart);
1005 std::stringstream aStream;
1006 boost::property_tree::write_json(aStream, aPayloadTree);
1007 std::string aPayload = aStream.str();
1008 if (m_pViewShell)
1010 SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyTextSelectionChanged: "
1011 "start: " << m_nSelectionStart << ", end: " << m_nSelectionEnd);
1012 m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_TEXT_SELECTION_CHANGED, aPayload.c_str());
1016 void LOKDocumentFocusListener::notifyFocusedCellChanged(
1017 sal_Int32 nOutCount, const std::vector<TableSizeType>& aInList,
1018 sal_Int32 nRow, sal_Int32 nCol, sal_Int32 nRowSpan, sal_Int32 nColSpan)
1020 aboutView("LOKDocumentFocusListener::notifyTablePositionChanged", this, m_pViewShell);
1021 boost::property_tree::ptree aPayloadTree;
1022 if (nOutCount > 0)
1024 aPayloadTree.put("outCount", nOutCount);
1026 if (!aInList.empty())
1028 boost::property_tree::ptree aInListNode;
1029 for (const auto& rTableSize: aInList)
1031 boost::property_tree::ptree aTableSizeNode;
1032 aTableSizeNode.put("rowCount", rTableSize.nRowCount);
1033 aTableSizeNode.put("colCount", rTableSize.nColCount);
1035 aInListNode.push_back(std::make_pair(std::string(), aTableSizeNode));
1037 aPayloadTree.add_child("inList", aInListNode);
1040 aPayloadTree.put("row", nRow);
1041 aPayloadTree.put("col", nCol);
1043 if (nRowSpan > 1)
1045 aPayloadTree.put("rowSpan", nRowSpan);
1047 if (nColSpan > 1)
1049 aPayloadTree.put("colSpan", nColSpan);
1052 boost::property_tree::ptree aContentNode;
1053 paragraphPropertiesToTree(aContentNode);
1054 aPayloadTree.add_child("paragraph", aContentNode);
1056 std::stringstream aStream;
1057 boost::property_tree::write_json(aStream, aPayloadTree);
1058 std::string aPayload = aStream.str();
1059 if (m_pViewShell)
1061 aboutFocusedCellChanged(nOutCount, aInList, nRow, nCol, nRowSpan, nColSpan);
1062 aboutParagraph("LOKDocumentFocusListener::notifyFocusedCellChanged: paragraph: ",
1063 m_sFocusedParagraph, m_nCaretPosition, m_nSelectionStart, m_nSelectionEnd,
1064 m_nListPrefixLength, false);
1066 m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_FOCUSED_CELL_CHANGED, aPayload.c_str());
1070 void LOKDocumentFocusListener::notifySelectionChanged(const uno::Reference<accessibility::XAccessible>& xAccObj,
1071 const OUString& sAction)
1073 using namespace accessibility;
1074 if (!xAccObj.is())
1075 return;
1077 aboutView("LOKDocumentFocusListener::notifySelectionChanged", this, m_pViewShell);
1078 uno::Reference<XAccessibleContext> xContext = xAccObj->getAccessibleContext();
1079 if (xContext.is())
1081 OUString sName = xContext->getAccessibleName();
1082 sName = sName.trim();
1083 if (sName == "GraphicObjectShape")
1084 sName = "Graphic";
1086 // check for text content and send it with some limitations:
1087 // no more than 10 paragraphs, no more than 1000 chars
1088 bool bIsCell = xContext->getAccessibleRole() == AccessibleRole::TABLE_CELL;
1089 OUString sTextContent;
1090 if (sAction == "create" || sAction == "add")
1092 const sal_Int64 nMaxJoinedParagraphs = 10;
1093 const sal_Int32 nMaxTextContentLength = 1000;
1094 if (bIsCell)
1096 uno::Reference<XAccessibleText> xAccText(xAccObj, uno::UNO_QUERY);
1097 if (xAccText.is())
1099 sTextContent = xAccText->getText();
1100 sal_Int32 nTextLength = sTextContent.getLength();
1101 if (nTextLength > nMaxTextContentLength)
1103 sTextContent = truncateText(sTextContent, nMaxTextContentLength);
1107 else
1109 sal_Int32 nTotalTextLength = 0;
1110 sal_Int64 nChildCount = xContext->getAccessibleChildCount();
1111 if (nChildCount > nMaxJoinedParagraphs)
1112 nChildCount = nMaxJoinedParagraphs;
1113 for (sal_Int64 i = 0; i < nChildCount; ++i)
1115 uno::Reference<XAccessible> xChild = xContext->getAccessibleChild(i);
1116 uno::Reference<XAccessibleText> xAccText(xChild, uno::UNO_QUERY);
1117 if (!xAccText.is())
1118 continue;
1119 OUString sText = xAccText->getText();
1120 sal_Int32 nTextLength = sText.getLength();
1121 if (nTextLength < 1)
1122 continue;
1123 if (nTotalTextLength + nTextLength < nMaxTextContentLength)
1125 nTotalTextLength += nTextLength;
1126 sTextContent += sText + " \n";
1128 else
1130 // truncate paragraph
1131 sal_Int32 nNewLength = nMaxTextContentLength - nTotalTextLength;
1132 sTextContent += truncateText(sText, nNewLength);
1133 break;
1139 boost::property_tree::ptree aPayloadTree;
1140 aPayloadTree.put("cell", bIsCell ? 1 : 0);
1141 aPayloadTree.put("action", sAction);
1142 aPayloadTree.put("name", sName);
1143 if (!sTextContent.isEmpty())
1144 aPayloadTree.put("text", sTextContent);
1145 std::stringstream aStream;
1146 boost::property_tree::write_json(aStream, aPayloadTree);
1147 std::string aPayload = aStream.str();
1148 if (m_pViewShell)
1150 SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifySelectionChanged: "
1151 "action: " << sAction << ", name: " << sName);
1152 m_pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_A11Y_SELECTION_CHANGED, aPayload.c_str());
1157 void LOKDocumentFocusListener::disposing( const lang::EventObject& aEvent )
1159 // Unref the object here, but do not remove as listener since the object
1160 // might no longer be in a state that safely allows this.
1161 if( aEvent.Source.is() )
1162 m_aRefList.erase(aEvent.Source);
1166 bool LOKDocumentFocusListener::updateParagraphInfo(const uno::Reference<css::accessibility::XAccessibleText>& xAccText,
1167 bool force, std::string msg)
1169 if (!xAccText.is())
1170 return false;
1172 bool bNotify = false;
1173 // If caret is present inside the paragraph (pos != -1), it means that paragraph has focus in the current view.
1174 sal_Int32 nCaretPosition = xAccText->getCaretPosition();
1175 if (nCaretPosition >= 0)
1177 OUString sText = xAccText->getText();
1178 m_nCaretPosition = nCaretPosition;
1179 m_nSelectionStart = xAccText->getSelectionStart();
1180 m_nSelectionEnd = xAccText->getSelectionEnd();
1181 m_nListPrefixLength = getListPrefixSize(xAccText);
1183 // Inside a text shape when there is no selection, selection-start and selection-end are
1184 // set to current caret position instead of -1. Moreover, inside a text shape pressing
1185 // delete or backspace with an empty selection really deletes text and not only the empty
1186 // selection as it occurs in a text paragraph in Writer.
1187 // So whenever selection-start == selection-end, and we are inside a shape we need
1188 // to set these parameters to -1 in order to have the client to handle delete and
1189 // backspace properly.
1190 if (m_nSelectionStart == m_nSelectionEnd && m_nSelectionStart != -1)
1192 uno::Reference<accessibility::XAccessibleContext> xContext(xAccText, uno::UNO_QUERY);
1193 sal_Int16 nParentRole = getParentRole(xContext);
1194 if (nParentRole == accessibility::AccessibleRole::SHAPE ||
1195 nParentRole == accessibility::AccessibleRole::TEXT_FRAME) // spreadsheet cell editing
1196 m_nSelectionStart = m_nSelectionEnd = -1;
1199 // In case only caret position or text selection are different we can rely on specific events.
1200 if (m_sFocusedParagraph != sText)
1202 m_sFocusedParagraph = sText;
1203 bNotify = true;
1206 else
1208 SAL_WARN("lok.a11y",
1209 "LOKDocumentFocusListener::updateParagraphInfo: skipped since no caret is present");
1212 std::string header = "LOKDocumentFocusListener::updateParagraphInfo";
1213 if (msg.size())
1214 header += ": " + msg;
1215 aboutParagraph(header, xAccText, force);
1216 return bNotify;
1220 void LOKDocumentFocusListener::updateAndNotifyParagraph(
1221 const uno::Reference<css::accessibility::XAccessibleText>& xAccText,
1222 bool force, std::string msg)
1224 if (updateParagraphInfo(xAccText, force, msg))
1225 notifyFocusedParagraphChanged(force);
1228 void LOKDocumentFocusListener::resetParagraphInfo()
1230 m_sFocusedParagraph = "";
1231 m_nCaretPosition = 0;
1232 m_nSelectionStart = -1;
1233 m_nSelectionEnd = -1;
1234 m_nListPrefixLength = 0;
1237 // For a presentation document when an accessible event of type SELECTION_CHANGED_XXX occurs
1238 // the selected (or unselected) object is put in NewValue, instead for a text document
1239 // the selected object is put in Source.
1240 // The following function helps to retrieve the selected object independently on where it has been put.
1241 uno::Reference< accessibility::XAccessible >
1242 LOKDocumentFocusListener::getSelectedObject(const accessibility::AccessibleEventObject& aEvent) const
1244 uno::Reference< accessibility::XAccessible > xSelectedObject;
1245 if (isText(m_nDocumentType))
1247 xSelectedObject.set(aEvent.Source, uno::UNO_QUERY);
1249 else
1251 aEvent.NewValue >>= xSelectedObject;
1253 return xSelectedObject;
1256 void LOKDocumentFocusListener::onShapeSelectionChanged(
1257 const uno::Reference<accessibility::XAccessible>& xSelectedObject,
1258 const OUString& sAction)
1260 // when a shape is selected or unselected we could need to notify that text content editing
1261 // is no more active, that allows on the client side to prevent default input.
1262 resetParagraphInfo();
1263 if (m_bIsEditingInSelection)
1265 m_bIsEditingInSelection = false;
1266 notifyEditingInSelectionState();
1268 notifySelectionChanged(xSelectedObject, sAction);
1271 void LOKDocumentFocusListener::onFocusedParagraphInWriterTable(
1272 const uno::Reference<accessibility::XAccessibleTable>& xTable,
1273 sal_Int64& nChildIndex,
1274 const uno::Reference<accessibility::XAccessibleText>& xAccText
1277 std::vector<TableSizeType> aInList;
1278 sal_Int32 nOutCount = 0;
1280 if (m_xLastTable.is())
1282 if (xTable != m_xLastTable)
1284 // do we get in one or more nested tables ?
1285 // check if xTable is a descendant of m_xLastTable
1286 XAccessibleTableList newTableAncestorList;
1287 bool isLastAncestorOfNew = getAncestorList(newTableAncestorList, xTable, m_xLastTable);
1288 bool isNewAncestorOfLast = false;
1289 if (!isLastAncestorOfNew)
1291 // do we get out of one or more nested tables ?
1292 // check if m_xLastTable is a descendant of xTable
1293 XAccessibleTableList lastTableAncestorList;
1294 isNewAncestorOfLast = getAncestorList(lastTableAncestorList, m_xLastTable, xTable);
1295 // we have to notify "out of table" for all m_xLastTable ancestors up to xTable
1296 // or the first not-a-table ancestor
1297 nOutCount = lastTableAncestorList.size();
1299 if (isLastAncestorOfNew || !isNewAncestorOfLast)
1301 // we have to notify row/col count for all xTable ancestors starting from the ancestor
1302 // which is a child of m_xLastTable (isLastAncestorOfNew) or the first not-a-table ancestor
1303 for (const auto& ancestor: newTableAncestorList)
1305 TableSizeType aTableSize{ancestor->getAccessibleRowCount(),
1306 ancestor->getAccessibleColumnCount()};
1307 aInList.push_back(aTableSize);
1312 else
1314 // cursor was not inside any table and gets inside one or more tables
1315 // we have to notify row/col count for all xTable ancestors starting from first not-a-table ancestor
1316 XAccessibleTableList newTableAncestorList;
1317 getAncestorList(newTableAncestorList, xTable);
1318 for (const auto& ancestor: newTableAncestorList)
1320 TableSizeType aTableSize{ancestor->getAccessibleRowCount(),
1321 ancestor->getAccessibleColumnCount()};
1322 aInList.push_back(aTableSize);
1326 // we have to notify current row/col of xTable and related row/col span
1327 sal_Int32 nRow = xTable->getAccessibleRow(nChildIndex);
1328 sal_Int32 nCol = xTable->getAccessibleColumn(nChildIndex);
1329 sal_Int32 nRowSpan = xTable->getAccessibleRowExtentAt(nRow, nCol);
1330 sal_Int32 nColSpan = xTable->getAccessibleColumnExtentAt(nRow, nCol);
1332 m_xLastTable = xTable;
1333 updateParagraphInfo(xAccText, false, "STATE_CHANGED: FOCUSED");
1334 notifyFocusedCellChanged(nOutCount, aInList, nRow, nCol, nRowSpan, nColSpan);
1337 void LOKDocumentFocusListener::notifyEvent(const accessibility::AccessibleEventObject& aEvent)
1339 using namespace accessibility;
1340 aboutView("LOKDocumentFocusListener::notifyEvent", this, m_pViewShell);
1344 aboutEvent("LOKDocumentFocusListener::notifyEvent", aEvent);
1346 switch (aEvent.EventId)
1348 case AccessibleEventId::STATE_CHANGED:
1350 // logging
1351 sal_Int64 nState = accessibility::AccessibleStateType::INVALID;
1352 aEvent.NewValue >>= nState;
1353 sal_Int64 nOldState = accessibility::AccessibleStateType::INVALID;
1354 aEvent.OldValue >>= nOldState;
1355 SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: STATE_CHANGED: "
1356 " New State: " << stateSetToString(nState)
1357 << ", Old State: " << stateSetToString(nOldState));
1359 // check validity
1360 uno::Reference< XAccessible > xAccessibleObject = getAccessible(aEvent);
1361 if (!xAccessibleObject.is())
1362 return;
1363 uno::Reference<XAccessibleContext> xContext(aEvent.Source, uno::UNO_QUERY);
1364 if (!xContext)
1365 return;
1367 sal_Int16 nRole = xContext->getAccessibleRole();
1369 if (nRole == AccessibleRole::PARAGRAPH)
1371 uno::Reference<XAccessibleText> xAccText(xAccessibleObject, uno::UNO_QUERY);
1372 if (!xAccText.is())
1373 return;
1375 switch (nState)
1377 case AccessibleStateType::ACTIVE:
1379 if (!m_bIsEditingInSelection && hasToBeActiveForEditing(getParentRole(xContext)))
1381 m_bIsEditingInSelection = true;
1383 break;
1385 case AccessibleStateType::FOCUSED:
1387 if (m_bIsEditingInSelection && m_xSelectedObject.is())
1389 updateParagraphInfo(xAccText, true, "STATE_CHANGED: FOCUSED");
1390 notifyEditingInSelectionState(getAccessibleSiblingCount(xContext) == 0);
1391 notifyFocusedParagraphChanged(true);
1392 // we clear selected object so when editing is over but shape is
1393 // still selected, the selection event is notified the same to the client
1394 m_xSelectedObject.clear();
1395 return;
1397 if (isText(m_nDocumentType))
1399 // check if we are inside a table: in case notify table and current cell info
1400 bool isInsideTable = false;
1401 uno::Reference<XAccessibleTable> xTable;
1402 sal_Int64 nChildIndex;
1403 lookForParentTable(xContext, xTable, nChildIndex);
1404 if (xTable.is())
1406 onFocusedParagraphInWriterTable(xTable, nChildIndex, xAccText);
1407 isInsideTable = true;
1409 // paragraph is not inside any table
1410 if (!isInsideTable)
1412 if (m_xLastTable.is())
1414 // we get out one or more tables
1415 // we have to notify "out of table" for all m_xLastTable ancestors
1416 // up to the first not-a-table ancestor
1417 XAccessibleTableList lastTableAncestorList;
1418 getAncestorList(lastTableAncestorList, m_xLastTable);
1419 sal_Int32 nOutCount = lastTableAncestorList.size();
1420 // no more inside a table
1421 m_xLastTable.clear();
1422 // notify
1423 std::vector<TableSizeType> aInList;
1424 updateParagraphInfo(xAccText, false, "STATE_CHANGED: FOCUSED");
1425 notifyFocusedCellChanged(nOutCount, aInList, -1, -1, 1, 1);
1427 else
1429 updateAndNotifyParagraph(xAccText, false, "STATE_CHANGED: FOCUSED");
1433 else if (isSpreadsheet(m_nDocumentType))
1435 if (m_bIsEditingCell)
1437 if (!hasState(aEvent, AccessibleStateType::ACTIVE))
1439 SAL_WARN("lok.a11y",
1440 "LOKDocumentFocusListener::notifyEvent: FOCUSED: "
1441 "cell not ACTIVE for editing yet");
1442 return;
1444 else if (m_xSelectedObject.is())
1446 updateParagraphInfo(xAccText, true, "STATE_CHANGED: ACTIVE");
1447 notifyEditingInSelectionState(getAccessibleSiblingCount(xContext) == 0);
1448 notifyFocusedParagraphChanged(true);
1449 m_xSelectedObject.clear();
1450 return;
1453 updateAndNotifyParagraph(xAccText, false, "STATE_CHANGED: FOCUSED");
1456 else if (isPresentation(m_nDocumentType))
1458 updateAndNotifyParagraph(xAccText, false, "STATE_CHANGED: FOCUSED");
1460 aboutTextFormatting("LOKDocumentFocusListener::notifyEvent: STATE_CHANGED: FOCUSED", xAccText);
1462 break;
1464 default:
1465 break;
1468 break;
1470 case AccessibleEventId::CARET_CHANGED:
1472 sal_Int32 nNewPos = -1;
1473 aEvent.NewValue >>= nNewPos;
1474 sal_Int32 nOldPos = -1;
1475 aEvent.OldValue >>= nOldPos;
1477 if (nNewPos >= 0)
1479 SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: CARET_CHANGED: "
1480 "new pos: " << nNewPos << ", nOldPos: " << nOldPos);
1482 uno::Reference<XAccessibleText> xAccText(getAccessible(aEvent), uno::UNO_QUERY);
1483 if (xAccText.is())
1485 m_nCaretPosition = nNewPos;
1486 // Let's say we are in the following case: 'Hello wor|ld',
1487 // where '|' is the cursor position for the current view.
1488 // Suppose that in another view it's typed <enter> soon before 'world'.
1489 // Now the new paragraph content and caret position is: 'wor|ld'.
1490 // Anyway no new paragraph focused event is emitted for current view.
1491 // Only a new caret position event is emitted.
1492 // So we could need to notify a new focused paragraph changed message.
1493 if (!isFocused(aEvent))
1495 if (updateParagraphInfo(xAccText, false, "CARET_CHANGED"))
1496 notifyFocusedParagraphChanged(true);
1498 else
1500 notifyCaretChanged();
1502 aboutParagraph("LOKDocumentFocusListener::notifyEvent: CARET_CHANGED", xAccText);
1505 break;
1507 case AccessibleEventId::TEXT_CHANGED:
1509 TextSegment aDeletedText;
1510 TextSegment aInsertedText;
1512 if (aEvent.OldValue >>= aDeletedText)
1514 SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: TEXT_CHANGED: "
1515 "deleted text: >" << aDeletedText.SegmentText << "<");
1517 if (aEvent.NewValue >>= aInsertedText)
1519 SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: TEXT_CHANGED: "
1520 "inserted text: >" << aInsertedText.SegmentText << "<");
1522 uno::Reference<XAccessibleText> xAccText(getAccessible(aEvent), uno::UNO_QUERY);
1524 // When the change has been performed in another view we need to force
1525 // paragraph content updating on the client, even if current editing involves composing.
1526 // We make a guess that if the paragraph accessibility node is not focused,
1527 // it means that the text change has been performed in another view.
1528 updateAndNotifyParagraph(xAccText, !isFocused(aEvent), "TEXT_CHANGED");
1530 break;
1532 case AccessibleEventId::TEXT_SELECTION_CHANGED:
1534 if (!isFocused(aEvent))
1536 SAL_WARN("lok.a11y",
1537 "LOKDocumentFocusListener::notifyEvent: TEXT_SELECTION_CHANGED: "
1538 "skip non focused paragraph");
1539 return;
1542 uno::Reference<XAccessibleText> xAccText(getAccessible(aEvent), uno::UNO_QUERY);
1543 if (xAccText.is())
1545 // We send a message to client also when start/end are -1, in this way the client knows
1546 // if a text selection object exists or not. That's needed because of the odd behavior
1547 // occurring when <backspace>/<delete> are hit and a text selection is empty,
1548 // but it still exists.
1549 // Such keys delete the empty selection instead of the previous/next char.
1550 updateParagraphInfo(xAccText, false, "TEXT_SELECTION_CHANGED");
1552 m_sSelectedText = xAccText->getSelectedText();
1553 SAL_INFO("lok.a11y",
1554 "LOKDocumentFocusListener::notifyEvent: TEXT_SELECTION_CHANGED: selected text: >"
1555 << m_sSelectedText << "<");
1557 // Calc: when editing a formula send the update content
1558 if (m_bIsEditingCell)
1560 OUString sText = xAccText->getText();
1561 if (!m_sSelectedCellAddress.isEmpty() &&
1562 !m_sSelectedText.isEmpty() && sText.startsWith("="))
1564 notifyFocusedParagraphChanged();
1567 notifyTextSelectionChanged();
1569 break;
1571 case AccessibleEventId::SELECTION_CHANGED:
1572 case AccessibleEventId::SELECTION_CHANGED_REMOVE:
1574 uno::Reference< XAccessible > xSelectedObject = getSelectedObject(aEvent);
1575 if (!xSelectedObject.is())
1576 return;
1577 uno::Reference< XAccessibleContext > xContext = xSelectedObject->getAccessibleContext();
1578 if (!xContext.is())
1579 return;
1581 if (aEvent.EventId == AccessibleEventId::SELECTION_CHANGED_REMOVE)
1582 m_xSelectedObject.clear();
1583 else if (m_xSelectedObject.is() && m_xSelectedObject == xSelectedObject)
1584 return; // selecting the same object; note: on editing selected object is cleared
1585 else
1586 m_xSelectedObject = xSelectedObject;
1587 SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: SELECTION_CHANGED: "
1588 "m_xSelectedObject.is(): " << m_xSelectedObject.is());
1590 OUString sAction = selectionEventTypeToString(aEvent.EventId);
1591 sal_Int16 nRole = xContext->getAccessibleRole();
1592 switch(nRole)
1594 case AccessibleRole::GRAPHIC:
1595 case AccessibleRole::EMBEDDED_OBJECT:
1596 case AccessibleRole::SHAPE:
1598 onShapeSelectionChanged(xSelectedObject, sAction);
1599 break;
1601 case AccessibleRole::TABLE_CELL:
1603 notifySelectionChanged(xSelectedObject, sAction);
1605 if (aEvent.EventId == AccessibleEventId::SELECTION_CHANGED)
1607 m_sSelectedCellAddress = xContext->getAccessibleName();
1608 if (m_bIsEditingCell && !m_sSelectedCellAddress.isEmpty())
1610 // Check cell address: "$Sheet1.A10".
1611 // On cell editing SELECTION_CHANGED is not emitted when selection is expanded.
1612 // So selection can't be a cell range.
1613 sal_Int32 nDotIndex = m_sSelectedText.indexOf('.');
1614 OUString sCellAddress = m_sSelectedText.copy(nDotIndex + 1);
1615 SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: SELECTION_CHANGED: "
1616 "cell address: >" << sCellAddress << "<");
1617 if (m_sSelectedCellAddress == sCellAddress)
1619 notifyFocusedParagraphChanged();
1620 notifyTextSelectionChanged();
1624 break;
1626 default:
1627 break;
1629 break;
1631 case AccessibleEventId::CHILD:
1633 uno::Reference< accessibility::XAccessible > xChild;
1634 if( (aEvent.OldValue >>= xChild) && xChild.is() )
1635 detachRecursive(xChild);
1637 if( (aEvent.NewValue >>= xChild) && xChild.is() )
1638 attachRecursive(xChild);
1640 break;
1642 case AccessibleEventId::INVALIDATE_ALL_CHILDREN:
1644 SAL_INFO("lok.a11y", "Invalidate all children called");
1645 break;
1647 default:
1648 break;
1651 catch( const lang::IndexOutOfBoundsException& )
1653 LOK_WARN("lok.a11y",
1654 "LOKDocumentFocusListener::notifyEvent:Focused object has invalid index in parent");
1658 uno::Reference< accessibility::XAccessible > LOKDocumentFocusListener::getAccessible(const lang::EventObject& aEvent )
1660 uno::Reference< accessibility::XAccessible > xAccessible(aEvent.Source, uno::UNO_QUERY);
1662 if( xAccessible.is() )
1663 return xAccessible;
1665 SAL_WARN("lok.a11y",
1666 "LOKDocumentFocusListener::getAccessible: Event source doesn't implement XAccessible.");
1668 uno::Reference< accessibility::XAccessibleContext > xContext(aEvent.Source, uno::UNO_QUERY);
1670 if( xContext.is() )
1672 uno::Reference< accessibility::XAccessible > xParent( xContext->getAccessibleParent() );
1673 if( xParent.is() )
1675 uno::Reference< accessibility::XAccessibleContext > xParentContext( xParent->getAccessibleContext() );
1676 if( xParentContext.is() )
1678 return xParentContext->getAccessibleChild( xContext->getAccessibleIndexInParent() );
1683 LOK_WARN("lok.a11y",
1684 "LOKDocumentFocusListener::getAccessible: Can't get any accessible object from event source.");
1686 return uno::Reference< accessibility::XAccessible >();
1689 void LOKDocumentFocusListener::attachRecursive(
1690 const uno::Reference< accessibility::XAccessible >& xAccessible
1693 LOK_INFO("lok.a11y", "LOKDocumentFocusListener::attachRecursive(1): xAccessible: " << xAccessible.get());
1695 uno::Reference< accessibility::XAccessibleContext > xContext =
1696 xAccessible->getAccessibleContext();
1698 if( xContext.is() )
1699 attachRecursive(xAccessible, xContext);
1702 void LOKDocumentFocusListener::attachRecursive(
1703 const uno::Reference< accessibility::XAccessible >& xAccessible,
1704 const uno::Reference< accessibility::XAccessibleContext >& xContext
1709 LOK_INFO("lok.a11y", "LOKDocumentFocusListener::attachRecursive(2): xAccessible: "
1710 << xAccessible.get() << ", role: " << xContext->getAccessibleRole()
1711 << ", name: " << xContext->getAccessibleName()
1712 << ", parent: " << xContext->getAccessibleParent().get()
1713 << ", child count: " << xContext->getAccessibleChildCount());
1715 sal_Int64 nStateSet = xContext->getAccessibleStateSet();
1717 if (!m_bIsEditingCell)
1719 ::rtl::OUString sName = xContext->getAccessibleName();
1720 m_bIsEditingCell = sName.startsWith("Cell");
1723 attachRecursive(xAccessible, xContext, nStateSet);
1725 catch (const uno::Exception& e)
1727 LOK_WARN("lok.a11y", "LOKDocumentFocusListener::attachRecursive(2): raised exception: " << e.Message);
1731 void LOKDocumentFocusListener::attachRecursive(
1732 const uno::Reference< accessibility::XAccessible >& xAccessible,
1733 const uno::Reference< accessibility::XAccessibleContext >& xContext,
1734 const sal_Int64 nStateSet
1737 aboutView("LOKDocumentFocusListener::attachRecursive (3)", this, m_pViewShell);
1738 SAL_INFO("lok.a11y", "LOKDocumentFocusListener::attachRecursive(3) #1: this: " << this
1739 << ", xAccessible: " << xAccessible.get()
1740 << ", role: " << xContext->getAccessibleRole()
1741 << ", name: " << xContext->getAccessibleName()
1742 << ", index in parent: " << xContext->getAccessibleIndexInParent()
1743 << ", state: " << stateSetToString(nStateSet)
1744 << ", parent: " << xContext->getAccessibleParent().get()
1745 << ", child count: " << xContext->getAccessibleChildCount());
1747 uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
1749 if (!xBroadcaster.is())
1750 return;
1751 SAL_INFO("lok.a11y", "LOKDocumentFocusListener::attachRecursive(3) #2: xBroadcaster.is()");
1752 // If not already done, add the broadcaster to the list and attach as listener.
1753 const uno::Reference< uno::XInterface >& xInterface = xBroadcaster;
1754 if( m_aRefList.insert(xInterface).second )
1756 SAL_INFO("lok.a11y", "LOKDocumentFocusListener::attachRecursive(3) #3: m_aRefList.insert(xInterface).second");
1757 xBroadcaster->addAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));
1759 if (isDocument(xContext->getAccessibleRole()))
1761 m_nDocumentType = xContext->getAccessibleRole();
1764 if (!(nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS))
1766 if ((nStateSet & accessibility::AccessibleStateType::SELECTED) &&
1767 selectionHasToBeNotified(xContext))
1769 uno::Reference< accessibility::XAccessible > xAccObj(xContext, uno::UNO_QUERY);
1770 onShapeSelectionChanged(xAccObj, "create");
1773 sal_Int64 nmax = xContext->getAccessibleChildCount();
1774 if( nmax > MAX_ATTACHABLE_CHILDREN )
1775 nmax = MAX_ATTACHABLE_CHILDREN;
1777 for( sal_Int64 n = 0; n < nmax; n++ )
1779 uno::Reference< accessibility::XAccessible > xChild( xContext->getAccessibleChild( n ) );
1781 if( xChild.is() )
1782 attachRecursive(xChild);
1785 else
1787 // Usually, when the document is loaded, a CARET_CHANGED accessibility event is automatically emitted
1788 // for the first paragraph. That allows to notify the paragraph content to the client, even if no input
1789 // event occurred yet. However, when switching to a11y enabled in the client and in Cypress tests
1790 // no accessibility event is automatically emitted until some input event occurs.
1791 // So we use the following workaround to notify the content of the focused paragraph,
1792 // without waiting for an input event.
1793 // Here we update the paragraph info related to the focused paragraph,
1794 // later when afterCallbackRegistered is executed we notify the paragraph content.
1795 sal_Int64 nChildCount = xContext->getAccessibleChildCount();
1796 if (nChildCount > 0 && nChildCount < 10)
1798 for (sal_Int64 n = 0; n < nChildCount; ++n)
1800 uno::Reference< accessibility::XAccessible > xChild(xContext->getAccessibleChild(n));
1801 if (xChild.is())
1803 uno::Reference<css::accessibility::XAccessibleText> xAccText(xChild, uno::UNO_QUERY);
1804 if (xAccText.is())
1806 sal_Int32 nPos = xAccText->getCaretPosition();
1807 if (nPos >= 0)
1809 attachRecursive(xChild);
1810 updateParagraphInfo(xAccText, false, "LOKDocumentFocusListener::attachRecursive(3)");
1811 break;
1821 void LOKDocumentFocusListener::detachRecursive(
1822 const uno::Reference< accessibility::XAccessible >& xAccessible,
1823 bool bForce
1826 uno::Reference< accessibility::XAccessibleContext > xContext =
1827 xAccessible->getAccessibleContext();
1829 if( xContext.is() )
1830 detachRecursive(xContext, bForce);
1833 void LOKDocumentFocusListener::detachRecursive(
1834 const uno::Reference< accessibility::XAccessibleContext >& xContext,
1835 bool bForce
1838 aboutView("LOKDocumentFocusListener::detachRecursive (2)", this, m_pViewShell);
1839 sal_Int64 nStateSet = xContext->getAccessibleStateSet();
1841 SAL_INFO("lok.a11y", "LOKDocumentFocusListener::detachRecursive(2): this: " << this
1842 << ", name: " << xContext->getAccessibleName()
1843 << ", parent: " << xContext->getAccessibleParent().get()
1844 << ", child count: " << xContext->getAccessibleChildCount());
1846 if (m_bIsEditingCell)
1848 ::rtl::OUString sName = xContext->getAccessibleName();
1849 m_bIsEditingCell = !sName.startsWith("Cell");
1850 if (!m_bIsEditingCell)
1852 m_sFocusedParagraph = "";
1853 m_nCaretPosition = 0;
1854 notifyFocusedParagraphChanged();
1858 detachRecursive(xContext, nStateSet, bForce);
1861 void LOKDocumentFocusListener::detachRecursive(
1862 const uno::Reference< accessibility::XAccessibleContext >& xContext,
1863 const sal_Int64 nStateSet,
1864 bool bForce
1867 uno::Reference< accessibility::XAccessibleEventBroadcaster > xBroadcaster(xContext, uno::UNO_QUERY);
1869 if (xBroadcaster.is() && 0 < m_aRefList.erase(xBroadcaster))
1871 xBroadcaster->removeAccessibleEventListener(static_cast< accessibility::XAccessibleEventListener *>(this));
1873 if ((nStateSet & accessibility::AccessibleStateType::SELECTED) &&
1874 selectionHasToBeNotified(xContext))
1876 uno::Reference< accessibility::XAccessible > xAccObj(xContext, uno::UNO_QUERY);
1877 onShapeSelectionChanged(xAccObj, "delete");
1880 if (bForce || !(nStateSet & accessibility::AccessibleStateType::MANAGES_DESCENDANTS))
1882 sal_Int64 nmax = xContext->getAccessibleChildCount();
1883 if (nmax > MAX_ATTACHABLE_CHILDREN)
1884 nmax = MAX_ATTACHABLE_CHILDREN;
1885 for (sal_Int64 n = 0; n < nmax; n++)
1887 uno::Reference< accessibility::XAccessible > xChild(xContext->getAccessibleChild(n));
1889 if (xChild.is())
1890 detachRecursive(xChild);
1896 sal_uInt32 SfxViewShell_Impl::m_nLastViewShellId = 0;
1898 SfxViewShell_Impl::SfxViewShell_Impl(SfxViewShellFlags const nFlags, ViewShellDocId nDocId)
1899 : m_bHasPrintOptions(nFlags & SfxViewShellFlags::HAS_PRINTOPTIONS)
1900 , m_nFamily(0xFFFF) // undefined, default set by TemplateDialog
1901 , m_pLibreOfficeKitViewCallback(nullptr)
1902 , m_bTiledSearching(false)
1903 , m_nViewShellId(SfxViewShell_Impl::m_nLastViewShellId++)
1904 , m_nDocId(nDocId)
1908 SfxViewShell_Impl::~SfxViewShell_Impl()
1912 std::vector< SfxInPlaceClient* >& SfxViewShell_Impl::GetIPClients_Impl()
1914 return maIPClients;
1917 SFX_IMPL_SUPERCLASS_INTERFACE(SfxViewShell,SfxShell)
1919 void SfxViewShell::InitInterface_Impl()
1924 /** search for a filter name dependent on type and module
1926 static OUString impl_retrieveFilterNameFromTypeAndModule(
1927 const css::uno::Reference< css::container::XContainerQuery >& rContainerQuery,
1928 const OUString& rType,
1929 const OUString& rModuleIdentifier,
1930 const sal_Int32 nFlags )
1932 // Retrieve filter from type
1933 css::uno::Sequence< css::beans::NamedValue > aQuery {
1934 { "Type", css::uno::Any( rType ) },
1935 { "DocumentService", css::uno::Any( rModuleIdentifier ) }
1938 css::uno::Reference< css::container::XEnumeration > xEnumeration =
1939 rContainerQuery->createSubSetEnumerationByProperties( aQuery );
1941 OUString aFoundFilterName;
1942 while ( xEnumeration->hasMoreElements() )
1944 ::comphelper::SequenceAsHashMap aFilterPropsHM( xEnumeration->nextElement() );
1945 OUString aFilterName = aFilterPropsHM.getUnpackedValueOrDefault(
1946 "Name",
1947 OUString() );
1949 sal_Int32 nFilterFlags = aFilterPropsHM.getUnpackedValueOrDefault(
1950 "Flags",
1951 sal_Int32( 0 ) );
1953 if ( nFilterFlags & nFlags )
1955 aFoundFilterName = aFilterName;
1956 break;
1960 return aFoundFilterName;
1963 namespace {
1965 /** search for an internal typename, which map to the current app module
1966 and map also to a "family" of file formats as e.g. PDF/MS Doc/OOo Doc.
1968 enum ETypeFamily
1970 E_MS_DOC,
1971 E_OOO_DOC
1976 static OUString impl_searchFormatTypeForApp(const css::uno::Reference< css::frame::XFrame >& xFrame ,
1977 ETypeFamily eTypeFamily)
1981 css::uno::Reference< css::uno::XComponentContext > xContext (::comphelper::getProcessComponentContext());
1982 css::uno::Reference< css::frame::XModuleManager2 > xModuleManager(css::frame::ModuleManager::create(xContext));
1984 OUString sModule = xModuleManager->identify(xFrame);
1985 OUString sType ;
1987 switch(eTypeFamily)
1989 case E_MS_DOC:
1991 if ( sModule == "com.sun.star.text.TextDocument" )
1992 sType = "writer_MS_Word_2007";
1993 else
1994 if ( sModule == "com.sun.star.sheet.SpreadsheetDocument" )
1995 sType = "MS Excel 2007 XML";
1996 else
1997 if ( sModule == "com.sun.star.presentation.PresentationDocument" )
1998 sType = "MS PowerPoint 2007 XML";
2000 break;
2002 case E_OOO_DOC:
2004 if ( sModule == "com.sun.star.text.TextDocument" )
2005 sType = "writer8";
2006 else
2007 if ( sModule == "com.sun.star.sheet.SpreadsheetDocument" )
2008 sType = "calc8";
2009 else
2010 if ( sModule == "com.sun.star.drawing.DrawingDocument" )
2011 sType = "draw8";
2012 else
2013 if ( sModule == "com.sun.star.presentation.PresentationDocument" )
2014 sType = "impress8";
2016 break;
2019 return sType;
2021 catch (const css::uno::RuntimeException&)
2023 throw;
2025 catch (const css::uno::Exception&)
2029 return OUString();
2032 void SfxViewShell::NewIPClient_Impl( SfxInPlaceClient *pIPClient )
2034 pImpl->GetIPClients_Impl().push_back(pIPClient);
2037 void SfxViewShell::IPClientGone_Impl( SfxInPlaceClient const *pIPClient )
2039 std::vector< SfxInPlaceClient* >& pClients = pImpl->GetIPClients_Impl();
2041 auto it = std::find(pClients.begin(), pClients.end(), pIPClient);
2042 if (it != pClients.end())
2043 pClients.erase( it );
2047 void SfxViewShell::ExecMisc_Impl( SfxRequest &rReq )
2049 const sal_uInt16 nId = rReq.GetSlot();
2050 switch( nId )
2052 case SID_STYLE_FAMILY :
2054 const SfxUInt16Item* pItem = rReq.GetArg<SfxUInt16Item>(nId);
2055 if (pItem)
2057 pImpl->m_nFamily = pItem->GetValue();
2059 break;
2061 case SID_ACTIVATE_STYLE_APPLY:
2063 uno::Reference< frame::XFrame > xFrame =
2064 GetViewFrame().GetFrame().GetFrameInterface();
2066 Reference< beans::XPropertySet > xPropSet( xFrame, UNO_QUERY );
2067 Reference< frame::XLayoutManager > xLayoutManager;
2068 if ( xPropSet.is() )
2072 Any aValue = xPropSet->getPropertyValue("LayoutManager");
2073 aValue >>= xLayoutManager;
2074 if ( xLayoutManager.is() )
2076 uno::Reference< ui::XUIElement > xElement = xLayoutManager->getElement( "private:resource/toolbar/textobjectbar" );
2077 if(!xElement.is())
2079 xElement = xLayoutManager->getElement( "private:resource/toolbar/frameobjectbar" );
2081 if(!xElement.is())
2083 xElement = xLayoutManager->getElement( "private:resource/toolbar/oleobjectbar" );
2085 if(xElement.is())
2087 uno::Reference< awt::XWindow > xWin( xElement->getRealInterface(), uno::UNO_QUERY_THROW );
2088 VclPtr<vcl::Window> pWin = VCLUnoHelper::GetWindow( xWin );
2089 ToolBox* pTextToolbox = dynamic_cast< ToolBox* >( pWin.get() );
2090 if( pTextToolbox )
2092 ToolBox::ImplToolItems::size_type nItemCount = pTextToolbox->GetItemCount();
2093 for( ToolBox::ImplToolItems::size_type nItem = 0; nItem < nItemCount; ++nItem )
2095 ToolBoxItemId nItemId = pTextToolbox->GetItemId( nItem );
2096 const OUString& rCommand = pTextToolbox->GetItemCommand( nItemId );
2097 if (rCommand == ".uno:StyleApply")
2099 vcl::Window* pItemWin = pTextToolbox->GetItemWindow( nItemId );
2100 if( pItemWin )
2101 pItemWin->GrabFocus();
2102 break;
2109 catch (const Exception&)
2113 rReq.Done();
2115 break;
2117 case SID_MAIL_SENDDOCASMS:
2118 case SID_MAIL_SENDDOCASOOO:
2119 case SID_MAIL_SENDDOCASPDF:
2120 case SID_MAIL_SENDDOC:
2121 case SID_MAIL_SENDDOCASFORMAT:
2123 SfxObjectShell* pDoc = GetObjectShell();
2124 if (!pDoc)
2125 break;
2126 pDoc->QueryHiddenInformation(HiddenWarningFact::WhenSaving);
2127 SfxMailModel aModel;
2128 OUString aDocType;
2130 const SfxStringItem* pMailRecipient = rReq.GetArg<SfxStringItem>(SID_MAIL_RECIPIENT);
2131 if ( pMailRecipient )
2133 OUString aRecipient( pMailRecipient->GetValue() );
2134 OUString aMailToStr("mailto:");
2136 if ( aRecipient.startsWith( aMailToStr ) )
2137 aRecipient = aRecipient.copy( aMailToStr.getLength() );
2138 aModel.AddToAddress( aRecipient );
2140 const SfxStringItem* pMailDocType = rReq.GetArg<SfxStringItem>(SID_TYPE_NAME);
2141 if ( pMailDocType )
2142 aDocType = pMailDocType->GetValue();
2144 uno::Reference < frame::XFrame > xFrame( rFrame.GetFrame().GetFrameInterface() );
2145 SfxMailModel::SendMailResult eResult = SfxMailModel::SEND_MAIL_ERROR;
2147 if ( nId == SID_MAIL_SENDDOC )
2148 eResult = aModel.SaveAndSend( xFrame, OUString() );
2149 else if ( nId == SID_MAIL_SENDDOCASPDF )
2150 eResult = aModel.SaveAndSend( xFrame, "pdf_Portable_Document_Format");
2151 else if ( nId == SID_MAIL_SENDDOCASMS )
2153 aDocType = impl_searchFormatTypeForApp(xFrame, E_MS_DOC);
2154 if (!aDocType.isEmpty())
2155 eResult = aModel.SaveAndSend( xFrame, aDocType );
2157 else if ( nId == SID_MAIL_SENDDOCASOOO )
2159 aDocType = impl_searchFormatTypeForApp(xFrame, E_OOO_DOC);
2160 if (!aDocType.isEmpty())
2161 eResult = aModel.SaveAndSend( xFrame, aDocType );
2164 if ( eResult == SfxMailModel::SEND_MAIL_ERROR )
2166 weld::Window* pWin = SfxGetpApp()->GetTopWindow();
2167 std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin,
2168 VclMessageType::Info, VclButtonsType::Ok,
2169 SfxResId(STR_ERROR_SEND_MAIL)));
2170 xBox->run();
2171 rReq.Ignore();
2173 else
2174 rReq.Done();
2176 break;
2178 case SID_BLUETOOTH_SENDDOC:
2180 SfxBluetoothModel aModel;
2181 SfxObjectShell* pDoc = GetObjectShell();
2182 if (!pDoc)
2183 break;
2184 pDoc->QueryHiddenInformation(HiddenWarningFact::WhenSaving);
2185 uno::Reference < frame::XFrame > xFrame( rFrame.GetFrame().GetFrameInterface() );
2186 SfxMailModel::SendMailResult eResult = aModel.SaveAndSend( xFrame );
2187 if( eResult == SfxMailModel::SEND_MAIL_ERROR )
2189 weld::Window* pWin = SfxGetpApp()->GetTopWindow();
2190 std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin,
2191 VclMessageType::Info, VclButtonsType::Ok,
2192 SfxResId(STR_ERROR_SEND_MAIL)));
2193 xBox->run();
2194 rReq.Ignore();
2196 else
2197 rReq.Done();
2199 break;
2201 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2202 case SID_WEBHTML:
2204 css::uno::Reference< lang::XMultiServiceFactory > xSMGR(::comphelper::getProcessServiceFactory(), css::uno::UNO_SET_THROW);
2205 css::uno::Reference< uno::XComponentContext > xContext(::comphelper::getProcessComponentContext(), css::uno::UNO_SET_THROW);
2206 css::uno::Reference< css::frame::XFrame > xFrame( rFrame.GetFrame().GetFrameInterface() );
2207 css::uno::Reference< css::frame::XModel > xModel;
2209 css::uno::Reference< css::frame::XModuleManager2 > xModuleManager( css::frame::ModuleManager::create(xContext) );
2211 OUString aModule;
2214 aModule = xModuleManager->identify( xFrame );
2216 catch (const css::uno::RuntimeException&)
2218 throw;
2220 catch (const css::uno::Exception&)
2224 if ( xFrame.is() )
2226 css::uno::Reference< css::frame::XController > xController = xFrame->getController();
2227 if ( xController.is() )
2228 xModel = xController->getModel();
2231 // We need at least a valid module name and model reference
2232 css::uno::Reference< css::frame::XStorable > xStorable( xModel, css::uno::UNO_QUERY );
2233 if ( xModel.is() && xStorable.is() )
2235 OUString aFilterName;
2236 OUString aTypeName( "generic_HTML" );
2237 OUString aFileName;
2239 OUString aLocation = xStorable->getLocation();
2240 INetURLObject aFileObj( aLocation );
2242 bool bPrivateProtocol = ( aFileObj.GetProtocol() == INetProtocol::PrivSoffice );
2243 bool bHasLocation = !aLocation.isEmpty() && !bPrivateProtocol;
2245 css::uno::Reference< css::container::XContainerQuery > xContainerQuery(
2246 xSMGR->createInstance( "com.sun.star.document.FilterFactory" ),
2247 css::uno::UNO_QUERY_THROW );
2249 // Retrieve filter from type
2251 sal_Int32 nFilterFlags = 0x00000002; // export
2252 aFilterName = impl_retrieveFilterNameFromTypeAndModule( xContainerQuery, aTypeName, aModule, nFilterFlags );
2253 if ( aFilterName.isEmpty() )
2255 // Draw/Impress uses a different type. 2nd chance try to use alternative type name
2256 aFilterName = impl_retrieveFilterNameFromTypeAndModule(
2257 xContainerQuery, "graphic_HTML", aModule, nFilterFlags );
2260 // No filter found => error
2261 // No type and no location => error
2262 if ( aFilterName.isEmpty() || aTypeName.isEmpty())
2264 rReq.Done();
2265 return;
2268 // Use provided save file name. If empty determine file name
2269 if ( !bHasLocation )
2271 // Create a default file name with the correct extension
2272 aFileName = "webpreview";
2274 else
2276 // Determine file name from model
2277 INetURLObject aFObj( xStorable->getLocation() );
2278 aFileName = aFObj.getName( INetURLObject::LAST_SEGMENT, true, INetURLObject::DecodeMechanism::NONE );
2281 OSL_ASSERT( !aFilterName.isEmpty() );
2282 OSL_ASSERT( !aFileName.isEmpty() );
2284 // Creates a temporary directory to store our predefined file into it (for the
2285 // flatpak case, create it in XDG_CACHE_HOME instead of /tmp for technical reasons,
2286 // so that it can be accessed by the browser running outside the sandbox):
2287 OUString * parent = nullptr;
2288 if (flatpak::isFlatpak() && !flatpak::createTemporaryHtmlDirectory(&parent))
2290 SAL_WARN("sfx.view", "cannot create Flatpak html temp dir");
2293 INetURLObject aFilePathObj( ::utl::CreateTempURL(parent, true) );
2294 aFilePathObj.insertName( aFileName );
2295 aFilePathObj.setExtension( u"htm" );
2297 OUString aFileURL = aFilePathObj.GetMainURL( INetURLObject::DecodeMechanism::NONE );
2299 css::uno::Sequence< css::beans::PropertyValue > aArgs{
2300 comphelper::makePropertyValue("FilterName", aFilterName)
2303 // Store document in the html format
2306 xStorable->storeToURL( aFileURL, aArgs );
2308 catch (const io::IOException&)
2310 rReq.Done();
2311 return;
2314 sfx2::openUriExternally(aFileURL, true, rReq.GetFrameWeld());
2315 rReq.Done(true);
2316 break;
2318 else
2320 rReq.Done();
2321 return;
2328 void SfxViewShell::GetState_Impl( SfxItemSet &rSet )
2331 SfxWhichIter aIter( rSet );
2332 SfxObjectShell *pSh = GetViewFrame().GetObjectShell();
2333 for ( sal_uInt16 nSID = aIter.FirstWhich(); nSID; nSID = aIter.NextWhich() )
2335 switch ( nSID )
2338 case SID_BLUETOOTH_SENDDOC:
2339 case SID_MAIL_SENDDOC:
2340 case SID_MAIL_SENDDOCASFORMAT:
2341 case SID_MAIL_SENDDOCASMS:
2342 case SID_MAIL_SENDDOCASOOO:
2343 case SID_MAIL_SENDDOCASPDF:
2345 #if HAVE_FEATURE_MACOSX_SANDBOX
2346 rSet.DisableItem(nSID);
2347 #endif
2348 if (pSh && pSh->isExportLocked())
2349 rSet.DisableItem(nSID);
2350 break;
2352 case SID_WEBHTML:
2354 if (pSh && pSh->isExportLocked())
2355 rSet.DisableItem(nSID);
2356 break;
2358 // Printer functions
2359 case SID_PRINTDOC:
2360 case SID_PRINTDOCDIRECT:
2361 case SID_SETUPPRINTER:
2362 case SID_PRINTER_NAME:
2364 if (Application::GetSettings().GetMiscSettings().GetDisablePrinting()
2365 || (pSh && pSh->isPrintLocked()))
2367 rSet.DisableItem(nSID);
2368 break;
2371 SfxPrinter *pPrinter = GetPrinter();
2373 if ( SID_PRINTDOCDIRECT == nSID )
2375 OUString aPrinterName;
2376 if ( pPrinter != nullptr )
2377 aPrinterName = pPrinter->GetName();
2378 else
2380 // tdf#109149 don't poll the Default Printer Name on every query.
2381 // We are queried on every change, so on every
2382 // keystroke, and we are only using this to fill in the
2383 // printername inside the label of "Print Directly (printer-name)"
2384 // On Printer::GetDefaultPrinterName() is implemented with
2385 // GetDefaultPrinter so don't call this excessively. 5 mins
2386 // seems a reasonable refresh time.
2387 std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
2388 std::chrono::minutes five_mins(5);
2389 if (now > pImpl->m_nDefaultPrinterNameFetchTime + five_mins)
2391 pImpl->m_sDefaultPrinterName = Printer::GetDefaultPrinterName();
2392 pImpl->m_nDefaultPrinterNameFetchTime = now;
2394 aPrinterName = pImpl->m_sDefaultPrinterName;
2396 if ( !aPrinterName.isEmpty() )
2398 uno::Reference < frame::XFrame > xFrame( rFrame.GetFrame().GetFrameInterface() );
2400 auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(".uno:PrintDefault",
2401 vcl::CommandInfoProvider::GetModuleIdentifier(xFrame));
2402 OUString val = vcl::CommandInfoProvider::GetLabelForCommand(aProperties) +
2403 " (" + aPrinterName + ")";
2405 rSet.Put( SfxStringItem( SID_PRINTDOCDIRECT, val ) );
2408 break;
2410 case SID_STYLE_FAMILY :
2412 rSet.Put( SfxUInt16Item( SID_STYLE_FAMILY, pImpl->m_nFamily ) );
2413 break;
2419 void SfxViewShell::SetZoomFactor( const Fraction &rZoomX,
2420 const Fraction &rZoomY )
2422 DBG_ASSERT( GetWindow(), "no window" );
2423 MapMode aMap( GetWindow()->GetMapMode() );
2424 aMap.SetScaleX( rZoomX );
2425 aMap.SetScaleY( rZoomY );
2426 GetWindow()->SetMapMode( aMap );
2429 ErrCode SfxViewShell::DoVerb(sal_Int32 /*nVerb*/)
2431 /* [Description]
2433 Virtual Method used to perform a Verb on a selected Object.
2434 Since this Object is only known by the derived classes, they must override
2435 DoVerb.
2439 return ERRCODE_SO_NOVERBS;
2442 void SfxViewShell::OutplaceActivated( bool bActive )
2444 if ( !bActive )
2445 GetFrame()->GetFrame().Appear();
2448 void SfxViewShell::UIActivating( SfxInPlaceClient* /*pClient*/ )
2450 uno::Reference < frame::XFrame > xOwnFrame( rFrame.GetFrame().GetFrameInterface() );
2451 uno::Reference < frame::XFramesSupplier > xParentFrame = xOwnFrame->getCreator();
2452 if ( xParentFrame.is() )
2453 xParentFrame->setActiveFrame( xOwnFrame );
2455 rFrame.GetBindings().HidePopups();
2456 rFrame.GetDispatcher()->Update_Impl( true );
2459 void SfxViewShell::UIDeactivated( SfxInPlaceClient* /*pClient*/ )
2461 if ( !rFrame.GetFrame().IsClosing_Impl() || SfxViewFrame::Current() != &rFrame )
2462 rFrame.GetDispatcher()->Update_Impl( true );
2463 rFrame.GetBindings().HidePopups(false);
2465 rFrame.GetBindings().InvalidateAll(true);
2468 SfxInPlaceClient* SfxViewShell::FindIPClient
2470 const uno::Reference < embed::XEmbeddedObject >& xObj,
2471 vcl::Window* pObjParentWin
2472 ) const
2474 std::vector< SfxInPlaceClient* >& rClients = pImpl->GetIPClients_Impl();
2475 if ( rClients.empty() )
2476 return nullptr;
2478 if( !pObjParentWin )
2479 pObjParentWin = GetWindow();
2480 for (SfxInPlaceClient* pIPClient : rClients)
2482 if ( pIPClient->GetObject() == xObj && pIPClient->GetEditWin() == pObjParentWin )
2483 return pIPClient;
2486 return nullptr;
2490 SfxInPlaceClient* SfxViewShell::GetIPClient() const
2492 return GetUIActiveClient();
2496 SfxInPlaceClient* SfxViewShell::GetUIActiveIPClient_Impl() const
2498 // this method is needed as long as SFX still manages the border space for ChildWindows (see SfxFrame::Resize)
2499 std::vector< SfxInPlaceClient* >& rClients = pImpl->GetIPClients_Impl();
2500 if ( rClients.empty() )
2501 return nullptr;
2503 for (SfxInPlaceClient* pIPClient : rClients)
2505 if ( pIPClient->IsUIActive() )
2506 return pIPClient;
2509 return nullptr;
2512 SfxInPlaceClient* SfxViewShell::GetUIActiveClient() const
2514 std::vector< SfxInPlaceClient* >& rClients = pImpl->GetIPClients_Impl();
2515 if ( rClients.empty() )
2516 return nullptr;
2518 const bool bIsTiledRendering = comphelper::LibreOfficeKit::isActive();
2520 for (SfxInPlaceClient* pIPClient : rClients)
2522 if ( pIPClient->IsObjectUIActive() || ( bIsTiledRendering && pIPClient->IsObjectInPlaceActive() ) )
2523 return pIPClient;
2526 return nullptr;
2530 void SfxViewShell::Activate( bool bMDI )
2532 if ( bMDI )
2534 SfxObjectShell *pSh = GetViewFrame().GetObjectShell();
2535 if (const auto xModel = pSh->GetModel())
2536 xModel->setCurrentController(GetController());
2538 SetCurrentDocument();
2543 void SfxViewShell::Deactivate(bool /*bMDI*/)
2548 void SfxViewShell::Move()
2550 /* [Description]
2552 This virtual Method is called when the window displayed in the
2553 SfxViewShell gets a StarView-Move() notification.
2555 This base implementation does not have to be called. .
2557 [Note]
2559 This Method can be used to cancel a selection, in order to catch the
2560 mouse movement which is due to moving a window.
2562 For now the notification does not work In-Place.
2569 void SfxViewShell::OuterResizePixel
2571 const Point& /*rToolOffset*/,// Upper left corner Tools in Frame-Window
2572 const Size& /*rSize*/ // All available sizes.
2575 /* [Description]
2577 Override this Method to be able to react to the size-change of
2578 the View. Thus the View is defined as the Edit window and also the
2579 attached Tools are defined (for example the ruler).
2581 The Edit window must not be changed either in size or position.
2583 The Vis-Area of SfxObjectShell, its scale and position can be changed
2584 here. The main use is to change the size of the Vis-Area.
2586 If the Border is changed due to the new calculation then this has to be set
2587 by <SfxViewShell::SetBorderPixel(const SvBorder&)>. The Positioning of Tools
2588 is only allowed after the calling of 'SetBorderPixel'.
2590 [Example]
2592 void AppViewSh::OuterViewResizePixel( const Point &rOfs, const Size &rSz )
2594 // Calculate Tool position and size externally, do not set!
2595 // (due to the following Border calculation)
2596 Point aHLinPos...; Size aHLinSz...;
2599 // Calculate and Set a Border of Tools which matches rSize.
2600 SvBorder aBorder...
2601 SetBorderPixel( aBorder ); // Allow Positioning from here on.
2603 // Arrange Tools
2604 pHLin->SetPosSizePixel( aHLinPos, aHLinSz );
2608 [Cross-reference]
2610 <SfxViewShell::InnerResizePixel(const Point&,const Size& rSize)>
2614 SetBorderPixel( SvBorder() );
2618 void SfxViewShell::InnerResizePixel
2620 const Point& /*rToolOffset*/,// Upper left corner Tools in Frame-Window
2621 const Size& /*rSize*/, // All available sizes.
2622 bool
2625 /* [Description]
2627 Override this Method to be able to react to the size-change of
2628 the Edit window.
2630 The Edit window must not be changed either in size or position.
2631 Neither the Vis-Area of SfxObjectShell nor its scale or position are
2632 allowed to be changed
2634 If the Border is changed due to the new calculation then is has to be set
2635 by <SfxViewShell::SetBorderPixel(const SvBorder&)>.
2636 The Positioning of Tools is only allowed after the calling of
2637 'SetBorderPixel'.
2640 [Note]
2642 void AppViewSh::InnerViewResizePixel( const Point &rOfs, const Size &rSz )
2644 // Calculate Tool position and size internally, do not set!
2645 // (due to the following Border calculation)
2646 Point aHLinPos...; Size aHLinSz...;
2649 // Calculate and Set a Border of Tools which matches rSize.
2650 SvBorder aBorder...
2651 SetBorderPixel( aBorder ); // Allow Positioning from here on.
2653 // Arrange Tools
2654 pHLin->SetPosSizePixel( aHLinPos, aHLinSz );
2658 [Cross-reference]
2660 <SfxViewShell::OuterResizePixel(const Point&,const Size& rSize)>
2664 SetBorderPixel( SvBorder() );
2667 void SfxViewShell::InvalidateBorder()
2669 GetViewFrame().InvalidateBorderImpl( this );
2670 if (pImpl->m_pController.is())
2672 pImpl->m_pController->BorderWidthsChanged_Impl();
2676 void SfxViewShell::SetBorderPixel( const SvBorder &rBorder )
2678 GetViewFrame().SetBorderPixelImpl( this, rBorder );
2680 // notify related controller that border size is changed
2681 if (pImpl->m_pController.is())
2683 pImpl->m_pController->BorderWidthsChanged_Impl();
2687 const SvBorder& SfxViewShell::GetBorderPixel() const
2689 return GetViewFrame().GetBorderPixelImpl();
2692 void SfxViewShell::SetWindow
2694 vcl::Window* pViewPort // For example Null pointer in the Destructor.
2697 /* [Description]
2699 With this method the SfxViewShell is set in the data window. This is
2700 needed for the in-place container and for restoring the proper focus.
2702 Even in-place-active the conversion of the ViewPort Windows is forbidden.
2706 if( pWindow == pViewPort )
2707 return;
2709 // Disconnect existing IP-Clients if possible
2710 DisconnectAllClients();
2712 // Switch View-Port
2713 bool bHadFocus = pWindow && pWindow->HasChildPathFocus( true );
2714 pWindow = pViewPort;
2716 if( pWindow )
2718 // Disable automatic GUI mirroring (right-to-left) for document windows
2719 pWindow->EnableRTL( false );
2722 if ( bHadFocus && pWindow )
2723 pWindow->GrabFocus();
2724 //TODO/CLEANUP
2725 //Do we still need this Method?!
2726 //SfxGetpApp()->GrabFocus( pWindow );
2729 ViewShellDocId SfxViewShell::mnCurrentDocId(0);
2731 SfxViewShell::SfxViewShell
2733 SfxViewFrame& rViewFrame, /* <SfxViewFrame>, which will be
2734 displayed in this View */
2735 SfxViewShellFlags nFlags /* See <SfxViewShell-Flags> */
2738 : SfxShell(this)
2739 , pImpl( new SfxViewShell_Impl(nFlags, SfxViewShell::mnCurrentDocId) )
2740 , rFrame(rViewFrame)
2741 , pWindow(nullptr)
2742 , bNoNewWindow( nFlags & SfxViewShellFlags::NO_NEWWINDOW )
2743 , mbPrinterSettingsModified(false)
2744 , maLOKLanguageTag(LANGUAGE_NONE)
2745 , maLOKLocale(LANGUAGE_NONE)
2746 , maLOKDeviceFormFactor(LOKDeviceFormFactor::UNKNOWN)
2747 , mbLOKAccessibilityEnabled(false)
2749 SetMargin( rViewFrame.GetMargin_Impl() );
2751 SetPool( &rViewFrame.GetObjectShell()->GetPool() );
2752 StartListening(*rViewFrame.GetObjectShell());
2754 // Insert into list
2755 std::vector<SfxViewShell*> &rViewArr = SfxGetpApp()->GetViewShells_Impl();
2756 rViewArr.push_back(this);
2758 if (comphelper::LibreOfficeKit::isActive())
2760 maLOKLanguageTag = SfxLokHelper::getDefaultLanguage();
2761 maLOKLocale = SfxLokHelper::getDefaultLanguage();
2763 const auto [isTimezoneSet, aTimezone] = SfxLokHelper::getDefaultTimezone();
2764 maLOKIsTimezoneSet = isTimezoneSet;
2765 maLOKTimezone = aTimezone;
2767 maLOKDeviceFormFactor = SfxLokHelper::getDeviceFormFactor();
2769 vcl::Window* pFrameWin = rViewFrame.GetWindow().GetFrameWindow();
2770 if (pFrameWin && !pFrameWin->GetLOKNotifier())
2771 pFrameWin->SetLOKNotifier(this, true);
2775 SfxViewShell::~SfxViewShell()
2777 // Remove from list
2778 const SfxViewShell *pThis = this;
2779 std::vector<SfxViewShell*> &rViewArr = SfxGetpApp()->GetViewShells_Impl();
2780 auto it = std::find( rViewArr.begin(), rViewArr.end(), pThis );
2781 rViewArr.erase( it );
2783 if ( pImpl->xClipboardListener.is() )
2785 pImpl->xClipboardListener->DisconnectViewShell();
2786 pImpl->xClipboardListener = nullptr;
2789 if (pImpl->m_pController.is())
2791 pImpl->m_pController->ReleaseShell_Impl();
2792 pImpl->m_pController.clear();
2795 vcl::Window* pFrameWin = GetViewFrame().GetWindow().GetFrameWindow();
2796 if (pFrameWin && pFrameWin->GetLOKNotifier() == this)
2797 pFrameWin->ReleaseLOKNotifier();
2800 OUString SfxViewShell::getA11yFocusedParagraph() const
2802 const LOKDocumentFocusListener& rDocFocusListener = GetLOKDocumentFocusListener();
2803 return rDocFocusListener.getFocusedParagraph();
2806 int SfxViewShell::getA11yCaretPosition() const
2808 const LOKDocumentFocusListener& rDocFocusListener = GetLOKDocumentFocusListener();
2809 return rDocFocusListener.getCaretPosition();
2812 bool SfxViewShell::PrepareClose
2814 bool bUI // TRUE: Allow Dialog and so on, FALSE: silent-mode
2817 if (GetViewFrame().GetWindow().GetLOKNotifier() == this)
2818 GetViewFrame().GetWindow().ReleaseLOKNotifier();
2820 SfxPrinter *pPrinter = GetPrinter();
2821 if ( pPrinter && pPrinter->IsPrinting() )
2823 if ( bUI )
2825 std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(GetViewFrame().GetFrameWeld(),
2826 VclMessageType::Info, VclButtonsType::Ok,
2827 SfxResId(STR_CANT_CLOSE)));
2828 xBox->run();
2831 return false;
2834 if( GetViewFrame().IsInModalMode() )
2835 return false;
2837 if( bUI && GetViewFrame().GetDispatcher()->IsLocked() )
2838 return false;
2840 return true;
2844 SfxViewShell* SfxViewShell::Current()
2846 SfxViewFrame *pCurrent = SfxViewFrame::Current();
2847 return pCurrent ? pCurrent->GetViewShell() : nullptr;
2850 bool SfxViewShell::IsCurrentLokViewReadOnly()
2852 if (!comphelper::LibreOfficeKit::isActive() || Current() == nullptr || !Current()->IsLokReadOnlyView())
2853 return false;
2854 else
2855 return true;
2858 SfxViewShell* SfxViewShell::Get( const Reference< XController>& i_rController )
2860 if ( !i_rController.is() )
2861 return nullptr;
2863 for ( SfxViewShell* pViewShell = SfxViewShell::GetFirst( false );
2864 pViewShell;
2865 pViewShell = SfxViewShell::GetNext( *pViewShell, false )
2868 if ( pViewShell->GetController() == i_rController )
2869 return pViewShell;
2871 return nullptr;
2875 SdrView* SfxViewShell::GetDrawView() const
2877 /* [Description]
2879 This virtual Method has to be overloaded by the sub classes, to be able
2880 make the Property-Editor available.
2882 The default implementation does always return zero.
2886 return nullptr;
2890 OUString SfxViewShell::GetSelectionText
2892 bool /*bCompleteWords*/, /* FALSE (default)
2893 Only the actual selected text is returned.
2895 TRUE
2896 The selected text is expanded so that only
2897 whole words are returned. As word separators
2898 these are used: white spaces and punctuation
2899 ".,;" and single and double quotes.
2901 bool /*bOnlyASample*/ /* used by some dialogs to avoid constructing monster strings e.g. in calc */
2904 /* [Description]
2906 Override this Method to return a text that
2907 is included in the current selection. This is for example used when
2908 sending emails.
2910 When called with "CompleteWords == TRUE", it is for example sufficient
2911 with having the Cursor positioned somewhere within a URL in-order
2912 to have the entire URL returned.
2916 return OUString();
2920 bool SfxViewShell::HasSelection( bool ) const
2922 /* [Description]
2924 With this virtual Method can a for example a Dialog be queried, to
2925 check if something is selected in the current view. If the Parameter
2926 is <BOOL> TRUE then it is checked whether some text is selected.
2930 return false;
2933 void SfxViewShell::AddSubShell( SfxShell& rShell )
2935 pImpl->aArr.push_back(&rShell);
2936 SfxDispatcher *pDisp = rFrame.GetDispatcher();
2937 if ( pDisp->IsActive(*this) )
2939 pDisp->Push(rShell);
2940 pDisp->Flush();
2944 void SfxViewShell::RemoveSubShell( SfxShell* pShell )
2946 SfxDispatcher *pDisp = rFrame.GetDispatcher();
2947 if ( !pShell )
2949 size_t nCount = pImpl->aArr.size();
2950 if ( pDisp->IsActive(*this) )
2952 for(size_t n = nCount; n > 0; --n)
2953 pDisp->Pop(*pImpl->aArr[n - 1]);
2954 pDisp->Flush();
2956 pImpl->aArr.clear();
2958 else
2960 SfxShellArr_Impl::iterator i = std::find(pImpl->aArr.begin(), pImpl->aArr.end(), pShell);
2961 if(i != pImpl->aArr.end())
2963 pImpl->aArr.erase(i);
2964 if(pDisp->IsActive(*this))
2966 pDisp->RemoveShell_Impl(*pShell);
2967 pDisp->Flush();
2973 SfxShell* SfxViewShell::GetSubShell( sal_uInt16 nNo )
2975 sal_uInt16 nCount = pImpl->aArr.size();
2976 if(nNo < nCount)
2977 return pImpl->aArr[nCount - nNo - 1];
2978 return nullptr;
2981 void SfxViewShell::PushSubShells_Impl( bool bPush )
2983 SfxDispatcher *pDisp = rFrame.GetDispatcher();
2984 if ( bPush )
2986 for (auto const& elem : pImpl->aArr)
2987 pDisp->Push(*elem);
2989 else if(!pImpl->aArr.empty())
2991 SfxShell& rPopUntil = *pImpl->aArr[0];
2992 if ( pDisp->GetShellLevel( rPopUntil ) != USHRT_MAX )
2993 pDisp->Pop( rPopUntil, SfxDispatcherPopFlags::POP_UNTIL );
2996 pDisp->Flush();
3000 void SfxViewShell::WriteUserData( OUString&, bool )
3005 void SfxViewShell::ReadUserData(const OUString&, bool )
3009 void SfxViewShell::ReadUserDataSequence ( const uno::Sequence < beans::PropertyValue >& )
3013 void SfxViewShell::WriteUserDataSequence ( uno::Sequence < beans::PropertyValue >& )
3018 // returns the first shell of spec. type viewing the specified doc.
3019 SfxViewShell* SfxViewShell::GetFirst
3021 bool bOnlyVisible,
3022 const std::function< bool ( const SfxViewShell* ) >& isViewShell
3025 // search for a SfxViewShell of the specified type
3026 std::vector<SfxViewShell*> &rShells = SfxGetpApp()->GetViewShells_Impl();
3027 for (SfxViewShell* pShell : rShells)
3029 if ( pShell )
3031 // This code used to check that the frame exists in the other list,
3032 // because of https://bz.apache.org/ooo/show_bug.cgi?id=62084, with the explanation:
3033 // sometimes dangling SfxViewShells exist that point to a dead SfxViewFrame
3034 // these ViewShells shouldn't be accessible anymore
3035 // a destroyed ViewFrame is not in the ViewFrame array anymore, so checking this array helps
3036 // That doesn't seem to be needed anymore, but keep an assert, just in case.
3037 assert(std::find(SfxGetpApp()->GetViewFrames_Impl().begin(), SfxGetpApp()->GetViewFrames_Impl().end(),
3038 &pShell->GetViewFrame()) != SfxGetpApp()->GetViewFrames_Impl().end());
3039 if ( ( !bOnlyVisible || pShell->GetViewFrame().IsVisible() ) && (!isViewShell || isViewShell(pShell)))
3040 return pShell;
3044 return nullptr;
3047 // returns the next shell of spec. type viewing the specified doc.
3048 SfxViewShell* SfxViewShell::GetNext
3050 const SfxViewShell& rPrev,
3051 bool bOnlyVisible,
3052 const std::function<bool ( const SfxViewShell* )>& isViewShell
3055 std::vector<SfxViewShell*> &rShells = SfxGetpApp()->GetViewShells_Impl();
3056 size_t nPos;
3057 for ( nPos = 0; nPos < rShells.size(); ++nPos )
3058 if ( rShells[nPos] == &rPrev )
3059 break;
3061 for ( ++nPos; nPos < rShells.size(); ++nPos )
3063 SfxViewShell *pShell = rShells[nPos];
3064 if ( pShell )
3066 assert(std::find(SfxGetpApp()->GetViewFrames_Impl().begin(), SfxGetpApp()->GetViewFrames_Impl().end(),
3067 &pShell->GetViewFrame()) != SfxGetpApp()->GetViewFrames_Impl().end());
3068 if ( ( !bOnlyVisible || pShell->GetViewFrame().IsVisible() ) && (!isViewShell || isViewShell(pShell)) )
3069 return pShell;
3073 return nullptr;
3077 void SfxViewShell::Notify( SfxBroadcaster& rBC,
3078 const SfxHint& rHint )
3080 if (rHint.GetId() != SfxHintId::ThisIsAnSfxEventHint ||
3081 static_cast<const SfxEventHint&>(rHint).GetEventId() != SfxEventHintId::LoadFinished)
3083 return;
3086 if ( !GetController().is() )
3087 return;
3089 // avoid access to dangling ViewShells
3090 auto &rFrames = SfxGetpApp()->GetViewFrames_Impl();
3091 for (SfxViewFrame* frame : rFrames)
3093 if ( frame == &GetViewFrame() && &rBC == GetObjectShell() )
3095 SfxItemSet& rSet = GetObjectShell()->GetMedium()->GetItemSet();
3096 const SfxUnoAnyItem* pItem = rSet.GetItem(SID_VIEW_DATA, false);
3097 if ( pItem )
3099 pImpl->m_pController->restoreViewData( pItem->GetValue() );
3100 rSet.ClearItem( SID_VIEW_DATA );
3102 break;
3107 bool SfxViewShell::ExecKey_Impl(const KeyEvent& aKey)
3109 bool setModuleConfig = false; // In case libreofficekit is active, we will re-set the module config class.
3110 if (!pImpl->m_xAccExec)
3112 pImpl->m_xAccExec = ::svt::AcceleratorExecute::createAcceleratorHelper();
3113 pImpl->m_xAccExec->init(::comphelper::getProcessComponentContext(),
3114 rFrame.GetFrame().GetFrameInterface());
3115 setModuleConfig = true;
3118 if (comphelper::LibreOfficeKit::isActive())
3120 // Get the module name.
3121 css::uno::Reference< css::uno::XComponentContext > xContext (::comphelper::getProcessComponentContext());
3122 css::uno::Reference< css::frame::XModuleManager2 > xModuleManager(css::frame::ModuleManager::create(xContext));
3123 OUString sModule = xModuleManager->identify(rFrame.GetFrame().GetFrameInterface());
3125 // Get the language name.
3126 OUString viewLang = GetLOKLanguageTag().getBcp47();
3128 // Merge them & have a key.
3129 OUString key = sModule + viewLang;
3131 // Check it in configurations map. Create a configuration manager if there isn't one for the key.
3132 std::unordered_map<OUString, css::uno::Reference<com::sun::star::ui::XAcceleratorConfiguration>>& acceleratorConfs = SfxApplication::Get()->GetAcceleratorConfs_Impl();
3133 if (acceleratorConfs.find(key) == acceleratorConfs.end())
3135 // Create a new configuration manager for the module.
3137 OUString actualLang = officecfg::Setup::L10N::ooLocale::get();
3139 std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
3140 officecfg::Setup::L10N::ooLocale::set(viewLang, batch);
3141 batch->commit();
3143 // We have set the language. Time to create the config manager.
3144 acceleratorConfs[key] = svt::AcceleratorExecute::lok_createNewAcceleratorConfiguration(::comphelper::getProcessComponentContext(), sModule);
3146 std::shared_ptr<comphelper::ConfigurationChanges> batch2(comphelper::ConfigurationChanges::create());
3147 officecfg::Setup::L10N::ooLocale::set(actualLang, batch2);
3148 batch2->commit();
3151 if (setModuleConfig)
3152 pImpl->m_xAccExec->lok_setModuleConfig(acceleratorConfs[key]);
3155 return pImpl->m_xAccExec->execute(aKey.GetKeyCode());
3158 void SfxViewShell::setLibreOfficeKitViewCallback(SfxLokCallbackInterface* pCallback)
3160 pImpl->m_pLibreOfficeKitViewCallback = pCallback;
3162 afterCallbackRegistered();
3164 if (!pImpl->m_pLibreOfficeKitViewCallback)
3165 return;
3167 // Ask other views to tell us about their cursors.
3168 SfxViewShell* pViewShell = SfxViewShell::GetFirst();
3169 while (pViewShell)
3171 if (pViewShell->GetDocId() == GetDocId())
3172 pViewShell->NotifyCursor(this);
3173 pViewShell = SfxViewShell::GetNext(*pViewShell);
3177 SfxLokCallbackInterface* SfxViewShell::getLibreOfficeKitViewCallback() const
3179 return pImpl->m_pLibreOfficeKitViewCallback;
3182 void SfxViewShell::dumpLibreOfficeKitViewState(rtl::OStringBuffer &rState)
3184 rState.append("\n SfxViewShell: ");
3185 rState.append(OString::number(reinterpret_cast<sal_uInt64>(this), 16));
3186 rState.append("\n\tDocId:\t");
3187 auto nDocId = static_cast<int>(GetDocId());
3188 rState.append(static_cast<sal_Int32>(nDocId));
3189 rState.append("\n\tViewId:\t");
3190 rState.append(static_cast<sal_Int32>(GetViewShellId()));
3191 rState.append("\n\tPart:\t");
3192 rState.append(static_cast<sal_Int32>(getPart()));
3193 rState.append("\n\tLang:\t");
3194 rState.append(OUStringToOString(GetLOKLanguageTag().getBcp47(), RTL_TEXTENCODING_UTF8));
3195 rState.append("\n\tA11y:\t");
3196 rState.append(GetLOKAccessibilityState() ? "enabled" : "disabled");
3198 if (pImpl->m_pLibreOfficeKitViewCallback)
3199 pImpl->m_pLibreOfficeKitViewCallback->dumpState(rState);
3202 static bool ignoreLibreOfficeKitViewCallback(int nType, const SfxViewShell_Impl* pImpl)
3204 if (!comphelper::LibreOfficeKit::isActive())
3205 return true;
3207 if (comphelper::LibreOfficeKit::isTiledPainting())
3209 switch (nType)
3211 case LOK_CALLBACK_FORM_FIELD_BUTTON:
3212 case LOK_CALLBACK_TEXT_SELECTION:
3213 case LOK_CALLBACK_COMMENT:
3214 break;
3215 default:
3216 // Reject e.g. invalidate during paint.
3217 return true;
3221 if (pImpl->m_bTiledSearching)
3223 switch (nType)
3225 case LOK_CALLBACK_TEXT_SELECTION:
3226 case LOK_CALLBACK_TEXT_VIEW_SELECTION:
3227 case LOK_CALLBACK_TEXT_SELECTION_START:
3228 case LOK_CALLBACK_TEXT_SELECTION_END:
3229 case LOK_CALLBACK_GRAPHIC_SELECTION:
3230 case LOK_CALLBACK_GRAPHIC_VIEW_SELECTION:
3231 return true;
3235 return false;
3238 void SfxViewShell::libreOfficeKitViewInvalidateTilesCallback(const tools::Rectangle* pRect, int nPart, int nMode) const
3240 if (ignoreLibreOfficeKitViewCallback(LOK_CALLBACK_INVALIDATE_TILES, pImpl.get()))
3241 return;
3242 if (pImpl->m_pLibreOfficeKitViewCallback)
3243 pImpl->m_pLibreOfficeKitViewCallback->libreOfficeKitViewInvalidateTilesCallback(pRect, nPart, nMode);
3244 else
3245 SAL_INFO(
3246 "sfx.view",
3247 "SfxViewShell::libreOfficeKitViewInvalidateTilesCallback no callback set!");
3250 void SfxViewShell::libreOfficeKitViewCallbackWithViewId(int nType, const OString& pPayload, int nViewId) const
3252 if (ignoreLibreOfficeKitViewCallback(nType, pImpl.get()))
3253 return;
3254 if (pImpl->m_pLibreOfficeKitViewCallback)
3255 pImpl->m_pLibreOfficeKitViewCallback->libreOfficeKitViewCallbackWithViewId(nType, pPayload, nViewId);
3256 else
3257 SAL_INFO(
3258 "sfx.view",
3259 "SfxViewShell::libreOfficeKitViewCallbackWithViewId no callback set! Dropped payload of type "
3260 << lokCallbackTypeToString(nType) << ": [" << pPayload << ']');
3263 void SfxViewShell::libreOfficeKitViewCallback(int nType, const OString& pPayload) const
3265 if (ignoreLibreOfficeKitViewCallback(nType, pImpl.get()))
3266 return;
3267 if (pImpl->m_pLibreOfficeKitViewCallback)
3268 pImpl->m_pLibreOfficeKitViewCallback->libreOfficeKitViewCallback(nType, pPayload);
3269 else
3270 SAL_INFO(
3271 "sfx.view",
3272 "SfxViewShell::libreOfficeKitViewCallback no callback set! Dropped payload of type "
3273 << lokCallbackTypeToString(nType) << ": [" << pPayload << ']');
3276 void SfxViewShell::libreOfficeKitViewUpdatedCallback(int nType) const
3278 if (ignoreLibreOfficeKitViewCallback(nType, pImpl.get()))
3279 return;
3280 if (pImpl->m_pLibreOfficeKitViewCallback)
3281 pImpl->m_pLibreOfficeKitViewCallback->libreOfficeKitViewUpdatedCallback(nType);
3282 else
3283 SAL_INFO(
3284 "sfx.view",
3285 "SfxViewShell::libreOfficeKitViewUpdatedCallback no callback set! Dropped payload of type "
3286 << lokCallbackTypeToString(nType));
3289 void SfxViewShell::libreOfficeKitViewUpdatedCallbackPerViewId(int nType, int nViewId, int nSourceViewId) const
3291 if (ignoreLibreOfficeKitViewCallback(nType, pImpl.get()))
3292 return;
3293 if (pImpl->m_pLibreOfficeKitViewCallback)
3294 pImpl->m_pLibreOfficeKitViewCallback->libreOfficeKitViewUpdatedCallbackPerViewId(nType, nViewId, nSourceViewId);
3295 else
3296 SAL_INFO(
3297 "sfx.view",
3298 "SfxViewShell::libreOfficeKitViewUpdatedCallbackPerViewId no callback set! Dropped payload of type "
3299 << lokCallbackTypeToString(nType));
3302 void SfxViewShell::libreOfficeKitViewAddPendingInvalidateTiles()
3304 if (pImpl->m_pLibreOfficeKitViewCallback)
3305 pImpl->m_pLibreOfficeKitViewCallback->libreOfficeKitViewAddPendingInvalidateTiles();
3306 else
3307 SAL_INFO(
3308 "sfx.view",
3309 "SfxViewShell::libreOfficeKitViewAddPendingInvalidateTiles no callback set!");
3312 void SfxViewShell::afterCallbackRegistered()
3314 LOK_INFO("sfx.view", "SfxViewShell::afterCallbackRegistered invoked");
3315 if (GetLOKAccessibilityState())
3317 LOKDocumentFocusListener& rDocFocusListener = GetLOKDocumentFocusListener();
3318 rDocFocusListener.notifyFocusedParagraphChanged();
3322 void SfxViewShell::flushPendingLOKInvalidateTiles()
3324 // SfxViewShell itself does not delay any tile invalidations.
3327 std::optional<OString> SfxViewShell::getLOKPayload(int nType, int /*nViewId*/) const
3329 // SfxViewShell itself currently doesn't handle any updated-payload types.
3330 SAL_WARN("sfx.view", "SfxViewShell::getLOKPayload unhandled type " << lokCallbackTypeToString(nType));
3331 abort();
3334 vcl::Window* SfxViewShell::GetEditWindowForActiveOLEObj() const
3336 vcl::Window* pEditWin = nullptr;
3337 SfxInPlaceClient* pIPClient = GetIPClient();
3338 if (pIPClient)
3340 pEditWin = pIPClient->GetEditWin();
3342 return pEditWin;
3345 ::Color SfxViewShell::GetColorConfigColor(svtools::ColorConfigEntry eEntry) const
3347 SAL_WARN("sfx.view", "SfxViewShell::GetColorConfigColor not overridden!");
3348 svtools::ColorConfig aColorConfig;
3349 return aColorConfig.GetColorValue(eEntry).nColor;
3352 void SfxViewShell::SetLOKLanguageTag(const OUString& rBcp47LanguageTag)
3354 LanguageTag aTag(rBcp47LanguageTag, true);
3356 css::uno::Sequence<OUString> inst(officecfg::Setup::Office::InstalledLocales::get()->getElementNames());
3357 LanguageTag aFallbackTag = LanguageTag(getInstalledLocaleForSystemUILanguage(inst, /* bRequestInstallIfMissing */ false, rBcp47LanguageTag), true).makeFallback();
3359 // If we want de-CH, and the de localisation is available, we don't want to use de-DE as then
3360 // the magic in Translate::get() won't turn ess-zet into double s. Possibly other similar cases?
3361 if (comphelper::LibreOfficeKit::isActive() && aTag.getLanguage() == aFallbackTag.getLanguage())
3362 maLOKLanguageTag = aTag;
3363 else
3364 maLOKLanguageTag = aFallbackTag;
3367 LOKDocumentFocusListener& SfxViewShell::GetLOKDocumentFocusListener()
3369 if (mpLOKDocumentFocusListener)
3370 return *mpLOKDocumentFocusListener;
3372 mpLOKDocumentFocusListener = new LOKDocumentFocusListener(this);
3373 return *mpLOKDocumentFocusListener;
3376 const LOKDocumentFocusListener& SfxViewShell::GetLOKDocumentFocusListener() const
3378 return const_cast<SfxViewShell*>(this)->GetLOKDocumentFocusListener();
3381 void SfxViewShell::SetLOKAccessibilityState(bool bEnabled)
3383 if (bEnabled == mbLOKAccessibilityEnabled)
3384 return;
3385 mbLOKAccessibilityEnabled = bEnabled;
3387 LOKDocumentFocusListener& rDocumentFocusListener = GetLOKDocumentFocusListener();
3389 if (!pWindow)
3390 return;
3392 uno::Reference< accessibility::XAccessible > xAccessible =
3393 pWindow->GetAccessible();
3395 if (!xAccessible.is())
3396 return;
3398 if (mbLOKAccessibilityEnabled)
3402 rDocumentFocusListener.attachRecursive(xAccessible);
3404 catch (const uno::Exception&)
3406 LOK_WARN("SetLOKAccessibilityState", "Exception caught processing LOKDocumentFocusListener::attachRecursive");
3409 else
3413 rDocumentFocusListener.detachRecursive(xAccessible, /*bForce*/ true);
3415 catch (const uno::Exception&)
3417 LOK_WARN("SetLOKAccessibilityState", "Exception caught processing LOKDocumentFocusListener::detachRecursive");
3422 void SfxViewShell::SetLOKLocale(const OUString& rBcp47LanguageTag)
3424 maLOKLocale = LanguageTag(rBcp47LanguageTag, true).makeFallback();
3427 void SfxViewShell::NotifyCursor(SfxViewShell* /*pViewShell*/) const
3431 void SfxViewShell::setTiledSearching(bool bTiledSearching)
3433 pImpl->m_bTiledSearching = bTiledSearching;
3436 int SfxViewShell::getPart() const
3438 return 0;
3441 int SfxViewShell::getEditMode() const
3443 return 0;
3446 ViewShellId SfxViewShell::GetViewShellId() const
3448 return pImpl->m_nViewShellId;
3451 void SfxViewShell::SetCurrentDocId(ViewShellDocId nId)
3453 mnCurrentDocId = nId;
3456 ViewShellDocId SfxViewShell::GetDocId() const
3458 assert(pImpl->m_nDocId >= ViewShellDocId(0) && "m_nDocId should have been initialized, but it is invalid.");
3459 return pImpl->m_nDocId;
3462 void SfxViewShell::notifyInvalidation(tools::Rectangle const* pRect) const
3464 SfxLokHelper::notifyInvalidation(this, pRect);
3467 void SfxViewShell::NotifyOtherViews(int nType, const OString& rKey, const OString& rPayload)
3469 SfxLokHelper::notifyOtherViews(this, nType, rKey, rPayload);
3472 void SfxViewShell::NotifyOtherView(OutlinerViewShell* pOther, int nType, const OString& rKey, const OString& rPayload)
3474 auto pOtherShell = dynamic_cast<SfxViewShell*>(pOther);
3475 if (!pOtherShell)
3476 return;
3478 SfxLokHelper::notifyOtherView(this, pOtherShell, nType, rKey, rPayload);
3481 void SfxViewShell::dumpAsXml(xmlTextWriterPtr pWriter) const
3483 (void)xmlTextWriterStartElement(pWriter, BAD_CAST("SfxViewShell"));
3484 (void)xmlTextWriterWriteFormatAttribute(pWriter, BAD_CAST("ptr"), "%p", this);
3485 (void)xmlTextWriterWriteAttribute(pWriter, BAD_CAST("id"), BAD_CAST(OString::number(static_cast<sal_Int32>(GetViewShellId())).getStr()));
3486 (void)xmlTextWriterEndElement(pWriter);
3489 bool SfxViewShell::KeyInput( const KeyEvent &rKeyEvent )
3491 /* [Description]
3493 This Method executes the KeyEvent 'rKeyEvent' of the Keys (Accelerator)
3494 configured either direct or indirect (for example by the Application)
3495 in the SfxViewShell.
3497 [Return value]
3499 bool TRUE
3500 The Key (Accelerator) is configured and the
3501 associated Handler was called
3503 FALSE
3504 The Key (Accelerator) is not configured and
3505 subsequently no Handler was called
3507 [Cross-reference]
3509 <SfxApplication::KeyInput(const KeyEvent&)>
3512 return ExecKey_Impl(rKeyEvent);
3515 bool SfxViewShell::GlobalKeyInput_Impl( const KeyEvent &rKeyEvent )
3517 return ExecKey_Impl(rKeyEvent);
3521 void SfxViewShell::ShowCursor( bool /*bOn*/ )
3523 /* [Description]
3525 Subclasses must override this Method so that SFx can switch the
3526 Cursor on and off, for example while a <SfxProgress> is running.
3533 void SfxViewShell::ResetAllClients_Impl( SfxInPlaceClient const *pIP )
3536 std::vector< SfxInPlaceClient* >& rClients = pImpl->GetIPClients_Impl();
3537 if ( rClients.empty() )
3538 return;
3540 for (SfxInPlaceClient* pIPClient : rClients)
3542 if( pIPClient != pIP )
3543 pIPClient->ResetObject();
3548 void SfxViewShell::DisconnectAllClients()
3550 std::vector< SfxInPlaceClient* >& rClients = pImpl->GetIPClients_Impl();
3551 if ( rClients.empty() )
3552 return;
3554 for ( size_t n = 0; n < rClients.size(); )
3555 // clients will remove themselves from the list
3556 delete rClients.at( n );
3560 void SfxViewShell::QueryObjAreaPixel( tools::Rectangle& ) const
3565 void SfxViewShell::VisAreaChanged()
3567 std::vector< SfxInPlaceClient* >& rClients = pImpl->GetIPClients_Impl();
3568 if ( rClients.empty() )
3569 return;
3571 for (SfxInPlaceClient* pIPClient : rClients)
3573 if ( pIPClient->IsObjectInPlaceActive() )
3574 // client is active, notify client that the VisArea might have changed
3575 pIPClient->VisAreaChanged();
3580 void SfxViewShell::CheckIPClient_Impl(
3581 SfxInPlaceClient const *const pIPClient, const tools::Rectangle& rVisArea)
3583 if ( GetObjectShell()->IsInClose() )
3584 return;
3586 bool bAlwaysActive =
3587 ( ( pIPClient->GetObjectMiscStatus() & embed::EmbedMisc::EMBED_ACTIVATEIMMEDIATELY ) != 0 );
3588 bool bActiveWhenVisible =
3589 ( pIPClient->GetObjectMiscStatus() & embed::EmbedMisc::MS_EMBED_ACTIVATEWHENVISIBLE ) != 0;
3591 // this method is called when a client is created
3592 if (pIPClient->IsObjectInPlaceActive())
3593 return;
3595 // object in client is currently not active
3596 // check if the object wants to be activated always or when it becomes at least partially visible
3597 // TODO/LATER: maybe we should use the scaled area instead of the ObjArea?!
3598 if (bAlwaysActive || (bActiveWhenVisible && rVisArea.Overlaps(pIPClient->GetObjArea())))
3602 pIPClient->GetObject()->changeState( embed::EmbedStates::INPLACE_ACTIVE );
3604 catch (const uno::Exception&)
3606 TOOLS_WARN_EXCEPTION("sfx.view", "SfxViewShell::CheckIPClient_Impl");
3611 SfxObjectShell* SfxViewShell::GetObjectShell()
3613 return rFrame.GetObjectShell();
3616 Reference< XModel > SfxViewShell::GetCurrentDocument() const
3618 Reference< XModel > xDocument;
3620 const SfxObjectShell* pDocShell( const_cast< SfxViewShell* >( this )->GetObjectShell() );
3621 OSL_ENSURE( pDocShell, "SfxViewFrame::GetCurrentDocument: no DocShell!?" );
3622 if ( pDocShell )
3623 xDocument = pDocShell->GetModel();
3624 return xDocument;
3628 void SfxViewShell::SetCurrentDocument() const
3630 uno::Reference< frame::XModel > xDocument( GetCurrentDocument() );
3631 if ( xDocument.is() )
3632 SfxObjectShell::SetCurrentComponent( xDocument );
3636 const Size& SfxViewShell::GetMargin() const
3638 return pImpl->aMargin;
3642 void SfxViewShell::SetMargin( const Size& rSize )
3644 // the default margin was verified using www.apple.com !!
3645 Size aMargin = rSize;
3646 if ( aMargin.Width() == -1 )
3647 aMargin.setWidth( DEFAULT_MARGIN_WIDTH );
3648 if ( aMargin.Height() == -1 )
3649 aMargin.setHeight( DEFAULT_MARGIN_HEIGHT );
3651 if ( aMargin != pImpl->aMargin )
3653 pImpl->aMargin = aMargin;
3654 MarginChanged();
3658 void SfxViewShell::MarginChanged()
3662 void SfxViewShell::JumpToMark( const OUString& rMark )
3664 SfxStringItem aMarkItem( SID_JUMPTOMARK, rMark );
3665 GetViewFrame().GetDispatcher()->ExecuteList(
3666 SID_JUMPTOMARK,
3667 SfxCallMode::SYNCHRON|SfxCallMode::RECORD,
3668 { &aMarkItem });
3671 void SfxViewShell::SetController( SfxBaseController* pController )
3673 pImpl->m_pController = pController;
3675 // there should be no old listener, but if there is one, it should be disconnected
3676 if ( pImpl->xClipboardListener.is() )
3677 pImpl->xClipboardListener->DisconnectViewShell();
3679 pImpl->xClipboardListener = new SfxClipboardChangeListener( this, GetClipboardNotifier() );
3682 Reference < XController > SfxViewShell::GetController() const
3684 return pImpl->m_pController;
3687 SfxBaseController* SfxViewShell::GetBaseController_Impl() const
3689 return pImpl->m_pController.get();
3692 void SfxViewShell::AddContextMenuInterceptor_Impl( const uno::Reference< ui::XContextMenuInterceptor >& xInterceptor )
3694 std::unique_lock g(pImpl->aMutex);
3695 pImpl->aInterceptorContainer.addInterface( g, xInterceptor );
3698 void SfxViewShell::RemoveContextMenuInterceptor_Impl( const uno::Reference< ui::XContextMenuInterceptor >& xInterceptor )
3700 std::unique_lock g(pImpl->aMutex);
3701 pImpl->aInterceptorContainer.removeInterface( g, xInterceptor );
3704 bool SfxViewShell::TryContextMenuInterception(const rtl::Reference<VCLXPopupMenu>& rIn,
3705 const OUString& rMenuIdentifier,
3706 rtl::Reference<VCLXPopupMenu>& rOut,
3707 ui::ContextMenuExecuteEvent aEvent)
3709 rOut.clear();
3710 bool bModified = false;
3712 // create container from menu
3713 aEvent.ActionTriggerContainer = ::framework::ActionTriggerHelper::CreateActionTriggerContainerFromMenu(
3714 rIn, &rMenuIdentifier);
3716 // get selection from controller
3717 aEvent.Selection.set( GetController(), uno::UNO_QUERY );
3719 // call interceptors
3720 std::unique_lock g(pImpl->aMutex);
3721 std::vector<uno::Reference< ui::XContextMenuInterceptor>> aInterceptors =
3722 pImpl->aInterceptorContainer.getElements(g);
3723 g.unlock();
3724 for (const auto & rListener : aInterceptors )
3728 ui::ContextMenuInterceptorAction eAction;
3730 SolarMutexReleaser rel;
3731 eAction = rListener->notifyContextMenuExecute( aEvent );
3733 switch ( eAction )
3735 case ui::ContextMenuInterceptorAction_CANCELLED :
3736 // interceptor does not want execution
3737 return false;
3738 case ui::ContextMenuInterceptorAction_EXECUTE_MODIFIED :
3739 // interceptor wants his modified menu to be executed
3740 bModified = true;
3741 break;
3742 case ui::ContextMenuInterceptorAction_CONTINUE_MODIFIED :
3743 // interceptor has modified menu, but allows for calling other interceptors
3744 bModified = true;
3745 continue;
3746 case ui::ContextMenuInterceptorAction_IGNORED :
3747 // interceptor is indifferent
3748 continue;
3749 default:
3750 OSL_FAIL("Wrong return value of ContextMenuInterceptor!");
3751 continue;
3754 catch (...)
3756 g.lock();
3757 pImpl->aInterceptorContainer.removeInterface(g, rListener);
3758 g.unlock();
3761 break;
3764 if (bModified)
3766 // container was modified, create a new menu out of it
3767 rOut = new VCLXPopupMenu();
3768 ::framework::ActionTriggerHelper::CreateMenuFromActionTriggerContainer(rOut, aEvent.ActionTriggerContainer);
3771 return true;
3774 bool SfxViewShell::TryContextMenuInterception(const rtl::Reference<VCLXPopupMenu>& rPopupMenu,
3775 const OUString& rMenuIdentifier, css::ui::ContextMenuExecuteEvent aEvent)
3777 bool bModified = false;
3779 // create container from menu
3780 aEvent.ActionTriggerContainer = ::framework::ActionTriggerHelper::CreateActionTriggerContainerFromMenu(
3781 rPopupMenu, &rMenuIdentifier);
3783 // get selection from controller
3784 aEvent.Selection = css::uno::Reference< css::view::XSelectionSupplier >( GetController(), css::uno::UNO_QUERY );
3786 // call interceptors
3787 std::unique_lock g(pImpl->aMutex);
3788 std::vector<uno::Reference< ui::XContextMenuInterceptor>> aInterceptors =
3789 pImpl->aInterceptorContainer.getElements(g);
3790 g.unlock();
3791 for (const auto & rListener : aInterceptors )
3795 css::ui::ContextMenuInterceptorAction eAction;
3797 SolarMutexReleaser rel;
3798 eAction = rListener->notifyContextMenuExecute( aEvent );
3800 switch ( eAction )
3802 case css::ui::ContextMenuInterceptorAction_CANCELLED:
3803 // interceptor does not want execution
3804 return false;
3805 case css::ui::ContextMenuInterceptorAction_EXECUTE_MODIFIED:
3806 // interceptor wants his modified menu to be executed
3807 bModified = true;
3808 break;
3809 case css::ui::ContextMenuInterceptorAction_CONTINUE_MODIFIED:
3810 // interceptor has modified menu, but allows for calling other interceptors
3811 bModified = true;
3812 continue;
3813 case css::ui::ContextMenuInterceptorAction_IGNORED:
3814 // interceptor is indifferent
3815 continue;
3816 default:
3817 SAL_WARN( "sfx.view", "Wrong return value of ContextMenuInterceptor!" );
3818 continue;
3821 catch (...)
3823 g.lock();
3824 pImpl->aInterceptorContainer.removeInterface(g, rListener);
3825 g.unlock();
3828 break;
3831 if ( bModified )
3833 rPopupMenu->clear();
3834 ::framework::ActionTriggerHelper::CreateMenuFromActionTriggerContainer(rPopupMenu, aEvent.ActionTriggerContainer);
3837 return true;
3840 bool SfxViewShell::HandleNotifyEvent_Impl( NotifyEvent const & rEvent )
3842 if (pImpl->m_pController.is())
3843 return pImpl->m_pController->HandleEvent_Impl( rEvent );
3844 return false;
3847 bool SfxViewShell::HasKeyListeners_Impl() const
3849 return (pImpl->m_pController.is())
3850 && pImpl->m_pController->HasKeyListeners_Impl();
3853 bool SfxViewShell::HasMouseClickListeners_Impl() const
3855 return (pImpl->m_pController.is())
3856 && pImpl->m_pController->HasMouseClickListeners_Impl();
3859 bool SfxViewShell::Escape()
3861 return GetViewFrame().GetBindings().Execute(SID_TERMINATE_INPLACEACTIVATION);
3864 Reference< view::XRenderable > SfxViewShell::GetRenderable()
3866 Reference< view::XRenderable >xRender;
3867 SfxObjectShell* pObj = GetObjectShell();
3868 if( pObj )
3870 Reference< frame::XModel > xModel( pObj->GetModel() );
3871 if( xModel.is() )
3872 xRender.set( xModel, UNO_QUERY );
3874 return xRender;
3877 void SfxViewShell::notifyWindow(vcl::LOKWindowId nDialogId, const OUString& rAction, const std::vector<vcl::LOKPayloadItem>& rPayload) const
3879 SfxLokHelper::notifyWindow(this, nDialogId, rAction, rPayload);
3882 OString SfxViewShell::dumpNotifyState() const
3884 return OString("sfxviewsh: " +
3885 OString::number(reinterpret_cast<sal_uInt64>(this), 16) +
3886 " doc: " + OString::number(static_cast<sal_Int32>(static_cast<int>(GetDocId()))) +
3887 " view: " +
3888 OString::number(static_cast<sal_Int32>(GetViewShellId())));
3891 uno::Reference< datatransfer::clipboard::XClipboardNotifier > SfxViewShell::GetClipboardNotifier() const
3893 uno::Reference< datatransfer::clipboard::XClipboardNotifier > xClipboardNotifier;
3894 xClipboardNotifier.set(GetViewFrame().GetWindow().GetClipboard(), uno::UNO_QUERY);
3895 return xClipboardNotifier;
3898 void SfxViewShell::AddRemoveClipboardListener( const uno::Reference < datatransfer::clipboard::XClipboardListener >& rClp, bool bAdd )
3902 uno::Reference< datatransfer::clipboard::XClipboard > xClipboard(GetViewFrame().GetWindow().GetClipboard());
3903 if( xClipboard.is() )
3905 uno::Reference< datatransfer::clipboard::XClipboardNotifier > xClpbrdNtfr( xClipboard, uno::UNO_QUERY );
3906 if( xClpbrdNtfr.is() )
3908 if( bAdd )
3909 xClpbrdNtfr->addClipboardListener( rClp );
3910 else
3911 xClpbrdNtfr->removeClipboardListener( rClp );
3915 catch (const uno::Exception&)
3920 weld::Window* SfxViewShell::GetFrameWeld() const
3922 return pWindow ? pWindow->GetFrameWeld() : nullptr;
3925 void SfxViewShell::setBlockedCommandList(const char* blockedCommandList)
3927 if(!mvLOKBlockedCommandList.empty())
3928 return;
3930 OUString BlockedListString(blockedCommandList, strlen(blockedCommandList), RTL_TEXTENCODING_UTF8);
3931 OUString command = BlockedListString.getToken(0, ' ');
3932 for (size_t i = 1; !command.isEmpty(); i++)
3934 mvLOKBlockedCommandList.emplace(command);
3935 command = BlockedListString.getToken(i, ' ');
3939 bool SfxViewShell::isBlockedCommand(OUString command) const
3941 return mvLOKBlockedCommandList.find(command) != mvLOKBlockedCommandList.end();
3944 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */