/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include "AccessibleFocusManager.hxx" #include "PresenterAccessibility.hxx" #include "PresenterTextView.hxx" #include "PresenterNotesView.hxx" #include "PresenterPaneBase.hxx" #include "PresenterPaneContainer.hxx" #include "PresenterPaneFactory.hxx" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::com::sun::star; using namespace ::com::sun::star::accessibility; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::drawing::framework; namespace sdext::presenter { namespace { class AccessibleConsole { public: static rtl::Reference Create() { const OUString sName = SdResId(STR_A11Y_PRESENTER_CONSOLE); rtl::Reference pObject ( new AccessibleObject(AccessibleRole::PANEL, sName)); pObject->LateInitialization(); pObject->UpdateStateSet(); return pObject; } }; //===== AccessiblePreview ===================================================== class AccessiblePreview { public: static rtl::Reference Create( const Reference& rxContentWindow, const Reference& rxBorderWindow) { const OUString sName = SdResId(STR_A11Y_PRESENTER_PREVIEW); rtl::Reference pObject ( new AccessibleObject( AccessibleRole::LABEL, sName)); pObject->LateInitialization(); pObject->UpdateStateSet(); pObject->SetWindow(rxContentWindow, rxBorderWindow); return pObject; } }; //===== AccessibleNotes ======================================================= class AccessibleNotes : public AccessibleObject { public: AccessibleNotes(const OUString& rsName); static rtl::Reference Create( const Reference& rxContentWindow, const Reference& rxBorderWindow, const std::shared_ptr& rpTextView); void SetTextView (const std::shared_ptr& rpTextView); virtual void SetWindow ( const css::uno::Reference& rxContentWindow, const css::uno::Reference& rxBorderWindow) override; private: std::shared_ptr mpTextView; void NotifyCaretChange ( const sal_Int32 nOldParagraphIndex, const sal_Int32 nOldCharacterIndex, const sal_Int32 nNewParagraphIndex, const sal_Int32 nNewCharacterIndex); }; } //===== PresenterAccessible =================================================== PresenterAccessible::PresenterAccessible ( ::rtl::Reference xPresenterController, const Reference& rxMainPane) : PresenterAccessibleInterfaceBase(m_aMutex), mpPresenterController(std::move(xPresenterController)), mxMainPane(rxMainPane, UNO_QUERY) { if (mxMainPane.is()) mxMainPane->setAccessible(this); } PresenterAccessible::~PresenterAccessible() { } PresenterPaneContainer::SharedPaneDescriptor PresenterAccessible::GetPreviewPane() const { PresenterPaneContainer::SharedPaneDescriptor pPreviewPane; if ( ! mpPresenterController.is()) return pPreviewPane; rtl::Reference pContainer (mpPresenterController->GetPaneContainer()); if ( ! pContainer.is()) return pPreviewPane; pPreviewPane = pContainer->FindPaneURL(PresenterPaneFactory::msCurrentSlidePreviewPaneURL); Reference xPreviewPane; if (pPreviewPane) xPreviewPane = pPreviewPane->mxPane.get(); if ( ! xPreviewPane.is()) { pPreviewPane = pContainer->FindPaneURL(PresenterPaneFactory::msSlideSorterPaneURL); } return pPreviewPane; } void PresenterAccessible::UpdateAccessibilityHierarchy() { if ( ! mpPresenterController.is()) return; Reference xConfigurationController( mpPresenterController->GetConfigurationController()); if ( ! xConfigurationController.is()) return; rtl::Reference pPaneContainer ( mpPresenterController->GetPaneContainer()); if ( ! pPaneContainer.is()) return; if ( ! mpAccessibleConsole.is()) return; // Get the preview pane (standard or notes view) or the slide overview // pane. PresenterPaneContainer::SharedPaneDescriptor pPreviewPane(GetPreviewPane()); Reference xPreviewPane; if (pPreviewPane) xPreviewPane = pPreviewPane->mxPane.get(); // Get the notes pane. PresenterPaneContainer::SharedPaneDescriptor pNotesPane( pPaneContainer->FindPaneURL(PresenterPaneFactory::msNotesPaneURL)); Reference xNotesPane; if (pNotesPane) xNotesPane = pNotesPane->mxPane.get(); // Get the notes view. Reference xNotesView; if (pNotesPane) xNotesView = pNotesPane->mxView; rtl::Reference pNotesView ( dynamic_cast(xNotesView.get())); UpdateAccessibilityHierarchy( pPreviewPane ? pPreviewPane->mxContentWindow : Reference(), pPreviewPane ? pPreviewPane->mxBorderWindow : Reference(), (pPreviewPane&&pPreviewPane->mxPane.is()) ? pPreviewPane->mxPane->GetTitle() : OUString(), pNotesPane ? pNotesPane->mxContentWindow : Reference(), pNotesPane ? pNotesPane->mxBorderWindow : Reference(), pNotesView.is() ? pNotesView->GetTextView() : std::shared_ptr()); } void PresenterAccessible::UpdateAccessibilityHierarchy ( const Reference& rxPreviewContentWindow, const Reference& rxPreviewBorderWindow, const OUString& rsTitle, const Reference& rxNotesContentWindow, const Reference& rxNotesBorderWindow, const std::shared_ptr& rpNotesTextView) { if ( ! mpAccessibleConsole.is()) return; if (mxPreviewContentWindow != rxPreviewContentWindow) { if (mpAccessiblePreview.is()) { mpAccessibleConsole->RemoveChild(mpAccessiblePreview); mpAccessiblePreview->dispose(); mpAccessiblePreview = nullptr; } mxPreviewContentWindow = rxPreviewContentWindow; mxPreviewBorderWindow = rxPreviewBorderWindow; if (mxPreviewContentWindow.is()) { mpAccessiblePreview = AccessiblePreview::Create( mxPreviewContentWindow, mxPreviewBorderWindow); mpAccessibleConsole->AddChild(mpAccessiblePreview); mpAccessiblePreview->SetAccessibleName(rsTitle); } } if (mxNotesContentWindow == rxNotesContentWindow) return; if (mpAccessibleNotes.is()) { mpAccessibleConsole->RemoveChild(mpAccessibleNotes); mpAccessibleNotes->dispose(); mpAccessibleNotes = nullptr; } mxNotesContentWindow = rxNotesContentWindow; mxNotesBorderWindow = rxNotesBorderWindow; if (mxNotesContentWindow.is()) { mpAccessibleNotes = AccessibleNotes::Create( mxNotesContentWindow, mxNotesBorderWindow, rpNotesTextView); mpAccessibleConsole->AddChild(mpAccessibleNotes); } } void PresenterAccessible::NotifyCurrentSlideChange () { if (mpAccessiblePreview.is()) { PresenterPaneContainer::SharedPaneDescriptor pPreviewPane (GetPreviewPane()); mpAccessiblePreview->SetAccessibleName( pPreviewPane&&pPreviewPane->mxPane.is() ? pPreviewPane->mxPane->GetTitle() : OUString()); } // Play some focus ping-pong to trigger AT tools. //AccessibleFocusManager::Instance()->FocusObject(mpAccessibleConsole); AccessibleFocusManager::Instance()->FocusObject(mpAccessiblePreview); } void SAL_CALL PresenterAccessible::disposing() { UpdateAccessibilityHierarchy( nullptr, nullptr, OUString(), nullptr, nullptr, std::shared_ptr()); if (mxMainWindow.is()) { mxMainWindow->removeFocusListener(this); if (mxMainPane.is()) mxMainPane->setAccessible(nullptr); } if (mpAccessiblePreview) mpAccessiblePreview->dispose(); mpAccessiblePreview = nullptr; if (mpAccessibleNotes) mpAccessibleNotes->dispose(); mpAccessibleNotes = nullptr; if (mpAccessibleConsole) mpAccessibleConsole->dispose(); mpAccessibleConsole = nullptr; } //----- XAccessible ----------------------------------------------------------- Reference SAL_CALL PresenterAccessible::getAccessibleContext() { if ( ! mpAccessibleConsole.is()) { Reference xMainPane (mxMainPane, UNO_QUERY); if (xMainPane.is()) { mxMainWindow = xMainPane->getWindow(); mxMainWindow->addFocusListener(this); } mpAccessibleConsole = AccessibleConsole::Create(); mpAccessibleConsole->SetWindow(mxMainWindow, nullptr); mpAccessibleConsole->SetAccessibleParent(mxAccessibleParent); UpdateAccessibilityHierarchy(); if (mpPresenterController.is()) mpPresenterController->SetAccessibilityActiveState(true); } return mpAccessibleConsole->getAccessibleContext(); } //----- XFocusListener ---------------------------------------------------- void SAL_CALL PresenterAccessible::focusGained (const css::awt::FocusEvent&) { SAL_INFO("sdext.presenter", __func__ << ": PresenterAccessible::focusGained at " << this << " and window " << mxMainWindow.get()); AccessibleFocusManager::Instance()->FocusObject(mpAccessibleConsole); } void SAL_CALL PresenterAccessible::focusLost (const css::awt::FocusEvent&) { SAL_INFO("sdext.presenter", __func__ << ": PresenterAccessible::focusLost at " << this); AccessibleFocusManager::Instance()->FocusObject(nullptr); } //----- XEventListener ---------------------------------------------------- void SAL_CALL PresenterAccessible::disposing (const css::lang::EventObject& rEvent) { if (rEvent.Source == mxMainWindow) mxMainWindow = nullptr; } //----- XInitialize ----------------------------------------------------------- void SAL_CALL PresenterAccessible::initialize (const css::uno::Sequence& rArguments) { if (rArguments.hasElements()) { mxAccessibleParent.set(rArguments[0], UNO_QUERY); if (mpAccessibleConsole.is()) mpAccessibleConsole->SetAccessibleParent(mxAccessibleParent); } } //===== AccessibleNotes ======================================================= AccessibleNotes::AccessibleNotes(const OUString& rsName) : AccessibleObject(AccessibleRole::PANEL, rsName) { } rtl::Reference AccessibleNotes::Create( const Reference& rxContentWindow, const Reference& rxBorderWindow, const std::shared_ptr& rpTextView) { const OUString sName = SdResId(STR_A11Y_PRESENTER_NOTES); rtl::Reference pObject ( new AccessibleNotes(sName)); pObject->LateInitialization(); pObject->SetTextView(rpTextView); pObject->UpdateStateSet(); pObject->SetWindow(rxContentWindow, rxBorderWindow); return pObject; } void AccessibleNotes::SetTextView ( const std::shared_ptr& rpTextView) { ::std::vector > aChildren; // Release any listeners to the current text view. if (mpTextView) { mpTextView->GetCaret()->SetCaretMotionBroadcaster( ::std::function()); mpTextView->SetTextChangeBroadcaster( ::std::function()); } mpTextView = rpTextView; if (!mpTextView) return; // Create a new set of children, one for each paragraph. const sal_Int32 nParagraphCount (mpTextView->GetParagraphCount()); for (sal_Int32 nIndex=0; nIndex pParagraph ( new AccessibleParagraph( "Paragraph"+OUString::number(nIndex), rpTextView->GetParagraph(nIndex), nIndex)); pParagraph->LateInitialization(); pParagraph->SetWindow(mxContentWindow, mxBorderWindow); pParagraph->SetAccessibleParent(this); aChildren.emplace_back(pParagraph.get()); } maChildren.swap(aChildren); FireAccessibleEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN, Any(), Any()); // Dispose the old children. (This will remove them from the focus // manager). for (const auto& rxChild : aChildren) { if (rxChild.is()) rxChild->dispose(); } // This class acts as a controller of who broadcasts caret motion // events and handles text changes. Register the corresponding // listeners here. mpTextView->GetCaret()->SetCaretMotionBroadcaster( [this](sal_Int32 a, sal_Int32 b, sal_Int32 c, sal_Int32 d) { return this->NotifyCaretChange(a, b, c, d); }); mpTextView->SetTextChangeBroadcaster( [this]() { return SetTextView(mpTextView); }); } void AccessibleNotes::SetWindow ( const css::uno::Reference& rxContentWindow, const css::uno::Reference& rxBorderWindow) { AccessibleObject::SetWindow(rxContentWindow, rxBorderWindow); // Set the windows at the children as well, so that every paragraph can // setup its geometry. for (auto& rxChild : maChildren) { rxChild->SetWindow(rxContentWindow, rxBorderWindow); } } void AccessibleNotes::NotifyCaretChange ( const sal_Int32 nOldParagraphIndex, const sal_Int32 nOldCharacterIndex, const sal_Int32 nNewParagraphIndex, const sal_Int32 nNewCharacterIndex) { AccessibleFocusManager::Instance()->FocusObject( nNewParagraphIndex >= 0 ? maChildren[nNewParagraphIndex] : this); if (nOldParagraphIndex != nNewParagraphIndex) { // Moved caret from one paragraph to another (or showed or // hid the caret). Move focus from one accessible // paragraph to another. if (nOldParagraphIndex >= 0) { maChildren[nOldParagraphIndex]->FireAccessibleEvent( AccessibleEventId::CARET_CHANGED, Any(nOldCharacterIndex), Any(sal_Int32(-1))); } if (nNewParagraphIndex >= 0) { maChildren[nNewParagraphIndex]->FireAccessibleEvent( AccessibleEventId::CARET_CHANGED, Any(sal_Int32(-1)), Any(nNewCharacterIndex)); } } else if (nNewParagraphIndex >= 0) { // Caret moved inside one paragraph. maChildren[nNewParagraphIndex]->FireAccessibleEvent( AccessibleEventId::CARET_CHANGED, Any(nOldCharacterIndex), Any(nNewCharacterIndex)); } } } // end of namespace ::sd::presenter /* vim:set shiftwidth=4 softtabstop=4 expandtab: */