diff options
Diffstat (limited to 'sdext/source/presenter')
67 files changed, 25949 insertions, 0 deletions
diff --git a/sdext/source/presenter/PresenterAccessibility.cxx b/sdext/source/presenter/PresenterAccessibility.cxx new file mode 100644 index 000000000..e3f49ed1d --- /dev/null +++ b/sdext/source/presenter/PresenterAccessibility.cxx @@ -0,0 +1,1849 @@ +/* -*- 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 "PresenterAccessibility.hxx" +#include "PresenterTextView.hxx" +#include "PresenterConfigurationAccess.hxx" +#include "PresenterNotesView.hxx" +#include "PresenterPaneBase.hxx" +#include "PresenterPaneContainer.hxx" +#include "PresenterPaneFactory.hxx" + +#include <com/sun/star/accessibility/AccessibleEventId.hpp> +#include <com/sun/star/accessibility/AccessibleRelationType.hpp> +#include <com/sun/star/accessibility/AccessibleRole.hpp> +#include <com/sun/star/accessibility/AccessibleScrollType.hpp> +#include <com/sun/star/accessibility/AccessibleStateType.hpp> +#include <com/sun/star/accessibility/XAccessibleComponent.hpp> +#include <com/sun/star/accessibility/XAccessibleContext.hpp> +#include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp> +#include <com/sun/star/accessibility/XAccessibleText.hpp> +#include <com/sun/star/awt/XWindow2.hpp> +#include <com/sun/star/drawing/framework/XPane.hpp> +#include <com/sun/star/drawing/framework/XView.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/implbase.hxx> +#include <o3tl/safeint.hxx> +#include <sal/log.hxx> + +#include <algorithm> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing::framework; + +//===== PresenterAccessibleObject ============================================= + +namespace sdext::presenter { + +namespace { + typedef ::cppu::WeakComponentImplHelper < + css::accessibility::XAccessible, + css::accessibility::XAccessibleContext, + css::accessibility::XAccessibleComponent, + css::accessibility::XAccessibleEventBroadcaster, + css::awt::XWindowListener + > PresenterAccessibleObjectInterfaceBase; +} + +class PresenterAccessible::AccessibleObject + : public ::cppu::BaseMutex, + public PresenterAccessibleObjectInterfaceBase +{ +public: + AccessibleObject ( + const css::lang::Locale& rLocale, + const sal_Int16 nRole, + const OUString& rsName); + void LateInitialization(); + + virtual void SetWindow ( + const css::uno::Reference<css::awt::XWindow>& rxContentWindow, + const css::uno::Reference<css::awt::XWindow>& rxBorderWindow); + void SetAccessibleParent (const css::uno::Reference<css::accessibility::XAccessible>& rxAccessibleParent); + + virtual void SAL_CALL disposing() override; + + void AddChild (const ::rtl::Reference<AccessibleObject>& rpChild); + void RemoveChild (const ::rtl::Reference<AccessibleObject>& rpChild); + + void SetIsFocused (const bool bIsFocused); + void SetAccessibleName (const OUString& rsName); + + void FireAccessibleEvent ( + const sal_Int16 nEventId, + const css::uno::Any& rOldValue, + const css::uno::Any& rNewValue); + + void UpdateStateSet(); + + //----- XAccessible ------------------------------------------------------- + + virtual css::uno::Reference<css::accessibility::XAccessibleContext> SAL_CALL + getAccessibleContext() override; + + //----- XAccessibleContext ---------------------------------------------- + + virtual sal_Int32 SAL_CALL getAccessibleChildCount() override; + + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL + getAccessibleChild (sal_Int32 nIndex) override; + + virtual css::uno::Reference< css::accessibility::XAccessible> SAL_CALL getAccessibleParent() override; + + virtual sal_Int32 SAL_CALL getAccessibleIndexInParent() override; + + virtual sal_Int16 SAL_CALL getAccessibleRole() override; + + virtual OUString SAL_CALL getAccessibleDescription() override; + + virtual OUString SAL_CALL getAccessibleName() override; + + virtual css::uno::Reference<css::accessibility::XAccessibleRelationSet> SAL_CALL + getAccessibleRelationSet() override; + + virtual css::uno::Reference<css::accessibility::XAccessibleStateSet> SAL_CALL + getAccessibleStateSet() override; + + virtual css::lang::Locale SAL_CALL getLocale() override; + + //----- XAccessibleComponent -------------------------------------------- + + virtual sal_Bool SAL_CALL containsPoint ( + const css::awt::Point& aPoint) override; + + virtual css::uno::Reference<css::accessibility::XAccessible> SAL_CALL + getAccessibleAtPoint ( + const css::awt::Point& aPoint) override; + + virtual css::awt::Rectangle SAL_CALL getBounds() override; + + virtual css::awt::Point SAL_CALL getLocation() override; + + virtual css::awt::Point SAL_CALL getLocationOnScreen() override; + + virtual css::awt::Size SAL_CALL getSize() override; + + virtual void SAL_CALL grabFocus() override; + + virtual sal_Int32 SAL_CALL getForeground() override; + + virtual sal_Int32 SAL_CALL getBackground() override; + + //----- XAccessibleEventBroadcaster -------------------------------------- + + virtual void SAL_CALL addAccessibleEventListener ( + const css::uno::Reference<css::accessibility::XAccessibleEventListener>& rxListener) override; + + virtual void SAL_CALL removeAccessibleEventListener ( + const css::uno::Reference<css::accessibility::XAccessibleEventListener>& rxListener) override; + + //----- XWindowListener --------------------------------------------------- + + virtual void SAL_CALL windowResized (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowMoved (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowShown (const css::lang::EventObject& rEvent) override; + + virtual void SAL_CALL windowHidden (const css::lang::EventObject& rEvent) override; + + //----- XEventListener ---------------------------------------------------- + + virtual void SAL_CALL disposing (const css::lang::EventObject& rEvent) override; + +protected: + OUString msName; + css::uno::Reference<css::awt::XWindow2> mxContentWindow; + css::uno::Reference<css::awt::XWindow2> mxBorderWindow; + const css::lang::Locale maLocale; + const sal_Int16 mnRole; + sal_uInt32 mnStateSet; + bool mbIsFocused; + css::uno::Reference<css::accessibility::XAccessible> mxParentAccessible; + ::std::vector<rtl::Reference<AccessibleObject> > maChildren; + ::std::vector<Reference<XAccessibleEventListener> > maListeners; + + virtual awt::Point GetRelativeLocation(); + virtual awt::Size GetSize(); + virtual awt::Point GetAbsoluteParentLocation(); + + virtual bool GetWindowState (const sal_Int16 nType) const; + + void UpdateState (const sal_Int16 aState, const bool bValue); + + /// @throws css::lang::DisposedException + void ThrowIfDisposed() const; +}; + +//===== AccessibleStateSet ==================================================== + +namespace { +typedef ::cppu::WeakComponentImplHelper < + css::accessibility::XAccessibleStateSet + > AccessibleStateSetInterfaceBase; + +class AccessibleStateSet + : public ::cppu::BaseMutex, + public AccessibleStateSetInterfaceBase +{ +public: + explicit AccessibleStateSet (const sal_Int32 nStateSet); + + static sal_uInt32 GetStateMask (const sal_Int16 nType); + + //----- XAccessibleStateSet ----------------------------------------------- + + virtual sal_Bool SAL_CALL isEmpty() override; + + virtual sal_Bool SAL_CALL contains (sal_Int16 nState) override; + + virtual sal_Bool SAL_CALL containsAll (const css::uno::Sequence<sal_Int16>& rStateSet) override; + + virtual css::uno::Sequence<sal_Int16> SAL_CALL getStates() override; + +private: + const sal_Int32 mnStateSet; +}; + +//===== AccessibleRelationSet ================================================= + +typedef ::cppu::WeakComponentImplHelper < + css::accessibility::XAccessibleRelationSet + > AccessibleRelationSetInterfaceBase; + +class AccessibleRelationSet + : public ::cppu::BaseMutex, + public AccessibleRelationSetInterfaceBase +{ +public: + AccessibleRelationSet(); + + void AddRelation ( + const sal_Int16 nRelationType, + const Reference<XInterface>& rxObject); + + //----- XAccessibleRelationSet -------------------------------------------- + + virtual sal_Int32 SAL_CALL getRelationCount() override; + + virtual AccessibleRelation SAL_CALL getRelation (sal_Int32 nIndex) override; + + virtual sal_Bool SAL_CALL containsRelation (sal_Int16 nRelationType) override; + + virtual AccessibleRelation SAL_CALL getRelationByType (sal_Int16 nRelationType) override; + +private: + ::std::vector<AccessibleRelation> maRelations; +}; + +//===== PresenterAccessibleParagraph ========================================== + +typedef ::cppu::ImplInheritanceHelper < + PresenterAccessible::AccessibleObject, + css::accessibility::XAccessibleText + > PresenterAccessibleParagraphInterfaceBase; +} + +class PresenterAccessible::AccessibleParagraph + : public PresenterAccessibleParagraphInterfaceBase +{ +public: + AccessibleParagraph ( + const css::lang::Locale& rLocale, + const OUString& rsName, + const SharedPresenterTextParagraph& rpParagraph, + const sal_Int32 nParagraphIndex); + + //----- XAccessibleContext ------------------------------------------------ + + virtual css::uno::Reference<css::accessibility::XAccessibleRelationSet> SAL_CALL + getAccessibleRelationSet() override; + + //----- XAccessibleText --------------------------------------------------- + + virtual sal_Int32 SAL_CALL getCaretPosition() override; + + virtual sal_Bool SAL_CALL setCaretPosition (sal_Int32 nIndex) override; + + virtual sal_Unicode SAL_CALL getCharacter (sal_Int32 nIndex) override; + + virtual css::uno::Sequence<css::beans::PropertyValue> SAL_CALL + getCharacterAttributes ( + ::sal_Int32 nIndex, + const css::uno::Sequence<OUString>& rRequestedAttributes) override; + + virtual css::awt::Rectangle SAL_CALL getCharacterBounds (sal_Int32 nIndex) override; + + virtual sal_Int32 SAL_CALL getCharacterCount() override; + + virtual sal_Int32 SAL_CALL getIndexAtPoint (const css::awt::Point& rPoint) override; + + virtual OUString SAL_CALL getSelectedText() override; + + virtual sal_Int32 SAL_CALL getSelectionStart() override; + + virtual sal_Int32 SAL_CALL getSelectionEnd() override; + + virtual sal_Bool SAL_CALL setSelection (sal_Int32 nStartIndex, sal_Int32 nEndIndex) override; + + virtual OUString SAL_CALL getText() override; + + virtual OUString SAL_CALL getTextRange ( + sal_Int32 nStartIndex, + sal_Int32 nEndIndex) override; + + virtual css::accessibility::TextSegment SAL_CALL getTextAtIndex ( + sal_Int32 nIndex, + sal_Int16 nTextType) override; + + virtual css::accessibility::TextSegment SAL_CALL getTextBeforeIndex ( + sal_Int32 nIndex, + sal_Int16 nTextType) override; + + virtual css::accessibility::TextSegment SAL_CALL getTextBehindIndex ( + sal_Int32 nIndex, + sal_Int16 nTextType) override; + + virtual sal_Bool SAL_CALL copyText (sal_Int32 nStartIndex, sal_Int32 nEndIndex) override; + + virtual sal_Bool SAL_CALL scrollSubstringTo( + sal_Int32 nStartIndex, + sal_Int32 nEndIndex, + css::accessibility::AccessibleScrollType aScrollType) override; + +protected: + virtual awt::Point GetRelativeLocation() override; + virtual awt::Size GetSize() override; + virtual awt::Point GetAbsoluteParentLocation() override; + virtual bool GetWindowState (const sal_Int16 nType) const override; + +private: + SharedPresenterTextParagraph mpParagraph; + const sal_Int32 mnParagraphIndex; +}; + +//===== AccessibleConsole ===================================================== + +namespace { + +class AccessibleConsole +{ +public: + static rtl::Reference<PresenterAccessible::AccessibleObject> Create ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const lang::Locale& rLocale) + { + OUString sName ("Presenter Console"); + PresenterConfigurationAccess aConfiguration ( + rxContext, + "/org.openoffice.Office.PresenterScreen/", + PresenterConfigurationAccess::READ_ONLY); + aConfiguration.GetConfigurationNode("Presenter/Accessibility/Console/String") + >>= sName; + + rtl::Reference<PresenterAccessible::AccessibleObject> pObject ( + new PresenterAccessible::AccessibleObject( + rLocale, AccessibleRole::PANEL, sName)); + pObject->LateInitialization(); + pObject->UpdateStateSet(); + + return pObject; + } +}; + +//===== AccessiblePreview ===================================================== + +class AccessiblePreview +{ +public: + static rtl::Reference<PresenterAccessible::AccessibleObject> Create ( + const Reference<css::uno::XComponentContext>& rxContext, + const lang::Locale& rLocale, + const Reference<awt::XWindow>& rxContentWindow, + const Reference<awt::XWindow>& rxBorderWindow) + { + OUString sName ("Presenter Notes Window"); + { + PresenterConfigurationAccess aConfiguration ( + rxContext, + "/org.openoffice.Office.PresenterScreen/", + PresenterConfigurationAccess::READ_ONLY); + aConfiguration.GetConfigurationNode("Presenter/Accessibility/Preview/String") + >>= sName; + } + + rtl::Reference<PresenterAccessible::AccessibleObject> pObject ( + new PresenterAccessible::AccessibleObject( + rLocale, + AccessibleRole::LABEL, + sName)); + pObject->LateInitialization(); + pObject->UpdateStateSet(); + pObject->SetWindow(rxContentWindow, rxBorderWindow); + + return pObject; + } +}; + +//===== AccessibleNotes ======================================================= + +class AccessibleNotes : public PresenterAccessible::AccessibleObject +{ +public: + AccessibleNotes ( + const css::lang::Locale& rLocale, + const OUString& rsName); + + static rtl::Reference<PresenterAccessible::AccessibleObject> Create ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const lang::Locale& rLocale, + const Reference<awt::XWindow>& rxContentWindow, + const Reference<awt::XWindow>& rxBorderWindow, + const std::shared_ptr<PresenterTextView>& rpTextView); + + void SetTextView (const std::shared_ptr<PresenterTextView>& rpTextView); + + virtual void SetWindow ( + const css::uno::Reference<css::awt::XWindow>& rxContentWindow, + const css::uno::Reference<css::awt::XWindow>& rxBorderWindow) override; + +private: + std::shared_ptr<PresenterTextView> mpTextView; + + void NotifyCaretChange ( + const sal_Int32 nOldParagraphIndex, + const sal_Int32 nOldCharacterIndex, + const sal_Int32 nNewParagraphIndex, + const sal_Int32 nNewCharacterIndex); +}; + +//===== AccessibleFocusManager ================================================ + +/** A singleton class that makes sure that only one accessibility object in + the PresenterConsole hierarchy has the focus. +*/ +class AccessibleFocusManager +{ +public: + static std::shared_ptr<AccessibleFocusManager> const & Instance(); + + void AddFocusableObject (const ::rtl::Reference<PresenterAccessible::AccessibleObject>& rpObject); + void RemoveFocusableObject (const ::rtl::Reference<PresenterAccessible::AccessibleObject>& rpObject); + + void FocusObject (const ::rtl::Reference<PresenterAccessible::AccessibleObject>& rpObject); + + ~AccessibleFocusManager(); + +private: + static std::shared_ptr<AccessibleFocusManager> mpInstance; + ::std::vector<rtl::Reference<PresenterAccessible::AccessibleObject> > maFocusableObjects; + bool m_isInDtor = false; + + AccessibleFocusManager(); +}; + +} + +//===== PresenterAccessible =================================================== + +PresenterAccessible::PresenterAccessible ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const ::rtl::Reference<PresenterController>& rpPresenterController, + const Reference<drawing::framework::XPane>& rxMainPane) + : PresenterAccessibleInterfaceBase(m_aMutex), + mxComponentContext(rxContext), + mpPresenterController(rpPresenterController), + 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<PresenterPaneContainer> pContainer (mpPresenterController->GetPaneContainer()); + if ( ! pContainer.is()) + return pPreviewPane; + + pPreviewPane = pContainer->FindPaneURL(PresenterPaneFactory::msCurrentSlidePreviewPaneURL); + Reference<drawing::framework::XPane> 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<drawing::framework::XConfigurationController> xConfigurationController( + mpPresenterController->GetConfigurationController()); + if ( ! xConfigurationController.is()) + return; + + rtl::Reference<PresenterPaneContainer> 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<drawing::framework::XPane> xPreviewPane; + if (pPreviewPane) + xPreviewPane = pPreviewPane->mxPane.get(); + + // Get the notes pane. + PresenterPaneContainer::SharedPaneDescriptor pNotesPane( + pPaneContainer->FindPaneURL(PresenterPaneFactory::msNotesPaneURL)); + Reference<drawing::framework::XPane> xNotesPane; + if (pNotesPane) + xNotesPane = pNotesPane->mxPane.get(); + + // Get the notes view. + Reference<drawing::framework::XView> xNotesView; + if (pNotesPane) + xNotesView = pNotesPane->mxView; + rtl::Reference<PresenterNotesView> pNotesView ( + dynamic_cast<PresenterNotesView*>(xNotesView.get())); + + UpdateAccessibilityHierarchy( + pPreviewPane ? pPreviewPane->mxContentWindow : Reference<awt::XWindow>(), + pPreviewPane ? pPreviewPane->mxBorderWindow : Reference<awt::XWindow>(), + (pPreviewPane&&pPreviewPane->mxPane.is()) ? pPreviewPane->mxPane->GetTitle() : OUString(), + pNotesPane ? pNotesPane->mxContentWindow : Reference<awt::XWindow>(), + pNotesPane ? pNotesPane->mxBorderWindow : Reference<awt::XWindow>(), + pNotesView.is() + ? pNotesView->GetTextView() + : std::shared_ptr<PresenterTextView>()); +} + +void PresenterAccessible::UpdateAccessibilityHierarchy ( + const Reference<awt::XWindow>& rxPreviewContentWindow, + const Reference<awt::XWindow>& rxPreviewBorderWindow, + const OUString& rsTitle, + const Reference<awt::XWindow>& rxNotesContentWindow, + const Reference<awt::XWindow>& rxNotesBorderWindow, + const std::shared_ptr<PresenterTextView>& rpNotesTextView) +{ + if ( ! mpAccessibleConsole.is()) + return; + + if (mxPreviewContentWindow != rxPreviewContentWindow) + { + if (mpAccessiblePreview.is()) + { + mpAccessibleConsole->RemoveChild(mpAccessiblePreview); + mpAccessiblePreview = nullptr; + } + + mxPreviewContentWindow = rxPreviewContentWindow; + mxPreviewBorderWindow = rxPreviewBorderWindow; + + if (mxPreviewContentWindow.is()) + { + mpAccessiblePreview = AccessiblePreview::Create( + mxComponentContext, + lang::Locale(), + mxPreviewContentWindow, + mxPreviewBorderWindow); + mpAccessibleConsole->AddChild(mpAccessiblePreview); + mpAccessiblePreview->SetAccessibleName(rsTitle); + } + } + + if (mxNotesContentWindow == rxNotesContentWindow) + return; + + if (mpAccessibleNotes.is()) + { + mpAccessibleConsole->RemoveChild(mpAccessibleNotes); + mpAccessibleNotes = nullptr; + } + + mxNotesContentWindow = rxNotesContentWindow; + mxNotesBorderWindow = rxNotesBorderWindow; + + if (mxNotesContentWindow.is()) + { + mpAccessibleNotes = AccessibleNotes::Create( + mxComponentContext, + lang::Locale(), + 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<PresenterTextView>()); + + if (mxMainWindow.is()) + { + mxMainWindow->removeFocusListener(this); + + if (mxMainPane.is()) + mxMainPane->setAccessible(nullptr); + } + + mpAccessiblePreview = nullptr; + mpAccessibleNotes = nullptr; + mpAccessibleConsole = nullptr; +} + +//----- XAccessible ----------------------------------------------------------- + +Reference<XAccessibleContext> SAL_CALL PresenterAccessible::getAccessibleContext() +{ + if ( ! mpAccessibleConsole.is()) + { + Reference<XPane> xMainPane (mxMainPane, UNO_QUERY); + if (xMainPane.is()) + { + mxMainWindow = xMainPane->getWindow(); + mxMainWindow->addFocusListener(this); + } + mpAccessibleConsole = AccessibleConsole::Create( + mxComponentContext, css::lang::Locale()); + 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<css::uno::Any>& rArguments) +{ + if (rArguments.hasElements()) + { + mxAccessibleParent.set(rArguments[0], UNO_QUERY); + if (mpAccessibleConsole.is()) + mpAccessibleConsole->SetAccessibleParent(mxAccessibleParent); + } +} + +//===== PresenterAccessible::AccessibleObject ========================================= + +PresenterAccessible::AccessibleObject::AccessibleObject ( + const lang::Locale& rLocale, + const sal_Int16 nRole, + const OUString& rsName) + : PresenterAccessibleObjectInterfaceBase(m_aMutex), + msName(rsName), + maLocale(rLocale), + mnRole(nRole), + mnStateSet(0), + mbIsFocused(false) +{ +} + +void PresenterAccessible::AccessibleObject::LateInitialization() +{ + AccessibleFocusManager::Instance()->AddFocusableObject(this); +} + +void PresenterAccessible::AccessibleObject::SetWindow ( + const Reference<awt::XWindow>& rxContentWindow, + const Reference<awt::XWindow>& rxBorderWindow) +{ + Reference<awt::XWindow2> xContentWindow (rxContentWindow, UNO_QUERY); + + if (mxContentWindow.get() == xContentWindow.get()) + return; + + if (mxContentWindow.is()) + { + mxContentWindow->removeWindowListener(this); + } + + mxContentWindow = xContentWindow; + mxBorderWindow.set(rxBorderWindow, UNO_QUERY); + + if (mxContentWindow.is()) + { + mxContentWindow->addWindowListener(this); + } + + UpdateStateSet(); +} + +void PresenterAccessible::AccessibleObject::SetAccessibleParent ( + const Reference<XAccessible>& rxAccessibleParent) +{ + mxParentAccessible = rxAccessibleParent; +} + +void SAL_CALL PresenterAccessible::AccessibleObject::disposing() +{ + AccessibleFocusManager::Instance()->RemoveFocusableObject(this); + SetWindow(nullptr, nullptr); +} + +//----- XAccessible ------------------------------------------------------- + +Reference<XAccessibleContext> SAL_CALL + PresenterAccessible::AccessibleObject::getAccessibleContext() +{ + ThrowIfDisposed(); + + return this; +} + +//----- XAccessibleContext ---------------------------------------------- + +sal_Int32 SAL_CALL PresenterAccessible::AccessibleObject::getAccessibleChildCount() +{ + ThrowIfDisposed(); + + const sal_Int32 nChildCount (maChildren.size()); + + return nChildCount; +} + +Reference<XAccessible> SAL_CALL + PresenterAccessible::AccessibleObject::getAccessibleChild (sal_Int32 nIndex) +{ + ThrowIfDisposed(); + + if (nIndex<0 || o3tl::make_unsigned(nIndex)>=maChildren.size()) + throw lang::IndexOutOfBoundsException("invalid child index", static_cast<uno::XWeak*>(this)); + + return maChildren[nIndex]; +} + +Reference<XAccessible> SAL_CALL + PresenterAccessible::AccessibleObject::getAccessibleParent() +{ + ThrowIfDisposed(); + + return mxParentAccessible; +} + +sal_Int32 SAL_CALL + PresenterAccessible::AccessibleObject::getAccessibleIndexInParent() +{ + ThrowIfDisposed(); + + const Reference<XAccessible> xThis (this); + if (mxParentAccessible.is()) + { + const Reference<XAccessibleContext> xContext (mxParentAccessible->getAccessibleContext()); + for (sal_Int32 nIndex=0,nCount=xContext->getAccessibleChildCount(); + nIndex<nCount; + ++nIndex) + { + if (xContext->getAccessibleChild(nIndex) == xThis) + return nIndex; + } + } + + return 0; +} + +sal_Int16 SAL_CALL + PresenterAccessible::AccessibleObject::getAccessibleRole() +{ + ThrowIfDisposed(); + + return mnRole; +} + +OUString SAL_CALL + PresenterAccessible::AccessibleObject::getAccessibleDescription() +{ + ThrowIfDisposed(); + + return msName; +} + +OUString SAL_CALL + PresenterAccessible::AccessibleObject::getAccessibleName() +{ + ThrowIfDisposed(); + + return msName; +} + +Reference<XAccessibleRelationSet> SAL_CALL + PresenterAccessible::AccessibleObject::getAccessibleRelationSet() +{ + ThrowIfDisposed(); + + return nullptr; +} + +Reference<XAccessibleStateSet> SAL_CALL + PresenterAccessible::AccessibleObject::getAccessibleStateSet() +{ + ThrowIfDisposed(); + + return Reference<XAccessibleStateSet>(new AccessibleStateSet(mnStateSet)); +} + +lang::Locale SAL_CALL + PresenterAccessible::AccessibleObject::getLocale() +{ + ThrowIfDisposed(); + + if (mxParentAccessible.is()) + { + Reference<XAccessibleContext> xParentContext (mxParentAccessible->getAccessibleContext()); + if (xParentContext.is()) + return xParentContext->getLocale(); + } + return maLocale; +} + +//----- XAccessibleComponent ------------------------------------------------ + +sal_Bool SAL_CALL PresenterAccessible::AccessibleObject::containsPoint ( + const awt::Point& rPoint) +{ + ThrowIfDisposed(); + + if (mxContentWindow.is()) + { + const awt::Rectangle aBox (getBounds()); + return rPoint.X>=aBox.X + && rPoint.Y>=aBox.Y + && rPoint.X<aBox.X+aBox.Width + && rPoint.Y<aBox.Y+aBox.Height; + } + else + return false; +} + +Reference<XAccessible> SAL_CALL + PresenterAccessible::AccessibleObject::getAccessibleAtPoint (const awt::Point&) +{ + ThrowIfDisposed(); + + return Reference<XAccessible>(); +} + +awt::Rectangle SAL_CALL PresenterAccessible::AccessibleObject::getBounds() +{ + ThrowIfDisposed(); + + const awt::Point aLocation (GetRelativeLocation()); + const awt::Size aSize (GetSize()); + + return awt::Rectangle (aLocation.X, aLocation.Y, aSize.Width, aSize.Height); +} + +awt::Point SAL_CALL PresenterAccessible::AccessibleObject::getLocation() +{ + ThrowIfDisposed(); + + const awt::Point aLocation (GetRelativeLocation()); + + return aLocation; +} + +awt::Point SAL_CALL PresenterAccessible::AccessibleObject::getLocationOnScreen() +{ + ThrowIfDisposed(); + + awt::Point aRelativeLocation (GetRelativeLocation()); + awt::Point aParentLocationOnScreen (GetAbsoluteParentLocation()); + + return awt::Point( + aRelativeLocation.X + aParentLocationOnScreen.X, + aRelativeLocation.Y + aParentLocationOnScreen.Y); +} + +awt::Size SAL_CALL PresenterAccessible::AccessibleObject::getSize() +{ + ThrowIfDisposed(); + + const awt::Size aSize (GetSize()); + + return aSize; +} + +void SAL_CALL PresenterAccessible::AccessibleObject::grabFocus() +{ + ThrowIfDisposed(); + if (mxBorderWindow.is()) + mxBorderWindow->setFocus(); + else if (mxContentWindow.is()) + mxContentWindow->setFocus(); +} + +sal_Int32 SAL_CALL PresenterAccessible::AccessibleObject::getForeground() +{ + ThrowIfDisposed(); + + return 0x00ffffff; +} + +sal_Int32 SAL_CALL PresenterAccessible::AccessibleObject::getBackground() +{ + ThrowIfDisposed(); + + return 0x00000000; +} + +//----- XAccessibleEventBroadcaster ------------------------------------------- + +void SAL_CALL PresenterAccessible::AccessibleObject::addAccessibleEventListener ( + const Reference<XAccessibleEventListener>& rxListener) +{ + if (!rxListener.is()) + return; + + const osl::MutexGuard aGuard(m_aMutex); + + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + uno::Reference<uno::XInterface> xThis (static_cast<XWeak*>(this), UNO_QUERY); + rxListener->disposing (lang::EventObject(xThis)); + } + else + { + maListeners.push_back(rxListener); + } +} + +void SAL_CALL PresenterAccessible::AccessibleObject::removeAccessibleEventListener ( + const Reference<XAccessibleEventListener>& rxListener) +{ + ThrowIfDisposed(); + if (rxListener.is()) + { + const osl::MutexGuard aGuard(m_aMutex); + + auto const it(std::remove(maListeners.begin(), maListeners.end(), rxListener)); + if (it != maListeners.end()) + { + maListeners.erase(it); + } + } +} + +//----- XWindowListener --------------------------------------------------- + +void SAL_CALL PresenterAccessible::AccessibleObject::windowResized ( + const css::awt::WindowEvent&) +{ + FireAccessibleEvent(AccessibleEventId::BOUNDRECT_CHANGED, Any(), Any()); +} + +void SAL_CALL PresenterAccessible::AccessibleObject::windowMoved ( + const css::awt::WindowEvent&) +{ + FireAccessibleEvent(AccessibleEventId::BOUNDRECT_CHANGED, Any(), Any()); +} + +void SAL_CALL PresenterAccessible::AccessibleObject::windowShown ( + const css::lang::EventObject&) +{ + UpdateStateSet(); +} + +void SAL_CALL PresenterAccessible::AccessibleObject::windowHidden ( + const css::lang::EventObject&) +{ + UpdateStateSet(); +} + +//----- XEventListener -------------------------------------------------------- + +void SAL_CALL PresenterAccessible::AccessibleObject::disposing (const css::lang::EventObject& rEvent) +{ + if (rEvent.Source == mxContentWindow) + { + mxContentWindow = nullptr; + mxBorderWindow = nullptr; + } + else + { + SetWindow(nullptr, nullptr); + } +} + +//----- private --------------------------------------------------------------- + +bool PresenterAccessible::AccessibleObject::GetWindowState (const sal_Int16 nType) const +{ + switch (nType) + { + case AccessibleStateType::ENABLED: + return mxContentWindow.is() && mxContentWindow->isEnabled(); + + case AccessibleStateType::FOCUSABLE: + return true; + + case AccessibleStateType::FOCUSED: + return mbIsFocused; + + case AccessibleStateType::SHOWING: + return mxContentWindow.is() && mxContentWindow->isVisible(); + + default: + return false; + } +} + +void PresenterAccessible::AccessibleObject::UpdateStateSet() +{ + UpdateState(AccessibleStateType::FOCUSABLE, true); + UpdateState(AccessibleStateType::VISIBLE, true); + UpdateState(AccessibleStateType::ENABLED, true); + UpdateState(AccessibleStateType::MULTI_LINE, true); + UpdateState(AccessibleStateType::SENSITIVE, true); + + UpdateState(AccessibleStateType::ENABLED, GetWindowState(AccessibleStateType::ENABLED)); + UpdateState(AccessibleStateType::FOCUSED, GetWindowState(AccessibleStateType::FOCUSED)); + UpdateState(AccessibleStateType::SHOWING, GetWindowState(AccessibleStateType::SHOWING)); + // UpdateState(AccessibleStateType::ACTIVE, GetWindowState(AccessibleStateType::ACTIVE)); +} + +void PresenterAccessible::AccessibleObject::UpdateState( + const sal_Int16 nState, + const bool bValue) +{ + const sal_uInt32 nStateMask (AccessibleStateSet::GetStateMask(nState)); + if (((mnStateSet & nStateMask) != 0) == bValue) + return; + if (bValue) + { + mnStateSet |= nStateMask; + FireAccessibleEvent(AccessibleEventId::STATE_CHANGED, Any(), Any(nState)); + } + else + { + mnStateSet &= ~nStateMask; + FireAccessibleEvent(AccessibleEventId::STATE_CHANGED, Any(nState), Any()); + } +} + +void PresenterAccessible::AccessibleObject::AddChild ( + const ::rtl::Reference<AccessibleObject>& rpChild) +{ + maChildren.push_back(rpChild); + rpChild->SetAccessibleParent(this); + FireAccessibleEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN, Any(), Any()); +} + +void PresenterAccessible::AccessibleObject::RemoveChild ( + const ::rtl::Reference<AccessibleObject>& rpChild) +{ + rpChild->SetAccessibleParent(Reference<XAccessible>()); + maChildren.erase(::std::find(maChildren.begin(), maChildren.end(), rpChild)); + FireAccessibleEvent(AccessibleEventId::INVALIDATE_ALL_CHILDREN, Any(), Any()); +} + +void PresenterAccessible::AccessibleObject::SetIsFocused (const bool bIsFocused) +{ + if (mbIsFocused != bIsFocused) + { + mbIsFocused = bIsFocused; + UpdateStateSet(); + } +} + +void PresenterAccessible::AccessibleObject::SetAccessibleName (const OUString& rsName) +{ + if (msName != rsName) + { + const OUString sOldName(msName); + msName = rsName; + FireAccessibleEvent(AccessibleEventId::NAME_CHANGED, Any(sOldName), Any(msName)); + } +} + +void PresenterAccessible::AccessibleObject::FireAccessibleEvent ( + const sal_Int16 nEventId, + const uno::Any& rOldValue, + const uno::Any& rNewValue ) +{ + AccessibleEventObject aEventObject; + + aEventObject.Source = Reference<XWeak>(this); + aEventObject.EventId = nEventId; + aEventObject.NewValue = rNewValue; + aEventObject.OldValue = rOldValue; + + ::std::vector<Reference<XAccessibleEventListener> > aListenerCopy(maListeners); + for (const auto& rxListener : aListenerCopy) + { + try + { + rxListener->notifyEvent(aEventObject); + } + catch (const lang::DisposedException&) + { + // Listener has been disposed and should have been removed + // already. + removeAccessibleEventListener(rxListener); + } + catch (const Exception&) + { + // Ignore all other exceptions and assume that they are + // caused by a temporary problem. + } + } +} + +awt::Point PresenterAccessible::AccessibleObject::GetRelativeLocation() +{ + awt::Point aLocation; + if (mxContentWindow.is()) + { + const awt::Rectangle aContentBox (mxContentWindow->getPosSize()); + aLocation.X = aContentBox.X; + aLocation.Y = aContentBox.Y; + if (mxBorderWindow.is()) + { + const awt::Rectangle aBorderBox (mxBorderWindow->getPosSize()); + aLocation.X += aBorderBox.X; + aLocation.Y += aBorderBox.Y; + } + } + return aLocation; +} + +awt::Size PresenterAccessible::AccessibleObject::GetSize() +{ + if (mxContentWindow.is()) + { + const awt::Rectangle aBox (mxContentWindow->getPosSize()); + return awt::Size(aBox.Width, aBox.Height); + } + else + return awt::Size(); +} + +awt::Point PresenterAccessible::AccessibleObject::GetAbsoluteParentLocation() +{ + Reference<XAccessibleComponent> xParentComponent; + if (mxParentAccessible.is()) + xParentComponent.set( mxParentAccessible->getAccessibleContext(), UNO_QUERY); + if (xParentComponent.is()) + return xParentComponent->getLocationOnScreen(); + else + return awt::Point(); +} + +void PresenterAccessible::AccessibleObject::ThrowIfDisposed() const +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + throw lang::DisposedException("object has already been disposed", uno::Reference<uno::XInterface>(const_cast<uno::XWeak*>(static_cast<uno::XWeak const *>(this)))); +} + +//===== AccessibleStateSet ==================================================== + +AccessibleStateSet::AccessibleStateSet (const sal_Int32 nStateSet) + : AccessibleStateSetInterfaceBase(m_aMutex), + mnStateSet (nStateSet) +{ +} + +sal_uInt32 AccessibleStateSet::GetStateMask (const sal_Int16 nState) +{ + if (nState<0 || o3tl::make_unsigned(nState)>=sizeof(sal_uInt32)*8) + { + throw RuntimeException("AccessibleStateSet::GetStateMask: invalid state"); + } + + return 1<<nState; +} + +//----- XAccessibleStateSet --------------------------------------------------- + +sal_Bool SAL_CALL AccessibleStateSet::isEmpty() +{ + return mnStateSet==0; +} + +sal_Bool SAL_CALL AccessibleStateSet::contains (sal_Int16 nState) +{ + return (mnStateSet & GetStateMask(nState)) != 0; +} + +sal_Bool SAL_CALL AccessibleStateSet::containsAll (const css::uno::Sequence<sal_Int16>& rStateSet) +{ + return std::none_of(rStateSet.begin(), rStateSet.end(), + [this](const sal_Int16 nState) { return (mnStateSet & GetStateMask(nState)) == 0; }); +} + +css::uno::Sequence<sal_Int16> SAL_CALL AccessibleStateSet::getStates() +{ + ::std::vector<sal_Int16> aStates; + aStates.reserve(sizeof(mnStateSet)*8); + for (sal_uInt16 nIndex=0; nIndex<sizeof(mnStateSet)*8; ++nIndex) + if ((mnStateSet & GetStateMask(nIndex)) != 0) + aStates.push_back(nIndex); + return Sequence<sal_Int16>(aStates.data(), aStates.size()); +} + +//===== AccessibleRelationSet ================================================= + +AccessibleRelationSet::AccessibleRelationSet() + : AccessibleRelationSetInterfaceBase(m_aMutex) +{ +} + +void AccessibleRelationSet::AddRelation ( + const sal_Int16 nRelationType, + const Reference<XInterface>& rxObject) +{ + maRelations.emplace_back(); + maRelations.back().RelationType = nRelationType; + maRelations.back().TargetSet = { rxObject }; +} + +//----- XAccessibleRelationSet ------------------------------------------------ + +sal_Int32 SAL_CALL AccessibleRelationSet::getRelationCount() +{ + return maRelations.size(); +} + +AccessibleRelation SAL_CALL AccessibleRelationSet::getRelation (sal_Int32 nIndex) +{ + if (nIndex<0 && o3tl::make_unsigned(nIndex)>=maRelations.size()) + return AccessibleRelation(); + else + return maRelations[nIndex]; +} + +sal_Bool SAL_CALL AccessibleRelationSet::containsRelation (sal_Int16 nRelationType) +{ + return std::any_of(maRelations.begin(), maRelations.end(), + [nRelationType](const AccessibleRelation& rRelation) { return rRelation.RelationType == nRelationType; }); +} + +AccessibleRelation SAL_CALL AccessibleRelationSet::getRelationByType (sal_Int16 nRelationType) +{ + auto iRelation = std::find_if(maRelations.begin(), maRelations.end(), + [nRelationType](const AccessibleRelation& rRelation) { return rRelation.RelationType == nRelationType; }); + if (iRelation != maRelations.end()) + return *iRelation; + return AccessibleRelation(); +} + +//===== PresenterAccessible::AccessibleParagraph ============================== + +PresenterAccessible::AccessibleParagraph::AccessibleParagraph ( + const lang::Locale& rLocale, + const OUString& rsName, + const SharedPresenterTextParagraph& rpParagraph, + const sal_Int32 nParagraphIndex) + : PresenterAccessibleParagraphInterfaceBase(rLocale, AccessibleRole::PARAGRAPH, rsName), + mpParagraph(rpParagraph), + mnParagraphIndex(nParagraphIndex) +{ +} + +//----- XAccessibleContext ---------------------------------------------------- + +Reference<XAccessibleRelationSet> SAL_CALL + PresenterAccessible::AccessibleParagraph::getAccessibleRelationSet() +{ + ThrowIfDisposed(); + + rtl::Reference<AccessibleRelationSet> pSet (new AccessibleRelationSet); + + if (mxParentAccessible.is()) + { + Reference<XAccessibleContext> xParentContext (mxParentAccessible->getAccessibleContext()); + if (xParentContext.is()) + { + if (mnParagraphIndex>0) + pSet->AddRelation( + AccessibleRelationType::CONTENT_FLOWS_FROM, + xParentContext->getAccessibleChild(mnParagraphIndex-1)); + + if (mnParagraphIndex<xParentContext->getAccessibleChildCount()-1) + pSet->AddRelation( + AccessibleRelationType::CONTENT_FLOWS_TO, + xParentContext->getAccessibleChild(mnParagraphIndex+1)); + } + } + + return pSet; +} + +//----- XAccessibleText ------------------------------------------------------- + +sal_Int32 SAL_CALL PresenterAccessible::AccessibleParagraph::getCaretPosition() +{ + ThrowIfDisposed(); + + sal_Int32 nPosition (-1); + if (mpParagraph) + nPosition = mpParagraph->GetCaretPosition(); + + return nPosition; +} + +sal_Bool SAL_CALL PresenterAccessible::AccessibleParagraph::setCaretPosition (sal_Int32 nIndex) +{ + ThrowIfDisposed(); + + if (mpParagraph) + { + mpParagraph->SetCaretPosition(nIndex); + return true; + } + else + return false; +} + +sal_Unicode SAL_CALL PresenterAccessible::AccessibleParagraph::getCharacter (sal_Int32 nIndex) +{ + ThrowIfDisposed(); + + if (!mpParagraph) + throw lang::IndexOutOfBoundsException("no text support in current mode", static_cast<uno::XWeak*>(this)); + return mpParagraph->GetCharacter(nIndex); +} + +Sequence<css::beans::PropertyValue> SAL_CALL + PresenterAccessible::AccessibleParagraph::getCharacterAttributes ( + ::sal_Int32 nIndex, + const css::uno::Sequence<OUString>& rRequestedAttributes) +{ + ThrowIfDisposed(); + +#if OSL_DEBUG_LEVEL > 0 + SAL_INFO( "sdext.presenter", __func__ << " at " << this << ", " << nIndex << " returns empty set" ); + for (sal_Int32 nAttributeIndex(0), nAttributeCount(rRequestedAttributes.getLength()); + nAttributeIndex < nAttributeCount; + ++nAttributeIndex) + { + SAL_INFO( "sdext.presenter", + " requested attribute " << nAttributeIndex << " is " << rRequestedAttributes[nAttributeIndex] ); + } +#else + (void)nIndex; + (void)rRequestedAttributes; +#endif + + // Character properties are not supported. + return Sequence<css::beans::PropertyValue>(); +} + +awt::Rectangle SAL_CALL PresenterAccessible::AccessibleParagraph::getCharacterBounds ( + sal_Int32 nIndex) +{ + ThrowIfDisposed(); + + awt::Rectangle aCharacterBox; + if (nIndex < 0) + { + throw lang::IndexOutOfBoundsException("invalid text index", static_cast<uno::XWeak*>(this)); + } + else if (mpParagraph) + { + aCharacterBox = mpParagraph->GetCharacterBounds(nIndex, false); + // Convert coordinates relative to the window origin into absolute + // screen coordinates. + const awt::Point aWindowLocationOnScreen (getLocationOnScreen()); + aCharacterBox.X += aWindowLocationOnScreen.X; + aCharacterBox.Y += aWindowLocationOnScreen.Y; + } + else + { + throw lang::IndexOutOfBoundsException("no text support in current mode", static_cast<uno::XWeak*>(this)); + } + + return aCharacterBox; +} + +sal_Int32 SAL_CALL PresenterAccessible::AccessibleParagraph::getCharacterCount() +{ + ThrowIfDisposed(); + + sal_Int32 nCount (0); + if (mpParagraph) + nCount = mpParagraph->GetCharacterCount(); + + return nCount; +} + +sal_Int32 SAL_CALL PresenterAccessible::AccessibleParagraph::getIndexAtPoint ( + const css::awt::Point& ) +{ + ThrowIfDisposed(); + return -1; +} + +OUString SAL_CALL PresenterAccessible::AccessibleParagraph::getSelectedText() +{ + ThrowIfDisposed(); + + return getTextRange(getSelectionStart(), getSelectionEnd()); +} + +sal_Int32 SAL_CALL PresenterAccessible::AccessibleParagraph::getSelectionStart() +{ + ThrowIfDisposed(); + + return getCaretPosition(); +} + +sal_Int32 SAL_CALL PresenterAccessible::AccessibleParagraph::getSelectionEnd() +{ + ThrowIfDisposed(); + + return getCaretPosition(); +} + +sal_Bool SAL_CALL PresenterAccessible::AccessibleParagraph::setSelection ( + sal_Int32 nStartIndex, + sal_Int32) +{ + ThrowIfDisposed(); + + return setCaretPosition(nStartIndex); +} + +OUString SAL_CALL PresenterAccessible::AccessibleParagraph::getText() +{ + ThrowIfDisposed(); + + OUString sText; + if (mpParagraph) + sText = mpParagraph->GetText(); + + return sText; +} + +OUString SAL_CALL PresenterAccessible::AccessibleParagraph::getTextRange ( + sal_Int32 nLocalStartIndex, + sal_Int32 nLocalEndIndex) +{ + ThrowIfDisposed(); + + OUString sText; + if (mpParagraph) + { + const TextSegment aSegment ( + mpParagraph->CreateTextSegment(nLocalStartIndex, nLocalEndIndex)); + sText = aSegment.SegmentText; + } + + return sText; +} + +TextSegment SAL_CALL PresenterAccessible::AccessibleParagraph::getTextAtIndex ( + sal_Int32 nLocalCharacterIndex, + sal_Int16 nTextType) +{ + ThrowIfDisposed(); + + TextSegment aSegment; + if (mpParagraph) + aSegment = mpParagraph->GetTextSegment(0, nLocalCharacterIndex, nTextType); + + return aSegment; +} + +TextSegment SAL_CALL PresenterAccessible::AccessibleParagraph::getTextBeforeIndex ( + sal_Int32 nLocalCharacterIndex, + sal_Int16 nTextType) +{ + ThrowIfDisposed(); + + TextSegment aSegment; + if (mpParagraph) + aSegment = mpParagraph->GetTextSegment(-1, nLocalCharacterIndex, nTextType); + + return aSegment; +} + +TextSegment SAL_CALL PresenterAccessible::AccessibleParagraph::getTextBehindIndex ( + sal_Int32 nLocalCharacterIndex, + sal_Int16 nTextType) +{ + ThrowIfDisposed(); + + TextSegment aSegment; + if (mpParagraph) + aSegment = mpParagraph->GetTextSegment(+1, nLocalCharacterIndex, nTextType); + + return aSegment; +} + +sal_Bool SAL_CALL PresenterAccessible::AccessibleParagraph::copyText ( + sal_Int32, + sal_Int32) +{ + ThrowIfDisposed(); + + // Return false because copying to clipboard is not supported. + // It IS supported in the notes view. There is no need to duplicate + // this here. + return false; +} + +sal_Bool SAL_CALL PresenterAccessible::AccessibleParagraph::scrollSubstringTo( + sal_Int32, + sal_Int32, + AccessibleScrollType) +{ + return false; +} + +//----- protected ------------------------------------------------------------- + +awt::Point PresenterAccessible::AccessibleParagraph::GetRelativeLocation() +{ + awt::Point aLocation (AccessibleObject::GetRelativeLocation()); + if (mpParagraph) + { + const awt::Point aParagraphLocation (mpParagraph->GetRelativeLocation()); + aLocation.X += aParagraphLocation.X; + aLocation.Y += aParagraphLocation.Y; + } + + return aLocation; +} + +awt::Size PresenterAccessible::AccessibleParagraph::GetSize() +{ + if (mpParagraph) + return mpParagraph->GetSize(); + else + return AccessibleObject::GetSize(); +} + +awt::Point PresenterAccessible::AccessibleParagraph::GetAbsoluteParentLocation() +{ + if (mxParentAccessible.is()) + { + Reference<XAccessibleContext> xParentContext = + mxParentAccessible->getAccessibleContext(); + if (xParentContext.is()) + { + Reference<XAccessibleComponent> xGrandParentComponent( + xParentContext->getAccessibleParent(), UNO_QUERY); + if (xGrandParentComponent.is()) + return xGrandParentComponent->getLocationOnScreen(); + } + } + + return awt::Point(); +} + +bool PresenterAccessible::AccessibleParagraph::GetWindowState (const sal_Int16 nType) const +{ + switch (nType) + { + case AccessibleStateType::EDITABLE: + return bool(mpParagraph); + + case AccessibleStateType::ACTIVE: + return true; + + default: + return AccessibleObject::GetWindowState(nType); + } +} + +//===== AccessibleNotes ======================================================= + +AccessibleNotes::AccessibleNotes ( + const css::lang::Locale& rLocale, + const OUString& rsName) + : AccessibleObject(rLocale,AccessibleRole::PANEL,rsName) +{ +} + +rtl::Reference<PresenterAccessible::AccessibleObject> AccessibleNotes::Create ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const lang::Locale& rLocale, + const Reference<awt::XWindow>& rxContentWindow, + const Reference<awt::XWindow>& rxBorderWindow, + const std::shared_ptr<PresenterTextView>& rpTextView) +{ + OUString sName ("Presenter Notes Text"); + { + PresenterConfigurationAccess aConfiguration ( + rxContext, + "/org.openoffice.Office.PresenterScreen/", + PresenterConfigurationAccess::READ_ONLY); + aConfiguration.GetConfigurationNode("Presenter/Accessibility/Notes/String") + >>= sName; + } + + rtl::Reference<AccessibleNotes> pObject ( + new AccessibleNotes( + rLocale, + sName)); + pObject->LateInitialization(); + pObject->SetTextView(rpTextView); + pObject->UpdateStateSet(); + pObject->SetWindow(rxContentWindow, rxBorderWindow); + + return pObject; +} + +void AccessibleNotes::SetTextView ( + const std::shared_ptr<PresenterTextView>& rpTextView) +{ + ::std::vector<rtl::Reference<PresenterAccessible::AccessibleObject> > aChildren; + + // Release any listeners to the current text view. + if (mpTextView) + { + mpTextView->GetCaret()->SetCaretMotionBroadcaster( + ::std::function<void (sal_Int32,sal_Int32,sal_Int32,sal_Int32)>()); + mpTextView->SetTextChangeBroadcaster( + ::std::function<void ()>()); + } + + 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<nParagraphCount; ++nIndex) + { + rtl::Reference<PresenterAccessible::AccessibleParagraph> pParagraph ( + new PresenterAccessible::AccessibleParagraph( + css::lang::Locale(), + "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) + { + Reference<lang::XComponent> xComponent = rxChild; + if (xComponent.is()) + xComponent->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<css::awt::XWindow>& rxContentWindow, + const css::uno::Reference<css::awt::XWindow>& 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)); + } +} + + +//===== AccessibleFocusManager ================================================ + +std::shared_ptr<AccessibleFocusManager> AccessibleFocusManager::mpInstance; + +std::shared_ptr<AccessibleFocusManager> const & AccessibleFocusManager::Instance() +{ + if ( ! mpInstance) + { + mpInstance.reset(new AccessibleFocusManager()); + } + return mpInstance; +} + +AccessibleFocusManager::AccessibleFocusManager() +{ +} + +AccessibleFocusManager::~AccessibleFocusManager() +{ + // copy member to stack, then drop it - otherwise will get use-after-free + // from AccessibleObject::disposing(), it will call ~Reference *twice* + auto const temp(std::move(maFocusableObjects)); + (void) temp; + m_isInDtor = true; +} + +void AccessibleFocusManager::AddFocusableObject ( + const ::rtl::Reference<PresenterAccessible::AccessibleObject>& rpObject) +{ + OSL_ASSERT(rpObject.is()); + OSL_ASSERT(::std::find(maFocusableObjects.begin(),maFocusableObjects.end(), rpObject)==maFocusableObjects.end()); + + maFocusableObjects.push_back(rpObject); +} + +void AccessibleFocusManager::RemoveFocusableObject ( + const ::rtl::Reference<PresenterAccessible::AccessibleObject>& rpObject) +{ + ::std::vector<rtl::Reference<PresenterAccessible::AccessibleObject> >::iterator iObject ( + ::std::find(maFocusableObjects.begin(),maFocusableObjects.end(), rpObject)); + + if (iObject != maFocusableObjects.end()) + maFocusableObjects.erase(iObject); + else + { + OSL_ASSERT(m_isInDtor); // in dtor, was removed already + } +} + +void AccessibleFocusManager::FocusObject ( + const ::rtl::Reference<PresenterAccessible::AccessibleObject>& rpObject) +{ + // Remove the focus of any of the other focusable objects. + for (auto& rxObject : maFocusableObjects) + { + if (rxObject!=rpObject) + rxObject->SetIsFocused(false); + } + + if (rpObject.is()) + rpObject->SetIsFocused(true); +} + +} // end of namespace ::sd::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterAccessibility.hxx b/sdext/source/presenter/PresenterAccessibility.hxx new file mode 100644 index 000000000..9789db525 --- /dev/null +++ b/sdext/source/presenter/PresenterAccessibility.hxx @@ -0,0 +1,115 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERACCESSIBILITY_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERACCESSIBILITY_HXX + +#include "PresenterPaneContainer.hxx" + +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <com/sun/star/awt/XFocusListener.hpp> +#include <com/sun/star/drawing/framework/XPane.hpp> +#include <com/sun/star/drawing/framework/XPane2.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> +#include <rtl/ref.hxx> +#include <memory> + + +namespace sdext::presenter { + +class PresenterController; +class PresenterTextView; + +typedef ::cppu::WeakComponentImplHelper < + css::accessibility::XAccessible, + css::lang::XInitialization, + css::awt::XFocusListener +> PresenterAccessibleInterfaceBase; + +class PresenterAccessible + : public ::cppu::BaseMutex, + public PresenterAccessibleInterfaceBase +{ +public: + PresenterAccessible ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const ::rtl::Reference<PresenterController>& rpPresenterController, + const css::uno::Reference<css::drawing::framework::XPane>& rxMainPane); + virtual ~PresenterAccessible() override; + + void UpdateAccessibilityHierarchy(); + + void NotifyCurrentSlideChange (); + + virtual void SAL_CALL disposing() override; + + //----- XAccessible ------------------------------------------------------- + + virtual css::uno::Reference<css::accessibility::XAccessibleContext> SAL_CALL + getAccessibleContext() override; + + //----- XFocusListener ---------------------------------------------------- + + virtual void SAL_CALL focusGained (const css::awt::FocusEvent& rEvent) override; + + virtual void SAL_CALL focusLost (const css::awt::FocusEvent& rEvent) override; + + //----- XEventListener ---------------------------------------------------- + + virtual void SAL_CALL disposing (const css::lang::EventObject& rEvent) override; + + //----- XInitialization --------------------------------------------------- + + virtual void SAL_CALL initialize (const css::uno::Sequence<css::uno::Any>& rArguments) override; + + class AccessibleObject; + class AccessibleParagraph; + +private: + const css::uno::Reference<css::uno::XComponentContext> mxComponentContext; + ::rtl::Reference<PresenterController> mpPresenterController; + css::uno::Reference<css::drawing::framework::XPane2> mxMainPane; + css::uno::Reference<css::awt::XWindow> mxMainWindow; + css::uno::Reference<css::awt::XWindow> mxPreviewContentWindow; + css::uno::Reference<css::awt::XWindow> mxPreviewBorderWindow; + css::uno::Reference<css::awt::XWindow> mxNotesContentWindow; + css::uno::Reference<css::awt::XWindow> mxNotesBorderWindow; + ::rtl::Reference<AccessibleObject> mpAccessibleConsole; + ::rtl::Reference<AccessibleObject> mpAccessiblePreview; + ::rtl::Reference<AccessibleObject> mpAccessibleNotes; + css::uno::Reference<css::accessibility::XAccessible> mxAccessibleParent; + + void UpdateAccessibilityHierarchy ( + const css::uno::Reference<css::awt::XWindow>& rxPreviewContentWindow, + const css::uno::Reference<css::awt::XWindow>& rxPreviewBorderWindow, + const OUString& rsTitle, + const css::uno::Reference<css::awt::XWindow>& rxNotesContentWindow, + const css::uno::Reference<css::awt::XWindow>& rxNotesBorderWindow, + const std::shared_ptr<PresenterTextView>& rpNotesTextView); + PresenterPaneContainer::SharedPaneDescriptor GetPreviewPane() const; +}; + +} // end of namespace ::sd::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterBitmapContainer.cxx b/sdext/source/presenter/PresenterBitmapContainer.cxx new file mode 100644 index 000000000..17609da85 --- /dev/null +++ b/sdext/source/presenter/PresenterBitmapContainer.cxx @@ -0,0 +1,399 @@ +/* -*- 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 "PresenterBitmapContainer.hxx" +#include "PresenterConfigurationAccess.hxx" + +#include <com/sun/star/drawing/XPresenterHelper.hpp> +#include <com/sun/star/lang/XMultiComponentFactory.hpp> +#include <osl/diagnose.h> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::std; + +namespace sdext::presenter { + +//===== PresenterBitmapContainer ============================================== + +PresenterBitmapContainer::PresenterBitmapContainer ( + const OUString& rsConfigurationBase, + const std::shared_ptr<PresenterBitmapContainer>& rpParentContainer, + const css::uno::Reference<css::uno::XComponentContext>& rxComponentContext, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::uno::Reference<css::drawing::XPresenterHelper>& rxPresenterHelper) + : mpParentContainer(rpParentContainer), + mxCanvas(rxCanvas), + mxPresenterHelper(rxPresenterHelper) +{ + Initialize(rxComponentContext); + + // Get access to the configuration. + PresenterConfigurationAccess aConfiguration ( + rxComponentContext, + "org.openoffice.Office.PresenterScreen", + PresenterConfigurationAccess::READ_ONLY); + Reference<container::XNameAccess> xBitmapList ( + aConfiguration.GetConfigurationNode(rsConfigurationBase), + UNO_QUERY_THROW); + + LoadBitmaps(xBitmapList); +} + +PresenterBitmapContainer::PresenterBitmapContainer ( + const css::uno::Reference<css::container::XNameAccess>& rxRootNode, + const std::shared_ptr<PresenterBitmapContainer>& rpParentContainer, + const css::uno::Reference<css::uno::XComponentContext>& rxComponentContext, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::uno::Reference<css::drawing::XPresenterHelper>& rxPresenterHelper) + : mpParentContainer(rpParentContainer), + mxCanvas(rxCanvas), + mxPresenterHelper(rxPresenterHelper) +{ + Initialize(rxComponentContext); + + LoadBitmaps(rxRootNode); +} + +void PresenterBitmapContainer::Initialize ( + const css::uno::Reference<css::uno::XComponentContext>& rxComponentContext) +{ + if ( mxPresenterHelper.is()) + return; + + // Create an object that is able to load the bitmaps in a format that is + // supported by the canvas. + Reference<lang::XMultiComponentFactory> xFactory = + rxComponentContext->getServiceManager(); + if ( ! xFactory.is()) + return; + mxPresenterHelper.set( + xFactory->createInstanceWithContext( + "com.sun.star.drawing.PresenterHelper", + rxComponentContext), + UNO_QUERY_THROW); +} + +PresenterBitmapContainer::~PresenterBitmapContainer() +{ + maIconContainer.clear(); +} + +std::shared_ptr<PresenterBitmapContainer::BitmapDescriptor> PresenterBitmapContainer::GetBitmap ( + const OUString& rsName) const +{ + BitmapContainer::const_iterator iSet (maIconContainer.find(rsName)); + if (iSet != maIconContainer.end()) + return iSet->second; + else if (mpParentContainer != nullptr) + return mpParentContainer->GetBitmap(rsName); + else + return SharedBitmapDescriptor(); +} + +void PresenterBitmapContainer::LoadBitmaps ( + const css::uno::Reference<css::container::XNameAccess>& rxBitmapList) +{ + if ( ! mxCanvas.is()) + return; + + if ( ! rxBitmapList.is()) + return; + + try + { + // Load all button bitmaps. + if (rxBitmapList.is()) + { + PresenterConfigurationAccess::ForAll( + rxBitmapList, + [this](OUString const& rKey, Reference<beans::XPropertySet> const& xProps) + { + this->ProcessBitmap(rKey, xProps); + }); + } + } + catch (Exception&) + { + OSL_ASSERT(false); + } +} + +std::shared_ptr<PresenterBitmapContainer::BitmapDescriptor> PresenterBitmapContainer::LoadBitmap ( + const css::uno::Reference<css::container::XHierarchicalNameAccess>& rxNode, + const OUString& rsPath, + const css::uno::Reference<css::drawing::XPresenterHelper>& rxPresenterHelper, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const std::shared_ptr<BitmapDescriptor>& rpDefault) +{ + SharedBitmapDescriptor pBitmap; + + if (rxNode.is()) + { + try + { + Reference<beans::XPropertySet> xBitmapProperties ( + PresenterConfigurationAccess::GetConfigurationNode(rxNode, rsPath), + UNO_QUERY); + if (xBitmapProperties.is()) + pBitmap = LoadBitmap( + xBitmapProperties, + rxPresenterHelper, + rxCanvas, + rpDefault); + } + catch (Exception&) + { + OSL_ASSERT(false); + } + } + + return pBitmap; +} + +void PresenterBitmapContainer::ProcessBitmap ( + const OUString& rsKey, + const Reference<beans::XPropertySet>& rxProperties) +{ + OUString sName; + if ( ! (PresenterConfigurationAccess::GetProperty(rxProperties, "Name") >>= sName)) + sName = rsKey; + + maIconContainer[sName] = LoadBitmap( + rxProperties, + mxPresenterHelper, + mxCanvas, + SharedBitmapDescriptor()); +} + +std::shared_ptr<PresenterBitmapContainer::BitmapDescriptor> PresenterBitmapContainer::LoadBitmap ( + const Reference<beans::XPropertySet>& rxProperties, + const css::uno::Reference<css::drawing::XPresenterHelper>& rxPresenterHelper, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const std::shared_ptr<BitmapDescriptor>& rpDefault) +{ + OSL_ASSERT(rxCanvas.is()); + OSL_ASSERT(rxPresenterHelper.is()); + + SharedBitmapDescriptor pBitmap = std::make_shared<BitmapDescriptor>(rpDefault); + + if ( ! rxProperties.is()) + return pBitmap; + + OUString sFileName; + + // Load bitmaps. + if (PresenterConfigurationAccess::GetProperty(rxProperties, "NormalFileName") >>= sFileName) + try + { + pBitmap->SetBitmap( + BitmapDescriptor::Normal, + rxPresenterHelper->loadBitmap(sFileName, rxCanvas)); + } + catch (Exception&) + {} + if (PresenterConfigurationAccess::GetProperty(rxProperties, "MouseOverFileName") >>= sFileName) + try + { + pBitmap->SetBitmap( + BitmapDescriptor::MouseOver, + rxPresenterHelper->loadBitmap(sFileName, rxCanvas)); + } + catch (Exception&) + {} + if (PresenterConfigurationAccess::GetProperty(rxProperties, "ButtonDownFileName") >>= sFileName) + try + { + pBitmap->SetBitmap( + BitmapDescriptor::ButtonDown, + rxPresenterHelper->loadBitmap(sFileName, rxCanvas)); + } + catch (Exception&) + {} + if (PresenterConfigurationAccess::GetProperty(rxProperties, "DisabledFileName") >>= sFileName) + try + { + pBitmap->SetBitmap( + BitmapDescriptor::Disabled, + rxPresenterHelper->loadBitmap(sFileName, rxCanvas)); + } + catch (Exception&) + {} + if (PresenterConfigurationAccess::GetProperty(rxProperties, "MaskFileName") >>= sFileName) + try + { + pBitmap->SetBitmap( + BitmapDescriptor::Mask, + rxPresenterHelper->loadBitmap(sFileName, rxCanvas)); + } + catch (Exception&) + {} + + PresenterConfigurationAccess::GetProperty(rxProperties, "XOffset") >>= pBitmap->mnXOffset; + PresenterConfigurationAccess::GetProperty(rxProperties, "YOffset") >>= pBitmap->mnYOffset; + + PresenterConfigurationAccess::GetProperty(rxProperties, "XHotSpot") >>= pBitmap->mnXHotSpot; + PresenterConfigurationAccess::GetProperty(rxProperties, "YHotSpot") >>= pBitmap->mnYHotSpot; + + PresenterConfigurationAccess::GetProperty(rxProperties, "ReplacementColor") >>= pBitmap->maReplacementColor; + + OUString sTexturingMode; + if (PresenterConfigurationAccess::GetProperty(rxProperties, "HorizontalTexturingMode") >>= sTexturingMode) + pBitmap->meHorizontalTexturingMode = StringToTexturingMode(sTexturingMode); + if (PresenterConfigurationAccess::GetProperty(rxProperties, "VerticalTexturingMode") >>= sTexturingMode) + pBitmap->meVerticalTexturingMode = StringToTexturingMode(sTexturingMode); + + return pBitmap; +} + +PresenterBitmapContainer::BitmapDescriptor::TexturingMode + PresenterBitmapContainer::StringToTexturingMode (std::u16string_view rsTexturingMode) +{ + if (rsTexturingMode == u"Once") + return PresenterBitmapContainer::BitmapDescriptor::Once; + else if (rsTexturingMode == u"Repeat") + return PresenterBitmapContainer::BitmapDescriptor::Repeat; + else if (rsTexturingMode == u"Stretch") + return PresenterBitmapContainer::BitmapDescriptor::Stretch; + else + return PresenterBitmapContainer::BitmapDescriptor::Once; +} + +//===== PresenterBitmapContainer::BitmapSet =================================== + +PresenterBitmapContainer::BitmapDescriptor::BitmapDescriptor() + : mnWidth(0), + mnHeight(0), + mnXOffset(0), + mnYOffset(0), + mnXHotSpot(0), + mnYHotSpot(0), + maReplacementColor(0x00000000), + meHorizontalTexturingMode(Once), + meVerticalTexturingMode(Once) +{ +} + +PresenterBitmapContainer::BitmapDescriptor::BitmapDescriptor ( + const std::shared_ptr<PresenterBitmapContainer::BitmapDescriptor>& rpDefault) + : mnWidth(0), + mnHeight(0), + mnXOffset(0), + mnYOffset(0), + mnXHotSpot(0), + mnYHotSpot(0), + maReplacementColor(0x00000000), + meHorizontalTexturingMode(Once), + meVerticalTexturingMode(Once) +{ + if (rpDefault == nullptr) + return; + + mnWidth = rpDefault->mnWidth; + mnHeight = rpDefault->mnHeight; + mnXOffset = rpDefault->mnXOffset; + mnYOffset = rpDefault->mnYOffset; + mnXHotSpot = rpDefault->mnXHotSpot; + mnYHotSpot = rpDefault->mnYHotSpot; + maReplacementColor = rpDefault->maReplacementColor; + meHorizontalTexturingMode = rpDefault->meHorizontalTexturingMode; + meVerticalTexturingMode = rpDefault->meVerticalTexturingMode; + mxNormalBitmap = rpDefault->mxNormalBitmap; + mxMouseOverBitmap = rpDefault->mxMouseOverBitmap; + mxButtonDownBitmap = rpDefault->mxButtonDownBitmap; + mxDisabledBitmap = rpDefault->mxDisabledBitmap; + mxMaskBitmap = rpDefault->mxMaskBitmap; +} + +const css::uno::Reference<css::rendering::XBitmap>& + PresenterBitmapContainer::BitmapDescriptor::GetNormalBitmap() const +{ + return mxNormalBitmap; +} + +css::uno::Reference<css::rendering::XBitmap> const & + PresenterBitmapContainer::BitmapDescriptor::GetBitmap(const Mode eMode) const +{ + switch (eMode) + { + case Normal: + default: + return mxNormalBitmap; + + case MouseOver: + if (mxMouseOverBitmap.is()) + return mxMouseOverBitmap; + else + return mxNormalBitmap; + + case ButtonDown: + if (mxButtonDownBitmap.is()) + return mxButtonDownBitmap; + else + return mxNormalBitmap; + + case Disabled: + if (mxDisabledBitmap.is()) + return mxDisabledBitmap; + else + return mxNormalBitmap; + + case Mask: + return mxMaskBitmap; + } +} + +void PresenterBitmapContainer::BitmapDescriptor::SetBitmap ( + const Mode eMode, + const css::uno::Reference<css::rendering::XBitmap>& rxBitmap) +{ + switch (eMode) + { + case Normal: + default: + mxNormalBitmap = rxBitmap; + if (mxNormalBitmap.is()) + { + const geometry::IntegerSize2D aSize (mxNormalBitmap->getSize()); + mnWidth = aSize.Width; + mnHeight = aSize.Height; + } + break; + + case MouseOver: + mxMouseOverBitmap = rxBitmap; + break; + + case ButtonDown: + mxButtonDownBitmap = rxBitmap; + break; + + case Disabled: + mxDisabledBitmap = rxBitmap; + break; + + case Mask: + mxMaskBitmap = rxBitmap; + break; + } +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterBitmapContainer.hxx b/sdext/source/presenter/PresenterBitmapContainer.hxx new file mode 100644 index 000000000..65f385b2b --- /dev/null +++ b/sdext/source/presenter/PresenterBitmapContainer.hxx @@ -0,0 +1,146 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERBITMAPCONTAINER_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERBITMAPCONTAINER_HXX + +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/drawing/XPresenterHelper.hpp> +#include <com/sun/star/rendering/XBitmap.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/util/Color.hpp> +#include <map> +#include <memory> + +namespace sdext::presenter { + +/** Manage a set of bitmap groups as they are used for buttons: three + bitmaps, one for the normal state, one for a mouse over effect and one + to show that the button has been pressed. + A bitmap group is defined by some entries in the configuration. +*/ +class PresenterBitmapContainer +{ +public: + /** There is one bitmap for the normal state, one for a mouse over effect and one + to show that a button has been pressed. + */ + class BitmapDescriptor + { + public: + BitmapDescriptor(); + explicit BitmapDescriptor (const std::shared_ptr<BitmapDescriptor>& rpDefault); + + enum Mode {Normal, MouseOver, ButtonDown, Disabled, Mask}; + const css::uno::Reference<css::rendering::XBitmap>& GetNormalBitmap() const; + css::uno::Reference<css::rendering::XBitmap> const & GetBitmap(const Mode eMode) const; + void SetBitmap ( + const Mode eMode, + const css::uno::Reference<css::rendering::XBitmap>& rxBitmap); + + sal_Int32 mnWidth; + sal_Int32 mnHeight; + sal_Int32 mnXOffset; + sal_Int32 mnYOffset; + sal_Int32 mnXHotSpot; + sal_Int32 mnYHotSpot; + css::util::Color maReplacementColor; + enum TexturingMode { Once, Repeat, Stretch }; + TexturingMode meHorizontalTexturingMode; + TexturingMode meVerticalTexturingMode; + + private: + css::uno::Reference<css::rendering::XBitmap> mxNormalBitmap; + css::uno::Reference<css::rendering::XBitmap> mxMouseOverBitmap; + css::uno::Reference<css::rendering::XBitmap> mxButtonDownBitmap; + css::uno::Reference<css::rendering::XBitmap> mxDisabledBitmap; + css::uno::Reference<css::rendering::XBitmap> mxMaskBitmap; + }; + + /** Create a new bitmap container from a section of the configuration. + @param rxComponentContext + The component context is used to create new API objects. + @param rxCanvas + Bitmaps are created specifically for this canvas. + @param rsConfigurationBase + The name of a configuration node whose sub-tree defines the + bitmap sets. + */ + PresenterBitmapContainer ( + const OUString& rsConfigurationBase, + const std::shared_ptr<PresenterBitmapContainer>& rpParentContainer, + const css::uno::Reference<css::uno::XComponentContext>& rxComponentContext, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::uno::Reference<css::drawing::XPresenterHelper>& rxPresenterHelper = nullptr); + PresenterBitmapContainer ( + const css::uno::Reference<css::container::XNameAccess>& rsRootNode, + const std::shared_ptr<PresenterBitmapContainer>& rpParentContainer, + const css::uno::Reference<css::uno::XComponentContext>& rxComponentContext, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::uno::Reference<css::drawing::XPresenterHelper>& rxPresenterHelper = nullptr); + ~PresenterBitmapContainer(); + PresenterBitmapContainer(const PresenterBitmapContainer&) = delete; + PresenterBitmapContainer& operator=(const PresenterBitmapContainer&) = delete; + + void Initialize ( + const css::uno::Reference<css::uno::XComponentContext>& rxComponentContext); + + /** Return the bitmap set that is associated with the given name. + */ + std::shared_ptr<BitmapDescriptor> GetBitmap (const OUString& rsName) const; + + static std::shared_ptr<BitmapDescriptor> LoadBitmap ( + const css::uno::Reference<css::container::XHierarchicalNameAccess>& rxNode, + const OUString& rsPathToBitmapNode, + const css::uno::Reference<css::drawing::XPresenterHelper>& rxPresenterHelper, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const std::shared_ptr<BitmapDescriptor>& rpDefaultBitmap); + +private: + std::shared_ptr<PresenterBitmapContainer> mpParentContainer; + typedef ::std::map<OUString, std::shared_ptr<BitmapDescriptor> > BitmapContainer; + BitmapContainer maIconContainer; + css::uno::Reference<css::rendering::XCanvas> mxCanvas; + css::uno::Reference<css::drawing::XPresenterHelper> mxPresenterHelper; + + void LoadBitmaps ( + const css::uno::Reference<css::container::XNameAccess>& rsRootNode); + void ProcessBitmap ( + const OUString& rsKey, + const css::uno::Reference<css::beans::XPropertySet>& rProperties); + static std::shared_ptr<BitmapDescriptor> LoadBitmap ( + const css::uno::Reference<css::beans::XPropertySet>& rxProperties, + const css::uno::Reference<css::drawing::XPresenterHelper>& rxPresenterHelper, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const std::shared_ptr<PresenterBitmapContainer::BitmapDescriptor>& rpDefault); + static BitmapDescriptor::TexturingMode + StringToTexturingMode (std::u16string_view rsTexturingMode); +}; + +typedef PresenterBitmapContainer::BitmapDescriptor PresenterBitmapDescriptor; +typedef std::shared_ptr<PresenterBitmapContainer::BitmapDescriptor> SharedBitmapDescriptor; + +} // end of namespace ::sdext::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterButton.cxx b/sdext/source/presenter/PresenterButton.cxx new file mode 100644 index 000000000..61de170c0 --- /dev/null +++ b/sdext/source/presenter/PresenterButton.cxx @@ -0,0 +1,447 @@ +/* -*- 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 "PresenterButton.hxx" +#include "PresenterCanvasHelper.hxx" +#include "PresenterController.hxx" +#include "PresenterGeometryHelper.hxx" +#include "PresenterPaintManager.hxx" +#include "PresenterUIPainter.hxx" +#include <com/sun/star/awt/PosSize.hpp> +#include <com/sun/star/awt/XWindowPeer.hpp> +#include <com/sun/star/drawing/XPresenterHelper.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/TextDirection.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace sdext::presenter { + +const double gnHorizontalBorder (15); +const double gnVerticalBorder (5); + +::rtl::Reference<PresenterButton> PresenterButton::Create ( + const css::uno::Reference<css::uno::XComponentContext>& rxComponentContext, + const ::rtl::Reference<PresenterController>& rpPresenterController, + const std::shared_ptr<PresenterTheme>& rpTheme, + const css::uno::Reference<css::awt::XWindow>& rxParentWindow, + const css::uno::Reference<css::rendering::XCanvas>& rxParentCanvas, + const OUString& rsConfigurationName) +{ + Reference<beans::XPropertySet> xProperties (GetConfigurationProperties( + rxComponentContext, + rsConfigurationName)); + if (xProperties.is()) + { + OUString sText; + OUString sAction; + PresenterConfigurationAccess::GetProperty(xProperties, "Text") >>= sText; + PresenterConfigurationAccess::GetProperty(xProperties, "Action") >>= sAction; + + PresenterTheme::SharedFontDescriptor pFont; + if (rpTheme != nullptr) + pFont = rpTheme->GetFont("ButtonFont"); + + PresenterTheme::SharedFontDescriptor pMouseOverFont; + if (rpTheme != nullptr) + pMouseOverFont = rpTheme->GetFont("ButtonMouseOverFont"); + + rtl::Reference<PresenterButton> pButton ( + new PresenterButton( + rxComponentContext, + rpPresenterController, + rpTheme, + rxParentWindow, + pFont, + pMouseOverFont, + sText, + sAction)); + pButton->SetCanvas(rxParentCanvas, rxParentWindow); + return pButton; + } + else + return nullptr; +} + +PresenterButton::PresenterButton ( + const css::uno::Reference<css::uno::XComponentContext>& rxComponentContext, + const ::rtl::Reference<PresenterController>& rpPresenterController, + const std::shared_ptr<PresenterTheme>& rpTheme, + const css::uno::Reference<css::awt::XWindow>& rxParentWindow, + const PresenterTheme::SharedFontDescriptor& rpFont, + const PresenterTheme::SharedFontDescriptor& rpMouseOverFont, + const OUString& rsText, + const OUString& rsAction) + : PresenterButtonInterfaceBase(m_aMutex), + mpPresenterController(rpPresenterController), + mpTheme(rpTheme), + msText(rsText), + mpFont(rpFont), + mpMouseOverFont(rpMouseOverFont), + msAction(rsAction), + maButtonSize(-1,-1), + meState(PresenterBitmapDescriptor::Normal) +{ + try + { + Reference<lang::XMultiComponentFactory> xFactory (rxComponentContext->getServiceManager()); + if ( ! xFactory.is()) + throw RuntimeException(); + + mxPresenterHelper.set( + xFactory->createInstanceWithContext( + "com.sun.star.comp.Draw.PresenterHelper", + rxComponentContext), + UNO_QUERY_THROW); + + if (mxPresenterHelper.is()) + mxWindow = mxPresenterHelper->createWindow(rxParentWindow, + false, + false, + false, + false); + + // Make the background transparent. + Reference<awt::XWindowPeer> xPeer (mxWindow, UNO_QUERY_THROW); + xPeer->setBackground(0xff000000); + + mxWindow->setVisible(true); + mxWindow->addPaintListener(this); + mxWindow->addMouseListener(this); + } + catch (RuntimeException&) + { + } +} + +PresenterButton::~PresenterButton() +{ +} + +void SAL_CALL PresenterButton::disposing() +{ + if (mxCanvas.is()) + { + Reference<lang::XComponent> xComponent (mxCanvas, UNO_QUERY); + mxCanvas = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + + if (mxWindow.is()) + { + mxWindow->removePaintListener(this); + mxWindow->removeMouseListener(this); + Reference<lang::XComponent> xComponent = mxWindow; + mxWindow = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } +} + +void PresenterButton::SetCenter (const css::geometry::RealPoint2D& rLocation) +{ + if (mxCanvas.is()) + { + Invalidate(); + + maCenter = rLocation; + mxWindow->setPosSize( + sal_Int32(0.5 + maCenter.X - maButtonSize.Width/2), + sal_Int32(0.5 + maCenter.Y - maButtonSize.Height/2), + maButtonSize.Width, + maButtonSize.Height, + awt::PosSize::POSSIZE); + + Invalidate(); + } + else + { + // The button can not be painted but we can at least store the new center. + maCenter = rLocation; + } +} + +void PresenterButton::SetCanvas ( + const css::uno::Reference<css::rendering::XCanvas>& rxParentCanvas, + const css::uno::Reference<css::awt::XWindow>& rxParentWindow) +{ + if (mxCanvas.is()) + { + Reference<lang::XComponent> xComponent (mxCanvas, UNO_QUERY); + mxCanvas = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + + if (!(mxPresenterHelper.is() && rxParentCanvas.is() && rxParentWindow.is())) + return; + + mxCanvas = mxPresenterHelper->createSharedCanvas ( + Reference<rendering::XSpriteCanvas>(rxParentCanvas, UNO_QUERY), + rxParentWindow, + rxParentCanvas, + rxParentWindow, + mxWindow); + if (mxCanvas.is()) + { + SetupButtonBitmaps(); + SetCenter(maCenter); + } +} + +css::geometry::IntegerSize2D const & PresenterButton::GetSize() +{ + if (maButtonSize.Width < 0) + CalculateButtonSize(); + return maButtonSize; +} + +//----- XPaintListener -------------------------------------------------------- + +void SAL_CALL PresenterButton::windowPaint (const css::awt::PaintEvent& rEvent) +{ + ThrowIfDisposed(); + if (!(mxWindow.is() && mxCanvas.is())) + return; + + Reference<rendering::XBitmap> xBitmap; + if (meState == PresenterBitmapDescriptor::MouseOver) + xBitmap = mxMouseOverBitmap; + else + xBitmap = mxNormalBitmap; + if ( ! xBitmap.is()) + return; + + rendering::ViewState aViewState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr); + rendering::RenderState aRenderState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + PresenterGeometryHelper::CreatePolygon(rEvent.UpdateRect, mxCanvas->getDevice()), + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + + mxCanvas->drawBitmap(xBitmap, aViewState, aRenderState); + + Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY); + if (xSpriteCanvas.is()) + xSpriteCanvas->updateScreen(false); +} + +//----- XMouseListener -------------------------------------------------------- + +void SAL_CALL PresenterButton::mousePressed (const css::awt::MouseEvent&) +{ + ThrowIfDisposed(); + meState = PresenterBitmapDescriptor::ButtonDown; +} + +void SAL_CALL PresenterButton::mouseReleased (const css::awt::MouseEvent&) +{ + ThrowIfDisposed(); + + if (meState == PresenterBitmapDescriptor::ButtonDown) + { + OSL_ASSERT(mpPresenterController); + mpPresenterController->DispatchUnoCommand(msAction); + + meState = PresenterBitmapDescriptor::Normal; + Invalidate(); + } +} + +void SAL_CALL PresenterButton::mouseEntered (const css::awt::MouseEvent&) +{ + ThrowIfDisposed(); + meState = PresenterBitmapDescriptor::MouseOver; + Invalidate(); +} + +void SAL_CALL PresenterButton::mouseExited (const css::awt::MouseEvent&) +{ + ThrowIfDisposed(); + meState = PresenterBitmapDescriptor::Normal; + Invalidate(); +} + +//----- lang::XEventListener -------------------------------------------------- + +void SAL_CALL PresenterButton::disposing (const css::lang::EventObject& rEvent) +{ + if (rEvent.Source == mxWindow) + mxWindow = nullptr; +} + + +css::geometry::IntegerSize2D PresenterButton::CalculateButtonSize() +{ + if (mpFont && !mpFont->mxFont.is() && mxCanvas.is()) + mpFont->PrepareFont(mxCanvas); + if (!mpFont || !mpFont->mxFont.is()) + return geometry::IntegerSize2D(-1,-1); + + geometry::RealSize2D aTextSize (PresenterCanvasHelper::GetTextSize(mpFont->mxFont,msText)); + + return geometry::IntegerSize2D ( + sal_Int32(0.5 + aTextSize.Width + 2*gnHorizontalBorder), + sal_Int32(0.5 + aTextSize.Height + 2*gnVerticalBorder)); +} + +void PresenterButton::RenderButton ( + const Reference<rendering::XCanvas>& rxCanvas, + const geometry::IntegerSize2D& rSize, + const PresenterTheme::SharedFontDescriptor& rpFont, + const PresenterBitmapDescriptor::Mode eMode, + const SharedBitmapDescriptor& rpLeft, + const SharedBitmapDescriptor& rpCenter, + const SharedBitmapDescriptor& rpRight) +{ + if ( ! rxCanvas.is()) + return; + + const awt::Rectangle aBox(0,0, rSize.Width, rSize.Height); + + PresenterUIPainter::PaintHorizontalBitmapComposite ( + rxCanvas, + aBox, + aBox, + GetBitmap(rpLeft, eMode), + GetBitmap(rpCenter, eMode), + GetBitmap(rpRight, eMode)); + + if (!rpFont || ! rpFont->mxFont.is()) + return; + + const rendering::StringContext aContext (msText, 0, msText.getLength()); + const Reference<rendering::XTextLayout> xLayout ( + rpFont->mxFont->createTextLayout(aContext,rendering::TextDirection::WEAK_LEFT_TO_RIGHT,0)); + const geometry::RealRectangle2D aTextBBox (xLayout->queryTextBounds()); + + rendering::RenderState aRenderState (geometry::AffineMatrix2D(1,0,0, 0,1,0), nullptr, + Sequence<double>(4), rendering::CompositeOperation::SOURCE); + PresenterCanvasHelper::SetDeviceColor(aRenderState, rpFont->mnColor); + + aRenderState.AffineTransform.m02 = (rSize.Width - aTextBBox.X2 + aTextBBox.X1)/2; + aRenderState.AffineTransform.m12 = (rSize.Height - aTextBBox.Y2 + aTextBBox.Y1)/2 - aTextBBox.Y1; + + /// this is responsible of the close button + rxCanvas->drawTextLayout( + xLayout, + rendering::ViewState(geometry::AffineMatrix2D(1,0,0, 0,1,0), nullptr), + aRenderState); +} + +void PresenterButton::Invalidate() +{ + mpPresenterController->GetPaintManager()->Invalidate(mxWindow); +} + +Reference<rendering::XBitmap> PresenterButton::GetBitmap ( + const SharedBitmapDescriptor& mpIcon, + const PresenterBitmapDescriptor::Mode eMode) +{ + if (mpIcon) + return mpIcon->GetBitmap(eMode); + else + { + OSL_ASSERT(mpIcon); + return nullptr; + } +} + +void PresenterButton::SetupButtonBitmaps() +{ + if ( ! mxCanvas.is()) + return; + if ( ! mxCanvas->getDevice().is()) + return; + + // Get the bitmaps for the button border. + SharedBitmapDescriptor pLeftBitmap (mpTheme->GetBitmap("ButtonFrameLeft")); + SharedBitmapDescriptor pCenterBitmap(mpTheme->GetBitmap("ButtonFrameCenter")); + SharedBitmapDescriptor pRightBitmap(mpTheme->GetBitmap("ButtonFrameRight")); + + maButtonSize = CalculateButtonSize(); + + if (maButtonSize.Height<=0 && maButtonSize.Width<= 0) + return; + + mxNormalBitmap = mxCanvas->getDevice()->createCompatibleAlphaBitmap(maButtonSize); + Reference<rendering::XCanvas> xCanvas (mxNormalBitmap, UNO_QUERY); + if (xCanvas.is()) + RenderButton( + xCanvas, + maButtonSize, + mpFont, + PresenterBitmapDescriptor::Normal, + pLeftBitmap, + pCenterBitmap, + pRightBitmap); + + mxMouseOverBitmap = mxCanvas->getDevice()->createCompatibleAlphaBitmap(maButtonSize); + xCanvas.set(mxMouseOverBitmap, UNO_QUERY); + if (mpMouseOverFont && !mpMouseOverFont->mxFont.is() && mxCanvas.is()) + mpMouseOverFont->PrepareFont(mxCanvas); + if (xCanvas.is()) + RenderButton( + xCanvas, + maButtonSize, + mpMouseOverFont, + PresenterBitmapDescriptor::MouseOver, + pLeftBitmap, + pCenterBitmap, + pRightBitmap); +} + +Reference<beans::XPropertySet> PresenterButton::GetConfigurationProperties ( + const css::uno::Reference<css::uno::XComponentContext>& rxComponentContext, + const OUString& rsConfigurationName) +{ + PresenterConfigurationAccess aConfiguration ( + rxComponentContext, + PresenterConfigurationAccess::msPresenterScreenRootName, + PresenterConfigurationAccess::READ_ONLY); + return Reference<beans::XPropertySet>( + PresenterConfigurationAccess::Find ( + Reference<container::XNameAccess>( + aConfiguration.GetConfigurationNode("PresenterScreenSettings/Buttons"), + UNO_QUERY), + [&rsConfigurationName](OUString const&, uno::Reference<beans::XPropertySet> const& xProps) -> bool + { + return PresenterConfigurationAccess::IsStringPropertyEqual( + rsConfigurationName, "Name", xProps); + }), + UNO_QUERY); +} + +void PresenterButton::ThrowIfDisposed() const +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ( + "PresenterButton object has already been disposed", + const_cast<uno::XWeak*>(static_cast<const uno::XWeak*>(this))); + } +} + +} // end of namespace sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterButton.hxx b/sdext/source/presenter/PresenterButton.hxx new file mode 100644 index 000000000..f722e7da1 --- /dev/null +++ b/sdext/source/presenter/PresenterButton.hxx @@ -0,0 +1,138 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERBUTTON_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERBUTTON_HXX + +#include "PresenterBitmapContainer.hxx" +#include "PresenterTheme.hxx" +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/awt/XPaintListener.hpp> +#include <com/sun/star/awt/XMouseListener.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XBitmap.hpp> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <rtl/ref.hxx> + +namespace sdext::presenter { + +class PresenterController; + +typedef ::cppu::WeakComponentImplHelper < + css::awt::XPaintListener, + css::awt::XMouseListener +> PresenterButtonInterfaceBase; + +/** Button for the presenter screen. It displays a text surrounded by a + frame. +*/ +class PresenterButton + : private ::cppu::BaseMutex, + public PresenterButtonInterfaceBase +{ +public: + static ::rtl::Reference<PresenterButton> Create ( + const css::uno::Reference<css::uno::XComponentContext>& rxComponentContext, + const ::rtl::Reference<PresenterController>& rpPresenterController, + const std::shared_ptr<PresenterTheme>& rpTheme, + const css::uno::Reference<css::awt::XWindow>& rxParentWindow, + const css::uno::Reference<css::rendering::XCanvas>& rxParentCanvas, + const OUString& rsConfigurationName); + virtual ~PresenterButton() override; + PresenterButton(const PresenterButton&) = delete; + PresenterButton& operator=(const PresenterButton&) = delete; + + virtual void SAL_CALL disposing() override; + + void SetCenter (const css::geometry::RealPoint2D& rLocation); + void SetCanvas ( + const css::uno::Reference<css::rendering::XCanvas>& rxParentCanvas, + const css::uno::Reference<css::awt::XWindow>& rxParentWindow); + css::geometry::IntegerSize2D const & GetSize(); + + // XPaintListener + + virtual void SAL_CALL windowPaint (const css::awt::PaintEvent& rEvent) override; + + // XMouseListener + + virtual void SAL_CALL mousePressed (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseReleased (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseEntered (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseExited (const css::awt::MouseEvent& rEvent) override; + + // lang::XEventListener + virtual void SAL_CALL disposing (const css::lang::EventObject& rEvent) override; + +private: + ::rtl::Reference<PresenterController> mpPresenterController; + std::shared_ptr<PresenterTheme> mpTheme; + css::uno::Reference<css::awt::XWindow> mxWindow; + css::uno::Reference<css::rendering::XCanvas> mxCanvas; + css::uno::Reference<css::drawing::XPresenterHelper> mxPresenterHelper; + const OUString msText; + const PresenterTheme::SharedFontDescriptor mpFont; + const PresenterTheme::SharedFontDescriptor mpMouseOverFont; + const OUString msAction; + css::geometry::RealPoint2D maCenter; + css::geometry::IntegerSize2D maButtonSize; + PresenterBitmapDescriptor::Mode meState; + css::uno::Reference<css::rendering::XBitmap> mxNormalBitmap; + css::uno::Reference<css::rendering::XBitmap> mxMouseOverBitmap; + + PresenterButton ( + const css::uno::Reference<css::uno::XComponentContext>& rxComponentContext, + const ::rtl::Reference<PresenterController>& rpPresenterController, + const std::shared_ptr<PresenterTheme>& rpTheme, + const css::uno::Reference<css::awt::XWindow>& rxParentWindow, + const PresenterTheme::SharedFontDescriptor& rFont, + const PresenterTheme::SharedFontDescriptor& rMouseOverFont, + const OUString& rxText, + const OUString& rxAction); + void RenderButton ( + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::geometry::IntegerSize2D& rSize, + const PresenterTheme::SharedFontDescriptor& rFont, + const PresenterBitmapDescriptor::Mode eMode, + const SharedBitmapDescriptor& rpLeft, + const SharedBitmapDescriptor& rpCenter, + const SharedBitmapDescriptor& rpRight); + css::geometry::IntegerSize2D CalculateButtonSize(); + void Invalidate(); + static css::uno::Reference<css::rendering::XBitmap> GetBitmap ( + const SharedBitmapDescriptor& mpIcon, + const PresenterBitmapDescriptor::Mode eMode); + void SetupButtonBitmaps(); + static css::uno::Reference<css::beans::XPropertySet> GetConfigurationProperties ( + const css::uno::Reference<css::uno::XComponentContext>& rxComponentContext, + const OUString& rsConfigurationName); + + /// @throws css::lang::DisposedException + void ThrowIfDisposed() const; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterCanvasHelper.cxx b/sdext/source/presenter/PresenterCanvasHelper.cxx new file mode 100644 index 000000000..4ff103958 --- /dev/null +++ b/sdext/source/presenter/PresenterCanvasHelper.cxx @@ -0,0 +1,289 @@ +/* -*- 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 "PresenterCanvasHelper.hxx" + +#include "PresenterGeometryHelper.hxx" +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <osl/diagnose.h> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace sdext::presenter { + +PresenterCanvasHelper::PresenterCanvasHelper() + : maDefaultViewState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr), + maDefaultRenderState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE) +{ +} + +PresenterCanvasHelper::~PresenterCanvasHelper() +{ +} + +void PresenterCanvasHelper::Paint ( + const SharedBitmapDescriptor& rpBitmap, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rRepaintBox, + const css::awt::Rectangle& rOuterBoundingBox, + const css::awt::Rectangle& rContentBoundingBox) const +{ + PaintRectangle(rpBitmap,rxCanvas,rRepaintBox,rOuterBoundingBox,rContentBoundingBox, + maDefaultViewState, maDefaultRenderState); +} + +void PresenterCanvasHelper::PaintRectangle ( + const SharedBitmapDescriptor& rpBitmap, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rRepaintBox, + const css::awt::Rectangle& rOuterBoundingBox, + const css::awt::Rectangle& rContentBoundingBox, + const css::rendering::ViewState& rDefaultViewState, + const css::rendering::RenderState& rDefaultRenderState) +{ + if (!rpBitmap) + return; + + if ( ! rxCanvas.is() || ! rxCanvas->getDevice().is()) + return; + + // Create a clip polypolygon that has the content box as hole. + ::std::vector<awt::Rectangle> aRectangles; + aRectangles.reserve(2); + aRectangles.push_back( + PresenterGeometryHelper::Intersection(rRepaintBox, rOuterBoundingBox)); + if (rContentBoundingBox.Width > 0 && rContentBoundingBox.Height > 0) + aRectangles.push_back( + PresenterGeometryHelper::Intersection(rRepaintBox, rContentBoundingBox)); + Reference<rendering::XPolyPolygon2D> xPolyPolygon ( + PresenterGeometryHelper::CreatePolygon( + aRectangles, + rxCanvas->getDevice())); + if ( ! xPolyPolygon.is()) + return; + xPolyPolygon->setFillRule(rendering::FillRule_EVEN_ODD); + + if (rpBitmap->GetNormalBitmap().is()) + { + if (rpBitmap->meHorizontalTexturingMode == PresenterBitmapDescriptor::Repeat + || rpBitmap->meVerticalTexturingMode == PresenterBitmapDescriptor::Repeat) + { + PaintTiledBitmap( + rpBitmap->GetNormalBitmap(), + rxCanvas, + rRepaintBox, + xPolyPolygon, + rContentBoundingBox, + rDefaultViewState, + rDefaultRenderState); + } + else + { + PaintBitmap( + rpBitmap->GetNormalBitmap(), + awt::Point(rOuterBoundingBox.X, rOuterBoundingBox.Y), + rxCanvas, + rRepaintBox, + xPolyPolygon, + rDefaultViewState, + rDefaultRenderState); + } + } + else + { + PaintColor( + rpBitmap->maReplacementColor, + rxCanvas, + rRepaintBox, + xPolyPolygon, + rDefaultViewState, + rDefaultRenderState); + } +} + +void PresenterCanvasHelper::PaintTiledBitmap ( + const css::uno::Reference<css::rendering::XBitmap>& rxTexture, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rRepaintBox, + const css::uno::Reference<css::rendering::XPolyPolygon2D>& rxPolygon, + const css::awt::Rectangle& rHole, + const css::rendering::ViewState& rDefaultViewState, + const css::rendering::RenderState& rDefaultRenderState) +{ + if ( ! rxCanvas.is() || ! rxCanvas->getDevice().is()) + return; + + if ( ! rxTexture.is()) + return; + + if ( ! rxPolygon.is()) + return; + + rendering::ViewState aViewState (rDefaultViewState); + aViewState.Clip = rxPolygon; + + // Create a local render state at which the location of the bitmap is + // set. + rendering::RenderState aRenderState (rDefaultRenderState); + + // Tile the bitmap over the repaint box. + const geometry::IntegerSize2D aBitmapSize (rxTexture->getSize()); + if( aBitmapSize.Width < 1 || aBitmapSize.Height < 1) + return; + + const sal_Int32 nLeft = (rRepaintBox.X / aBitmapSize.Width) * aBitmapSize.Width; + const sal_Int32 nTop = (rRepaintBox.Y / aBitmapSize.Height) * aBitmapSize.Height; + const sal_Int32 nRight = ((rRepaintBox.X + rRepaintBox.Width - 1 + aBitmapSize.Width - 1) + / aBitmapSize.Width) * aBitmapSize.Width; + const sal_Int32 nBottom = ((rRepaintBox.Y + rRepaintBox.Height - 1 + aBitmapSize.Height - 1) + / aBitmapSize.Height) * aBitmapSize.Height; + + for (sal_Int32 nY=nTop; nY<=nBottom; nY+=aBitmapSize.Height) + for (sal_Int32 nX=nLeft; nX<=nRight; nX+=aBitmapSize.Width) + { + if (PresenterGeometryHelper::IsInside( + awt::Rectangle(nX,nY,aBitmapSize.Width,aBitmapSize.Height), + rHole)) + { + continue; + } + aRenderState.AffineTransform.m02 = nX; + aRenderState.AffineTransform.m12 = nY; + rxCanvas->drawBitmap( + rxTexture, + aViewState, + aRenderState); + } +} + +void PresenterCanvasHelper::PaintBitmap ( + const css::uno::Reference<css::rendering::XBitmap>& rxBitmap, + const awt::Point& rLocation, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rRepaintBox, + const css::uno::Reference<css::rendering::XPolyPolygon2D>& rxPolygon, + const css::rendering::ViewState& rDefaultViewState, + const css::rendering::RenderState& rDefaultRenderState) +{ + if ( ! rxCanvas.is() || ! rxCanvas->getDevice().is()) + return; + + if ( ! rxBitmap.is()) + return; + + if ( ! rxPolygon.is()) + return; + + // Set the repaint box as clip rectangle at the view state. + rendering::ViewState aViewState (rDefaultViewState); + aViewState.Clip = PresenterGeometryHelper::CreatePolygon(rRepaintBox, rxCanvas->getDevice()); + + // Setup the rendering state so that the bitmap is painted top left in + // the polygon bounding box. + rendering::RenderState aRenderState (rDefaultRenderState); + aRenderState.AffineTransform = geometry::AffineMatrix2D(1,0, rLocation.X, 0,1,rLocation.Y); + aRenderState.Clip = rxPolygon; + + rxCanvas->drawBitmap( + rxBitmap, + aViewState, + aRenderState); +} + +void PresenterCanvasHelper::PaintColor ( + const css::util::Color nColor, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rRepaintBox, + const css::uno::Reference<css::rendering::XPolyPolygon2D>& rxPolygon, + const css::rendering::ViewState& rDefaultViewState, + const css::rendering::RenderState& rDefaultRenderState) +{ + if ( ! rxCanvas.is() || ! rxCanvas->getDevice().is()) + return; + + if ( ! rxPolygon.is()) + return; + + // Set the repaint box as clip rectangle at the view state. + rendering::ViewState aViewState (rDefaultViewState); + aViewState.Clip = PresenterGeometryHelper::CreatePolygon(rRepaintBox, rxCanvas->getDevice()); + + // Setup the rendering state to use the given color. + rendering::RenderState aRenderState (rDefaultRenderState); + SetDeviceColor(aRenderState, nColor); + + rxCanvas->fillPolyPolygon( + rxPolygon, + aViewState, + aRenderState); +} + +void PresenterCanvasHelper::SetDeviceColor( + rendering::RenderState& rRenderState, + const util::Color aColor) +{ + // Other component counts then 4 (RGBA) are not accepted (anymore). + + OSL_ASSERT(rRenderState.DeviceColor.getLength() == 4); + if (rRenderState.DeviceColor.getLength() == 4) + { + auto pDeviceColor = rRenderState.DeviceColor.getArray(); + pDeviceColor[0] = ((aColor >> 16) & 0x0ff) / 255.0; + pDeviceColor[1] = ((aColor >> 8) & 0x0ff) / 255.0; + pDeviceColor[2] = ((aColor >> 0) & 0x0ff) / 255.0; + pDeviceColor[3] = 1.0 - ((aColor >> 24) & 0x0ff) / 255.0; + } +} + +css::geometry::RealRectangle2D PresenterCanvasHelper::GetTextBoundingBox ( + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont, + const OUString& rsText, + const sal_Int8 nTextDirection) +{ + if (rxFont.is() && !rsText.isEmpty()) + { + rendering::StringContext aContext (rsText, 0, rsText.getLength()); + Reference<rendering::XTextLayout> xLayout ( + rxFont->createTextLayout(aContext, nTextDirection, 0)); + return xLayout->queryTextBounds(); + } + else + { + return geometry::RealRectangle2D(0,0,0,0); + } +} + +css::geometry::RealSize2D PresenterCanvasHelper::GetTextSize ( + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont, + const OUString& rsText) +{ + const geometry::RealRectangle2D aTextBBox (GetTextBoundingBox(rxFont, rsText)); + return css::geometry::RealSize2D(aTextBBox.X2 - aTextBBox.X1, aTextBBox.Y2 - aTextBBox.Y1); +} + +} // end of namespace sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterCanvasHelper.hxx b/sdext/source/presenter/PresenterCanvasHelper.hxx new file mode 100644 index 000000000..8902a9712 --- /dev/null +++ b/sdext/source/presenter/PresenterCanvasHelper.hxx @@ -0,0 +1,107 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERCANVASHELPER_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERCANVASHELPER_HXX + +#include "PresenterBitmapContainer.hxx" +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/rendering/TextDirection.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XCanvasFont.hpp> +#include <com/sun/star/rendering/XPolyPolygon2D.hpp> + +namespace sdext::presenter { + +/** Collection of functions to ease the life of a canvas user. +*/ +class PresenterCanvasHelper +{ +public: + PresenterCanvasHelper(); + ~PresenterCanvasHelper(); + PresenterCanvasHelper(const PresenterCanvasHelper&) = delete; + PresenterCanvasHelper& operator=(const PresenterCanvasHelper&) = delete; + + void Paint ( + const SharedBitmapDescriptor& rpBitmap, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rRepaintBox, + const css::awt::Rectangle& rBackgroundBoundingBox, + const css::awt::Rectangle& rContentBoundingBox) const; + + static void PaintRectangle ( + const SharedBitmapDescriptor& rpBitmap, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rRepaintBox, + const css::awt::Rectangle& rBackgroundBoundingBox, + const css::awt::Rectangle& rContentBoundingBox, + const css::rendering::ViewState& rDefaultViewState, + const css::rendering::RenderState& rDefaultRenderState); + + static void SetDeviceColor( + css::rendering::RenderState& rRenderState, + const css::util::Color aColor); + + static css::geometry::RealRectangle2D GetTextBoundingBox ( + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont, + const OUString& rsText, + const sal_Int8 = css::rendering::TextDirection::WEAK_LEFT_TO_RIGHT); + + static css::geometry::RealSize2D GetTextSize ( + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont, + const OUString& rsText ); + +private: + const css::rendering::ViewState maDefaultViewState; + const css::rendering::RenderState maDefaultRenderState; + + static void PaintTiledBitmap ( + const css::uno::Reference<css::rendering::XBitmap>& rxTexture, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rRepaintBox, + const css::uno::Reference<css::rendering::XPolyPolygon2D>& rxPolygon, + const css::awt::Rectangle& rHole, + const css::rendering::ViewState& rDefaultViewState, + const css::rendering::RenderState& rDefaultRenderState); + + static void PaintBitmap ( + const css::uno::Reference<css::rendering::XBitmap>& rxBitmap, + const css::awt::Point& rLocation, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rRepaintBox, + const css::uno::Reference<css::rendering::XPolyPolygon2D>& rxPolygon, + const css::rendering::ViewState& rDefaultViewState, + const css::rendering::RenderState& rDefaultRenderState); + + static void PaintColor ( + const css::util::Color nColor, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rRepaintBox, + const css::uno::Reference<css::rendering::XPolyPolygon2D>& rxPolygon, + const css::rendering::ViewState& rDefaultViewState, + const css::rendering::RenderState& rDefaultRenderState); +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterConfigurationAccess.cxx b/sdext/source/presenter/PresenterConfigurationAccess.cxx new file mode 100644 index 000000000..e7bd4524a --- /dev/null +++ b/sdext/source/presenter/PresenterConfigurationAccess.cxx @@ -0,0 +1,274 @@ +/* -*- 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 "PresenterConfigurationAccess.hxx" + +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/util/XChangesBatch.hpp> +#include <comphelper/propertysequence.hxx> +#include <osl/diagnose.h> +#include <tools/diagnose_ex.h> +#include <sal/log.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace sdext::presenter { + +PresenterConfigurationAccess::PresenterConfigurationAccess ( + const Reference<XComponentContext>& rxContext, + const OUString& rsRootName, + WriteMode eMode) +{ + try + { + if (rxContext.is()) + { + uno::Sequence<uno::Any> aCreationArguments(comphelper::InitAnyPropertySequence( + { + {"nodepath", uno::Any(rsRootName)}, + {"depth", uno::Any(sal_Int32(-1))} + })); + + OUString sAccessService; + if (eMode == READ_ONLY) + sAccessService = "com.sun.star.configuration.ConfigurationAccess"; + else + sAccessService = "com.sun.star.configuration.ConfigurationUpdateAccess"; + + Reference<lang::XMultiServiceFactory> xProvider = + configuration::theDefaultProvider::get( rxContext ); + mxRoot = xProvider->createInstanceWithArguments( + sAccessService, aCreationArguments); + maNode <<= mxRoot; + } + } + catch (const Exception&) + { + DBG_UNHANDLED_EXCEPTION("sdext.presenter", "caught exception while opening configuration"); + } +} + +PresenterConfigurationAccess::~PresenterConfigurationAccess() +{ +} + +bool PresenterConfigurationAccess::IsValid() const +{ + return mxRoot.is(); +} + +Any PresenterConfigurationAccess::GetConfigurationNode (const OUString& sPathToNode) +{ + return GetConfigurationNode( + Reference<container::XHierarchicalNameAccess>(mxRoot, UNO_QUERY), + sPathToNode); +} + +bool PresenterConfigurationAccess::GoToChild (const OUString& rsPathToNode) +{ + if ( ! IsValid()) + return false; + + Reference<container::XHierarchicalNameAccess> xNode (maNode, UNO_QUERY); + if (xNode.is()) + { + maNode = GetConfigurationNode( + Reference<container::XHierarchicalNameAccess>(maNode, UNO_QUERY), + rsPathToNode); + if (Reference<XInterface>(maNode, UNO_QUERY).is()) + return true; + } + + mxRoot = nullptr; + return false; +} + +bool PresenterConfigurationAccess::GoToChild (const Predicate& rPredicate) +{ + if ( ! IsValid()) + return false; + + maNode = Find(Reference<container::XNameAccess>(maNode,UNO_QUERY), rPredicate); + if (Reference<XInterface>(maNode, UNO_QUERY).is()) + return true; + + mxRoot = nullptr; + return false; +} + +bool PresenterConfigurationAccess::SetProperty ( + const OUString& rsPropertyName, + const Any& rValue) +{ + Reference<beans::XPropertySet> xProperties (maNode, UNO_QUERY); + if (xProperties.is()) + { + xProperties->setPropertyValue(rsPropertyName, rValue); + return true; + } + else + return false; +} + +Any PresenterConfigurationAccess::GetConfigurationNode ( + const css::uno::Reference<css::container::XHierarchicalNameAccess>& rxNode, + const OUString& sPathToNode) +{ + if (sPathToNode.isEmpty()) + return Any(rxNode); + + try + { + if (rxNode.is()) + { + return rxNode->getByHierarchicalName(sPathToNode); + } + } + catch (const Exception&) + { + TOOLS_WARN_EXCEPTION("sdext.presenter", "caught exception while getting configuration node " << sPathToNode); + } + + return Any(); +} + +Reference<beans::XPropertySet> PresenterConfigurationAccess::GetNodeProperties ( + const css::uno::Reference<css::container::XHierarchicalNameAccess>& rxNode, + const OUString& rsPathToNode) +{ + return Reference<beans::XPropertySet>(GetConfigurationNode(rxNode, rsPathToNode), UNO_QUERY); +} + +void PresenterConfigurationAccess::CommitChanges() +{ + Reference<util::XChangesBatch> xConfiguration (mxRoot, UNO_QUERY); + if (xConfiguration.is()) + xConfiguration->commitChanges(); +} + +void PresenterConfigurationAccess::ForAll ( + const Reference<container::XNameAccess>& rxContainer, + const ::std::vector<OUString>& rArguments, + const ItemProcessor& rProcessor) +{ + if (!rxContainer.is()) + return; + + ::std::vector<Any> aValues(rArguments.size()); + const Sequence<OUString> aKeys (rxContainer->getElementNames()); + for (const OUString& rsKey : aKeys) + { + bool bHasAllValues (true); + Reference<container::XNameAccess> xSetItem (rxContainer->getByName(rsKey), UNO_QUERY); + Reference<beans::XPropertySet> xSet (xSetItem, UNO_QUERY); + OSL_ASSERT(xSet.is()); + if (xSetItem.is()) + { + // Get from the current item of the container the children + // that match the names in the rArguments list. + for (size_t nValueIndex=0; nValueIndex<aValues.size(); ++nValueIndex) + { + if ( ! xSetItem->hasByName(rArguments[nValueIndex])) + bHasAllValues = false; + else + aValues[nValueIndex] = xSetItem->getByName(rArguments[nValueIndex]); + } + } + else + bHasAllValues = false; + if (bHasAllValues) + rProcessor(aValues); + } +} + +void PresenterConfigurationAccess::ForAll ( + const Reference<container::XNameAccess>& rxContainer, + const PropertySetProcessor& rProcessor) +{ + if (rxContainer.is()) + { + const Sequence<OUString> aKeys (rxContainer->getElementNames()); + for (const OUString& rsKey : aKeys) + { + Reference<beans::XPropertySet> xSet (rxContainer->getByName(rsKey), UNO_QUERY); + if (xSet.is()) + rProcessor(rsKey, xSet); + } + } +} + +Any PresenterConfigurationAccess::Find ( + const Reference<container::XNameAccess>& rxContainer, + const Predicate& rPredicate) +{ + if (rxContainer.is()) + { + const Sequence<OUString> aKeys (rxContainer->getElementNames()); + for (const auto& rKey : aKeys) + { + Reference<beans::XPropertySet> xProperties ( + rxContainer->getByName(rKey), + UNO_QUERY); + if (xProperties.is()) + if (rPredicate(rKey, xProperties)) + return Any(xProperties); + } + } + return Any(); +} + +bool PresenterConfigurationAccess::IsStringPropertyEqual ( + std::u16string_view rsValue, + const OUString& rsPropertyName, + const css::uno::Reference<css::beans::XPropertySet>& rxNode) +{ + OUString sValue; + if (GetProperty(rxNode, rsPropertyName) >>= sValue) + return sValue == rsValue; + else + return false; +} + +Any PresenterConfigurationAccess::GetProperty ( + const Reference<beans::XPropertySet>& rxProperties, + const OUString& rsKey) +{ + OSL_ASSERT(rxProperties.is()); + if ( ! rxProperties.is()) + return Any(); + try + { + Reference<beans::XPropertySetInfo> xInfo (rxProperties->getPropertySetInfo()); + if (xInfo.is()) + if ( ! xInfo->hasPropertyByName(rsKey)) + return Any(); + return rxProperties->getPropertyValue(rsKey); + } + catch (beans::UnknownPropertyException&) + { + } + return Any(); +} + +} // end of namespace sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterConfigurationAccess.hxx b/sdext/source/presenter/PresenterConfigurationAccess.hxx new file mode 100644 index 000000000..afd1b9aa4 --- /dev/null +++ b/sdext/source/presenter/PresenterConfigurationAccess.hxx @@ -0,0 +1,178 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERCONFIGURATIONACCESS_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERCONFIGURATIONACCESS_HXX + +#include <rtl/ustring.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <vector> +#include <functional> + +namespace sdext::presenter { + +/** This class gives access to the configuration. Create an object of this + class for one node of the configuration. This will be the root node. + From this one you can use this class in two ways. + + <p>In a stateless mode (with exception of the root node) you can use static + methods for obtaining child nodes, get values from properties at leaf + nodes and iterate over children of inner nodes.</p> + + <p>In a stateful mode use non-static methods like GoToChild() to + navigate to children.</p> + + <p>Note to call CommitChanges() after making changes to + PresenterConfigurationAccess object that was opened in READ_WRITE mode.</p> +*/ +class PresenterConfigurationAccess +{ +public: + enum WriteMode { READ_WRITE, READ_ONLY }; + typedef ::std::function<bool ( + const OUString&, + const css::uno::Reference<css::beans::XPropertySet>&)> Predicate; + static constexpr OUStringLiteral msPresenterScreenRootName = + u"/org.openoffice.Office.PresenterScreen/"; + + /** Create a new object to access the configuration entries below the + given root. + @param rsRootName + Name of the root. You can use msPresenterScreenRootName to + access the configuration of the presenter screen. + @param eMode + This flag specifies whether to give read-write or read-only + access. + */ + PresenterConfigurationAccess( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const OUString& rsRootName, + WriteMode eMode); + + ~PresenterConfigurationAccess(); + + /** Return a configuration node below the root of the called object. + @param rsPathToNode + The relative path from the root (as given the constructor) to the node. + */ + css::uno::Any GetConfigurationNode ( + const OUString& rsPathToNode); + + /** Return <TRUE/> when opening the configuration (via creating a new + PresenterConfigurationAccess object) or previous calls to + GoToChild() left the called PresenterConfigurationAccess object in a + valid state. + */ + bool IsValid() const; + + /** Move the focused node to the (possibly indirect) child specified by the given path. + */ + bool GoToChild (const OUString& rsPathToNode); + + /** Move the focused node to the first direct child that fulfills the given predicate. + */ + bool GoToChild (const Predicate& rPredicate); + + /** Modify the property child of the currently focused node. Keep in + mind to call CommitChanges() to write the change back to the + configuration. + */ + bool SetProperty (const OUString& rsPropertyName, const css::uno::Any& rValue); + + /** Return a configuration node below the given node. + @param rxNode + The node that acts as root to the given relative path. + @param rsPathToNode + The relative path from the given node to the requested node. + When this string is empty then rxNode is returned. + @return + The type of the returned node varies with the requested node. + It is empty when the node was not found. + */ + static css::uno::Any GetConfigurationNode ( + const css::uno::Reference<css::container::XHierarchicalNameAccess>& rxNode, + const OUString& rsPathToNode); + + static css::uno::Reference<css::beans::XPropertySet> GetNodeProperties ( + const css::uno::Reference<css::container::XHierarchicalNameAccess>& rxNode, + const OUString& rsPathToNode); + + /** Write any changes that have been made back to the configuration. + This call is ignored when the called ConfigurationAccess object was + not create with read-write mode. + */ + void CommitChanges(); + + typedef ::std::function<void ( + const ::std::vector<css::uno::Any>&) > ItemProcessor; + typedef ::std::function<void ( + const OUString&, + const css::uno::Reference<css::beans::XPropertySet>&) > PropertySetProcessor; + + /** Execute a functor for all elements of the given container. + @param rxContainer + The container is a XNameAccess to a list of the configuration. + This can be a node returned by GetConfigurationNode(). + @param rArguments + The functor is called with arguments that are children of each + element of the container. The set of children is specified this + list. + @param rFunctor + The functor to be executed for some or all of the elements in + the given container. + */ + static void ForAll ( + const css::uno::Reference<css::container::XNameAccess>& rxContainer, + const ::std::vector<OUString>& rArguments, + const ItemProcessor& rProcessor); + static void ForAll ( + const css::uno::Reference<css::container::XNameAccess>& rxContainer, + const PropertySetProcessor& rProcessor); + + static css::uno::Any Find ( + const css::uno::Reference<css::container::XNameAccess>& rxContainer, + const Predicate& rPredicate); + + static bool IsStringPropertyEqual ( + std::u16string_view rsValue, + const OUString& rsPropertyName, + const css::uno::Reference<css::beans::XPropertySet>& rxNode); + + /** This method wraps a call to getPropertyValue() and returns an empty + Any instead of throwing an exception when the property does not + exist. + */ + static css::uno::Any GetProperty ( + const css::uno::Reference<css::beans::XPropertySet>& rxProperties, + const OUString& rsKey); + +private: + css::uno::Reference<css::uno::XInterface> mxRoot; + css::uno::Any maNode; +}; + +} // end of namespace sdext::tools + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterController.cxx b/sdext/source/presenter/PresenterController.cxx new file mode 100644 index 000000000..1062d892d --- /dev/null +++ b/sdext/source/presenter/PresenterController.cxx @@ -0,0 +1,1185 @@ +/* -*- 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 <sal/config.h> + +#include "PresenterController.hxx" + +#include "PresenterAccessibility.hxx" +#include "PresenterCanvasHelper.hxx" +#include "PresenterCurrentSlideObserver.hxx" +#include "PresenterScreen.hxx" +#include "PresenterPaintManager.hxx" +#include "PresenterPaneBase.hxx" +#include "PresenterPaneContainer.hxx" +#include "PresenterPaneBorderPainter.hxx" +#include "PresenterTheme.hxx" +#include "PresenterViewFactory.hxx" +#include "PresenterWindowManager.hxx" + +#include <com/sun/star/awt/Key.hpp> +#include <com/sun/star/awt/KeyModifier.hpp> +#include <com/sun/star/awt/MouseButton.hpp> +#include <com/sun/star/container/XNamed.hpp> +#include <com/sun/star/drawing/XDrawView.hpp> +#include <com/sun/star/drawing/XDrawPagesSupplier.hpp> +#include <com/sun/star/drawing/framework/ResourceActivationMode.hpp> +#include <com/sun/star/drawing/framework/ResourceId.hpp> +#include <com/sun/star/drawing/framework/XControllerManager.hpp> +#include <com/sun/star/frame/FrameSearchFlag.hpp> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/presentation/AnimationEffect.hpp> +#include <com/sun/star/presentation/XPresentation.hpp> +#include <com/sun/star/presentation/XPresentationSupplier.hpp> +#include <com/sun/star/rendering/TextDirection.hpp> +#include <com/sun/star/util/URLTransformer.hpp> + +#include <rtl/ustrbuf.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::presentation; +using namespace ::com::sun::star::drawing::framework; + +namespace { + const sal_Int32 ResourceActivationEventType = 0; + const sal_Int32 ResourceDeactivationEventType = 1; + const sal_Int32 ConfigurationUpdateEndEventType = 2; +} + +namespace sdext::presenter { + +IPresentationTime::~IPresentationTime() +{ +} + +PresenterController::InstanceContainer PresenterController::maInstances; + +::rtl::Reference<PresenterController> PresenterController::Instance ( + const css::uno::Reference<css::frame::XFrame>& rxFrame) +{ + InstanceContainer::const_iterator iInstance (maInstances.find(rxFrame)); + if (iInstance != maInstances.end()) + return iInstance->second; + else + return ::rtl::Reference<PresenterController>(); +} + +PresenterController::PresenterController ( + const css::uno::WeakReference<css::lang::XEventListener> &rxScreen, + const Reference<XComponentContext>& rxContext, + const Reference<frame::XController>& rxController, + const Reference<presentation::XSlideShowController>& rxSlideShowController, + const rtl::Reference<PresenterPaneContainer>& rpPaneContainer, + const Reference<XResourceId>& rxMainPaneId) + : PresenterControllerInterfaceBase(m_aMutex), + mxScreen(rxScreen), + mxComponentContext(rxContext), + mxController(rxController), + mxSlideShowController(rxSlideShowController), + mxMainPaneId(rxMainPaneId), + mpPaneContainer(rpPaneContainer), + mnCurrentSlideIndex(-1), + mpWindowManager(new PresenterWindowManager(rxContext,mpPaneContainer,this)), + mpCanvasHelper(std::make_shared<PresenterCanvasHelper>()), + mnPendingSlideNumber(-1), + mbIsAccessibilityActive(false) +{ + OSL_ASSERT(mxController.is()); + + if ( ! mxSlideShowController.is()) + throw lang::IllegalArgumentException( + "missing slide show controller", + static_cast<XWeak*>(this), + 2); + + new PresenterCurrentSlideObserver(this,rxSlideShowController); + + // Listen for configuration changes. + Reference<XControllerManager> xCM (mxController, UNO_QUERY_THROW); + mxConfigurationController = xCM->getConfigurationController(); + if (mxConfigurationController.is()) + { + mxConfigurationController->addConfigurationChangeListener( + this, + "ResourceActivation", + Any(ResourceActivationEventType)); + mxConfigurationController->addConfigurationChangeListener( + this, + "ResourceDeactivation", + Any(ResourceDeactivationEventType)); + mxConfigurationController->addConfigurationChangeListener( + this, + "ConfigurationUpdateEnd", + Any(ConfigurationUpdateEndEventType)); + } + + // Listen for the frame being activated. + Reference<frame::XFrame> xFrame (mxController->getFrame()); + if (xFrame.is()) + xFrame->addFrameActionListener(this); + + // Create the border painter. + mpPaneBorderPainter = new PresenterPaneBorderPainter(rxContext); + mpWindowManager->SetPaneBorderPainter(mpPaneBorderPainter); + + // Create an object that is able to load the bitmaps in a format that is + // supported by the canvas. + Reference<lang::XMultiComponentFactory> xFactory = + rxContext->getServiceManager(); + if ( ! xFactory.is()) + return; + mxPresenterHelper.set( + xFactory->createInstanceWithContext( + "com.sun.star.drawing.PresenterHelper", + rxContext), + UNO_QUERY_THROW); + + if (mxSlideShowController.is()) + { + mxSlideShowController->activate(); + Reference<beans::XPropertySet> xProperties (mxSlideShowController, UNO_QUERY); + if (xProperties.is()) + { + Reference<awt::XWindow> xWindow ( + xProperties->getPropertyValue("ParentWindow"), UNO_QUERY); + if (xWindow.is()) + xWindow->addKeyListener(this); + } + } + + UpdateCurrentSlide(0); + + maInstances[mxController->getFrame()] = this; + + // Create a URLTransformer. + if (xFactory.is()) + { + mxUrlTransformer.set(util::URLTransformer::create(mxComponentContext)); + } +} + +PresenterController::~PresenterController() +{ +} + +void PresenterController::disposing() +{ + maInstances.erase(mxController->getFrame()); + + if (mxMainWindow.is()) + { + mxMainWindow->removeKeyListener(this); + mxMainWindow->removeMouseListener(this); + mxMainWindow = nullptr; + } + if (mxConfigurationController.is()) + mxConfigurationController->removeConfigurationChangeListener(this); + + if (mxController.is()) + { + Reference<frame::XFrame> xFrame (mxController->getFrame()); + if (xFrame.is()) + xFrame->removeFrameActionListener(this); + mxController = nullptr; + } + + Reference<XComponent> xWindowManagerComponent = mpWindowManager; + mpWindowManager = nullptr; + if (xWindowManagerComponent.is()) + xWindowManagerComponent->dispose(); + + mxComponentContext = nullptr; + mxConfigurationController = nullptr; + mxSlideShowController = nullptr; + mxMainPaneId = nullptr; + mpPaneContainer = nullptr; + mnCurrentSlideIndex = -1; + mxCurrentSlide = nullptr; + mxNextSlide = nullptr; + mpTheme.reset(); + { + Reference<lang::XComponent> xComponent = mpPaneBorderPainter; + mpPaneBorderPainter = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + mpCanvasHelper.reset(); + { + Reference<lang::XComponent> xComponent (mxPresenterHelper, UNO_QUERY); + mxPresenterHelper = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + mpPaintManager.reset(); + mnPendingSlideNumber = -1; + { + Reference<lang::XComponent> xComponent (mxUrlTransformer, UNO_QUERY); + mxUrlTransformer = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } +} + +void PresenterController::UpdateCurrentSlide (const sal_Int32 nOffset) +{ + // std::cerr << "Updating current Slide to " << nOffset << std::endl; + GetSlides(nOffset); + UpdatePaneTitles(); + UpdateViews(); + + // Update the accessibility object. + if (IsAccessibilityActive()) + { + mpAccessibleObject->NotifyCurrentSlideChange(); + } +} + +void PresenterController::GetSlides (const sal_Int32 nOffset) +{ + if ( ! mxSlideShowController.is()) + return; + + // Get the current slide from the slide show controller. + mxCurrentSlide = nullptr; + Reference<container::XIndexAccess> xIndexAccess(mxSlideShowController, UNO_QUERY); + try + { + sal_Int32 nSlideIndex = mxSlideShowController->getCurrentSlideIndex() + nOffset; + if (mxSlideShowController->isPaused()) + nSlideIndex = -1; + + if (xIndexAccess.is() && nSlideIndex>=0) + { + if (nSlideIndex < xIndexAccess->getCount()) + { + mnCurrentSlideIndex = nSlideIndex; + mxCurrentSlide.set( xIndexAccess->getByIndex(nSlideIndex), UNO_QUERY); + } + } + } + catch (RuntimeException&) + { + } + + // Get the next slide. + mxNextSlide = nullptr; + try + { + const sal_Int32 nNextSlideIndex (mxSlideShowController->getNextSlideIndex()+nOffset); + if (nNextSlideIndex >= 0) + { + if (xIndexAccess.is()) + { + if (nNextSlideIndex < xIndexAccess->getCount()) + mxNextSlide.set( xIndexAccess->getByIndex(nNextSlideIndex), UNO_QUERY); + } + } + } + catch (RuntimeException&) + { + } +} + +void PresenterController::UpdatePaneTitles() +{ + if ( ! mxSlideShowController.is()) + return; + + // Get placeholders and their values. + static const OUStringLiteral sCurrentSlideNumberPlaceholder (u"CURRENT_SLIDE_NUMBER"); + static const OUStringLiteral sCurrentSlideNamePlaceholder (u"CURRENT_SLIDE_NAME"); + static const OUStringLiteral sSlideCountPlaceholder (u"SLIDE_COUNT"); + + // Get string for slide count. + OUString sSlideCount ("---"); + Reference<container::XIndexAccess> xIndexAccess(mxSlideShowController, UNO_QUERY); + if (xIndexAccess.is()) + sSlideCount = OUString::number(xIndexAccess->getCount()); + + // Get string for current slide index. + OUString sCurrentSlideNumber (OUString::number(mnCurrentSlideIndex + 1)); + + // Get name of the current slide. + OUString sCurrentSlideName; + Reference<container::XNamed> xNamedSlide (mxCurrentSlide, UNO_QUERY); + if (xNamedSlide.is()) + sCurrentSlideName = xNamedSlide->getName(); + Reference<beans::XPropertySet> xSlideProperties (mxCurrentSlide, UNO_QUERY); + if (xSlideProperties.is()) + { + try + { + OUString sName; + if (xSlideProperties->getPropertyValue("LinkDisplayName") >>= sName) + { + // Find out whether the name of the current slide has been + // automatically created or has been set by the user. + if (sName != sCurrentSlideName) + sCurrentSlideName = sName; + } + } + catch (const beans::UnknownPropertyException&) + { + } + } + + // Replace the placeholders with their current values. + for (auto& rxPane : mpPaneContainer->maPanes) + { + OSL_ASSERT(rxPane != nullptr); + + OUString sTemplate (IsAccessibilityActive() + ? rxPane->msAccessibleTitleTemplate + : rxPane->msTitleTemplate); + if (sTemplate.isEmpty()) + continue; + + OUStringBuffer sResult; + sResult.ensureCapacity(sTemplate.getLength()); + + sal_Int32 nIndex (0); + while (true) + { + sal_Int32 nStartIndex = sTemplate.indexOf('%', nIndex); + if (nStartIndex < 0) + { + // Add the remaining part of the string. + sResult.append(sTemplate.subView(nIndex)); + break; + } + else + { + // Add the part preceding the next %. + sResult.append(sTemplate.subView(nIndex, nStartIndex-nIndex)); + + // Get the placeholder + ++nStartIndex; + const sal_Int32 nEndIndex (sTemplate.indexOf('%', nStartIndex+1)); + const std::u16string_view sPlaceholder (sTemplate.subView(nStartIndex, nEndIndex-nStartIndex)); + nIndex = nEndIndex+1; + + // Replace the placeholder with its current value. + if (sPlaceholder == sCurrentSlideNumberPlaceholder) + sResult.append(sCurrentSlideNumber); + else if (sPlaceholder == sCurrentSlideNamePlaceholder) + sResult.append(sCurrentSlideName); + else if (sPlaceholder == sSlideCountPlaceholder) + sResult.append(sSlideCount); + } + } + + rxPane->msTitle = sResult.makeStringAndClear(); + if (rxPane->mxPane.is()) + rxPane->mxPane->SetTitle(rxPane->msTitle); + } +} + +void PresenterController::UpdateViews() +{ + // Tell all views about the slides they should display. + for (const auto& rxPane : mpPaneContainer->maPanes) + { + Reference<drawing::XDrawView> xDrawView (rxPane->mxView, UNO_QUERY); + if (xDrawView.is()) + xDrawView->setCurrentPage(mxCurrentSlide); + } +} + +SharedBitmapDescriptor + PresenterController::GetViewBackground (const OUString& rsViewURL) const +{ + if (mpTheme != nullptr) + { + const OUString sStyleName (mpTheme->GetStyleName(rsViewURL)); + return mpTheme->GetBitmap(sStyleName, "Background"); + } + return SharedBitmapDescriptor(); +} + +PresenterTheme::SharedFontDescriptor + PresenterController::GetViewFont (const OUString& rsViewURL) const +{ + if (mpTheme != nullptr) + { + const OUString sStyleName (mpTheme->GetStyleName(rsViewURL)); + return mpTheme->GetFont(sStyleName); + } + return PresenterTheme::SharedFontDescriptor(); +} + +const std::shared_ptr<PresenterTheme>& PresenterController::GetTheme() const +{ + return mpTheme; +} + +const ::rtl::Reference<PresenterWindowManager>& PresenterController::GetWindowManager() const +{ + return mpWindowManager; +} + +const Reference<presentation::XSlideShowController>& + PresenterController::GetSlideShowController() const +{ + return mxSlideShowController; +} + +const rtl::Reference<PresenterPaneContainer>& PresenterController::GetPaneContainer() const +{ + return mpPaneContainer; +} + +const ::rtl::Reference<PresenterPaneBorderPainter>& PresenterController::GetPaneBorderPainter() const +{ + return mpPaneBorderPainter; +} + +const std::shared_ptr<PresenterCanvasHelper>& PresenterController::GetCanvasHelper() const +{ + return mpCanvasHelper; +} + +const Reference<drawing::XPresenterHelper>& PresenterController::GetPresenterHelper() const +{ + return mxPresenterHelper; +} + +const std::shared_ptr<PresenterPaintManager>& PresenterController::GetPaintManager() const +{ + return mpPaintManager; +} + +void PresenterController::ShowView (const OUString& rsViewURL) +{ + PresenterPaneContainer::SharedPaneDescriptor pDescriptor ( + mpPaneContainer->FindViewURL(rsViewURL)); + if (!pDescriptor) + return; + + pDescriptor->mbIsActive = true; + mxConfigurationController->requestResourceActivation( + pDescriptor->mxPaneId, + ResourceActivationMode_ADD); + mxConfigurationController->requestResourceActivation( + ResourceId::createWithAnchor( + mxComponentContext, + rsViewURL, + pDescriptor->mxPaneId), + ResourceActivationMode_REPLACE); +} + +void PresenterController::HideView (const OUString& rsViewURL) +{ + PresenterPaneContainer::SharedPaneDescriptor pDescriptor ( + mpPaneContainer->FindViewURL(rsViewURL)); + if (pDescriptor) + { + mxConfigurationController->requestResourceDeactivation( + ResourceId::createWithAnchor( + mxComponentContext, + rsViewURL, + pDescriptor->mxPaneId)); + } +} + +void PresenterController::DispatchUnoCommand (const OUString& rsCommand) const +{ + if ( ! mxUrlTransformer.is()) + return; + + util::URL aURL; + aURL.Complete = rsCommand; + mxUrlTransformer->parseStrict(aURL); + + Reference<frame::XDispatch> xDispatch (GetDispatch(aURL)); + if ( ! xDispatch.is()) + return; + + xDispatch->dispatch(aURL, Sequence<beans::PropertyValue>()); +} + +Reference<css::frame::XDispatch> PresenterController::GetDispatch (const util::URL& rURL) const +{ + if ( ! mxController.is()) + return nullptr; + + Reference<frame::XDispatchProvider> xDispatchProvider (mxController->getFrame(), UNO_QUERY); + if ( ! xDispatchProvider.is()) + return nullptr; + + return xDispatchProvider->queryDispatch( + rURL, + OUString(), + frame::FrameSearchFlag::SELF); +} + +util::URL PresenterController::CreateURLFromString (const OUString& rsURL) const +{ + util::URL aURL; + + if (mxUrlTransformer.is()) + { + aURL.Complete = rsURL; + mxUrlTransformer->parseStrict(aURL); + } + + return aURL; +} + +const Reference<drawing::framework::XConfigurationController>& + PresenterController::GetConfigurationController() const +{ + return mxConfigurationController; +} + +const Reference<drawing::XDrawPage>& PresenterController::GetCurrentSlide() const +{ + return mxCurrentSlide; +} + +bool PresenterController::HasTransition (Reference<drawing::XDrawPage> const & rxPage) +{ + bool bTransition = false; + if( rxPage.is() ) + { + Reference<beans::XPropertySet> xSlidePropertySet (rxPage, UNO_QUERY); + try + { + sal_uInt16 aTransitionType = 0; + xSlidePropertySet->getPropertyValue("TransitionType") >>= aTransitionType; + if (aTransitionType > 0) + { + bTransition = true; + } + } + catch (const beans::UnknownPropertyException&) + { + } + } + return bTransition; +} + +bool PresenterController::HasCustomAnimation (Reference<drawing::XDrawPage> const & rxPage) +{ + bool bCustomAnimation = false; + if( rxPage.is() ) + { + sal_uInt32 i, nCount = rxPage->getCount(); + for ( i = 0; i < nCount; i++ ) + { + Reference<drawing::XShape> xShape(rxPage->getByIndex(i), UNO_QUERY); + Reference<beans::XPropertySet> xShapePropertySet(xShape, UNO_QUERY); + presentation::AnimationEffect aEffect = presentation::AnimationEffect_NONE; + presentation::AnimationEffect aTextEffect = presentation::AnimationEffect_NONE; + try + { + xShapePropertySet->getPropertyValue("Effect") >>= aEffect; + xShapePropertySet->getPropertyValue("TextEffect") >>= aTextEffect; + } + catch (const beans::UnknownPropertyException&) + { + } + if( aEffect != presentation::AnimationEffect_NONE || + aTextEffect != presentation::AnimationEffect_NONE ) + { + bCustomAnimation = true; + break; + } + } + } + return bCustomAnimation; +} + +void PresenterController::SetAccessibilityActiveState (const bool bIsActive) +{ + if ( mbIsAccessibilityActive != bIsActive) + { + mbIsAccessibilityActive = bIsActive; + UpdatePaneTitles(); + } +} + + +void PresenterController::HandleMouseClick (const awt::MouseEvent& rEvent) +{ + if (!mxSlideShowController.is()) + return; + + switch (rEvent.Buttons) + { + case awt::MouseButton::LEFT: + if (rEvent.Modifiers == awt::KeyModifier::MOD2) + mxSlideShowController->gotoNextSlide(); + else + mxSlideShowController->gotoNextEffect(); + break; + + case awt::MouseButton::RIGHT: + mxSlideShowController->gotoPreviousSlide(); + break; + + default: + // Other or multiple buttons. + break; + } +} + +void PresenterController::RequestViews ( + const bool bIsSlideSorterActive, + const bool bIsNotesViewActive, + const bool bIsHelpViewActive) +{ + for (const auto& rxPane : mpPaneContainer->maPanes) + { + bool bActivate (true); + const OUString sViewURL (rxPane->msViewURL); + if (sViewURL == PresenterViewFactory::msNotesViewURL) + { + bActivate = bIsNotesViewActive && !bIsSlideSorterActive && !bIsHelpViewActive; + } + else if (sViewURL == PresenterViewFactory::msSlideSorterURL) + { + bActivate = bIsSlideSorterActive; + } + else if (sViewURL == PresenterViewFactory::msCurrentSlidePreviewViewURL + || sViewURL == PresenterViewFactory::msNextSlidePreviewViewURL) + { + bActivate = !bIsSlideSorterActive && ! bIsHelpViewActive; + } + else if (sViewURL == PresenterViewFactory::msToolBarViewURL) + { + bActivate = true; + } + else if (sViewURL == PresenterViewFactory::msHelpViewURL) + { + bActivate = bIsHelpViewActive; + } + + if (bActivate) + ShowView(sViewURL); + else + HideView(sViewURL); + } +} + +void PresenterController::SetPresentationTime(IPresentationTime* pPresentationTime) +{ + mpPresentationTime = pPresentationTime; +} + +IPresentationTime* PresenterController::GetPresentationTime() +{ + return mpPresentationTime; +} + +//----- XConfigurationChangeListener ------------------------------------------ + +void SAL_CALL PresenterController::notifyConfigurationChange ( + const ConfigurationChangeEvent& rEvent) +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ( + "PresenterController object has already been disposed", + static_cast<uno::XWeak*>(this)); + } + + sal_Int32 nType (0); + if ( ! (rEvent.UserData >>= nType)) + return; + + switch (nType) + { + case ResourceActivationEventType: + if (rEvent.ResourceId->compareTo(mxMainPaneId) == 0) + { + InitializeMainPane(Reference<XPane>(rEvent.ResourceObject,UNO_QUERY)); + } + else if (rEvent.ResourceId->isBoundTo(mxMainPaneId,AnchorBindingMode_DIRECT)) + { + // A pane bound to the main pane has been created and is + // stored in the pane container. + Reference<XPane> xPane (rEvent.ResourceObject,UNO_QUERY); + if (xPane.is()) + { + mpPaneContainer->FindPaneId(xPane->getResourceId()); + } + } + else if (rEvent.ResourceId->isBoundTo(mxMainPaneId,AnchorBindingMode_INDIRECT)) + { + // A view bound to one of the panes has been created and is + // stored in the pane container along with its pane. + Reference<XView> xView (rEvent.ResourceObject,UNO_QUERY); + if (xView.is()) + { + mpPaneContainer->StoreView(xView); + UpdateViews(); + mpWindowManager->NotifyViewCreation(xView); + } + } + break; + + case ResourceDeactivationEventType: + if (rEvent.ResourceId->isBoundTo(mxMainPaneId,AnchorBindingMode_INDIRECT)) + { + // If this is a view then remove it from the pane container. + Reference<XView> xView (rEvent.ResourceObject,UNO_QUERY); + if (xView.is()) + { + PresenterPaneContainer::SharedPaneDescriptor pDescriptor( + mpPaneContainer->RemoveView(xView)); + + // A possibly opaque view has been removed. Update() + // updates the clip polygon. + mpWindowManager->Update(); + // Request the repainting of the area previously + // occupied by the view. + if (pDescriptor) + GetPaintManager()->Invalidate(pDescriptor->mxBorderWindow); + } + } + break; + + case ConfigurationUpdateEndEventType: + if (IsAccessibilityActive()) + { + mpAccessibleObject->UpdateAccessibilityHierarchy(); + UpdateCurrentSlide(0); + } + break; + } +} + +//----- XEventListener -------------------------------------------------------- + +void SAL_CALL PresenterController::disposing ( + const lang::EventObject& rEvent) +{ + if (rEvent.Source == mxController) + mxController = nullptr; + else if (rEvent.Source == mxConfigurationController) + mxConfigurationController = nullptr; + else if (rEvent.Source == mxSlideShowController) + mxSlideShowController = nullptr; + else if (rEvent.Source == mxMainWindow) + mxMainWindow = nullptr; +} + +//----- XFrameActionListener -------------------------------------------------- + +void SAL_CALL PresenterController::frameAction ( + const frame::FrameActionEvent& rEvent) +{ + if (rEvent.Action == frame::FrameAction_FRAME_ACTIVATED) + { + if (mxSlideShowController.is()) + mxSlideShowController->activate(); + } +} + +//----- XKeyListener ---------------------------------------------------------- + +void SAL_CALL PresenterController::keyPressed (const awt::KeyEvent& rEvent) +{ + // Tell all views about the unhandled key event. + for (const auto& rxPane : mpPaneContainer->maPanes) + { + if ( ! rxPane->mbIsActive) + continue; + + Reference<awt::XKeyListener> xKeyListener (rxPane->mxView, UNO_QUERY); + if (xKeyListener.is()) + xKeyListener->keyPressed(rEvent); + } +} + +void SAL_CALL PresenterController::keyReleased (const awt::KeyEvent& rEvent) +{ + if (rEvent.Source != mxMainWindow) + return; + + switch (rEvent.KeyCode) + { + case awt::Key::ESCAPE: + case awt::Key::SUBTRACT: + { + if( mxController.is() ) + { + Reference< XPresentationSupplier > xPS( mxController->getModel(), UNO_QUERY ); + if( xPS.is() ) + { + Reference< XPresentation > xP( xPS->getPresentation() ); + if( xP.is() ) + xP->end(); + } + } + } + break; + + case awt::Key::PAGEDOWN: + if (mxSlideShowController.is()) + { + if (rEvent.Modifiers == awt::KeyModifier::MOD2) + mxSlideShowController->gotoNextSlide(); + else + mxSlideShowController->gotoNextEffect(); + } + break; + + case awt::Key::RIGHT: + case awt::Key::SPACE: + case awt::Key::DOWN: + if (mxSlideShowController.is()) + { + mxSlideShowController->gotoNextEffect(); + } + break; + + case awt::Key::PAGEUP: + if (mxSlideShowController.is()) + { + if (rEvent.Modifiers == awt::KeyModifier::MOD2) + mxSlideShowController->gotoPreviousSlide(); + else + mxSlideShowController->gotoPreviousEffect(); + } + break; + + case awt::Key::LEFT: + case awt::Key::UP: + case awt::Key::BACKSPACE: + if (mxSlideShowController.is()) + { + mxSlideShowController->gotoPreviousEffect(); + } + break; + + case awt::Key::P: + if (mxSlideShowController.is()) + { + bool bPenEnabled = mxSlideShowController->getUsePen(); + mxSlideShowController->setUsePen( !bPenEnabled ); + } + break; + + // tdf#149351 Ctrl+A disables pointer as pen mode + case awt::Key::A: + if (mxSlideShowController.is()) + { + if (rEvent.Modifiers == awt::KeyModifier::MOD1) + { + mxSlideShowController->setUsePen( false ); + } + } + break; + + case awt::Key::E: + if (mxSlideShowController.is()) + { + mxSlideShowController->setEraseAllInk( true ); + } + break; + + case awt::Key::HOME: + if (mxSlideShowController.is()) + { + mxSlideShowController->gotoFirstSlide(); + } + break; + + case awt::Key::END: + if (mxSlideShowController.is()) + { + mxSlideShowController->gotoLastSlide(); + } + break; + + case awt::Key::W: + case awt::Key::COMMA: + if (mxSlideShowController.is()) + { + if (mxSlideShowController->isPaused()) + mxSlideShowController->resume(); + else + mxSlideShowController->blankScreen(0x00ffffff); + } + break; + + case awt::Key::B: + case awt::Key::POINT: + if (mxSlideShowController.is()) + { + if (mxSlideShowController->isPaused()) + mxSlideShowController->resume(); + else + mxSlideShowController->blankScreen(0x00000000); + } + break; + + case awt::Key::NUM0: + case awt::Key::NUM1: + case awt::Key::NUM2: + case awt::Key::NUM3: + case awt::Key::NUM4: + case awt::Key::NUM5: + case awt::Key::NUM6: + case awt::Key::NUM7: + case awt::Key::NUM8: + case awt::Key::NUM9: + HandleNumericKeyPress(rEvent.KeyCode-awt::Key::NUM0, rEvent.Modifiers); + break; + + case awt::Key::RETURN: + if (mnPendingSlideNumber > 0) + { + if (mxSlideShowController.is()) + mxSlideShowController->gotoSlideIndex(mnPendingSlideNumber - 1); + mnPendingSlideNumber = -1; + } + else + { + if (mxSlideShowController.is()) + mxSlideShowController->gotoNextEffect(); + } + + break; + + case awt::Key::F1: + // Toggle the help view. + if (mpWindowManager) + { + if (mpWindowManager->GetViewMode() != PresenterWindowManager::VM_Help) + mpWindowManager->SetViewMode(PresenterWindowManager::VM_Help); + else + mpWindowManager->SetHelpViewState(false); + } + + break; + + default: + // Tell all views about the unhandled key event. + for (const auto& rxPane : mpPaneContainer->maPanes) + { + if ( ! rxPane->mbIsActive) + continue; + + Reference<awt::XKeyListener> xKeyListener (rxPane->mxView, UNO_QUERY); + if (xKeyListener.is()) + xKeyListener->keyReleased(rEvent); + } + break; + } +} + +void PresenterController::HandleNumericKeyPress ( + const sal_Int32 nKey, + const sal_Int32 nModifiers) +{ + switch (nModifiers) + { + case 0: + if (mnPendingSlideNumber == -1) + mnPendingSlideNumber = 0; + UpdatePendingSlideNumber(mnPendingSlideNumber * 10 + nKey); + break; + + case awt::KeyModifier::MOD1: + // Ctrl-1, Ctrl-2, and Ctrl-3 are used to switch between views + // (slide view, notes view, normal). Ctrl-4 switches monitors + mnPendingSlideNumber = -1; + if (!mpWindowManager) + return; + switch(nKey) + { + case 1: + mpWindowManager->SetViewMode(PresenterWindowManager::VM_Standard); + break; + case 2: + mpWindowManager->SetViewMode(PresenterWindowManager::VM_Notes); + break; + case 3: + mpWindowManager->SetViewMode(PresenterWindowManager::VM_SlideOverview); + break; + case 4: + SwitchMonitors(); + break; + default: + // Ignore unsupported key. + break; + } + break; + + default: + // Ignore unsupported modifiers. + break; + } +} + +//----- XMouseListener -------------------------------------------------------- + +void SAL_CALL PresenterController::mousePressed (const css::awt::MouseEvent&) +{ + if (mxMainWindow.is()) + mxMainWindow->setFocus(); +} + +void SAL_CALL PresenterController::mouseReleased (const css::awt::MouseEvent&) {} + +void SAL_CALL PresenterController::mouseEntered (const css::awt::MouseEvent&) {} + +void SAL_CALL PresenterController::mouseExited (const css::awt::MouseEvent&) {} + +void PresenterController::InitializeMainPane (const Reference<XPane>& rxPane) +{ + if ( ! rxPane.is()) + return; + + mpAccessibleObject = new PresenterAccessible( + mxComponentContext, + this, + rxPane); + + LoadTheme(rxPane); + + // Main pane has been created and is now observed by the window + // manager. + mpWindowManager->SetParentPane(rxPane); + mpWindowManager->SetTheme(mpTheme); + + if (mpPaneBorderPainter) + mpPaneBorderPainter->SetTheme(mpTheme); + + // Add key listener + mxMainWindow = rxPane->getWindow(); + if (mxMainWindow.is()) + { + mxMainWindow->addKeyListener(this); + mxMainWindow->addMouseListener(this); + } + Reference<XPane2> xPane2 (rxPane, UNO_QUERY); + if (xPane2.is()) + xPane2->setVisible(true); + + mpPaintManager = std::make_shared<PresenterPaintManager>(mxMainWindow, mxPresenterHelper, mpPaneContainer); + + mxCanvas.set(rxPane->getCanvas(), UNO_QUERY); + + if (mxSlideShowController.is()) + mxSlideShowController->activate(); + + UpdateCurrentSlide(0); +} + +void PresenterController::LoadTheme (const Reference<XPane>& rxPane) +{ + // Create (load) the current theme. + if (rxPane.is()) + mpTheme = std::make_shared<PresenterTheme>(mxComponentContext, rxPane->getCanvas()); +} + +double PresenterController::GetSlideAspectRatio() const +{ + double nSlideAspectRatio (28.0/21.0); + + try + { + if (mxController.is()) + { + Reference<drawing::XDrawPagesSupplier> xSlideSupplier ( + mxController->getModel(), UNO_QUERY_THROW); + Reference<drawing::XDrawPages> xSlides (xSlideSupplier->getDrawPages()); + if (xSlides.is() && xSlides->getCount()>0) + { + Reference<beans::XPropertySet> xProperties(xSlides->getByIndex(0),UNO_QUERY_THROW); + sal_Int32 nWidth (28000); + sal_Int32 nHeight (21000); + if ((xProperties->getPropertyValue("Width") >>= nWidth) + && (xProperties->getPropertyValue("Height") >>= nHeight) + && nHeight > 0) + { + nSlideAspectRatio = double(nWidth) / double(nHeight); + } + } + } + } + catch (RuntimeException&) + { + OSL_ASSERT(false); + } + + return nSlideAspectRatio; +} + +void PresenterController::UpdatePendingSlideNumber (const sal_Int32 nPendingSlideNumber) +{ + mnPendingSlideNumber = nPendingSlideNumber; + + if (mpTheme == nullptr) + return; + + if ( ! mxMainWindow.is()) + return; + + PresenterTheme::SharedFontDescriptor pFont ( + mpTheme->GetFont("PendingSlideNumberFont")); + if (!pFont) + return; + + pFont->PrepareFont(mxCanvas); + if ( ! pFont->mxFont.is()) + return; + + const OUString sText (OUString::number(mnPendingSlideNumber)); + rendering::StringContext aContext (sText, 0, sText.getLength()); + pFont->mxFont->createTextLayout( + aContext, + rendering::TextDirection::WEAK_LEFT_TO_RIGHT, + 0); +} + +void PresenterController::SwitchMonitors() +{ + Reference<lang::XEventListener> xScreen( mxScreen ); + if (!xScreen.is()) + return; + + PresenterScreen *pScreen = dynamic_cast<PresenterScreen *>(xScreen.get()); + if (!pScreen) + return; + + pScreen->SwitchMonitors(); +} + +void PresenterController::ExitPresenter() +{ + if( mxController.is() ) + { + Reference< XPresentationSupplier > xPS( mxController->getModel(), UNO_QUERY ); + if( xPS.is() ) + { + Reference< XPresentation > xP( xPS->getPresentation() ); + if( xP.is() ) + xP->end(); + } + } +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterController.hxx b/sdext/source/presenter/PresenterController.hxx new file mode 100644 index 000000000..1a9d8a1aa --- /dev/null +++ b/sdext/source/presenter/PresenterController.hxx @@ -0,0 +1,222 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERCONTROLLER_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERCONTROLLER_HXX + +#include "PresenterAccessibility.hxx" +#include "PresenterPaneContainer.hxx" +#include "PresenterTheme.hxx" +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> +#include <com/sun/star/awt/XKeyListener.hpp> +#include <com/sun/star/awt/XMouseListener.hpp> +#include <com/sun/star/drawing/XPresenterHelper.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/presentation/XSlideShowController.hpp> +#include <com/sun/star/frame/XFrameActionListener.hpp> +#include <com/sun/star/drawing/framework/XConfigurationChangeListener.hpp> +#include <com/sun/star/drawing/framework/XConfigurationController.hpp> +#include <com/sun/star/drawing/framework/XPane.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/util/XURLTransformer.hpp> +#include <rtl/ref.hxx> +#include <map> +#include <memory> + +namespace sdext::presenter { + +class PresenterCanvasHelper; +class PresenterPaintManager; +class PresenterPaneAnimator; +class PresenterPaneContainer; +class PresenterPaneBorderPainter; +class PresenterTheme; +class PresenterWindowManager; + +typedef ::cppu::WeakComponentImplHelper < + css::drawing::framework::XConfigurationChangeListener, + css::frame::XFrameActionListener, + css::awt::XKeyListener, + css::awt::XMouseListener +> PresenterControllerInterfaceBase; + +/// Represents an element in the toolbar that shows the time elapsed since the presentation started. +class IPresentationTime +{ +public: + virtual void restart() = 0; + virtual bool isPaused() = 0; + virtual void setPauseStatus(const bool pauseStatus) = 0; + virtual ~IPresentationTime(); +}; + +/** The controller of the presenter screen is responsible for telling the + individual views which slides to show. Additionally it provides access + to frequently used values of the current theme. +*/ +class PresenterController + : protected ::cppu::BaseMutex, + public PresenterControllerInterfaceBase +{ +public: + static ::rtl::Reference<PresenterController> Instance ( + const css::uno::Reference<css::frame::XFrame>& rxFrame); + + PresenterController ( + const css::uno::WeakReference<css::lang::XEventListener> &rxScreen, + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::frame::XController>& rxController, + const css::uno::Reference<css::presentation::XSlideShowController>& rxSlideShowController, + const rtl::Reference<PresenterPaneContainer>& rpPaneContainer, + const css::uno::Reference<css::drawing::framework::XResourceId>& rxMainPaneId); + virtual ~PresenterController() override; + + virtual void SAL_CALL disposing() override; + + void UpdateCurrentSlide (const sal_Int32 nOffset); + + SharedBitmapDescriptor + GetViewBackground (const OUString& rsViewURL) const; + PresenterTheme::SharedFontDescriptor + GetViewFont (const OUString& rsViewURL) const; + const std::shared_ptr<PresenterTheme>& GetTheme() const; + const ::rtl::Reference<PresenterWindowManager>& GetWindowManager() const; + const css::uno::Reference<css::presentation::XSlideShowController>& + GetSlideShowController() const; + const rtl::Reference<PresenterPaneContainer>& GetPaneContainer() const; + const ::rtl::Reference<PresenterPaneBorderPainter>& GetPaneBorderPainter() const; + const std::shared_ptr<PresenterCanvasHelper>& GetCanvasHelper() const; + const css::uno::Reference<css::drawing::XPresenterHelper>& GetPresenterHelper() const; + const std::shared_ptr<PresenterPaintManager>& GetPaintManager() const; + double GetSlideAspectRatio() const; + void ShowView (const OUString& rsViewURL); + void HideView (const OUString& rsViewURL); + void SwitchMonitors(); + void ExitPresenter(); + void DispatchUnoCommand (const OUString& rsCommand) const; + css::uno::Reference<css::frame::XDispatch> GetDispatch ( + const css::util::URL& rURL) const; + css::util::URL CreateURLFromString (const OUString& rsURL) const; + const css::uno::Reference<css::drawing::framework::XConfigurationController>& + GetConfigurationController() const; + const css::uno::Reference<css::drawing::XDrawPage>& GetCurrentSlide() const; + static bool HasTransition (css::uno::Reference<css::drawing::XDrawPage> const & rxPage); + static bool HasCustomAnimation (css::uno::Reference<css::drawing::XDrawPage> const & rxPage); + void SetAccessibilityActiveState (const bool bIsActive); + bool IsAccessibilityActive() const { return mbIsAccessibilityActive;} + + void HandleMouseClick (const css::awt::MouseEvent& rEvent); + void UpdatePaneTitles(); + + /** Request activation or deactivation of (some of) the views according + to the given parameters. + */ + void RequestViews ( + const bool bIsSlideSorterActive, + const bool bIsNotesViewActive, + const bool bIsHelpViewActive); + + void SetPresentationTime(IPresentationTime* pPresentationTime); + IPresentationTime* GetPresentationTime(); + + // XConfigurationChangeListener + + virtual void SAL_CALL notifyConfigurationChange ( + const css::drawing::framework::ConfigurationChangeEvent& rEvent) override; + + // XEventListener + + virtual void SAL_CALL disposing ( + const css::lang::EventObject& rEvent) override; + + // XFrameActionListener + + virtual void SAL_CALL frameAction ( + const css::frame::FrameActionEvent& rEvent) override; + + // XKeyListener + + virtual void SAL_CALL keyPressed (const css::awt::KeyEvent& rEvent) override; + virtual void SAL_CALL keyReleased (const css::awt::KeyEvent& rEvent) override; + + // XMouseListener + + virtual void SAL_CALL mousePressed (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseReleased (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseEntered (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseExited (const css::awt::MouseEvent& rEvent) override; + +private: + typedef ::std::map<css::uno::Reference<css::frame::XFrame>,rtl::Reference<PresenterController> > InstanceContainer; + static InstanceContainer maInstances; + + css::uno::WeakReference<css::lang::XEventListener> mxScreen; + css::uno::Reference<css::uno::XComponentContext> mxComponentContext; + css::uno::Reference<css::rendering::XSpriteCanvas> mxCanvas; + css::uno::Reference<css::frame::XController> mxController; + css::uno::Reference<css::drawing::framework::XConfigurationController> + mxConfigurationController; + css::uno::Reference<css::presentation::XSlideShowController> mxSlideShowController; + css::uno::Reference<css::drawing::framework::XResourceId> mxMainPaneId; + rtl::Reference<PresenterPaneContainer> mpPaneContainer; + sal_Int32 mnCurrentSlideIndex; + css::uno::Reference<css::drawing::XDrawPage> mxCurrentSlide; + css::uno::Reference<css::drawing::XDrawPage> mxNextSlide; + ::rtl::Reference<PresenterWindowManager> mpWindowManager; + std::shared_ptr<PresenterTheme> mpTheme; + css::uno::Reference<css::awt::XWindow> mxMainWindow; + ::rtl::Reference<PresenterPaneBorderPainter> mpPaneBorderPainter; + std::shared_ptr<PresenterCanvasHelper> mpCanvasHelper; + css::uno::Reference<css::drawing::XPresenterHelper> mxPresenterHelper; + std::shared_ptr<PresenterPaintManager> mpPaintManager; + sal_Int32 mnPendingSlideNumber; + css::uno::Reference<css::util::XURLTransformer> mxUrlTransformer; + ::rtl::Reference<PresenterAccessible> mpAccessibleObject; + bool mbIsAccessibilityActive; + IPresentationTime* mpPresentationTime; + + void GetSlides (const sal_Int32 nOffset); + void UpdateViews(); + void InitializeMainPane (const css::uno::Reference<css::drawing::framework::XPane>& rxPane); + void LoadTheme (const css::uno::Reference<css::drawing::framework::XPane>& rxPane); + void UpdatePendingSlideNumber (const sal_Int32 nPendingSlideNumber); + + /** This method is called when the user pressed one of the numerical + keys. Depending on the modifier, numeric keys switch to another + slide (no modifier), or change to another view (Ctrl modifier). + @param nKey + Numeric value that is printed on the pressed key. For example + pressing the key '4' will result in the value 4, not the ASCII + code (0x34?). + @param nModifiers + The modifier bit field as provided by the key up event. + */ + void HandleNumericKeyPress (const sal_Int32 nKey, const sal_Int32 nModifiers); +}; + +} // end of namespace ::sdext::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterCurrentSlideObserver.cxx b/sdext/source/presenter/PresenterCurrentSlideObserver.cxx new file mode 100644 index 000000000..0cd33d9c0 --- /dev/null +++ b/sdext/source/presenter/PresenterCurrentSlideObserver.cxx @@ -0,0 +1,130 @@ +/* -*- 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 "PresenterCurrentSlideObserver.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace sdext::presenter { + +//===== PresenterCurrentSlideObserver ========================================= + +PresenterCurrentSlideObserver::PresenterCurrentSlideObserver ( + const ::rtl::Reference<PresenterController>& rxPresenterController, + const Reference<presentation::XSlideShowController>& rxSlideShowController) + : PresenterCurrentSlideObserverInterfaceBase(m_aMutex), + mpPresenterController(rxPresenterController), + mxSlideShowController(rxSlideShowController) +{ + if( mpPresenterController.is() ) + { + mpPresenterController->addEventListener(this); + } + + if( mxSlideShowController.is() ) + { + // Listen for events from the slide show controller. + mxSlideShowController->addSlideShowListener(static_cast<XSlideShowListener*>(this)); + } +} + +PresenterCurrentSlideObserver::~PresenterCurrentSlideObserver() +{ +} + +void SAL_CALL PresenterCurrentSlideObserver::disposing() +{ + // Disconnect form the slide show controller. + if(mxSlideShowController.is()) + { + mxSlideShowController->removeSlideShowListener(static_cast<XSlideShowListener*>(this)); + mxSlideShowController = nullptr; + } + + if (mpPresenterController.is()) + mpPresenterController->removeEventListener(this); +} + +//----- XSlideShowListener ---------------------------------------------------- + +void SAL_CALL PresenterCurrentSlideObserver::beginEvent ( + const Reference<animations::XAnimationNode>&) +{} + +void SAL_CALL PresenterCurrentSlideObserver::endEvent ( + const Reference<animations::XAnimationNode>&) +{} + +void SAL_CALL PresenterCurrentSlideObserver::repeat ( + const css::uno::Reference<css::animations::XAnimationNode>&, + sal_Int32) +{} + +void SAL_CALL PresenterCurrentSlideObserver::paused() +{ +} + +void SAL_CALL PresenterCurrentSlideObserver::resumed() +{ +} + +void SAL_CALL PresenterCurrentSlideObserver::slideEnded (sal_Bool bReverse) +{ + // Determine whether the new current slide (the one after the one that + // just ended) is the slide past the last slide in the presentation, + // i.e. the one that says something like "click to end presentation...". + if (mxSlideShowController.is() && !bReverse) + if (mxSlideShowController->getNextSlideIndex() < 0) + if( mpPresenterController.is() ) + mpPresenterController->UpdateCurrentSlide(+1); +} + +void SAL_CALL PresenterCurrentSlideObserver::hyperLinkClicked (const OUString &) +{ +} + +void SAL_CALL PresenterCurrentSlideObserver::slideTransitionStarted() +{ + if( mpPresenterController.is() ) + mpPresenterController->UpdateCurrentSlide(0); +} + +void SAL_CALL PresenterCurrentSlideObserver::slideTransitionEnded() +{ +} + +void SAL_CALL PresenterCurrentSlideObserver::slideAnimationsEnded() +{ +} + +//----- XEventListener -------------------------------------------------------- + +void SAL_CALL PresenterCurrentSlideObserver::disposing ( + const lang::EventObject& rEvent) +{ + if (rEvent.Source == Reference<XInterface>(static_cast<XWeak*>(mpPresenterController.get()))) + dispose(); + else if (rEvent.Source == mxSlideShowController) + mxSlideShowController = nullptr; +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterCurrentSlideObserver.hxx b/sdext/source/presenter/PresenterCurrentSlideObserver.hxx new file mode 100644 index 000000000..786744bfb --- /dev/null +++ b/sdext/source/presenter/PresenterCurrentSlideObserver.hxx @@ -0,0 +1,81 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERCURRENTSLIDEOBSERVER_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERCURRENTSLIDEOBSERVER_HXX + +#include "PresenterController.hxx" +#include <com/sun/star/presentation/XSlideShowController.hpp> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> +#include <rtl/ref.hxx> + +namespace sdext::presenter { + +typedef ::cppu::WeakComponentImplHelper < + css::presentation::XSlideShowListener +> PresenterCurrentSlideObserverInterfaceBase; + +/** Check periodically the slide show controller and the + frame::XController whether the current slide has changed. If so, + then inform the presenter controller about it. + + Objects of this class have their own lifetime control and destroy + themselves when the presenter controller is disposed. +*/ +class PresenterCurrentSlideObserver + : protected ::cppu::BaseMutex, + public PresenterCurrentSlideObserverInterfaceBase +{ +public: + PresenterCurrentSlideObserver ( + const ::rtl::Reference<PresenterController>& rxPresenterController, + const css::uno::Reference<css::presentation::XSlideShowController>& rxSlideShowController); + virtual ~PresenterCurrentSlideObserver() override; + + virtual void SAL_CALL disposing() override; + + // XSlideShowListener + virtual void SAL_CALL paused( ) override; + virtual void SAL_CALL resumed( ) override; + virtual void SAL_CALL slideTransitionStarted( ) override; + virtual void SAL_CALL slideTransitionEnded( ) override; + virtual void SAL_CALL slideAnimationsEnded( ) override; + virtual void SAL_CALL slideEnded(sal_Bool bReverse) override; + virtual void SAL_CALL hyperLinkClicked( const OUString& hyperLink ) override; + + // XAnimationListener + virtual void SAL_CALL beginEvent( const css::uno::Reference< css::animations::XAnimationNode >& Node ) override; + virtual void SAL_CALL endEvent( const css::uno::Reference< css::animations::XAnimationNode >& Node ) override; + virtual void SAL_CALL repeat( const css::uno::Reference< css::animations::XAnimationNode >& Node, ::sal_Int32 Repeat ) override; + + // XEventListener + virtual void SAL_CALL disposing ( + const css::lang::EventObject& rEvent) override; + +private: + ::rtl::Reference<PresenterController> mpPresenterController; + css::uno::Reference<css::presentation::XSlideShowController> mxSlideShowController; +}; + +} // end of namespace ::sdext::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterFrameworkObserver.cxx b/sdext/source/presenter/PresenterFrameworkObserver.cxx new file mode 100644 index 000000000..0f56da0b0 --- /dev/null +++ b/sdext/source/presenter/PresenterFrameworkObserver.cxx @@ -0,0 +1,109 @@ +/* -*- 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 "PresenterFrameworkObserver.hxx" +#include <com/sun/star/lang/IllegalArgumentException.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing::framework; + + +namespace sdext::presenter { + +PresenterFrameworkObserver::PresenterFrameworkObserver ( + const css::uno::Reference<css::drawing::framework::XConfigurationController>&rxController, + const Action& rAction) + : PresenterFrameworkObserverInterfaceBase(m_aMutex), + mxConfigurationController(rxController), + maAction(rAction) +{ + if ( ! mxConfigurationController.is()) + throw lang::IllegalArgumentException(); + + if (mxConfigurationController->hasPendingRequests()) + { + mxConfigurationController->addConfigurationChangeListener( + this, + "ConfigurationUpdateEnd", + Any()); + } + else + { + rAction(true); + } +} + +PresenterFrameworkObserver::~PresenterFrameworkObserver() +{ +} + +void PresenterFrameworkObserver::RunOnUpdateEnd ( + const css::uno::Reference<css::drawing::framework::XConfigurationController>&rxController, + const Action& rAction) +{ + new PresenterFrameworkObserver( + rxController, + rAction); +} + +void SAL_CALL PresenterFrameworkObserver::disposing() +{ + if (maAction) + maAction(false); + Shutdown(); +} + +void PresenterFrameworkObserver::Shutdown() +{ + maAction = Action(); + if (mxConfigurationController != nullptr) + { + mxConfigurationController->removeConfigurationChangeListener(this); + mxConfigurationController = nullptr; + } +} + +void SAL_CALL PresenterFrameworkObserver::disposing (const lang::EventObject& rEvent) +{ + if ( ! rEvent.Source.is()) + return; + + if (rEvent.Source == mxConfigurationController) + { + mxConfigurationController = nullptr; + if (maAction) + maAction(false); + } +} + +void SAL_CALL PresenterFrameworkObserver::notifyConfigurationChange ( + const ConfigurationChangeEvent& /*rEvent*/) +{ + Action aAction(maAction); + Shutdown(); + aAction(true); + + maAction = nullptr; + dispose(); +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterFrameworkObserver.hxx b/sdext/source/presenter/PresenterFrameworkObserver.hxx new file mode 100644 index 000000000..bc4b4bda1 --- /dev/null +++ b/sdext/source/presenter/PresenterFrameworkObserver.hxx @@ -0,0 +1,81 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERFRAMEWORKOBSERVER_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERFRAMEWORKOBSERVER_HXX + +#include <com/sun/star/drawing/framework/XConfigurationChangeListener.hpp> +#include <com/sun/star/drawing/framework/XConfigurationController.hpp> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> + +#include <functional> + +namespace sdext::presenter { + +typedef ::cppu::WeakComponentImplHelper < + css::drawing::framework::XConfigurationChangeListener + > PresenterFrameworkObserverInterfaceBase; + +/** Watch the drawing framework for changes and run callbacks when a certain + change takes place. +*/ +class PresenterFrameworkObserver + : private ::cppu::BaseMutex, + public PresenterFrameworkObserverInterfaceBase +{ +public: + typedef ::std::function<void (bool)> Action; + + PresenterFrameworkObserver(const PresenterFrameworkObserver&) = delete; + PresenterFrameworkObserver& operator=(const PresenterFrameworkObserver&) = delete; + + static void RunOnUpdateEnd ( + const css::uno::Reference<css::drawing::framework::XConfigurationController>&rxController, + const Action& rAction); + + virtual void SAL_CALL disposing() override; + virtual void SAL_CALL disposing (const css::lang::EventObject& rEvent) override; + virtual void SAL_CALL notifyConfigurationChange ( + const css::drawing::framework::ConfigurationChangeEvent& rEvent) override; + +private: + css::uno::Reference<css::drawing::framework::XConfigurationController> mxConfigurationController; + Action maAction; + + /** Create a new PresenterFrameworkObserver object. + @param rPredicate + This functor tests whether the action is to be executed or not. + @param rAction + The functor to execute when the predicate returns true, + e.g. when some resource has been created. + */ + PresenterFrameworkObserver ( + const css::uno::Reference<css::drawing::framework::XConfigurationController>&rxController, + const Action& rAction); + virtual ~PresenterFrameworkObserver() override; + + void Shutdown(); +}; + +} // end of namespace ::sdext::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterGeometryHelper.cxx b/sdext/source/presenter/PresenterGeometryHelper.cxx new file mode 100644 index 000000000..b2ad35c63 --- /dev/null +++ b/sdext/source/presenter/PresenterGeometryHelper.cxx @@ -0,0 +1,262 @@ +/* -*- 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 "PresenterGeometryHelper.hxx" + +#include <math.h> +#include <algorithm> +#include <o3tl/safeint.hxx> + + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace { + +sal_Int32 Right (const awt::Rectangle& rBox) +{ + return rBox.X + rBox.Width - 1; +} + +sal_Int32 Bottom (const awt::Rectangle& rBox) +{ + return rBox.Y + rBox.Height - 1; +} + +sal_Int32 Width (const sal_Int32 nLeft, const sal_Int32 nRight) +{ + return nRight - nLeft + 1; +} + +sal_Int32 Height (const sal_Int32 nTop, const sal_Int32 nBottom) +{ + return nBottom - nTop + 1; +} + +} // end of anonymous namespace + +namespace sdext::presenter { + +sal_Int32 PresenterGeometryHelper::Floor (const double nValue) +{ + return sal::static_int_cast<sal_Int32>(floor(nValue)); +} + +sal_Int32 PresenterGeometryHelper::Ceil (const double nValue) +{ + return sal::static_int_cast<sal_Int32>(ceil(nValue)); +} + +sal_Int32 PresenterGeometryHelper::Round (const double nValue) +{ + return sal::static_int_cast<sal_Int32>(floor(0.5 + nValue)); +} + +awt::Rectangle PresenterGeometryHelper::ConvertRectangle ( + const geometry::RealRectangle2D& rBox) +{ + const sal_Int32 nLeft (Floor(rBox.X1)); + const sal_Int32 nTop (Floor(rBox.Y1)); + const sal_Int32 nRight (Ceil(rBox.X2)); + const sal_Int32 nBottom (Ceil(rBox.Y2)); + return awt::Rectangle (nLeft,nTop,nRight-nLeft,nBottom-nTop); +} + +awt::Rectangle PresenterGeometryHelper::ConvertRectangleWithConstantSize ( + const geometry::RealRectangle2D& rBox) +{ + return awt::Rectangle ( + Round(rBox.X1), + Round(rBox.Y1), + Round(rBox.X2 - rBox.X1), + Round(rBox.Y2 - rBox.Y1)); +} + +geometry::RealRectangle2D PresenterGeometryHelper::ConvertRectangle ( + const css::awt::Rectangle& rBox) +{ + return geometry::RealRectangle2D( + rBox.X, + rBox.Y, + rBox.X + rBox.Width, + rBox.Y + rBox.Height); +} + +awt::Rectangle PresenterGeometryHelper::TranslateRectangle ( + const css::awt::Rectangle& rBox, + const sal_Int32 nXOffset, + const sal_Int32 nYOffset) +{ + return awt::Rectangle(rBox.X + nXOffset, rBox.Y + nYOffset, rBox.Width, rBox.Height); +} + +awt::Rectangle PresenterGeometryHelper::Intersection ( + const css::awt::Rectangle& rBox1, + const css::awt::Rectangle& rBox2) +{ + const sal_Int32 nLeft (::std::max(rBox1.X, rBox2.X)); + const sal_Int32 nTop (::std::max(rBox1.Y, rBox2.Y)); + const sal_Int32 nRight (::std::min(Right(rBox1), Right(rBox2))); + const sal_Int32 nBottom (::std::min(Bottom(rBox1), Bottom(rBox2))); + if (nLeft >= nRight || nTop >= nBottom) + return awt::Rectangle(); + else + return awt::Rectangle(nLeft,nTop, Width(nLeft,nRight), Height(nTop,nBottom)); +} + +geometry::RealRectangle2D PresenterGeometryHelper::Intersection ( + const geometry::RealRectangle2D& rBox1, + const geometry::RealRectangle2D& rBox2) +{ + const double nLeft (::std::max(rBox1.X1, rBox2.X1)); + const double nTop (::std::max(rBox1.Y1, rBox2.Y1)); + const double nRight (::std::min(rBox1.X2, rBox2.X2)); + const double nBottom (::std::min(rBox1.Y2, rBox2.Y2)); + if (nLeft >= nRight || nTop >= nBottom) + return geometry::RealRectangle2D(0,0,0,0); + else + return geometry::RealRectangle2D(nLeft,nTop, nRight, nBottom); +} + +bool PresenterGeometryHelper::IsInside ( + const css::geometry::RealRectangle2D& rBox, + const css::geometry::RealPoint2D& rPoint) +{ + return rBox.X1 <= rPoint.X + && rBox.Y1 <= rPoint.Y + && rBox.X2 >= rPoint.X + && rBox.Y2 >= rPoint.Y; +} + +bool PresenterGeometryHelper::IsInside ( + const css::awt::Rectangle& rBox1, + const css::awt::Rectangle& rBox2) +{ + return rBox1.X >= rBox2.X + && rBox1.Y >= rBox2.Y + && rBox1.X+rBox1.Width <= rBox2.X+rBox2.Width + && rBox1.Y+rBox1.Height <= rBox2.Y+rBox2.Height; +} + +geometry::RealRectangle2D PresenterGeometryHelper::Union ( + const geometry::RealRectangle2D& rBox1, + const geometry::RealRectangle2D& rBox2) +{ + const double nLeft (::std::min(rBox1.X1, rBox2.X1)); + const double nTop (::std::min(rBox1.Y1, rBox2.Y1)); + const double nRight (::std::max(rBox1.X2, rBox2.X2)); + const double nBottom (::std::max(rBox1.Y2, rBox2.Y2)); + if (nLeft >= nRight || nTop >= nBottom) + return geometry::RealRectangle2D(0,0,0,0); + else + return geometry::RealRectangle2D(nLeft,nTop, nRight, nBottom); +} + +bool PresenterGeometryHelper::AreRectanglesDisjoint ( + const css::awt::Rectangle& rBox1, + const css::awt::Rectangle& rBox2) +{ + return rBox1.X+rBox1.Width <= rBox2.X + || rBox1.Y+rBox1.Height <= rBox2.Y + || rBox1.X >= rBox2.X+rBox2.Width + || rBox1.Y >= rBox2.Y+rBox2.Height; +} + +Reference<rendering::XPolyPolygon2D> PresenterGeometryHelper::CreatePolygon( + const awt::Rectangle& rBox, + const Reference<rendering::XGraphicDevice>& rxDevice) +{ + if ( ! rxDevice.is()) + return nullptr; + + Sequence<Sequence<geometry::RealPoint2D> > aPoints + { + { + { o3tl::narrowing<double>(rBox.X), o3tl::narrowing<double>(rBox.Y) }, + { o3tl::narrowing<double>(rBox.X), o3tl::narrowing<double>(rBox.Y+rBox.Height) }, + { o3tl::narrowing<double>(rBox.X+rBox.Width), o3tl::narrowing<double>(rBox.Y+rBox.Height) }, + { o3tl::narrowing<double>(rBox.X+rBox.Width), o3tl::narrowing<double>(rBox.Y) } + } + }; + Reference<rendering::XLinePolyPolygon2D> xPolygon ( + rxDevice->createCompatibleLinePolyPolygon(aPoints)); + if (xPolygon.is()) + xPolygon->setClosed(0, true); + + return xPolygon; +} + +Reference<rendering::XPolyPolygon2D> PresenterGeometryHelper::CreatePolygon( + const geometry::RealRectangle2D& rBox, + const Reference<rendering::XGraphicDevice>& rxDevice) +{ + if ( ! rxDevice.is()) + return nullptr; + + Sequence<Sequence<geometry::RealPoint2D> > aPoints + { + { + { rBox.X1, rBox.Y1 }, + { rBox.X1, rBox.Y2 }, + { rBox.X2, rBox.Y2 }, + { rBox.X2, rBox.Y1 } + } + }; + Reference<rendering::XLinePolyPolygon2D> xPolygon ( + rxDevice->createCompatibleLinePolyPolygon(aPoints)); + if (xPolygon.is()) + xPolygon->setClosed(0, true); + + return xPolygon; +} + +Reference<rendering::XPolyPolygon2D> PresenterGeometryHelper::CreatePolygon( + const ::std::vector<css::awt::Rectangle>& rBoxes, + const Reference<rendering::XGraphicDevice>& rxDevice) +{ + if ( ! rxDevice.is()) + return nullptr; + + const sal_Int32 nCount (rBoxes.size()); + Sequence<Sequence<geometry::RealPoint2D> > aPoints(nCount); + auto aPointsRange = asNonConstRange(aPoints); + for (sal_Int32 nIndex=0; nIndex<nCount; ++nIndex) + { + const awt::Rectangle& rBox (rBoxes[nIndex]); + aPointsRange[nIndex] = Sequence<geometry::RealPoint2D> + { + { o3tl::narrowing<double>(rBox.X), o3tl::narrowing<double>(rBox.Y) }, + { o3tl::narrowing<double>(rBox.X), o3tl::narrowing<double>(rBox.Y+rBox.Height) }, + { o3tl::narrowing<double>(rBox.X+rBox.Width), o3tl::narrowing<double>(rBox.Y+rBox.Height) }, + { o3tl::narrowing<double>(rBox.X+rBox.Width), o3tl::narrowing<double>(rBox.Y) } + }; + } + + Reference<rendering::XLinePolyPolygon2D> xPolygon ( + rxDevice->createCompatibleLinePolyPolygon(aPoints)); + if (xPolygon.is()) + for (sal_Int32 nIndex=0; nIndex<nCount; ++nIndex) + xPolygon->setClosed(nIndex, true); + + return xPolygon; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterGeometryHelper.hxx b/sdext/source/presenter/PresenterGeometryHelper.hxx new file mode 100644 index 000000000..c2f55757e --- /dev/null +++ b/sdext/source/presenter/PresenterGeometryHelper.hxx @@ -0,0 +1,117 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERGEOMETRYHELPER_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERGEOMETRYHELPER_HXX + +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/XPolyPolygon2D.hpp> +#include <com/sun/star/geometry/RealRectangle2D.hpp> +#include <vector> + +namespace sdext::presenter { + +/** Collection of geometry related convenience functions. +*/ +class PresenterGeometryHelper +{ +public: + static sal_Int32 Round (const double nValue); + static sal_Int32 Floor (const double nValue); + static sal_Int32 Ceil (const double nValue); + + /** Return the bounding box with integer coordinates of the given + rectangle. Note that due to different rounding of the left/top and + the right/bottom border the width of the resulting rectangle may + differ for different positions but constant width and height. + */ + static css::awt::Rectangle ConvertRectangle ( + const css::geometry::RealRectangle2D& rBox); + + /** Convert the given rectangle to integer coordinates so that width and + height remain constant when only the position changes. + */ + static css::awt::Rectangle ConvertRectangleWithConstantSize ( + const css::geometry::RealRectangle2D& rBox); + + static css::geometry::RealRectangle2D ConvertRectangle ( + const css::awt::Rectangle& rBox); + + // static css::awt::Size ConvertSize ( + // const css::geometry::RealSize2D& rSize); + + static css::awt::Rectangle TranslateRectangle ( + const css::awt::Rectangle& rBox, + const sal_Int32 nXOffset, + const sal_Int32 nYOffset); + + static css::awt::Rectangle Intersection ( + const css::awt::Rectangle& rBox1, + const css::awt::Rectangle& rBox2); + + static css::geometry::RealRectangle2D Intersection ( + const css::geometry::RealRectangle2D& rBox1, + const css::geometry::RealRectangle2D& rBox2); + + static bool IsInside ( + const css::geometry::RealRectangle2D& rBox, + const css::geometry::RealPoint2D& rPoint); + + /** Return whether rBox1 is completely inside rBox2. + */ + static bool IsInside ( + const css::awt::Rectangle& rBox1, + const css::awt::Rectangle& rBox2); + + static css::geometry::RealRectangle2D Union ( + const css::geometry::RealRectangle2D& rBox1, + const css::geometry::RealRectangle2D& rBox2); + + static bool AreRectanglesDisjoint ( + const css::awt::Rectangle& rBox1, + const css::awt::Rectangle& rBox2); + + static css::uno::Reference<css::rendering::XPolyPolygon2D> CreatePolygon( + const css::awt::Rectangle& rBox, + const css::uno::Reference<css::rendering::XGraphicDevice>& rxDevice); + + static css::uno::Reference<css::rendering::XPolyPolygon2D> CreatePolygon( + const css::geometry::RealRectangle2D& rBox, + const css::uno::Reference<css::rendering::XGraphicDevice>& rxDevice); + + static css::uno::Reference<css::rendering::XPolyPolygon2D> CreatePolygon( + const ::std::vector<css::awt::Rectangle>& rBoxes, + const css::uno::Reference<css::rendering::XGraphicDevice>& rxDevice); + + /** Create a polygon for a rounded rectangle. + */ + /* static css::uno::Reference<css::rendering::XPolyPolygon2D> CreatePolygon( + const css::awt::Rectangle& rBox, + const double nRadius, + const css::uno::Reference<css::rendering::XGraphicDevice>& + rxDevice); + */ +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterHelpView.cxx b/sdext/source/presenter/PresenterHelpView.cxx new file mode 100644 index 000000000..74adeedd1 --- /dev/null +++ b/sdext/source/presenter/PresenterHelpView.cxx @@ -0,0 +1,747 @@ +/* -*- 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 <vcl/settings.hxx> +#include "PresenterHelpView.hxx" +#include "PresenterButton.hxx" +#include "PresenterCanvasHelper.hxx" +#include "PresenterGeometryHelper.hxx" +#include <com/sun/star/awt/XWindowPeer.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/drawing/framework/XConfigurationController.hpp> +#include <com/sun/star/drawing/framework/XControllerManager.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/TextDirection.hpp> +#include <com/sun/star/util/Color.hpp> +#include <algorithm> +#include <numeric> +#include <string_view> +#include <vector> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing::framework; +using ::std::vector; + +namespace sdext::presenter { + +namespace { + const sal_Int32 gnHorizontalGap (20); + const sal_Int32 gnVerticalBorder (30); + const sal_Int32 gnVerticalButtonPadding (12); + + class LineDescriptor + { + public: + LineDescriptor(); + void AddPart ( + std::u16string_view rsLine, + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont); + bool IsEmpty() const; + + OUString msLine; + geometry::RealSize2D maSize; + double mnVerticalOffset; + + void CalculateSize (const css::uno::Reference<css::rendering::XCanvasFont>& rxFont); + }; + + class LineDescriptorList + { + public: + LineDescriptorList ( + const OUString& rsText, + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont, + const sal_Int32 nMaximalWidth); + + void Update ( + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont, + const sal_Int32 nMaximalWidth); + + double Paint( + const Reference<rendering::XCanvas>& rxCanvas, + const geometry::RealRectangle2D& rBBox, + const bool bFlushLeft, + const rendering::ViewState& rViewState, + rendering::RenderState& rRenderState, + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont) const; + double GetHeight() const; + + private: + const OUString msText; + std::shared_ptr<vector<LineDescriptor> > mpLineDescriptors; + + static void SplitText (const OUString& rsText, vector<OUString>& rTextParts); + void FormatText ( + const vector<OUString>& rTextParts, + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont, + const sal_Int32 nMaximalWidth); + }; + + class Block + { + public: + Block ( + const OUString& rsLeftText, + const OUString& rsRightText, + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont, + const sal_Int32 nMaximalWidth); + Block(const Block&) = delete; + Block& operator=(const Block&) = delete; + void Update ( + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont, + const sal_Int32 nMaximalWidth); + + LineDescriptorList maLeft; + LineDescriptorList maRight; + }; +} // end of anonymous namespace + +class PresenterHelpView::TextContainer : public vector<std::shared_ptr<Block> > +{ +}; + +PresenterHelpView::PresenterHelpView ( + const Reference<uno::XComponentContext>& rxContext, + const Reference<XResourceId>& rxViewId, + const Reference<frame::XController>& rxController, + const ::rtl::Reference<PresenterController>& rpPresenterController) + : PresenterHelpViewInterfaceBase(m_aMutex), + mxComponentContext(rxContext), + mxViewId(rxViewId), + mpPresenterController(rpPresenterController), + mnSeparatorY(0), + mnMaximalWidth(0) +{ + try + { + // Get the content window via the pane anchor. + Reference<XControllerManager> xCM (rxController, UNO_QUERY_THROW); + Reference<XConfigurationController> xCC ( + xCM->getConfigurationController(), UNO_SET_THROW); + mxPane.set(xCC->getResource(rxViewId->getAnchor()), UNO_QUERY_THROW); + + mxWindow = mxPane->getWindow(); + ProvideCanvas(); + + mxWindow->addWindowListener(this); + mxWindow->addPaintListener(this); + Reference<awt::XWindowPeer> xPeer (mxWindow, UNO_QUERY); + if (xPeer.is()) + xPeer->setBackground(util::Color(0xff000000)); + mxWindow->setVisible(true); + + if (mpPresenterController.is()) + { + mpFont = mpPresenterController->GetViewFont(mxViewId->getResourceURL()); + if (mpFont) + { + mpFont->PrepareFont(mxCanvas); + } + } + + // Create the close button. + mpCloseButton = PresenterButton::Create( + mxComponentContext, + mpPresenterController, + mpPresenterController->GetTheme(), + mxWindow, + mxCanvas, + "HelpViewCloser"); + + ReadHelpStrings(); + Resize(); + } + catch (RuntimeException&) + { + mxViewId = nullptr; + mxWindow = nullptr; + throw; + } +} + +PresenterHelpView::~PresenterHelpView() +{ +} + +void SAL_CALL PresenterHelpView::disposing() +{ + mxViewId = nullptr; + + if (mpCloseButton.is()) + { + Reference<lang::XComponent> xComponent = mpCloseButton; + mpCloseButton = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + + if (mxWindow.is()) + { + mxWindow->removeWindowListener(this); + mxWindow->removePaintListener(this); + } +} + +//----- lang::XEventListener -------------------------------------------------- + +void SAL_CALL PresenterHelpView::disposing (const lang::EventObject& rEventObject) +{ + if (rEventObject.Source == mxCanvas) + { + mxCanvas = nullptr; + } + else if (rEventObject.Source == mxWindow) + { + mxWindow = nullptr; + dispose(); + } +} + +//----- XWindowListener ------------------------------------------------------- + +void SAL_CALL PresenterHelpView::windowResized (const awt::WindowEvent&) +{ + ThrowIfDisposed(); + Resize(); +} + +void SAL_CALL PresenterHelpView::windowMoved (const awt::WindowEvent&) +{ + ThrowIfDisposed(); +} + +void SAL_CALL PresenterHelpView::windowShown (const lang::EventObject&) +{ + ThrowIfDisposed(); + Resize(); +} + +void SAL_CALL PresenterHelpView::windowHidden (const lang::EventObject&) +{ + ThrowIfDisposed(); +} + +//----- XPaintListener -------------------------------------------------------- + +void SAL_CALL PresenterHelpView::windowPaint (const css::awt::PaintEvent& rEvent) +{ + Paint(rEvent.UpdateRect); +} + +void PresenterHelpView::Paint (const awt::Rectangle& rUpdateBox) +{ + ProvideCanvas(); + if ( ! mxCanvas.is()) + return; + + // Clear background. + const awt::Rectangle aWindowBox (mxWindow->getPosSize()); + mpPresenterController->GetCanvasHelper()->Paint( + mpPresenterController->GetViewBackground(mxViewId->getResourceURL()), + mxCanvas, + rUpdateBox, + awt::Rectangle(0,0,aWindowBox.Width,aWindowBox.Height), + awt::Rectangle()); + + // Paint vertical divider. + + rendering::ViewState aViewState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + PresenterGeometryHelper::CreatePolygon(rUpdateBox, mxCanvas->getDevice())); + + rendering::RenderState aRenderState ( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor); + + mxCanvas->drawLine( + geometry::RealPoint2D((aWindowBox.Width/2.0), gnVerticalBorder), + geometry::RealPoint2D((aWindowBox.Width/2.0), mnSeparatorY - gnVerticalBorder), + aViewState, + aRenderState); + + // Paint the horizontal separator. + mxCanvas->drawLine( + geometry::RealPoint2D(0, mnSeparatorY), + geometry::RealPoint2D(aWindowBox.Width, mnSeparatorY), + aViewState, + aRenderState); + + // Paint text. + double nY (gnVerticalBorder); + for (const auto& rxBlock : *mpTextContainer) + { + sal_Int32 LeftX1 = gnHorizontalGap; + sal_Int32 LeftX2 = aWindowBox.Width/2 - gnHorizontalGap; + sal_Int32 RightX1 = aWindowBox.Width/2 + gnHorizontalGap; + sal_Int32 RightX2 = aWindowBox.Width - gnHorizontalGap; + /* check whether RTL interface or not + then replace the windowbox position */ + if(AllSettings::GetLayoutRTL()) + { + LeftX1 = aWindowBox.Width/2 + gnHorizontalGap; + LeftX2 = aWindowBox.Width - gnHorizontalGap; + RightX1 = gnHorizontalGap; + RightX2 = aWindowBox.Width/2 - gnHorizontalGap; + } + const double nLeftHeight ( + rxBlock->maLeft.Paint(mxCanvas, + geometry::RealRectangle2D( + LeftX1, + nY, + LeftX2, + aWindowBox.Height - gnVerticalBorder), + false, + aViewState, + aRenderState, + mpFont->mxFont)); + const double nRightHeight ( + rxBlock->maRight.Paint(mxCanvas, + geometry::RealRectangle2D( + RightX1, + nY, + RightX2, + aWindowBox.Height - gnVerticalBorder), + true, + aViewState, + aRenderState, + mpFont->mxFont)); + + nY += ::std::max(nLeftHeight,nRightHeight); + } + + Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY); + if (xSpriteCanvas.is()) + xSpriteCanvas->updateScreen(false); +} + +void PresenterHelpView::ReadHelpStrings() +{ + mpTextContainer.reset(new TextContainer); + PresenterConfigurationAccess aConfiguration ( + mxComponentContext, + "/org.openoffice.Office.PresenterScreen/", + PresenterConfigurationAccess::READ_ONLY); + Reference<container::XNameAccess> xStrings ( + aConfiguration.GetConfigurationNode("PresenterScreenSettings/HelpView/HelpStrings"), + UNO_QUERY); + PresenterConfigurationAccess::ForAll( + xStrings, + [this](OUString const&, uno::Reference<beans::XPropertySet> const& xProps) + { + return this->ProcessString(xProps); + }); +} + +void PresenterHelpView::ProcessString ( + const Reference<beans::XPropertySet>& rsProperties) +{ + if ( ! rsProperties.is()) + return; + + OUString sLeftText; + PresenterConfigurationAccess::GetProperty(rsProperties, "Left") >>= sLeftText; + OUString sRightText; + PresenterConfigurationAccess::GetProperty(rsProperties, "Right") >>= sRightText; + mpTextContainer->push_back( + std::make_shared<Block>( + sLeftText, sRightText, mpFont->mxFont, mnMaximalWidth)); +} + +void PresenterHelpView::CheckFontSize() +{ + if (!mpFont) + return; + + sal_Int32 nBestSize (6); + + // Scaling down and then reformatting can cause the text to be too large + // still. So do this again and again until the text size is + // small enough. Restrict the number of loops. + for (int nLoopCount=0; nLoopCount<5; ++nLoopCount) + { + double nY = std::accumulate(mpTextContainer->begin(), mpTextContainer->end(), double(0), + [](const double& sum, const std::shared_ptr<Block>& rxBlock) { + return sum + std::max( + rxBlock->maLeft.GetHeight(), + rxBlock->maRight.GetHeight()); + }); + + const double nHeightDifference (nY - (mnSeparatorY-gnVerticalBorder)); + if (nHeightDifference <= 0 && nHeightDifference > -50) + { + // We have found a good font size that is large and leaves not + // too much space below the help text. + return; + } + + // Use a simple linear transformation to calculate initial guess of + // a size that lets all help text be shown inside the window. + const double nScale (double(mnSeparatorY-gnVerticalBorder) / nY); + if (nScale > 1.0 && nScale < 1.05) + break; + + sal_Int32 nFontSizeGuess (sal_Int32(mpFont->mnSize * nScale)); + if (nHeightDifference<=0 && mpFont->mnSize>nBestSize) + nBestSize = mpFont->mnSize; + mpFont->mnSize = nFontSizeGuess; + mpFont->mxFont = nullptr; + mpFont->PrepareFont(mxCanvas); + + // Reformat blocks. + for (auto& rxBlock : *mpTextContainer) + rxBlock->Update(mpFont->mxFont, mnMaximalWidth); + } + + if (nBestSize != mpFont->mnSize) + { + mpFont->mnSize = nBestSize; + mpFont->mxFont = nullptr; + mpFont->PrepareFont(mxCanvas); + + // Reformat blocks. + for (auto& rxBlock : *mpTextContainer) + { + rxBlock->Update(mpFont->mxFont, mnMaximalWidth); + } + } +} + +//----- XResourceId ----------------------------------------------------------- + +Reference<XResourceId> SAL_CALL PresenterHelpView::getResourceId() +{ + ThrowIfDisposed(); + return mxViewId; +} + +sal_Bool SAL_CALL PresenterHelpView::isAnchorOnly() +{ + return false; +} + + +void PresenterHelpView::ProvideCanvas() +{ + if ( ! mxCanvas.is() && mxPane.is()) + { + mxCanvas = mxPane->getCanvas(); + if ( ! mxCanvas.is()) + return; + Reference<lang::XComponent> xComponent (mxCanvas, UNO_QUERY); + if (xComponent.is()) + xComponent->addEventListener(static_cast<awt::XPaintListener*>(this)); + + if (mpCloseButton.is()) + mpCloseButton->SetCanvas(mxCanvas, mxWindow); + } +} + +void PresenterHelpView::Resize() +{ + if (!(mpCloseButton && mxWindow.is())) + return; + + const awt::Rectangle aWindowBox (mxWindow->getPosSize()); + mnMaximalWidth = (mxWindow->getPosSize().Width - 4*gnHorizontalGap) / 2; + + // Place vertical separator. + mnSeparatorY = aWindowBox.Height + - mpCloseButton->GetSize().Height - gnVerticalButtonPadding; + + mpCloseButton->SetCenter(geometry::RealPoint2D( + aWindowBox.Width/2.0, + aWindowBox.Height - mpCloseButton->GetSize().Height/2.0)); + + CheckFontSize(); +} + +void PresenterHelpView::ThrowIfDisposed() +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ( + "PresenterHelpView has been already disposed", + static_cast<uno::XWeak*>(this)); + } +} + +//===== LineDescriptor ========================================================= + +namespace { + +LineDescriptor::LineDescriptor() + : maSize(0,0), + mnVerticalOffset(0) +{ +} + +void LineDescriptor::AddPart ( + std::u16string_view rsLine, + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont) +{ + msLine += rsLine; + + CalculateSize(rxFont); +} + +bool LineDescriptor::IsEmpty() const +{ + return msLine.isEmpty(); +} + +void LineDescriptor::CalculateSize ( + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont) +{ + OSL_ASSERT(rxFont.is()); + + rendering::StringContext aContext (msLine, 0, msLine.getLength()); + Reference<rendering::XTextLayout> xLayout ( + rxFont->createTextLayout(aContext, rendering::TextDirection::WEAK_LEFT_TO_RIGHT, 0)); + const geometry::RealRectangle2D aTextBBox (xLayout->queryTextBounds()); + maSize = css::geometry::RealSize2D(aTextBBox.X2 - aTextBBox.X1, aTextBBox.Y2 - aTextBBox.Y1); + mnVerticalOffset = aTextBBox.Y2; +} + +} // end of anonymous namespace + +//===== LineDescriptorList ==================================================== + +namespace { + +LineDescriptorList::LineDescriptorList ( + const OUString& rsText, + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont, + const sal_Int32 nMaximalWidth) + : msText(rsText) +{ + Update(rxFont, nMaximalWidth); +} + +double LineDescriptorList::Paint( + const Reference<rendering::XCanvas>& rxCanvas, + const geometry::RealRectangle2D& rBBox, + const bool bFlushLeft, + const rendering::ViewState& rViewState, + rendering::RenderState& rRenderState, + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont) const +{ + if ( ! rxCanvas.is()) + return 0; + + double nY (rBBox.Y1); + for (const auto& rLine : *mpLineDescriptors) + { + double nX; + /// check whether RTL interface or not + if(!AllSettings::GetLayoutRTL()) + { + nX = rBBox.X1; + if ( ! bFlushLeft) + nX = rBBox.X2 - rLine.maSize.Width; + } + else + { + nX=rBBox.X2 - rLine.maSize.Width; + if ( ! bFlushLeft) + nX = rBBox.X1; + } + rRenderState.AffineTransform.m02 = nX; + rRenderState.AffineTransform.m12 = nY + rLine.maSize.Height - rLine.mnVerticalOffset; + + const rendering::StringContext aContext (rLine.msLine, 0, rLine.msLine.getLength()); + Reference<rendering::XTextLayout> xLayout ( + rxFont->createTextLayout(aContext, rendering::TextDirection::WEAK_LEFT_TO_RIGHT, 0)); + rxCanvas->drawTextLayout ( + xLayout, + rViewState, + rRenderState); + + nY += rLine.maSize.Height * 1.2; + } + + return nY - rBBox.Y1; +} + +double LineDescriptorList::GetHeight() const +{ + return std::accumulate(mpLineDescriptors->begin(), mpLineDescriptors->end(), double(0), + [](const double& nHeight, const LineDescriptor& rLine) { + return nHeight + rLine.maSize.Height * 1.2; + }); +} + +void LineDescriptorList::Update ( + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont, + const sal_Int32 nMaximalWidth) +{ + vector<OUString> aTextParts; + SplitText(msText, aTextParts); + FormatText(aTextParts, rxFont, nMaximalWidth); +} + +void LineDescriptorList::SplitText ( + const OUString& rsText, + vector<OUString>& rTextParts) +{ + const char cQuote ('\''); + const char cSeparator (','); + + sal_Int32 nIndex (0); + sal_Int32 nStart (0); + sal_Int32 nLength (rsText.getLength()); + bool bIsQuoted (false); + while (nIndex < nLength) + { + const sal_Int32 nQuoteIndex (rsText.indexOf(cQuote, nIndex)); + const sal_Int32 nSeparatorIndex (rsText.indexOf(cSeparator, nIndex)); + if (nQuoteIndex>=0 && (nSeparatorIndex==-1 || nQuoteIndex<nSeparatorIndex)) + { + bIsQuoted = !bIsQuoted; + nIndex = nQuoteIndex+1; + continue; + } + + const sal_Int32 nNextIndex = nSeparatorIndex; + if (nNextIndex < 0) + { + break; + } + else if ( ! bIsQuoted) + { + rTextParts.push_back(rsText.copy(nStart, nNextIndex-nStart)); + nStart = nNextIndex + 1; + } + nIndex = nNextIndex+1; + } + if (nStart < nLength) + rTextParts.push_back(rsText.copy(nStart, nLength-nStart)); +} + +void LineDescriptorList::FormatText ( + const vector<OUString>& rTextParts, + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont, + const sal_Int32 nMaximalWidth) +{ + LineDescriptor aLineDescriptor; + + mpLineDescriptors = std::make_shared<vector<LineDescriptor>>(); + + vector<OUString>::const_iterator iPart (rTextParts.begin()); + vector<OUString>::const_iterator iEnd (rTextParts.end()); + while (iPart!=iEnd) + { + if (aLineDescriptor.IsEmpty()) + { + // Avoid empty lines. + if (PresenterCanvasHelper::GetTextSize( + rxFont, *iPart).Width > nMaximalWidth) + { + const char cSpace (' '); + + sal_Int32 nIndex (0); + sal_Int32 nStart (0); + sal_Int32 nLength (iPart->getLength()); + while (nIndex < nLength) + { + sal_Int32 nSpaceIndex (iPart->indexOf(cSpace, nIndex)); + while (nSpaceIndex >= 0 && PresenterCanvasHelper::GetTextSize( + rxFont, iPart->copy(nStart, nSpaceIndex-nStart)).Width <= nMaximalWidth) + { + nIndex = nSpaceIndex; + nSpaceIndex = iPart->indexOf(cSpace, nIndex+1); + } + + if (nSpaceIndex < 0 && PresenterCanvasHelper::GetTextSize( + rxFont, iPart->copy(nStart, nLength-nStart)).Width <= nMaximalWidth) + { + nIndex = nLength; + } + + if (nIndex == nStart) + { + nIndex = nLength; + } + + aLineDescriptor.AddPart(iPart->subView(nStart, nIndex-nStart), rxFont); + if (nIndex != nLength) + { + mpLineDescriptors->push_back(aLineDescriptor); + aLineDescriptor = LineDescriptor(); + } + nStart = nIndex; + } + } + else + { + aLineDescriptor.AddPart(*iPart, rxFont); + } + } + else if (PresenterCanvasHelper::GetTextSize( + rxFont, aLineDescriptor.msLine+", "+*iPart).Width > nMaximalWidth) + { + aLineDescriptor.AddPart(u",", rxFont); + mpLineDescriptors->push_back(aLineDescriptor); + aLineDescriptor = LineDescriptor(); + continue; + } + else + { + aLineDescriptor.AddPart(OUStringConcatenation(", "+*iPart), rxFont); + } + ++iPart; + } + if ( ! aLineDescriptor.IsEmpty()) + { + mpLineDescriptors->push_back(aLineDescriptor); + } +} + +} // end of anonymous namespace + +//===== Block ================================================================= + +namespace { + +Block::Block ( + const OUString& rsLeftText, + const OUString& rsRightText, + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont, + const sal_Int32 nMaximalWidth) + : maLeft(rsLeftText, rxFont, nMaximalWidth), + maRight(rsRightText, rxFont, nMaximalWidth) +{ +} + +void Block::Update ( + const css::uno::Reference<css::rendering::XCanvasFont>& rxFont, + const sal_Int32 nMaximalWidth) +{ + maLeft.Update(rxFont, nMaximalWidth); + maRight.Update(rxFont, nMaximalWidth); +} + +} // end of anonymous namespace + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterHelpView.hxx b/sdext/source/presenter/PresenterHelpView.hxx new file mode 100644 index 000000000..58f629a36 --- /dev/null +++ b/sdext/source/presenter/PresenterHelpView.hxx @@ -0,0 +1,121 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERHELPVIEW_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERHELPVIEW_HXX + +#include "PresenterController.hxx" +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <com/sun/star/awt/XPaintListener.hpp> +#include <com/sun/star/awt/XWindowListener.hpp> +#include <com/sun/star/drawing/framework/XView.hpp> +#include <com/sun/star/drawing/framework/XResourceId.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <memory> + +namespace sdext::presenter { + +class PresenterButton; + +typedef cppu::WeakComponentImplHelper< + css::drawing::framework::XView, + css::awt::XWindowListener, + css::awt::XPaintListener + > PresenterHelpViewInterfaceBase; + +/** Show help text that describes the defined keys. +*/ +class PresenterHelpView + : private ::cppu::BaseMutex, + public PresenterHelpViewInterfaceBase +{ +public: + explicit PresenterHelpView ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId, + const css::uno::Reference<css::frame::XController>& rxController, + const ::rtl::Reference<PresenterController>& rpPresenterController); + virtual ~PresenterHelpView() override; + + virtual void SAL_CALL disposing() override; + + // lang::XEventListener + + virtual void SAL_CALL + disposing (const css::lang::EventObject& rEventObject) override; + + // XWindowListener + + virtual void SAL_CALL windowResized (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowMoved (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowShown (const css::lang::EventObject& rEvent) override; + + virtual void SAL_CALL windowHidden (const css::lang::EventObject& rEvent) override; + + // XPaintListener + + virtual void SAL_CALL windowPaint (const css::awt::PaintEvent& rEvent) override; + + // XResourceId + + virtual css::uno::Reference<css::drawing::framework::XResourceId> SAL_CALL getResourceId() override; + + virtual sal_Bool SAL_CALL isAnchorOnly() override; + +private: + class TextContainer; + + css::uno::Reference<css::uno::XComponentContext> mxComponentContext; + css::uno::Reference<css::drawing::framework::XResourceId> mxViewId; + css::uno::Reference<css::drawing::framework::XPane> mxPane; + css::uno::Reference<css::awt::XWindow> mxWindow; + css::uno::Reference<css::rendering::XCanvas> mxCanvas; + ::rtl::Reference<PresenterController> mpPresenterController; + PresenterTheme::SharedFontDescriptor mpFont; + std::unique_ptr<TextContainer> mpTextContainer; + ::rtl::Reference<PresenterButton> mpCloseButton; + sal_Int32 mnSeparatorY; + sal_Int32 mnMaximalWidth; + + void ProvideCanvas(); + void Resize(); + void Paint (const css::awt::Rectangle& rRedrawArea); + void ReadHelpStrings(); + void ProcessString ( + const css::uno::Reference<css::beans::XPropertySet>& rsProperties); + + /** Find a font size, so that all text can be displayed at the same + time. + */ + void CheckFontSize(); + + /** @throws css::lang::DisposedException when the object has already been + disposed. + */ + void ThrowIfDisposed(); +}; + +} // end of namespace ::sdext::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterHelper.cxx b/sdext/source/presenter/PresenterHelper.cxx new file mode 100644 index 000000000..76bec0ece --- /dev/null +++ b/sdext/source/presenter/PresenterHelper.cxx @@ -0,0 +1,56 @@ +/* -*- 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 "PresenterHelper.hxx" + +#include <com/sun/star/presentation/XPresentationSupplier.hpp> +#include <com/sun/star/presentation/XPresentation2.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::presentation; + +namespace sdext::presenter { + +constexpr OUStringLiteral msPaneURLPrefix( u"private:resource/pane/"); +const OUString PresenterHelper::msFullScreenPaneURL( msPaneURLPrefix + "FullScreenPane"); + +Reference<presentation::XSlideShowController> PresenterHelper::GetSlideShowController ( + const Reference<frame::XController>& rxController) +{ + Reference<presentation::XSlideShowController> xSlideShowController; + + if( rxController.is() ) try + { + Reference<XPresentationSupplier> xPS ( rxController->getModel(), UNO_QUERY_THROW); + + Reference<XPresentation2> xPresentation(xPS->getPresentation(), UNO_QUERY_THROW); + + xSlideShowController = xPresentation->getController(); + } + catch(RuntimeException&) + { + } + + return xSlideShowController; +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterHelper.hxx b/sdext/source/presenter/PresenterHelper.hxx new file mode 100644 index 000000000..69fc57333 --- /dev/null +++ b/sdext/source/presenter/PresenterHelper.hxx @@ -0,0 +1,48 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERHELPER_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERHELPER_HXX + +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/presentation/XSlideShowController.hpp> + +namespace sdext::presenter +{ +/** Collection of helper functions that do not fit in anywhere else. + Provide access to frequently used strings of the drawing framework. +*/ +namespace PresenterHelper +{ +extern const OUString msFullScreenPaneURL; + +/** Return the slide show controller of a running presentation that has + the same document as the given framework controller. + @return + When no presentation is running this method returns an empty reference. +*/ +css::uno::Reference<css::presentation::XSlideShowController> +GetSlideShowController(const css::uno::Reference<css::frame::XController>& rxController); +} + +} // end of namespace presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterNotesView.cxx b/sdext/source/presenter/PresenterNotesView.cxx new file mode 100644 index 000000000..457be1f61 --- /dev/null +++ b/sdext/source/presenter/PresenterNotesView.cxx @@ -0,0 +1,650 @@ +/* -*- 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 <vcl/settings.hxx> +#include "PresenterNotesView.hxx" +#include "PresenterButton.hxx" +#include "PresenterCanvasHelper.hxx" +#include "PresenterGeometryHelper.hxx" +#include "PresenterPaintManager.hxx" +#include "PresenterScrollBar.hxx" +#include "PresenterTextView.hxx" +#include <com/sun/star/accessibility/AccessibleTextType.hpp> +#include <com/sun/star/awt/Key.hpp> +#include <com/sun/star/awt/KeyModifier.hpp> +#include <com/sun/star/awt/PosSize.hpp> +#include <com/sun/star/drawing/framework/XControllerManager.hpp> +#include <com/sun/star/drawing/framework/XConfigurationController.hpp> +#include <com/sun/star/drawing/framework/XPane.hpp> +#include <com/sun/star/lang/XServiceName.hpp> +#include <com/sun/star/presentation/XPresentationPage.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/XSpriteCanvas.hpp> +#include <com/sun/star/text/XTextRange.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing::framework; + +const sal_Int32 gnSpaceBelowSeparator (10); +const sal_Int32 gnSpaceAboveSeparator (10); +const double gnLineScrollFactor (1.2); + +namespace sdext::presenter { + +//===== PresenterNotesView ==================================================== + +PresenterNotesView::PresenterNotesView ( + const Reference<XComponentContext>& rxComponentContext, + const Reference<XResourceId>& rxViewId, + const Reference<frame::XController>& rxController, + const ::rtl::Reference<PresenterController>& rpPresenterController) + : PresenterNotesViewInterfaceBase(m_aMutex), + mxViewId(rxViewId), + mpPresenterController(rpPresenterController), + maSeparatorColor(0xffffff), + mnSeparatorYLocation(0), + mnTop(0) +{ + try + { + Reference<XControllerManager> xCM (rxController, UNO_QUERY_THROW); + Reference<XConfigurationController> xCC (xCM->getConfigurationController(), UNO_SET_THROW); + Reference<XPane> xPane (xCC->getResource(rxViewId->getAnchor()), UNO_QUERY_THROW); + + mxParentWindow = xPane->getWindow(); + mxCanvas = xPane->getCanvas(); + mpTextView = std::make_shared<PresenterTextView>( + rxComponentContext, + mxCanvas, + mpPresenterController->GetPaintManager()->GetInvalidator(mxParentWindow)); + + const OUString sResourceURL (mxViewId->getResourceURL()); + mpFont = std::make_shared<PresenterTheme::FontDescriptor>( + rpPresenterController->GetViewFont(sResourceURL)); + maSeparatorColor = mpFont->mnColor; + mpTextView->SetFont(mpFont); + + CreateToolBar(rxComponentContext, rpPresenterController); + + mpCloseButton = PresenterButton::Create( + rxComponentContext, + mpPresenterController, + mpPresenterController->GetTheme(), + mxParentWindow, + mxCanvas, + "NotesViewCloser"); + + if (mxParentWindow.is()) + { + mxParentWindow->addWindowListener(this); + mxParentWindow->addPaintListener(this); + mxParentWindow->addKeyListener(this); + mxParentWindow->setVisible(true); + } + + mpScrollBar = new PresenterVerticalScrollBar( + rxComponentContext, + mxParentWindow, + mpPresenterController->GetPaintManager(), + [this](double f) { return this->SetTop(f); }); + mpScrollBar->SetBackground( + mpPresenterController->GetViewBackground(mxViewId->getResourceURL())); + + mpScrollBar->SetCanvas(mxCanvas); + + Layout(); + } + catch (RuntimeException&) + { + PresenterNotesView::disposing(); + throw; + } +} + +PresenterNotesView::~PresenterNotesView() +{ +} + +void SAL_CALL PresenterNotesView::disposing() +{ + if (mxParentWindow.is()) + { + mxParentWindow->removeWindowListener(this); + mxParentWindow->removePaintListener(this); + mxParentWindow->removeKeyListener(this); + mxParentWindow = nullptr; + } + + // Dispose tool bar. + { + Reference<XComponent> xComponent = mpToolBar; + mpToolBar = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + { + Reference<XComponent> xComponent (mxToolBarCanvas, UNO_QUERY); + mxToolBarCanvas = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + { + Reference<XComponent> xComponent = mxToolBarWindow; + mxToolBarWindow = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + + // Dispose close button + { + Reference<XComponent> xComponent = mpCloseButton; + mpCloseButton = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + + // Create the tool bar. + + mpScrollBar = nullptr; + + mxViewId = nullptr; +} + +void PresenterNotesView::CreateToolBar ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const ::rtl::Reference<PresenterController>& rpPresenterController) +{ + if (!rpPresenterController) + return; + + Reference<drawing::XPresenterHelper> xPresenterHelper ( + rpPresenterController->GetPresenterHelper()); + if ( ! xPresenterHelper.is()) + return; + + // Create a new window as container of the tool bar. + mxToolBarWindow = xPresenterHelper->createWindow( + mxParentWindow, + false, + true, + false, + false); + mxToolBarCanvas = xPresenterHelper->createSharedCanvas ( + Reference<rendering::XSpriteCanvas>(mxCanvas, UNO_QUERY), + mxParentWindow, + mxCanvas, + mxParentWindow, + mxToolBarWindow); + + // Create the tool bar. + mpToolBar = new PresenterToolBar( + rxContext, + mxToolBarWindow, + mxToolBarCanvas, + rpPresenterController, + PresenterToolBar::Left); + mpToolBar->Initialize( + "PresenterScreenSettings/ToolBars/NotesToolBar"); +} + +void PresenterNotesView::SetSlide (const Reference<drawing::XDrawPage>& rxNotesPage) +{ + static constexpr OUStringLiteral sNotesShapeName ( + u"com.sun.star.presentation.NotesShape"); + static constexpr OUStringLiteral sTextShapeName ( + u"com.sun.star.drawing.TextShape"); + + if (!rxNotesPage.is()) + return; + + // Iterate over all shapes and find the one that holds the text. + sal_Int32 nCount (rxNotesPage->getCount()); + for (sal_Int32 nIndex=0; nIndex<nCount; ++nIndex) + { + + Reference<lang::XServiceName> xServiceName ( + rxNotesPage->getByIndex(nIndex), UNO_QUERY); + if (xServiceName.is() + && xServiceName->getServiceName() == sNotesShapeName) + { + } + else + { + Reference<drawing::XShapeDescriptor> xShapeDescriptor ( + rxNotesPage->getByIndex(nIndex), UNO_QUERY); + if (xShapeDescriptor.is()) + { + OUString sType (xShapeDescriptor->getShapeType()); + if (sType == sNotesShapeName || sType == sTextShapeName) + { + Reference<text::XTextRange> xText ( + rxNotesPage->getByIndex(nIndex), UNO_QUERY); + if (xText.is()) + { + mpTextView->SetText(Reference<text::XText>(xText, UNO_QUERY)); + } + } + } + } + } + + Layout(); + + if (mpScrollBar) + { + mpScrollBar->SetThumbPosition(0, false); + UpdateScrollBar(); + } + + Invalidate(); +} + +//----- lang::XEventListener ------------------------------------------------- + +void SAL_CALL PresenterNotesView::disposing (const lang::EventObject& rEventObject) +{ + if (rEventObject.Source == mxParentWindow) + mxParentWindow = nullptr; +} + +//----- XWindowListener ------------------------------------------------------- + +void SAL_CALL PresenterNotesView::windowResized (const awt::WindowEvent&) +{ + Layout(); +} + +void SAL_CALL PresenterNotesView::windowMoved (const awt::WindowEvent&) {} + +void SAL_CALL PresenterNotesView::windowShown (const lang::EventObject&) {} + +void SAL_CALL PresenterNotesView::windowHidden (const lang::EventObject&) {} + +//----- XPaintListener -------------------------------------------------------- + +void SAL_CALL PresenterNotesView::windowPaint (const awt::PaintEvent& rEvent) +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ( + "PresenterNotesView object has already been disposed", + static_cast<uno::XWeak*>(this)); + } + + if ( ! mbIsPresenterViewActive) + return; + + ::osl::MutexGuard aSolarGuard (::osl::Mutex::getGlobalMutex()); + Paint(rEvent.UpdateRect); +} + +//----- XResourceId ----------------------------------------------------------- + +Reference<XResourceId> SAL_CALL PresenterNotesView::getResourceId() +{ + return mxViewId; +} + +sal_Bool SAL_CALL PresenterNotesView::isAnchorOnly() +{ + return false; +} + +//----- XDrawView ------------------------------------------------------------- + +void SAL_CALL PresenterNotesView::setCurrentPage (const Reference<drawing::XDrawPage>& rxSlide) +{ + // Get the associated notes page. + mxCurrentNotesPage = nullptr; + try + { + Reference<presentation::XPresentationPage> xPresentationPage(rxSlide, UNO_QUERY); + if (xPresentationPage.is()) + mxCurrentNotesPage = xPresentationPage->getNotesPage(); + } + catch (RuntimeException&) + { + } + + SetSlide(mxCurrentNotesPage); +} + +Reference<drawing::XDrawPage> SAL_CALL PresenterNotesView::getCurrentPage() +{ + return nullptr; +} + +//----- XKeyListener ---------------------------------------------------------- + +void SAL_CALL PresenterNotesView::keyPressed (const awt::KeyEvent& rEvent) +{ + switch (rEvent.KeyCode) + { + case awt::Key::A: + Scroll(-gnLineScrollFactor * mpFont->mnSize); + break; + + case awt::Key::Y: + case awt::Key::Z: + Scroll(+gnLineScrollFactor * mpFont->mnSize); + break; + + case awt::Key::S: + ChangeFontSize(-1); + break; + + case awt::Key::G: + ChangeFontSize(+1); + break; + + case awt::Key::H: + if (mpTextView) + mpTextView->MoveCaret( + -1, + (rEvent.Modifiers == awt::KeyModifier::SHIFT) + ? css::accessibility::AccessibleTextType::CHARACTER + : css::accessibility::AccessibleTextType::WORD); + break; + + case awt::Key::L: + if (mpTextView) + mpTextView->MoveCaret( + +1, + (rEvent.Modifiers == awt::KeyModifier::SHIFT) + ? css::accessibility::AccessibleTextType::CHARACTER + : css::accessibility::AccessibleTextType::WORD); + break; + } +} + +void SAL_CALL PresenterNotesView::keyReleased (const awt::KeyEvent&) {} + + +void PresenterNotesView::Layout() +{ + if ( ! mxParentWindow.is()) + return; + awt::Rectangle aWindowBox (mxParentWindow->getPosSize()); + geometry::RealRectangle2D aNewTextBoundingBox (0,0,aWindowBox.Width, aWindowBox.Height); + // Size the tool bar and the horizontal separator above it. + if (mxToolBarWindow.is()) + { + const geometry::RealSize2D aToolBarSize (mpToolBar->GetMinimalSize()); + const sal_Int32 nToolBarHeight = sal_Int32(aToolBarSize.Height + 0.5); + mxToolBarWindow->setPosSize(0, aWindowBox.Height - nToolBarHeight, + sal_Int32(aToolBarSize.Width + 0.5), nToolBarHeight, + awt::PosSize::POSSIZE); + mnSeparatorYLocation = aWindowBox.Height - nToolBarHeight - gnSpaceBelowSeparator; + aNewTextBoundingBox.Y2 = mnSeparatorYLocation - gnSpaceAboveSeparator; + // Place the close button. + if (mpCloseButton) + mpCloseButton->SetCenter(geometry::RealPoint2D( + (aWindowBox.Width + aToolBarSize.Width) / 2, + aWindowBox.Height - aToolBarSize.Height/2)); + } + // Check whether the vertical scroll bar is necessary. + if (mpScrollBar) + { + bool bShowVerticalScrollbar (false); + try + { + const double nTextBoxHeight (aNewTextBoundingBox.Y2 - aNewTextBoundingBox.Y1); + const double nHeight (mpTextView->GetTotalTextHeight()); + if (nHeight > nTextBoxHeight) + { + bShowVerticalScrollbar = true; + if(!AllSettings::GetLayoutRTL()) + aNewTextBoundingBox.X2 -= mpScrollBar->GetSize(); + else + aNewTextBoundingBox.X1 += mpScrollBar->GetSize(); + } + mpScrollBar->SetTotalSize(nHeight); + } + catch(beans::UnknownPropertyException&) + { + OSL_ASSERT(false); + } + if(AllSettings::GetLayoutRTL()) + { + mpScrollBar->SetVisible(bShowVerticalScrollbar); + mpScrollBar->SetPosSize( + geometry::RealRectangle2D( + aNewTextBoundingBox.X1 - mpScrollBar->GetSize(), + aNewTextBoundingBox.Y1, + aNewTextBoundingBox.X1, + aNewTextBoundingBox.Y2)); + if( ! bShowVerticalScrollbar) + mpScrollBar->SetThumbPosition(0, false); + UpdateScrollBar(); + } + else + { + mpScrollBar->SetVisible(bShowVerticalScrollbar); + mpScrollBar->SetPosSize( + geometry::RealRectangle2D( + aWindowBox.Width - mpScrollBar->GetSize(), + aNewTextBoundingBox.Y1, + aNewTextBoundingBox.X2 + mpScrollBar->GetSize(), + aNewTextBoundingBox.Y2)); + if( ! bShowVerticalScrollbar) + mpScrollBar->SetThumbPosition(0, false); + UpdateScrollBar(); + } + } + // Has the text area has changed it position or size? + if (aNewTextBoundingBox.X1 != maTextBoundingBox.X1 + || aNewTextBoundingBox.Y1 != maTextBoundingBox.Y1 + || aNewTextBoundingBox.X2 != maTextBoundingBox.X2 + || aNewTextBoundingBox.Y2 != maTextBoundingBox.Y2) + { + maTextBoundingBox = aNewTextBoundingBox; + mpTextView->SetLocation( + geometry::RealPoint2D( + aNewTextBoundingBox.X1, + aNewTextBoundingBox.Y1)); + mpTextView->SetSize( + geometry::RealSize2D( + aNewTextBoundingBox.X2 - aNewTextBoundingBox.X1, + aNewTextBoundingBox.Y2 - aNewTextBoundingBox.Y1)); + } +} + +void PresenterNotesView::Paint (const awt::Rectangle& rUpdateBox) +{ + if ( ! mxParentWindow.is()) + return; + if ( ! mxCanvas.is()) + return; + + if (!mpBackground) + mpBackground = mpPresenterController->GetViewBackground(mxViewId->getResourceURL()); + + if (rUpdateBox.Y < maTextBoundingBox.Y2 + && rUpdateBox.X < maTextBoundingBox.X2) + { + PaintText(rUpdateBox); + } + + mpTextView->Paint(rUpdateBox); + + if (rUpdateBox.Y + rUpdateBox.Height > maTextBoundingBox.Y2) + { + PaintToolBar(rUpdateBox); + } +} + +void PresenterNotesView::PaintToolBar (const awt::Rectangle& rUpdateBox) +{ + awt::Rectangle aWindowBox (mxParentWindow->getPosSize()); + + rendering::ViewState aViewState ( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr); + rendering::RenderState aRenderState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + + if (mpBackground) + { + // Paint the background. + mpPresenterController->GetCanvasHelper()->Paint( + mpBackground, + mxCanvas, + rUpdateBox, + awt::Rectangle(0,sal_Int32(maTextBoundingBox.Y2),aWindowBox.Width,aWindowBox.Height), + awt::Rectangle()); + } + + // Paint the horizontal separator. + OSL_ASSERT(mxViewId.is()); + PresenterCanvasHelper::SetDeviceColor(aRenderState, maSeparatorColor); + + mxCanvas->drawLine( + geometry::RealPoint2D(0,mnSeparatorYLocation), + geometry::RealPoint2D(aWindowBox.Width,mnSeparatorYLocation), + aViewState, + aRenderState); +} + +void PresenterNotesView::PaintText (const awt::Rectangle& rUpdateBox) +{ + const awt::Rectangle aBox (PresenterGeometryHelper::Intersection(rUpdateBox, + PresenterGeometryHelper::ConvertRectangle(maTextBoundingBox))); + + if (aBox.Width <= 0 || aBox.Height <= 0) + return; + + if (mpBackground) + { + // Paint the background. + mpPresenterController->GetCanvasHelper()->Paint( + mpBackground, + mxCanvas, + rUpdateBox, + aBox, + awt::Rectangle()); + } + + Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY); + if (xSpriteCanvas.is()) + xSpriteCanvas->updateScreen(false); +} + +void PresenterNotesView::Invalidate() +{ + mpPresenterController->GetPaintManager()->Invalidate( + mxParentWindow, + PresenterGeometryHelper::ConvertRectangle(maTextBoundingBox)); +} + +void PresenterNotesView::Scroll (const double rnDistance) +{ + try + { + mnTop += rnDistance; + mpTextView->SetOffset(0, mnTop); + + UpdateScrollBar(); + Invalidate(); + } + catch (beans::UnknownPropertyException&) + {} +} + +void PresenterNotesView::SetTop (const double nTop) +{ + try + { + mnTop = nTop; + mpTextView->SetOffset(0, mnTop); + + UpdateScrollBar(); + Invalidate(); + } + catch (beans::UnknownPropertyException&) + {} +} + +void PresenterNotesView::ChangeFontSize (const sal_Int32 nSizeChange) +{ + const sal_Int32 nNewSize (mpFont->mnSize + nSizeChange); + if (nNewSize <= 5) + return; + + mpFont->mnSize = nNewSize; + mpFont->mxFont = nullptr; + mpTextView->SetFont(mpFont); + + Layout(); + UpdateScrollBar(); + Invalidate(); + + // Write the new font size to the configuration to make it persistent. + try + { + const OUString sStyleName (mpPresenterController->GetTheme()->GetStyleName( + mxViewId->getResourceURL())); + std::shared_ptr<PresenterConfigurationAccess> pConfiguration ( + mpPresenterController->GetTheme()->GetNodeForViewStyle( + sStyleName)); + if (pConfiguration == nullptr || !pConfiguration->IsValid()) + return; + + pConfiguration->GoToChild("Font"); + pConfiguration->SetProperty("Size", Any(static_cast<sal_Int32>(nNewSize+0.5))); + pConfiguration->CommitChanges(); + } + catch (Exception&) + { + OSL_ASSERT(false); + } +} + +const std::shared_ptr<PresenterTextView>& PresenterNotesView::GetTextView() const +{ + return mpTextView; +} + +void PresenterNotesView::UpdateScrollBar() +{ + if (!mpScrollBar) + return; + + try + { + mpScrollBar->SetTotalSize(mpTextView->GetTotalTextHeight()); + } + catch(beans::UnknownPropertyException&) + { + OSL_ASSERT(false); + } + + mpScrollBar->SetLineHeight(mpFont->mnSize*1.2); + mpScrollBar->SetThumbPosition(mnTop, false); + + mpScrollBar->SetThumbSize(maTextBoundingBox.Y2 - maTextBoundingBox.Y1); + mpScrollBar->CheckValues(); +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterNotesView.hxx b/sdext/source/presenter/PresenterNotesView.hxx new file mode 100644 index 000000000..1af3f241f --- /dev/null +++ b/sdext/source/presenter/PresenterNotesView.hxx @@ -0,0 +1,156 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERNOTESVIEW_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERNOTESVIEW_HXX + +#include "PresenterController.hxx" +#include "PresenterToolBar.hxx" +#include "PresenterViewFactory.hxx" +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <com/sun/star/awt/XWindowListener.hpp> +#include <com/sun/star/drawing/XDrawPage.hpp> +#include <com/sun/star/drawing/XDrawView.hpp> +#include <com/sun/star/drawing/framework/XView.hpp> +#include <com/sun/star/drawing/framework/XResourceId.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <rtl/ref.hxx> +#include <memory> + +namespace sdext::presenter { + +class PresenterButton; +class PresenterScrollBar; +class PresenterTextView; + +typedef cppu::WeakComponentImplHelper< + css::awt::XWindowListener, + css::awt::XPaintListener, + css::drawing::framework::XView, + css::drawing::XDrawView, + css::awt::XKeyListener + > PresenterNotesViewInterfaceBase; + +/** A drawing framework view of the notes of a slide. At the moment this is + a simple text view that does not show the original formatting of the + notes text. +*/ +class PresenterNotesView + : private ::cppu::BaseMutex, + public PresenterNotesViewInterfaceBase, + public CachablePresenterView +{ +public: + explicit PresenterNotesView ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId, + const css::uno::Reference<css::frame::XController>& rxController, + const ::rtl::Reference<PresenterController>& rpPresenterController); + virtual ~PresenterNotesView() override; + + virtual void SAL_CALL disposing() override; + + /** Typically called from setCurrentSlide() with the notes page that is + associated with the slide given to setCurrentSlide(). + + Iterates over all text shapes on the given notes page and displays + the concatenated text of these. + */ + void SetSlide ( + const css::uno::Reference<css::drawing::XDrawPage>& rxNotesPage); + + void ChangeFontSize (const sal_Int32 nSizeChange); + + const std::shared_ptr<PresenterTextView>& GetTextView() const; + + // lang::XEventListener + + virtual void SAL_CALL + disposing (const css::lang::EventObject& rEventObject) override; + + // XWindowListener + + virtual void SAL_CALL windowResized (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowMoved (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowShown (const css::lang::EventObject& rEvent) override; + + virtual void SAL_CALL windowHidden (const css::lang::EventObject& rEvent) override; + + // XPaintListener + + virtual void SAL_CALL windowPaint (const css::awt::PaintEvent& rEvent) override; + + // XResourceId + + virtual css::uno::Reference<css::drawing::framework::XResourceId> SAL_CALL getResourceId() override; + + virtual sal_Bool SAL_CALL isAnchorOnly() override; + + // XDrawView + + virtual void SAL_CALL setCurrentPage ( + const css::uno::Reference<css::drawing::XDrawPage>& rxSlide) override; + + virtual css::uno::Reference<css::drawing::XDrawPage> SAL_CALL getCurrentPage() override; + + // XKeyListener + + virtual void SAL_CALL keyPressed (const css::awt::KeyEvent& rEvent) override; + virtual void SAL_CALL keyReleased (const css::awt::KeyEvent& rEvent) override; + +private: + css::uno::Reference<css::drawing::framework::XResourceId> mxViewId; + ::rtl::Reference<PresenterController> mpPresenterController; + css::uno::Reference<css::awt::XWindow> mxParentWindow; + css::uno::Reference<css::rendering::XCanvas> mxCanvas; + css::uno::Reference<css::drawing::XDrawPage> mxCurrentNotesPage; + ::rtl::Reference<PresenterScrollBar> mpScrollBar; + css::uno::Reference<css::awt::XWindow> mxToolBarWindow; + css::uno::Reference<css::rendering::XCanvas> mxToolBarCanvas; + ::rtl::Reference<PresenterToolBar> mpToolBar; + ::rtl::Reference<PresenterButton> mpCloseButton; + css::util::Color maSeparatorColor; + sal_Int32 mnSeparatorYLocation; + css::geometry::RealRectangle2D maTextBoundingBox; + SharedBitmapDescriptor mpBackground; + double mnTop; + PresenterTheme::SharedFontDescriptor mpFont; + std::shared_ptr<PresenterTextView> mpTextView; + + void CreateToolBar ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const ::rtl::Reference<PresenterController>& rpPresenterController); + void Layout(); + void Paint (const css::awt::Rectangle& rUpdateBox); + void PaintToolBar (const css::awt::Rectangle& rUpdateBox); + void PaintText (const css::awt::Rectangle& rUpdateBox); + void Invalidate(); + void Scroll (const double nDistance); + void SetTop (const double nTop); + void UpdateScrollBar(); +}; + +} // end of namespace ::sdext::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterPaintManager.cxx b/sdext/source/presenter/PresenterPaintManager.cxx new file mode 100644 index 000000000..ba1bc48de --- /dev/null +++ b/sdext/source/presenter/PresenterPaintManager.cxx @@ -0,0 +1,141 @@ +/* -*- 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 "PresenterPaintManager.hxx" + +#include "PresenterPaneContainer.hxx" +#include <com/sun/star/awt/InvalidateStyle.hpp> +#include <com/sun/star/awt/XWindowPeer.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace sdext::presenter { + +PresenterPaintManager::PresenterPaintManager ( + const css::uno::Reference<css::awt::XWindow>& rxParentWindow, + const css::uno::Reference<css::drawing::XPresenterHelper>& rxPresenterHelper, + const rtl::Reference<PresenterPaneContainer>& rpPaneContainer) + : mxParentWindow(rxParentWindow), + mxParentWindowPeer(rxParentWindow, UNO_QUERY), + mxPresenterHelper(rxPresenterHelper), + mpPaneContainer(rpPaneContainer) +{ +} + +::std::function<void (const css::awt::Rectangle& rRepaintBox)> + PresenterPaintManager::GetInvalidator ( + const css::uno::Reference<css::awt::XWindow>& rxWindow) +{ + return [this, rxWindow] (css::awt::Rectangle const& rRepaintBox) + { + return this->Invalidate(rxWindow, rRepaintBox /* , bSynchronous=false */); + }; +} + +void PresenterPaintManager::Invalidate ( + const css::uno::Reference<css::awt::XWindow>& rxWindow) +{ + sal_Int16 nInvalidateMode (awt::InvalidateStyle::CHILDREN); + + PresenterPaneContainer::SharedPaneDescriptor pDescriptor( + mpPaneContainer->FindContentWindow(rxWindow)); + if (!pDescriptor || ! pDescriptor->mbIsOpaque) + nInvalidateMode |= awt::InvalidateStyle::TRANSPARENT; + else + nInvalidateMode |= awt::InvalidateStyle::NOTRANSPARENT; + + Invalidate(rxWindow, nInvalidateMode); +} + +void PresenterPaintManager::Invalidate ( + const css::uno::Reference<css::awt::XWindow>& rxWindow, + const sal_Int16 nInvalidateFlags) +{ + if ((nInvalidateFlags & awt::InvalidateStyle::TRANSPARENT) != 0) + { + // Window is transparent and parent window(s) have to be painted as + // well. Invalidate the parent explicitly. + if (mxPresenterHelper.is() && mxParentWindowPeer.is()) + { + const awt::Rectangle aBBox ( + mxPresenterHelper->getWindowExtentsRelative(rxWindow, mxParentWindow)); + mxParentWindowPeer->invalidateRect(aBBox, nInvalidateFlags); + } + } + else + { + Reference<awt::XWindowPeer> xPeer (rxWindow, UNO_QUERY); + if (xPeer.is()) + xPeer->invalidate(nInvalidateFlags); + } +} + +void PresenterPaintManager::Invalidate ( + const css::uno::Reference<css::awt::XWindow>& rxWindow, + const css::awt::Rectangle& rRepaintBox, + const bool bSynchronous) +{ + sal_Int16 nInvalidateMode (awt::InvalidateStyle::CHILDREN); + if (bSynchronous) + nInvalidateMode |= awt::InvalidateStyle::UPDATE; + + PresenterPaneContainer::SharedPaneDescriptor pDescriptor( + mpPaneContainer->FindContentWindow(rxWindow)); + if (!pDescriptor || ! pDescriptor->mbIsOpaque) + nInvalidateMode |= awt::InvalidateStyle::TRANSPARENT; + else + nInvalidateMode |= awt::InvalidateStyle::NOTRANSPARENT; + + Invalidate(rxWindow, rRepaintBox, nInvalidateMode); +} + +void PresenterPaintManager::Invalidate ( + const css::uno::Reference<css::awt::XWindow>& rxWindow, + const css::awt::Rectangle& rRepaintBox, + const sal_Int16 nInvalidateFlags) +{ + if ((nInvalidateFlags & awt::InvalidateStyle::TRANSPARENT) != 0) + { + // Window is transparent and parent window(s) have to be painted as + // well. Invalidate the parent explicitly. + if (mxPresenterHelper.is() && mxParentWindowPeer.is()) + { + const awt::Rectangle aBBox ( + mxPresenterHelper->getWindowExtentsRelative(rxWindow, mxParentWindow)); + mxParentWindowPeer->invalidateRect( + awt::Rectangle( + rRepaintBox.X + aBBox.X, + rRepaintBox.Y + aBBox.Y, + rRepaintBox.Width, + rRepaintBox.Height), + nInvalidateFlags); + } + } + else + { + Reference<awt::XWindowPeer> xPeer (rxWindow, UNO_QUERY); + if (xPeer.is()) + xPeer->invalidateRect(rRepaintBox, nInvalidateFlags); + } +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterPaintManager.hxx b/sdext/source/presenter/PresenterPaintManager.hxx new file mode 100644 index 000000000..d3013209a --- /dev/null +++ b/sdext/source/presenter/PresenterPaintManager.hxx @@ -0,0 +1,89 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERPAINTMANAGER_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERPAINTMANAGER_HXX + +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/awt/XWindowPeer.hpp> +#include <com/sun/star/drawing/XPresenterHelper.hpp> +#include <rtl/ref.hxx> + +#include <functional> + +#include "PresenterPaneContainer.hxx" + +namespace sdext::presenter { + +class PresenterPaneContainer; + +/** Synchronize painting of windows and canvases. At the moment there is + just some processing of invalidate calls. + This could be extended to process incoming windowPaint() calls. +*/ +class PresenterPaintManager +{ +public: + /** Create paint manager with the window that is the top node in the + local window hierarchy. + */ + PresenterPaintManager ( + const css::uno::Reference<css::awt::XWindow>& rxParentWindow, + const css::uno::Reference<css::drawing::XPresenterHelper>& rxPresenterHelper, + const rtl::Reference<PresenterPaneContainer>& rpPaneContainer); + + ::std::function<void (const css::awt::Rectangle& rRepaintBox)> + GetInvalidator ( + const css::uno::Reference<css::awt::XWindow>& rxWindow); + + /** Request a repaint of the whole window. + @param rxWindow + May be the parent window or one of its descendents. + */ + void Invalidate ( + const css::uno::Reference<css::awt::XWindow>& rxWindow); + void Invalidate ( + const css::uno::Reference<css::awt::XWindow>& rxWindow, + const sal_Int16 nInvalidateFlags); + + /** Request a repaint of a part of a window. + @param rxWindow + May be the parent window or one of its descendents. + */ + void Invalidate ( + const css::uno::Reference<css::awt::XWindow>& rxWindow, + const css::awt::Rectangle& rRepaintBox, + const bool bSynchronous = false); + void Invalidate ( + const css::uno::Reference<css::awt::XWindow>& rxWindow, + const css::awt::Rectangle& rRepaintBox, + const sal_Int16 nInvalidateFlags); + +private: + css::uno::Reference<css::awt::XWindow> mxParentWindow; + css::uno::Reference<css::awt::XWindowPeer> mxParentWindowPeer; + css::uno::Reference<css::drawing::XPresenterHelper> mxPresenterHelper; + ::rtl::Reference<PresenterPaneContainer> mpPaneContainer; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterPane.cxx b/sdext/source/presenter/PresenterPane.cxx new file mode 100644 index 000000000..ad3531543 --- /dev/null +++ b/sdext/source/presenter/PresenterPane.cxx @@ -0,0 +1,169 @@ +/* -*- 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 "PresenterPane.hxx" +#include "PresenterController.hxx" +#include "PresenterPaintManager.hxx" +#include <com/sun/star/lang/XMultiComponentFactory.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing::framework; + +namespace sdext::presenter { + +//===== PresenterPane ========================================================= + +PresenterPane::PresenterPane ( + const Reference<XComponentContext>& rxContext, + const ::rtl::Reference<PresenterController>& rpPresenterController) + : PresenterPaneBase(rxContext, rpPresenterController) +{ + Reference<lang::XMultiComponentFactory> xFactory ( + mxComponentContext->getServiceManager(), UNO_SET_THROW); + mxPresenterHelper.set( + xFactory->createInstanceWithContext( + "com.sun.star.comp.Draw.PresenterHelper", + mxComponentContext), + UNO_QUERY_THROW); +} + +PresenterPane::~PresenterPane() +{ +} + +//----- XPane ----------------------------------------------------------------- + +Reference<awt::XWindow> SAL_CALL PresenterPane::getWindow() +{ + ThrowIfDisposed(); + return mxContentWindow; +} + +Reference<rendering::XCanvas> SAL_CALL PresenterPane::getCanvas() +{ + ThrowIfDisposed(); + return mxContentCanvas; +} + +//----- XWindowListener ------------------------------------------------------- + +void SAL_CALL PresenterPane::windowResized (const awt::WindowEvent& rEvent) +{ + PresenterPaneBase::windowResized(rEvent); + + Invalidate(maBoundingBox); + + LayoutContextWindow(); + ToTop(); + + UpdateBoundingBox(); + Invalidate(maBoundingBox); +} + +void SAL_CALL PresenterPane::windowMoved (const awt::WindowEvent& rEvent) +{ + PresenterPaneBase::windowMoved(rEvent); + + Invalidate(maBoundingBox); + + ToTop(); + + UpdateBoundingBox(); + Invalidate(maBoundingBox); +} + +void SAL_CALL PresenterPane::windowShown (const lang::EventObject& rEvent) +{ + PresenterPaneBase::windowShown(rEvent); + + ToTop(); + + if (mxContentWindow.is()) + { + LayoutContextWindow(); + mxContentWindow->setVisible(true); + } + + UpdateBoundingBox(); + Invalidate(maBoundingBox); +} + +void SAL_CALL PresenterPane::windowHidden (const lang::EventObject& rEvent) +{ + PresenterPaneBase::windowHidden(rEvent); + + if (mxContentWindow.is()) + mxContentWindow->setVisible(false); +} + +//----- XPaintListener -------------------------------------------------------- + +void SAL_CALL PresenterPane::windowPaint (const awt::PaintEvent& rEvent) +{ + ThrowIfDisposed(); + + PaintBorder(rEvent.UpdateRect); +} + + +void PresenterPane::CreateCanvases ( + const Reference<rendering::XSpriteCanvas>& rxParentCanvas) +{ + if ( ! mxPresenterHelper.is()) + return; + if ( ! mxParentWindow.is()) + return; + if ( ! rxParentCanvas.is()) + return; + + mxBorderCanvas = mxPresenterHelper->createSharedCanvas( + rxParentCanvas, + mxParentWindow, + rxParentCanvas, + mxParentWindow, + mxBorderWindow); + mxContentCanvas = mxPresenterHelper->createSharedCanvas( + rxParentCanvas, + mxParentWindow, + rxParentCanvas, + mxParentWindow, + mxContentWindow); + + PaintBorder(mxBorderWindow->getPosSize()); +} + +void PresenterPane::Invalidate (const css::awt::Rectangle& rRepaintBox) +{ + // Invalidate the parent window to be able to invalidate an area outside + // the current window area. + mpPresenterController->GetPaintManager()->Invalidate(mxParentWindow, rRepaintBox); +} + +void PresenterPane::UpdateBoundingBox() +{ + if (mxBorderWindow.is() && IsVisible()) + maBoundingBox = mxBorderWindow->getPosSize(); + else + maBoundingBox = awt::Rectangle(); +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterPane.hxx b/sdext/source/presenter/PresenterPane.hxx new file mode 100644 index 000000000..2a057229f --- /dev/null +++ b/sdext/source/presenter/PresenterPane.hxx @@ -0,0 +1,80 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERPANE_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERPANE_HXX + +#include "PresenterPaneBase.hxx" +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <rtl/ref.hxx> + +namespace sdext::presenter +{ +/** Pane used by the presenter screen. Pane objects are stored in the + PresenterPaneContainer. Sizes and positions are controlled + by the PresenterWindowManager. Interactive positioning and resizing is + managed by the PresenterPaneBorderManager. Borders around panes are + painted by the PresenterPaneBorderPainter. +*/ +class PresenterPane : public PresenterPaneBase +{ +public: + PresenterPane(const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const ::rtl::Reference<PresenterController>& rpPresenterController); + virtual ~PresenterPane() override; + + // XPane + + css::uno::Reference<css::awt::XWindow> SAL_CALL getWindow() override; + + css::uno::Reference<css::rendering::XCanvas> SAL_CALL getCanvas() override; + + // XWindowListener + + virtual void SAL_CALL windowResized(const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowMoved(const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowShown(const css::lang::EventObject& rEvent) override; + + virtual void SAL_CALL windowHidden(const css::lang::EventObject& rEvent) override; + + // XPaintListener + + virtual void SAL_CALL windowPaint(const css::awt::PaintEvent& rEvent) override; + +private: + /** Store the bounding box so that when the window is resized or moved + we still know the old position and size. + */ + css::awt::Rectangle maBoundingBox; + + virtual void CreateCanvases( + const css::uno::Reference<css::rendering::XSpriteCanvas>& rxParentCanvas) override; + + void Invalidate(const css::awt::Rectangle& rRepaintBox); + void UpdateBoundingBox(); +}; + +} // end of namespace ::sd::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterPaneBase.cxx b/sdext/source/presenter/PresenterPaneBase.cxx new file mode 100644 index 000000000..aac8d082a --- /dev/null +++ b/sdext/source/presenter/PresenterPaneBase.cxx @@ -0,0 +1,342 @@ +/* -*- 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 "PresenterPaneBase.hxx" +#include "PresenterController.hxx" +#include "PresenterPaintManager.hxx" +#include <com/sun/star/awt/PosSize.hpp> +#include <com/sun/star/awt/XWindow2.hpp> + +using namespace css; +using namespace css::uno; +using namespace css::drawing::framework; + +namespace sdext::presenter { + +//===== PresenterPaneBase ===================================================== + +PresenterPaneBase::PresenterPaneBase ( + const Reference<XComponentContext>& rxContext, + const ::rtl::Reference<PresenterController>& rpPresenterController) + : PresenterPaneBaseInterfaceBase(m_aMutex), + mpPresenterController(rpPresenterController), + mxComponentContext(rxContext) +{ + if (mpPresenterController) + mxPresenterHelper = mpPresenterController->GetPresenterHelper(); +} + +PresenterPaneBase::~PresenterPaneBase() +{ +} + +void PresenterPaneBase::disposing() +{ + if (mxBorderWindow.is()) + { + mxBorderWindow->removeWindowListener(this); + mxBorderWindow->removePaintListener(this); + } + + { + Reference<XComponent> xComponent (mxContentCanvas, UNO_QUERY); + mxContentCanvas = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + + { + Reference<XComponent> xComponent = mxContentWindow; + mxContentWindow = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + + { + Reference<XComponent> xComponent (mxBorderCanvas, UNO_QUERY); + mxBorderCanvas = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + + { + Reference<XComponent> xComponent = mxBorderWindow; + mxBorderWindow = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + + mxComponentContext = nullptr; +} + +void PresenterPaneBase::SetTitle (const OUString& rsTitle) +{ + msTitle = rsTitle; + + OSL_ASSERT(mpPresenterController); + OSL_ASSERT(mpPresenterController->GetPaintManager() != nullptr); + + mpPresenterController->GetPaintManager()->Invalidate(mxBorderWindow); +} + +const OUString& PresenterPaneBase::GetTitle() const +{ + return msTitle; +} + +const Reference<drawing::framework::XPaneBorderPainter>& + PresenterPaneBase::GetPaneBorderPainter() const +{ + return mxBorderPainter; +} + +//----- XInitialization ------------------------------------------------------- + +void SAL_CALL PresenterPaneBase::initialize (const Sequence<Any>& rArguments) +{ + ThrowIfDisposed(); + + if ( ! mxComponentContext.is()) + { + throw RuntimeException( + "PresenterSpritePane: missing component context", + static_cast<XWeak*>(this)); + } + + if (rArguments.getLength() != 5 && rArguments.getLength() != 6) + { + throw RuntimeException( + "PresenterSpritePane: invalid number of arguments", + static_cast<XWeak*>(this)); + } + + try + { + // Get the resource id from the first argument. + if ( ! (rArguments[0] >>= mxPaneId)) + { + throw lang::IllegalArgumentException( + "PresenterPane: invalid pane id", + static_cast<XWeak*>(this), + 0); + } + + if ( ! (rArguments[1] >>= mxParentWindow)) + { + throw lang::IllegalArgumentException( + "PresenterPane: invalid parent window", + static_cast<XWeak*>(this), + 1); + } + + Reference<rendering::XSpriteCanvas> xParentCanvas; + if ( ! (rArguments[2] >>= xParentCanvas)) + { + throw lang::IllegalArgumentException( + "PresenterPane: invalid parent canvas", + static_cast<XWeak*>(this), + 2); + } + + if ( ! (rArguments[3] >>= msTitle)) + { + throw lang::IllegalArgumentException( + "PresenterPane: invalid title", + static_cast<XWeak*>(this), + 3); + } + + if ( ! (rArguments[4] >>= mxBorderPainter)) + { + throw lang::IllegalArgumentException( + "PresenterPane: invalid border painter", + static_cast<XWeak*>(this), + 4); + } + + bool bIsWindowVisibleOnCreation (true); + if (rArguments.getLength()>5 && ! (rArguments[5] >>= bIsWindowVisibleOnCreation)) + { + throw lang::IllegalArgumentException( + "PresenterPane: invalid window visibility flag", + static_cast<XWeak*>(this), + 5); + } + + CreateWindows(bIsWindowVisibleOnCreation); + + if (mxBorderWindow.is()) + { + mxBorderWindow->addWindowListener(this); + mxBorderWindow->addPaintListener(this); + } + + CreateCanvases(xParentCanvas); + + // Raise new windows. + ToTop(); + } + catch (Exception&) + { + mxContentWindow = nullptr; + mxComponentContext = nullptr; + throw; + } +} + +//----- XResourceId ----------------------------------------------------------- + +Reference<XResourceId> SAL_CALL PresenterPaneBase::getResourceId() +{ + ThrowIfDisposed(); + return mxPaneId; +} + +sal_Bool SAL_CALL PresenterPaneBase::isAnchorOnly() +{ + return true; +} + +//----- XWindowListener ------------------------------------------------------- + +void SAL_CALL PresenterPaneBase::windowResized (const awt::WindowEvent&) +{ + ThrowIfDisposed(); +} + +void SAL_CALL PresenterPaneBase::windowMoved (const awt::WindowEvent&) +{ + ThrowIfDisposed(); +} + +void SAL_CALL PresenterPaneBase::windowShown (const lang::EventObject&) +{ + ThrowIfDisposed(); +} + +void SAL_CALL PresenterPaneBase::windowHidden (const lang::EventObject&) +{ + ThrowIfDisposed(); +} + +//----- lang::XEventListener -------------------------------------------------- + +void SAL_CALL PresenterPaneBase::disposing (const lang::EventObject& rEvent) +{ + if (rEvent.Source == mxBorderWindow) + { + mxBorderWindow = nullptr; + } +} + + +void PresenterPaneBase::CreateWindows ( + const bool bIsWindowVisibleOnCreation) +{ + if (!(mxPresenterHelper.is() && mxParentWindow.is())) + return; + + mxBorderWindow = mxPresenterHelper->createWindow( + mxParentWindow, + false, + bIsWindowVisibleOnCreation, + false, + false); + mxContentWindow = mxPresenterHelper->createWindow( + mxBorderWindow, + false, + bIsWindowVisibleOnCreation, + false, + false); +} + +const Reference<awt::XWindow>& PresenterPaneBase::GetBorderWindow() const +{ + return mxBorderWindow; +} + +void PresenterPaneBase::ToTop() +{ + if (mxPresenterHelper.is()) + mxPresenterHelper->toTop(mxContentWindow); +} + +void PresenterPaneBase::PaintBorder (const awt::Rectangle& rUpdateBox) +{ + OSL_ASSERT(mxPaneId.is()); + + if (!(mxBorderPainter.is() && mxBorderWindow.is() && mxBorderCanvas.is())) + return; + + awt::Rectangle aBorderBox (mxBorderWindow->getPosSize()); + awt::Rectangle aLocalBorderBox (0,0, aBorderBox.Width, aBorderBox.Height); + + //TODO: paint border background? + + mxBorderPainter->paintBorder( + mxPaneId->getResourceURL(), + mxBorderCanvas, + aLocalBorderBox, + rUpdateBox, + msTitle); +} + +void PresenterPaneBase::LayoutContextWindow() +{ + OSL_ASSERT(mxPaneId.is()); + OSL_ASSERT(mxBorderWindow.is()); + OSL_ASSERT(mxContentWindow.is()); + if (!(mxBorderPainter.is() && mxPaneId.is() && mxBorderWindow.is() && mxContentWindow.is())) + return; + + const awt::Rectangle aBorderBox (mxBorderWindow->getPosSize()); + const awt::Rectangle aInnerBox (mxBorderPainter->removeBorder( + mxPaneId->getResourceURL(), + aBorderBox, + drawing::framework::BorderType_TOTAL_BORDER)); + mxContentWindow->setPosSize( + aInnerBox.X - aBorderBox.X, + aInnerBox.Y - aBorderBox.Y, + aInnerBox.Width, + aInnerBox.Height, + awt::PosSize::POSSIZE); +} + +bool PresenterPaneBase::IsVisible() const +{ + Reference<awt::XWindow2> xWindow2 (mxBorderPainter, UNO_QUERY); + if (xWindow2.is()) + return xWindow2->isVisible(); + + return false; +} + +void PresenterPaneBase::ThrowIfDisposed() +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ( + "PresenterPane object has already been disposed", + static_cast<uno::XWeak*>(this)); + } +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterPaneBase.hxx b/sdext/source/presenter/PresenterPaneBase.hxx new file mode 100644 index 000000000..6df93af3e --- /dev/null +++ b/sdext/source/presenter/PresenterPaneBase.hxx @@ -0,0 +1,128 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERPANEBASE_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERPANEBASE_HXX + +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <com/sun/star/awt/XWindowListener.hpp> +#include <com/sun/star/drawing/XPresenterHelper.hpp> +#include <com/sun/star/drawing/framework/XPane.hpp> +#include <com/sun/star/drawing/framework/XPaneBorderPainter.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <rtl/ref.hxx> + + +namespace sdext::presenter { + +class PresenterController; + +typedef ::cppu::WeakComponentImplHelper < + css::drawing::framework::XPane, + css::lang::XInitialization, + css::awt::XWindowListener, + css::awt::XPaintListener +> PresenterPaneBaseInterfaceBase; + +/** Base class of the panes used by the presenter screen. Pane objects are + stored in the PresenterPaneContainer. Sizes and positions are + controlled by the PresenterWindowManager. Interactive positioning and + resizing is managed by the PresenterPaneBorderManager. Borders around + panes are painted by the PresenterPaneBorderPainter. +*/ +class PresenterPaneBase + : protected ::cppu::BaseMutex, + public PresenterPaneBaseInterfaceBase +{ +public: + PresenterPaneBase ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const ::rtl::Reference<PresenterController>& rpPresenterController); + virtual ~PresenterPaneBase() override; + PresenterPaneBase(const PresenterPaneBase&) = delete; + PresenterPaneBase& operator=(const PresenterPaneBase&) = delete; + + virtual void SAL_CALL disposing() override; + + const css::uno::Reference<css::awt::XWindow>& GetBorderWindow() const; + void SetTitle (const OUString& rsTitle); + const OUString& GetTitle() const; + const css::uno::Reference<css::drawing::framework::XPaneBorderPainter>& GetPaneBorderPainter() const; + + // XInitialization + + virtual void SAL_CALL initialize (const css::uno::Sequence<css::uno::Any>& rArguments) override; + + // XResourceId + + virtual css::uno::Reference<css::drawing::framework::XResourceId> SAL_CALL getResourceId() override; + + virtual sal_Bool SAL_CALL isAnchorOnly() override; + + // XWindowListener + + virtual void SAL_CALL windowResized (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowMoved (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowShown (const css::lang::EventObject& rEvent) override; + + virtual void SAL_CALL windowHidden (const css::lang::EventObject& rEvent) override; + + // lang::XEventListener + + virtual void SAL_CALL disposing (const css::lang::EventObject& rEvent) override; + +protected: + ::rtl::Reference<PresenterController> mpPresenterController; + css::uno::Reference<css::awt::XWindow> mxParentWindow; + css::uno::Reference<css::awt::XWindow> mxBorderWindow; + css::uno::Reference<css::rendering::XCanvas> mxBorderCanvas; + css::uno::Reference<css::awt::XWindow> mxContentWindow; + css::uno::Reference<css::rendering::XCanvas> mxContentCanvas; + css::uno::Reference<css::drawing::framework::XResourceId> mxPaneId; + css::uno::Reference<css::drawing::framework::XPaneBorderPainter> mxBorderPainter; + css::uno::Reference<css::drawing::XPresenterHelper> mxPresenterHelper; + OUString msTitle; + css::uno::Reference<css::uno::XComponentContext> mxComponentContext; + + virtual void CreateCanvases ( + const css::uno::Reference<css::rendering::XSpriteCanvas>& rxParentCanvas) = 0; + + void CreateWindows ( + const bool bIsWindowVisibleOnCreation); + void PaintBorder (const css::awt::Rectangle& rUpdateRectangle); + void ToTop(); + void LayoutContextWindow(); + bool IsVisible() const; + + /** @throws css::lang::DisposedException when the object has already been + disposed. + */ + void ThrowIfDisposed(); +}; + +} // end of namespace ::sd::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterPaneBorderPainter.cxx b/sdext/source/presenter/PresenterPaneBorderPainter.cxx new file mode 100644 index 000000000..6c0198c7e --- /dev/null +++ b/sdext/source/presenter/PresenterPaneBorderPainter.cxx @@ -0,0 +1,882 @@ +/* -*- 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 "PresenterPaneBorderPainter.hxx" +#include "PresenterCanvasHelper.hxx" +#include "PresenterGeometryHelper.hxx" +#include "PresenterTheme.hxx" +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/drawing/XPresenterHelper.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/FillRule.hpp> +#include <com/sun/star/rendering/TextDirection.hpp> +#include <com/sun/star/rendering/XSpriteCanvas.hpp> +#include <map> +#include <memory> +#include <vector> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace sdext::presenter { + +namespace { + class BorderSize + { + public: + BorderSize(); + sal_Int32 mnLeft; + sal_Int32 mnTop; + sal_Int32 mnRight; + sal_Int32 mnBottom; + }; + + class RendererPaneStyle + { + public: + RendererPaneStyle ( + const std::shared_ptr<PresenterTheme>& rpTheme, + const OUString& rsStyleName); + + awt::Rectangle AddBorder ( + const awt::Rectangle& rBox, + drawing::framework::BorderType eBorderType) const; + awt::Rectangle RemoveBorder ( + const awt::Rectangle& rBox, + drawing::framework::BorderType eBorderType) const; + Reference<rendering::XCanvasFont> GetFont ( + const Reference<rendering::XCanvas>& rxCanvas) const; + + SharedBitmapDescriptor mpTopLeft; + SharedBitmapDescriptor mpTop; + SharedBitmapDescriptor mpTopRight; + SharedBitmapDescriptor mpLeft; + SharedBitmapDescriptor mpRight; + SharedBitmapDescriptor mpBottomLeft; + SharedBitmapDescriptor mpBottom; + SharedBitmapDescriptor mpBottomRight; + SharedBitmapDescriptor mpBottomCallout; + SharedBitmapDescriptor mpEmpty; + PresenterTheme::SharedFontDescriptor mpFont; + sal_Int32 mnFontXOffset; + sal_Int32 mnFontYOffset; + enum class Anchor { Left, Right, Center }; + Anchor meFontAnchor; + BorderSize maInnerBorderSize; + BorderSize maOuterBorderSize; + BorderSize maTotalBorderSize; + private: + void UpdateBorderSizes(); + SharedBitmapDescriptor GetBitmap( + const std::shared_ptr<PresenterTheme>& rpTheme, + const OUString& rsStyleName, + const OUString& rsBitmapName); + }; +} + +class PresenterPaneBorderPainter::Renderer +{ +public: + Renderer ( + const Reference<XComponentContext>& rxContext, + const std::shared_ptr<PresenterTheme>& rpTheme); + + void SetCanvas (const Reference<rendering::XCanvas>& rxCanvas); + void PaintBorder ( + const OUString& rsTitle, + const awt::Rectangle& rBBox, + const awt::Rectangle& rUpdateBox, + const OUString& rsPaneURL); + void PaintTitle ( + const OUString& rsTitle, + const std::shared_ptr<RendererPaneStyle>& rpStyle, + const awt::Rectangle& rUpdateBox, + const awt::Rectangle& rOuterBox, + const awt::Rectangle& rInnerBox); + void SetupClipping ( + const awt::Rectangle& rUpdateBox, + const awt::Rectangle& rOuterBox, + const OUString& rsPaneStyleName); + std::shared_ptr<RendererPaneStyle> GetRendererPaneStyle (const OUString& rsResourceURL); + void SetCalloutAnchor ( + const awt::Point& rCalloutAnchor); + +private: + std::shared_ptr<PresenterTheme> mpTheme; + typedef ::std::map<OUString, std::shared_ptr<RendererPaneStyle> > RendererPaneStyleContainer; + RendererPaneStyleContainer maRendererPaneStyles; + Reference<rendering::XCanvas> mxCanvas; + Reference<drawing::XPresenterHelper> mxPresenterHelper; + css::rendering::ViewState maViewState; + Reference<rendering::XPolyPolygon2D> mxViewStateClip; + bool mbHasCallout; + awt::Point maCalloutAnchor; + + void PaintBitmap( + const awt::Rectangle& rBox, + const awt::Rectangle& rUpdateBox, + const sal_Int32 nXPosition, + const sal_Int32 nYPosition, + const sal_Int32 nStartOffset, + const sal_Int32 nEndOffset, + const bool bExpand, + const SharedBitmapDescriptor& rpBitmap); +}; + +// ===== PresenterPaneBorderPainter =========================================== + +PresenterPaneBorderPainter::PresenterPaneBorderPainter ( + const Reference<XComponentContext>& rxContext) + : PresenterPaneBorderPainterInterfaceBase(m_aMutex), + mxContext(rxContext) +{ +} + +PresenterPaneBorderPainter::~PresenterPaneBorderPainter() +{ +} + +//----- XPaneBorderPainter ---------------------------------------------------- + +awt::Rectangle SAL_CALL PresenterPaneBorderPainter::addBorder ( + const OUString& rsPaneBorderStyleName, + const css::awt::Rectangle& rRectangle, + drawing::framework::BorderType eBorderType) +{ + ThrowIfDisposed(); + + ProvideTheme(); + + return AddBorder(rsPaneBorderStyleName, rRectangle, eBorderType); +} + +awt::Rectangle SAL_CALL PresenterPaneBorderPainter::removeBorder ( + const OUString& rsPaneBorderStyleName, + const css::awt::Rectangle& rRectangle, + drawing::framework::BorderType eBorderType) +{ + ThrowIfDisposed(); + + ProvideTheme(); + + return RemoveBorder(rsPaneBorderStyleName, rRectangle, eBorderType); +} + +void SAL_CALL PresenterPaneBorderPainter::paintBorder ( + const OUString& rsPaneBorderStyleName, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rOuterBorderRectangle, + const css::awt::Rectangle& rRepaintArea, + const OUString& rsTitle) +{ + ThrowIfDisposed(); + + // Early reject paints completely outside the repaint area. + if (rRepaintArea.X >= rOuterBorderRectangle.X+rOuterBorderRectangle.Width + || rRepaintArea.Y >= rOuterBorderRectangle.Y+rOuterBorderRectangle.Height + || rRepaintArea.X+rRepaintArea.Width <= rOuterBorderRectangle.X + || rRepaintArea.Y+rRepaintArea.Height <= rOuterBorderRectangle.Y) + { + return; + } + ProvideTheme(rxCanvas); + + if (mpRenderer == nullptr) + return; + + mpRenderer->SetCanvas(rxCanvas); + mpRenderer->SetupClipping( + rRepaintArea, + rOuterBorderRectangle, + rsPaneBorderStyleName); + mpRenderer->PaintBorder( + rsTitle, + rOuterBorderRectangle, + rRepaintArea, + rsPaneBorderStyleName); +} + +void SAL_CALL PresenterPaneBorderPainter::paintBorderWithCallout ( + const OUString& rsPaneBorderStyleName, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rOuterBorderRectangle, + const css::awt::Rectangle& rRepaintArea, + const OUString& rsTitle, + const css::awt::Point& rCalloutAnchor) +{ + ThrowIfDisposed(); + + // Early reject paints completely outside the repaint area. + if (rRepaintArea.X >= rOuterBorderRectangle.X+rOuterBorderRectangle.Width + || rRepaintArea.Y >= rOuterBorderRectangle.Y+rOuterBorderRectangle.Height + || rRepaintArea.X+rRepaintArea.Width <= rOuterBorderRectangle.X + || rRepaintArea.Y+rRepaintArea.Height <= rOuterBorderRectangle.Y) + { + return; + } + ProvideTheme(rxCanvas); + + if (mpRenderer == nullptr) + return; + + mpRenderer->SetCanvas(rxCanvas); + mpRenderer->SetupClipping( + rRepaintArea, + rOuterBorderRectangle, + rsPaneBorderStyleName); + mpRenderer->SetCalloutAnchor(rCalloutAnchor); + mpRenderer->PaintBorder( + rsTitle, + rOuterBorderRectangle, + rRepaintArea, + rsPaneBorderStyleName); +} + +awt::Point SAL_CALL PresenterPaneBorderPainter::getCalloutOffset ( + const OUString& rsPaneBorderStyleName) +{ + ThrowIfDisposed(); + ProvideTheme(); + if (mpRenderer != nullptr) + { + const std::shared_ptr<RendererPaneStyle> pRendererPaneStyle( + mpRenderer->GetRendererPaneStyle(rsPaneBorderStyleName)); + if (pRendererPaneStyle != nullptr && pRendererPaneStyle->mpBottomCallout) + { + return awt::Point ( + 0, + pRendererPaneStyle->mpBottomCallout->mnHeight + - pRendererPaneStyle->mpBottomCallout->mnYHotSpot); + } + } + + return awt::Point(0,0); +} + + +bool PresenterPaneBorderPainter::ProvideTheme (const Reference<rendering::XCanvas>& rxCanvas) +{ + bool bModified (false); + + if ( ! mxContext.is()) + return false; + + if (mpTheme != nullptr) + { + // Check if the theme already has a canvas. + if ( ! mpTheme->HasCanvas()) + { + mpTheme->ProvideCanvas(rxCanvas); + bModified = true; + } + } + else + { + mpTheme = std::make_shared<PresenterTheme>(mxContext, rxCanvas); + bModified = true; + } + + if (bModified) + { + if (mpRenderer == nullptr) + mpRenderer.reset(new Renderer(mxContext, mpTheme)); + else + mpRenderer->SetCanvas(rxCanvas); + } + + return bModified; +} + +void PresenterPaneBorderPainter::ProvideTheme() +{ + if (mpTheme == nullptr) + { + // Create a theme without bitmaps (no canvas => no bitmaps). + ProvideTheme(nullptr); + } + // When there already is a theme then without a canvas we can not + // add anything new. +} + +void PresenterPaneBorderPainter::SetTheme (const std::shared_ptr<PresenterTheme>& rpTheme) +{ + mpTheme = rpTheme; + if (mpRenderer == nullptr) + mpRenderer.reset(new Renderer(mxContext, mpTheme)); +} + +awt::Rectangle PresenterPaneBorderPainter::AddBorder ( + const OUString& rsPaneURL, + const awt::Rectangle& rInnerBox, + const css::drawing::framework::BorderType eBorderType) const +{ + if (mpRenderer != nullptr) + { + const std::shared_ptr<RendererPaneStyle> pRendererPaneStyle(mpRenderer->GetRendererPaneStyle(rsPaneURL)); + if (pRendererPaneStyle != nullptr) + return pRendererPaneStyle->AddBorder(rInnerBox, eBorderType); + } + return rInnerBox; +} + +awt::Rectangle PresenterPaneBorderPainter::RemoveBorder ( + const OUString& rsPaneURL, + const css::awt::Rectangle& rOuterBox, + const css::drawing::framework::BorderType eBorderType) const +{ + if (mpRenderer != nullptr) + { + const std::shared_ptr<RendererPaneStyle> pRendererPaneStyle(mpRenderer->GetRendererPaneStyle(rsPaneURL)); + if (pRendererPaneStyle != nullptr) + return pRendererPaneStyle->RemoveBorder(rOuterBox, eBorderType); + } + return rOuterBox; +} + +void PresenterPaneBorderPainter::ThrowIfDisposed() const +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ( + "PresenterPaneBorderPainter object has already been disposed", + const_cast<uno::XWeak*>(static_cast<const uno::XWeak*>(this))); + } +} + +//===== PresenterPaneBorderPainter::Renderer ===================================== + +PresenterPaneBorderPainter::Renderer::Renderer ( + const Reference<XComponentContext>& rxContext, + const std::shared_ptr<PresenterTheme>& rpTheme) + : mpTheme(rpTheme), + maViewState(geometry::AffineMatrix2D(1,0,0, 0,1,0), nullptr), + mbHasCallout(false) +{ + Reference<lang::XMultiComponentFactory> xFactory (rxContext->getServiceManager()); + if (xFactory.is()) + { + mxPresenterHelper.set( + xFactory->createInstanceWithContext( + "com.sun.star.comp.Draw.PresenterHelper", + rxContext), + UNO_QUERY_THROW); + } +} + +void PresenterPaneBorderPainter::Renderer::SetCanvas (const Reference<rendering::XCanvas>& rxCanvas) +{ + if (mxCanvas != rxCanvas) + { + mxCanvas = rxCanvas; + } +} + +void PresenterPaneBorderPainter::Renderer::PaintBorder ( + const OUString& rsTitle, + const awt::Rectangle& rBBox, + const awt::Rectangle& rUpdateBox, + const OUString& rsPaneURL) +{ + if ( ! mxCanvas.is()) + return; + + // Create the outer and inner border of the, ahm, border. + std::shared_ptr<RendererPaneStyle> pStyle (GetRendererPaneStyle(rsPaneURL)); + if (pStyle == nullptr) + return; + + awt::Rectangle aOuterBox (rBBox); + awt::Rectangle aCenterBox ( + pStyle->RemoveBorder(aOuterBox, drawing::framework::BorderType_OUTER_BORDER)); + awt::Rectangle aInnerBox ( + pStyle->RemoveBorder(aOuterBox, drawing::framework::BorderType_TOTAL_BORDER)); + + // Prepare references for all used bitmaps. + SharedBitmapDescriptor pTop (pStyle->mpTop); + SharedBitmapDescriptor pTopLeft (pStyle->mpTopLeft); + SharedBitmapDescriptor pTopRight (pStyle->mpTopRight); + SharedBitmapDescriptor pLeft (pStyle->mpLeft); + SharedBitmapDescriptor pRight (pStyle->mpRight); + SharedBitmapDescriptor pBottomLeft (pStyle->mpBottomLeft); + SharedBitmapDescriptor pBottomRight (pStyle->mpBottomRight); + SharedBitmapDescriptor pBottom (pStyle->mpBottom); + + // Paint the sides. + PaintBitmap(aCenterBox, rUpdateBox, 0,-1, + pTopLeft->mnXOffset, pTopRight->mnXOffset, true, pTop); + PaintBitmap(aCenterBox, rUpdateBox, -1,0, + pTopLeft->mnYOffset, pBottomLeft->mnYOffset, true, pLeft); + PaintBitmap(aCenterBox, rUpdateBox, +1,0, + pTopRight->mnYOffset, pBottomRight->mnYOffset, true, pRight); + if (mbHasCallout && pStyle->mpBottomCallout->GetNormalBitmap().is()) + { + const sal_Int32 nCalloutWidth (pStyle->mpBottomCallout->mnWidth); + sal_Int32 nCalloutX (maCalloutAnchor.X - pStyle->mpBottomCallout->mnXHotSpot + - (aCenterBox.X - aOuterBox.X)); + if (nCalloutX < pBottomLeft->mnXOffset + aCenterBox.X) + nCalloutX = pBottomLeft->mnXOffset + aCenterBox.X; + if (nCalloutX > pBottomRight->mnXOffset + aCenterBox.X + aCenterBox.Width) + nCalloutX = pBottomRight->mnXOffset + aCenterBox.X + aCenterBox.Width; + // Paint bottom callout. + PaintBitmap(aCenterBox, rUpdateBox, 0,+1, nCalloutX,0, false, pStyle->mpBottomCallout); + // Paint regular bottom bitmap left and right. + PaintBitmap(aCenterBox, rUpdateBox, 0,+1, + pBottomLeft->mnXOffset, nCalloutX-aCenterBox.Width, true, pBottom); + PaintBitmap(aCenterBox, rUpdateBox, 0,+1, + nCalloutX+nCalloutWidth, pBottomRight->mnXOffset, true, pBottom); + } + else + { + // Stretch the bottom bitmap over the full width. + PaintBitmap(aCenterBox, rUpdateBox, 0,+1, + pBottomLeft->mnXOffset, pBottomRight->mnXOffset, true, pBottom); + } + + // Paint the corners. + PaintBitmap(aCenterBox, rUpdateBox, -1,-1, 0,0, false, pTopLeft); + PaintBitmap(aCenterBox, rUpdateBox, +1,-1, 0,0, false, pTopRight); + PaintBitmap(aCenterBox, rUpdateBox, -1,+1, 0,0, false, pBottomLeft); + PaintBitmap(aCenterBox, rUpdateBox, +1,+1, 0,0, false, pBottomRight); + + // Paint the title. + PaintTitle(rsTitle, pStyle, rUpdateBox, aOuterBox, aInnerBox); + + // In a double buffering environment request to make the changes visible. + Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY); + if (xSpriteCanvas.is()) + xSpriteCanvas->updateScreen(false); +} + +void PresenterPaneBorderPainter::Renderer::PaintTitle ( + const OUString& rsTitle, + const std::shared_ptr<RendererPaneStyle>& rpStyle, + const awt::Rectangle& rUpdateBox, + const awt::Rectangle& rOuterBox, + const awt::Rectangle& rInnerBox) +{ + if ( ! mxCanvas.is()) + return; + + if (rsTitle.isEmpty()) + return; + + Reference<rendering::XCanvasFont> xFont (rpStyle->GetFont(mxCanvas)); + if ( ! xFont.is()) + return; + + rendering::StringContext aContext ( + rsTitle, + 0, + rsTitle.getLength()); + Reference<rendering::XTextLayout> xLayout (xFont->createTextLayout( + aContext, + rendering::TextDirection::WEAK_LEFT_TO_RIGHT, + 0)); + if ( ! xLayout.is()) + return; + + /// this is responsible of the texts above the slide windows + geometry::RealRectangle2D aBox (xLayout->queryTextBounds()); + const double nTextHeight = aBox.Y2 - aBox.Y1; + const double nTextWidth = aBox.X1 + aBox.X2; + const sal_Int32 nTitleBarHeight = rInnerBox.Y - rOuterBox.Y - 1; + double nY = rOuterBox.Y + (nTitleBarHeight - nTextHeight) / 2 - aBox.Y1; + if (nY >= rInnerBox.Y) + nY = rInnerBox.Y - 1; + double nX; + switch (rpStyle->meFontAnchor) + { + case RendererPaneStyle::Anchor::Left: + nX = rInnerBox.X; + break; + case RendererPaneStyle::Anchor::Right: + nX = rInnerBox.X + rInnerBox.Width - nTextWidth; + break; + default: // RendererPaneStyle::Anchor::Center + nX = rInnerBox.X + (rInnerBox.Width - nTextWidth)/2; + break; + } + nX += rpStyle->mnFontXOffset; + nY += rpStyle->mnFontYOffset; + + if (rUpdateBox.X >= nX+nTextWidth + || rUpdateBox.Y >= nY+nTextHeight + || rUpdateBox.X+rUpdateBox.Width <= nX + || rUpdateBox.Y+rUpdateBox.Height <= nY) + { + return; + } + + rendering::RenderState aRenderState( + geometry::AffineMatrix2D(1,0,nX, 0,1,nY), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + + PresenterCanvasHelper::SetDeviceColor( + aRenderState, + rpStyle->mpFont->mnColor); + + mxCanvas->drawTextLayout ( + xLayout, + maViewState, + aRenderState); +} + +std::shared_ptr<RendererPaneStyle> + PresenterPaneBorderPainter::Renderer::GetRendererPaneStyle (const OUString& rsResourceURL) +{ + OSL_ASSERT(mpTheme != nullptr); + + RendererPaneStyleContainer::const_iterator iStyle (maRendererPaneStyles.find(rsResourceURL)); + if (iStyle == maRendererPaneStyles.end()) + { + OUString sPaneStyleName ("DefaultRendererPaneStyle"); + + // Get pane layout name for resource URL. + const OUString sStyleName (mpTheme->GetStyleName(rsResourceURL)); + if (!sStyleName.isEmpty()) + sPaneStyleName = sStyleName; + + // Create a new pane style object and initialize it with bitmaps. + auto pStyle = std::make_shared<RendererPaneStyle>(mpTheme,sPaneStyleName); + iStyle = maRendererPaneStyles.emplace(rsResourceURL, pStyle).first; + } + if (iStyle != maRendererPaneStyles.end()) + return iStyle->second; + else + return std::shared_ptr<RendererPaneStyle>(); +} + +void PresenterPaneBorderPainter::Renderer::SetCalloutAnchor ( + const awt::Point& rCalloutAnchor) +{ + mbHasCallout = true; + maCalloutAnchor = rCalloutAnchor; +} + +void PresenterPaneBorderPainter::Renderer::PaintBitmap( + const awt::Rectangle& rBox, + const awt::Rectangle& rUpdateBox, + const sal_Int32 nXPosition, + const sal_Int32 nYPosition, + const sal_Int32 nStartOffset, + const sal_Int32 nEndOffset, + const bool bExpand, + const SharedBitmapDescriptor& rpBitmap) +{ + bool bUseCanvas (mxCanvas.is()); + if ( ! bUseCanvas) + return; + + if (rpBitmap->mnWidth<=0 || rpBitmap->mnHeight<=0) + return; + + Reference<rendering::XBitmap> xBitmap = rpBitmap->GetNormalBitmap(); + if ( ! xBitmap.is()) + return; + + // Calculate position, and for side bitmaps, the size. + sal_Int32 nX = 0; + sal_Int32 nY = 0; + sal_Int32 nW = rpBitmap->mnWidth; + sal_Int32 nH = rpBitmap->mnHeight; + if (nXPosition < 0) + { + nX = rBox.X - rpBitmap->mnWidth + rpBitmap->mnXOffset; + } + else if (nXPosition > 0) + { + nX = rBox.X + rBox.Width + rpBitmap->mnXOffset; + } + else + { + nX = rBox.X + nStartOffset; + if (bExpand) + nW = rBox.Width - nStartOffset + nEndOffset; + } + + if (nYPosition < 0) + { + nY = rBox.Y - rpBitmap->mnHeight + rpBitmap->mnYOffset; + } + else if (nYPosition > 0) + { + nY = rBox.Y + rBox.Height + rpBitmap->mnYOffset; + } + else + { + nY = rBox.Y + nStartOffset; + if (bExpand) + nH = rBox.Height - nStartOffset + nEndOffset; + } + + // Do not paint when bitmap area does not intersect with update box. + if (nX >= rUpdateBox.X + rUpdateBox.Width + || nX+nW <= rUpdateBox.X + || nY >= rUpdateBox.Y + rUpdateBox.Height + || nY+nH <= rUpdateBox.Y) + { + return; + } + + /* + Reference<rendering::XBitmap> xMaskedBitmap ( + PresenterBitmapHelper::FillMaskedWithColor ( + mxCanvas, + Reference<rendering::XIntegerBitmap>(xBitmap, UNO_QUERY), + rBitmap.mxMaskBitmap, + 0x00ff0000, + rBackgroundBitmap.maReplacementColor)); + if (xMaskedBitmap.is()) + xBitmap = xMaskedBitmap; + else if (rBitmap.mxMaskBitmap.is() && mxPresenterHelper.is()) + { + const static sal_Int32 nOutsideMaskColor (0x00ff0000); + Reference<rendering::XIntegerBitmap> xMask ( + mxPresenterHelper->createMask( + mxCanvas, + rBitmap.mxMaskBitmap, + nOutsideMaskColor, + false)); + xBitmap = mxPresenterHelper->applyBitmapMaskWithColor( + mxCanvas, + Reference<rendering::XIntegerBitmap>(xBitmap, UNO_QUERY), + xMask, + rBackgroundBitmap.maReplacementColor); + } + */ + rendering::RenderState aRenderState ( + geometry::AffineMatrix2D( + double(nW)/rpBitmap->mnWidth, 0, nX, + 0, double(nH)/rpBitmap->mnHeight, nY), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::OVER); + + if (xBitmap.is()) + mxCanvas->drawBitmap( + xBitmap, + maViewState, + aRenderState); +} + +void PresenterPaneBorderPainter::Renderer::SetupClipping ( + const awt::Rectangle& rUpdateBox, + const awt::Rectangle& rOuterBox, + const OUString& rsPaneStyleName) +{ + mxViewStateClip = nullptr; + maViewState.Clip = nullptr; + + if ( ! mxCanvas.is()) + return; + + std::shared_ptr<RendererPaneStyle> pStyle (GetRendererPaneStyle(rsPaneStyleName)); + if (pStyle == nullptr) + { + mxViewStateClip = PresenterGeometryHelper::CreatePolygon( + rUpdateBox, + mxCanvas->getDevice()); + } + else + { + awt::Rectangle aInnerBox ( + pStyle->RemoveBorder(rOuterBox, drawing::framework::BorderType_TOTAL_BORDER)); + ::std::vector<awt::Rectangle> aRectangles + { + PresenterGeometryHelper::Intersection(rUpdateBox, rOuterBox), + PresenterGeometryHelper::Intersection(rUpdateBox, aInnerBox) + }; + mxViewStateClip = PresenterGeometryHelper::CreatePolygon( + aRectangles, + mxCanvas->getDevice()); + if (mxViewStateClip.is()) + mxViewStateClip->setFillRule(rendering::FillRule_EVEN_ODD); + } + maViewState.Clip = mxViewStateClip; +} + +namespace { + +//===== BorderSize ============================================================ + +BorderSize::BorderSize() + : mnLeft(0), + mnTop(0), + mnRight(0), + mnBottom(0) +{ +} + +//===== RendererPaneStyle ============================================================ + +RendererPaneStyle::RendererPaneStyle ( + const std::shared_ptr<PresenterTheme>& rpTheme, + const OUString& rsStyleName) + : mpEmpty(std::make_shared<PresenterBitmapDescriptor>()), + mnFontXOffset(0), + mnFontYOffset(0), + meFontAnchor(Anchor::Center) +{ + if (rpTheme == nullptr) + return; + + mpTopLeft = GetBitmap(rpTheme, rsStyleName, "TopLeft"); + mpTop = GetBitmap(rpTheme, rsStyleName, "Top"); + mpTopRight = GetBitmap(rpTheme, rsStyleName, "TopRight"); + mpLeft = GetBitmap(rpTheme, rsStyleName,"Left"); + mpRight = GetBitmap(rpTheme, rsStyleName, "Right"); + mpBottomLeft = GetBitmap(rpTheme, rsStyleName, "BottomLeft"); + mpBottom = GetBitmap(rpTheme, rsStyleName, "Bottom"); + mpBottomRight = GetBitmap(rpTheme, rsStyleName, "BottomRight"); + mpBottomCallout = GetBitmap(rpTheme, rsStyleName, "BottomCallout"); + + // Get font description. + mpFont = rpTheme->GetFont(rsStyleName); + + OUString sAnchor ("Left"); + if (mpFont) + { + sAnchor = mpFont->msAnchor; + mnFontXOffset = mpFont->mnXOffset; + mnFontYOffset = mpFont->mnYOffset; + } + + if ( sAnchor == "Left" ) + meFontAnchor = Anchor::Left; + else if ( sAnchor == "Right" ) + meFontAnchor = Anchor::Right; + else + meFontAnchor = Anchor::Center; + + // Get border sizes. + try + { + ::std::vector<sal_Int32> aInnerBorder (rpTheme->GetBorderSize(rsStyleName, false)); + OSL_ASSERT(aInnerBorder.size()==4); + maInnerBorderSize.mnLeft = aInnerBorder[0]; + maInnerBorderSize.mnTop = aInnerBorder[1]; + maInnerBorderSize.mnRight = aInnerBorder[2]; + maInnerBorderSize.mnBottom = aInnerBorder[3]; + + ::std::vector<sal_Int32> aOuterBorder (rpTheme->GetBorderSize(rsStyleName, true)); + OSL_ASSERT(aOuterBorder.size()==4); + maOuterBorderSize.mnLeft = aOuterBorder[0]; + maOuterBorderSize.mnTop = aOuterBorder[1]; + maOuterBorderSize.mnRight = aOuterBorder[2]; + maOuterBorderSize.mnBottom = aOuterBorder[3]; + } + catch(beans::UnknownPropertyException&) + { + OSL_ASSERT(false); + } + + UpdateBorderSizes(); +} + +awt::Rectangle RendererPaneStyle::AddBorder ( + const awt::Rectangle& rBox, + const drawing::framework::BorderType eBorderType) const +{ + const BorderSize* pBorderSize = nullptr; + switch (eBorderType) + { + case drawing::framework::BorderType_INNER_BORDER: + pBorderSize = &maInnerBorderSize; + break; + case drawing::framework::BorderType_OUTER_BORDER: + pBorderSize = &maOuterBorderSize; + break; + case drawing::framework::BorderType_TOTAL_BORDER: + pBorderSize = &maTotalBorderSize; + break; + default: + return rBox; + } + return awt::Rectangle ( + rBox.X - pBorderSize->mnLeft, + rBox.Y - pBorderSize->mnTop, + rBox.Width + pBorderSize->mnLeft + pBorderSize->mnRight, + rBox.Height + pBorderSize->mnTop + pBorderSize->mnBottom); +} + +awt::Rectangle RendererPaneStyle::RemoveBorder ( + const awt::Rectangle& rBox, + const css::drawing::framework::BorderType eBorderType) const +{ + const BorderSize* pBorderSize = nullptr; + switch (eBorderType) + { + case drawing::framework::BorderType_INNER_BORDER: + pBorderSize = &maInnerBorderSize; + break; + case drawing::framework::BorderType_OUTER_BORDER: + pBorderSize = &maOuterBorderSize; + break; + case drawing::framework::BorderType_TOTAL_BORDER: + pBorderSize = &maTotalBorderSize; + break; + default: + return rBox; + } + return awt::Rectangle ( + rBox.X + pBorderSize->mnLeft, + rBox.Y + pBorderSize->mnTop, + rBox.Width - pBorderSize->mnLeft - pBorderSize->mnRight, + rBox.Height - pBorderSize->mnTop - pBorderSize->mnBottom); +} + +Reference<rendering::XCanvasFont> RendererPaneStyle::GetFont ( + const Reference<rendering::XCanvas>& rxCanvas) const +{ + if (mpFont) + { + mpFont->PrepareFont(rxCanvas); + return mpFont->mxFont; + } + return Reference<rendering::XCanvasFont>(); +} + +void RendererPaneStyle::UpdateBorderSizes() +{ + maTotalBorderSize.mnLeft = maInnerBorderSize.mnLeft + maOuterBorderSize.mnLeft; + maTotalBorderSize.mnTop = maInnerBorderSize.mnTop + maOuterBorderSize.mnTop; + maTotalBorderSize.mnRight = maInnerBorderSize.mnRight + maOuterBorderSize.mnRight; + maTotalBorderSize.mnBottom = maInnerBorderSize.mnBottom + maOuterBorderSize.mnBottom; +} + +SharedBitmapDescriptor RendererPaneStyle::GetBitmap( + const std::shared_ptr<PresenterTheme>& rpTheme, + const OUString& rsStyleName, + const OUString& rsBitmapName) +{ + SharedBitmapDescriptor pDescriptor (rpTheme->GetBitmap(rsStyleName, rsBitmapName)); + if (pDescriptor) + return pDescriptor; + else + return mpEmpty; +} + +} // end of anonymous namespace + +} // end of namespace ::sd::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterPaneBorderPainter.hxx b/sdext/source/presenter/PresenterPaneBorderPainter.hxx new file mode 100644 index 000000000..b7b9c0de1 --- /dev/null +++ b/sdext/source/presenter/PresenterPaneBorderPainter.hxx @@ -0,0 +1,138 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERPANEBORDERPAINTER_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERPANEBORDERPAINTER_HXX + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/drawing/framework/XPaneBorderPainter.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <memory> + +namespace sdext::presenter { + +class PresenterPane; +class PresenterTheme; + +typedef ::cppu::WeakComponentImplHelper< + css::drawing::framework::XPaneBorderPainter +> PresenterPaneBorderPainterInterfaceBase; + +/** This class is responsible for painting window borders of PresenterPane + objects. +*/ +class PresenterPaneBorderPainter + : protected ::cppu::BaseMutex, + public PresenterPaneBorderPainterInterfaceBase +{ +public: + explicit PresenterPaneBorderPainter ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext); + virtual ~PresenterPaneBorderPainter() override; + PresenterPaneBorderPainter(const PresenterPaneBorderPainter&) = delete; + PresenterPaneBorderPainter& operator=(const PresenterPaneBorderPainter&) = delete; + + /** Transform the bounding box of the window content to the outer + bounding box of the border that is painted around it. + @param rsPaneURL + Specifies the pane style that is used to determine the border sizes. + @param rInnerBox + The rectangle of the inner window content. + */ + css::awt::Rectangle AddBorder ( + const OUString& rsPaneURL, + const css::awt::Rectangle& rInnerBox, + const css::drawing::framework::BorderType eBorderType) const; + + /** Transform the outer bounding box of a window to the bounding box of + the inner content area. + @param rsPaneURL + Specifies the pane style that is used to determine the border sizes. + @param rOuterBox + The bounding box of the rectangle around the window. + @param bIsTitleVisible + This flag controls whether the upper part of the frame is + supposed to contain the window title. + */ + css::awt::Rectangle RemoveBorder ( + const OUString& rsPaneURL, + const css::awt::Rectangle& rOuterBox, + const css::drawing::framework::BorderType eBorderType) const; + + void SetTheme (const std::shared_ptr<PresenterTheme>& rpTheme); + + class Renderer; + + // XPaneBorderPainter + + virtual css::awt::Rectangle SAL_CALL addBorder ( + const OUString& rsPaneBorderStyleName, + const css::awt::Rectangle& rRectangle, + css::drawing::framework::BorderType eBorderType) override; + + virtual css::awt::Rectangle SAL_CALL removeBorder ( + const OUString& rsPaneBorderStyleName, + const css::awt::Rectangle& rRectangle, + css::drawing::framework::BorderType eBorderType) override; + + virtual void SAL_CALL paintBorder ( + const OUString& rsPaneBorderStyleName, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rOuterBorderRectangle, + const css::awt::Rectangle& rRepaintArea, + const OUString& rsTitle) override; + + virtual void SAL_CALL paintBorderWithCallout ( + const OUString& rsPaneBorderStyleName, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rOuterBorderRectangle, + const css::awt::Rectangle& rRepaintArea, + const OUString& rsTitle, + const css::awt::Point& rCalloutAnchor) override; + + virtual css::awt::Point SAL_CALL getCalloutOffset ( + const OUString& rsPaneBorderStyleName) override; + +private: + css::uno::Reference<css::uno::XComponentContext> mxContext; + std::shared_ptr<PresenterTheme> mpTheme; + std::unique_ptr<Renderer> mpRenderer; + + /** When the theme for the border has not yet been loaded then try again + when this method is called. + @return + Returns <TRUE/> only one time when the theme is loaded and/or the + renderer is initialized. + */ + bool ProvideTheme ( + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas); + void ProvideTheme(); + + /// @throws css::lang::DisposedException + void ThrowIfDisposed() const; +}; + +} // end of namespace ::sd::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterPaneContainer.cxx b/sdext/source/presenter/PresenterPaneContainer.cxx new file mode 100644 index 000000000..b28f36a39 --- /dev/null +++ b/sdext/source/presenter/PresenterPaneContainer.cxx @@ -0,0 +1,331 @@ +/* -*- 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 "PresenterPaneContainer.hxx" +#include "PresenterPaneBase.hxx" + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing::framework; + +namespace sdext::presenter { + +PresenterPaneContainer::PresenterPaneContainer ( + const Reference<XComponentContext>& rxContext) + : PresenterPaneContainerInterfaceBase(m_aMutex) +{ + Reference<lang::XMultiComponentFactory> xFactory (rxContext->getServiceManager()); + if (xFactory.is()) + { + mxPresenterHelper.set( + xFactory->createInstanceWithContext( + "com.sun.star.comp.Draw.PresenterHelper", + rxContext), + UNO_QUERY_THROW); + } +} + +PresenterPaneContainer::~PresenterPaneContainer() +{ +} + +void PresenterPaneContainer::PreparePane ( + const Reference<XResourceId>& rxPaneId, + const OUString& rsViewURL, + const OUString& rsTitle, + const OUString& rsAccessibleTitle, + const bool bIsOpaque, + const ViewInitializationFunction& rViewInitialization) +{ + if ( ! rxPaneId.is()) + return; + + SharedPaneDescriptor pPane (FindPaneURL(rxPaneId->getResourceURL())); + if (pPane) + return; + + // No entry found for the given pane id. Create a new one. + SharedPaneDescriptor pDescriptor = std::make_shared<PaneDescriptor>(); + pDescriptor->mxPaneId = rxPaneId; + pDescriptor->msViewURL = rsViewURL; + pDescriptor->mxPane = nullptr; + if (rsTitle.indexOf('%') < 0) + { + pDescriptor->msTitle = rsTitle; + pDescriptor->msTitleTemplate.clear(); + } + else + { + pDescriptor->msTitleTemplate = rsTitle; + pDescriptor->msTitle.clear(); + } + pDescriptor->msAccessibleTitleTemplate = rsAccessibleTitle; + pDescriptor->maViewInitialization = rViewInitialization; + pDescriptor->mbIsActive = true; + pDescriptor->mbIsOpaque = bIsOpaque; + pDescriptor->mbIsSprite = false; + + maPanes.push_back(pDescriptor); +} + +void SAL_CALL PresenterPaneContainer::disposing() +{ + for (const auto& rxPane : maPanes) + if (rxPane->mxPaneId.is()) + RemovePane(rxPane->mxPaneId); +} + +PresenterPaneContainer::SharedPaneDescriptor + PresenterPaneContainer::StorePane (const rtl::Reference<PresenterPaneBase>& rxPane) +{ + SharedPaneDescriptor pDescriptor; + + if (rxPane.is()) + { + OUString sPaneURL; + Reference<XResourceId> xPaneId (rxPane->getResourceId()); + if (xPaneId.is()) + sPaneURL = xPaneId->getResourceURL(); + + pDescriptor = FindPaneURL(sPaneURL); + if (!pDescriptor) + PreparePane(xPaneId, OUString(), OUString(), OUString(), + false, ViewInitializationFunction()); + pDescriptor = FindPaneURL(sPaneURL); + if (pDescriptor) + { + Reference<awt::XWindow> xWindow (rxPane->getWindow()); + pDescriptor->mxContentWindow = xWindow; + pDescriptor->mxPaneId = xPaneId; + pDescriptor->mxPane = rxPane; + pDescriptor->mxPane->SetTitle(pDescriptor->msTitle); + + if (xWindow.is()) + xWindow->addEventListener(this); + } + } + + return pDescriptor; +} + +PresenterPaneContainer::SharedPaneDescriptor + PresenterPaneContainer::StoreBorderWindow( + const Reference<XResourceId>& rxPaneId, + const Reference<awt::XWindow>& rxBorderWindow) +{ + // The content window may not be present. Use the resource URL of the + // pane id as key. + OUString sPaneURL; + if (rxPaneId.is()) + sPaneURL = rxPaneId->getResourceURL(); + + SharedPaneDescriptor pDescriptor (FindPaneURL(sPaneURL)); + if (pDescriptor) + { + pDescriptor->mxBorderWindow = rxBorderWindow; + return pDescriptor; + } + else + return SharedPaneDescriptor(); +} + +PresenterPaneContainer::SharedPaneDescriptor + PresenterPaneContainer::StoreView ( + const Reference<XView>& rxView) +{ + SharedPaneDescriptor pDescriptor; + + if (rxView.is()) + { + OUString sPaneURL; + Reference<XResourceId> xViewId (rxView->getResourceId()); + if (xViewId.is()) + { + Reference<XResourceId> xPaneId (xViewId->getAnchor()); + if (xPaneId.is()) + sPaneURL = xPaneId->getResourceURL(); + } + + pDescriptor = FindPaneURL(sPaneURL); + if (pDescriptor) + { + pDescriptor->mxView = rxView; + try + { + if (pDescriptor->maViewInitialization) + pDescriptor->maViewInitialization(rxView); + } + catch (RuntimeException&) + { + OSL_ASSERT(false); + } + } + } + + return pDescriptor; +} + +PresenterPaneContainer::SharedPaneDescriptor + PresenterPaneContainer::RemovePane (const Reference<XResourceId>& rxPaneId) +{ + SharedPaneDescriptor pDescriptor (FindPaneId(rxPaneId)); + if (pDescriptor) + { + if (pDescriptor->mxContentWindow.is()) + pDescriptor->mxContentWindow->removeEventListener(this); + pDescriptor->mxContentWindow = nullptr; + pDescriptor->mxBorderWindow = nullptr; + pDescriptor->mxPane = nullptr; + pDescriptor->mxView = nullptr; + pDescriptor->mbIsActive = false; + } + return pDescriptor; +} + +PresenterPaneContainer::SharedPaneDescriptor + PresenterPaneContainer::RemoveView (const Reference<XView>& rxView) +{ + SharedPaneDescriptor pDescriptor; + + if (rxView.is()) + { + OUString sPaneURL; + Reference<XResourceId> xViewId (rxView->getResourceId()); + if (xViewId.is()) + { + Reference<XResourceId> xPaneId (xViewId->getAnchor()); + if (xPaneId.is()) + sPaneURL = xPaneId->getResourceURL(); + } + + pDescriptor = FindPaneURL(sPaneURL); + if (pDescriptor) + { + pDescriptor->mxView = nullptr; + } + } + + return pDescriptor; +} + +PresenterPaneContainer::SharedPaneDescriptor PresenterPaneContainer::FindBorderWindow ( + const Reference<awt::XWindow>& rxBorderWindow) +{ + auto iPane = std::find_if(maPanes.begin(), maPanes.end(), + [&rxBorderWindow](const SharedPaneDescriptor& rxPane) { return rxPane->mxBorderWindow == rxBorderWindow; }); + if (iPane != maPanes.end()) + return *iPane; + return SharedPaneDescriptor(); +} + +PresenterPaneContainer::SharedPaneDescriptor PresenterPaneContainer::FindContentWindow ( + const Reference<awt::XWindow>& rxContentWindow) +{ + auto iPane = std::find_if(maPanes.begin(), maPanes.end(), + [&rxContentWindow](const SharedPaneDescriptor& rxPane) { return rxPane->mxContentWindow == rxContentWindow; }); + if (iPane != maPanes.end()) + return *iPane; + return SharedPaneDescriptor(); +} + +PresenterPaneContainer::SharedPaneDescriptor PresenterPaneContainer::FindPaneURL ( + const OUString& rsPaneURL) +{ + auto iPane = std::find_if(maPanes.begin(), maPanes.end(), + [&rsPaneURL](const SharedPaneDescriptor& rxPane) { return rxPane->mxPaneId->getResourceURL() == rsPaneURL; }); + if (iPane != maPanes.end()) + return *iPane; + return SharedPaneDescriptor(); +} + +PresenterPaneContainer::SharedPaneDescriptor PresenterPaneContainer::FindPaneId ( + const Reference<XResourceId>& rxPaneId) +{ + if ( ! rxPaneId.is()) + return SharedPaneDescriptor(); + + auto iPane = std::find_if(maPanes.begin(), maPanes.end(), + [&rxPaneId](const SharedPaneDescriptor& rxPane) { return rxPaneId->compareTo(rxPane->mxPaneId) == 0; }); + if (iPane != maPanes.end()) + return *iPane; + return SharedPaneDescriptor(); +} + +PresenterPaneContainer::SharedPaneDescriptor PresenterPaneContainer::FindViewURL ( + const OUString& rsViewURL) +{ + auto iPane = std::find_if(maPanes.begin(), maPanes.end(), + [&rsViewURL](const SharedPaneDescriptor& rxPane) { return rsViewURL == rxPane->msViewURL; }); + if (iPane != maPanes.end()) + return *iPane; + return SharedPaneDescriptor(); +} + +OUString PresenterPaneContainer::GetPaneURLForViewURL (const OUString& rsViewURL) +{ + SharedPaneDescriptor pDescriptor (FindViewURL(rsViewURL)); + if (pDescriptor) + if (pDescriptor->mxPaneId.is()) + return pDescriptor->mxPaneId->getResourceURL(); + return OUString(); +} + +void PresenterPaneContainer::ToTop (const SharedPaneDescriptor& rpDescriptor) +{ + if (!rpDescriptor) + return; + + // Find iterator for pDescriptor. + PaneList::iterator iEnd (maPanes.end()); + auto iPane = std::find_if(maPanes.begin(), iEnd, + [&rpDescriptor](SharedPaneDescriptor& rxPane) { return rxPane.get() == rpDescriptor.get(); }); + OSL_ASSERT(iPane!=iEnd); + if (iPane == iEnd) + return; + + if (mxPresenterHelper.is()) + mxPresenterHelper->toTop(rpDescriptor->mxBorderWindow); + + maPanes.erase(iPane); + maPanes.push_back(rpDescriptor); +} + +//----- XEventListener -------------------------------------------------------- + +void SAL_CALL PresenterPaneContainer::disposing ( + const css::lang::EventObject& rEvent) +{ + SharedPaneDescriptor pDescriptor ( + FindContentWindow(Reference<awt::XWindow>(rEvent.Source, UNO_QUERY))); + if (pDescriptor) + { + RemovePane(pDescriptor->mxPaneId); + } +} + +//===== PresenterPaneContainer::PaneDescriptor ================================ + +void PresenterPaneContainer::PaneDescriptor::SetActivationState (const bool bIsActive) +{ + mbIsActive = bIsActive; +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterPaneContainer.hxx b/sdext/source/presenter/PresenterPaneContainer.hxx new file mode 100644 index 000000000..136c25690 --- /dev/null +++ b/sdext/source/presenter/PresenterPaneContainer.hxx @@ -0,0 +1,161 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERPANECONTAINER_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERPANECONTAINER_HXX + +#include "PresenterPaneBase.hxx" +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/drawing/XPresenterHelper.hpp> +#include <com/sun/star/drawing/framework/XResourceId.hpp> +#include <com/sun/star/drawing/framework/XView.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <rtl/ref.hxx> + +#include <functional> +#include <memory> +#include <vector> + +namespace sdext::presenter { + +class PresenterPaneBase; +class PresenterSprite; + +typedef ::cppu::WeakComponentImplHelper < + css::lang::XEventListener +> PresenterPaneContainerInterfaceBase; + +/** This class could also be called PresenterPaneAndViewContainer because it + stores not only references to all panes that belong to the presenter + screen but stores the views displayed in these panes as well. +*/ +class PresenterPaneContainer + : private ::cppu::BaseMutex, + public PresenterPaneContainerInterfaceBase +{ +public: + explicit PresenterPaneContainer ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext); + virtual ~PresenterPaneContainer() override; + PresenterPaneContainer(const PresenterPaneContainer&) = delete; + PresenterPaneContainer& operator=(const PresenterPaneContainer&) = delete; + + virtual void SAL_CALL disposing() override; + + typedef ::std::function<void (const css::uno::Reference<css::drawing::framework::XView>&)> + ViewInitializationFunction; + + /** Each pane descriptor holds references to one pane and the view + displayed in this pane as well as the other information that is used + to manage the pane window like an XWindow reference, the title, and + the coordinates. + + A initialization function for the view is stored as well. This + function is executed as soon as a view is created. + */ + class PaneDescriptor + { + public: + css::uno::Reference<css::drawing::framework::XResourceId> mxPaneId; + OUString msViewURL; + ::rtl::Reference<PresenterPaneBase> mxPane; + css::uno::Reference<css::drawing::framework::XView> mxView; + css::uno::Reference<css::awt::XWindow> mxContentWindow; + css::uno::Reference<css::awt::XWindow> mxBorderWindow; + OUString msTitleTemplate; + OUString msAccessibleTitleTemplate; + OUString msTitle; + ViewInitializationFunction maViewInitialization; + bool mbIsActive; + bool mbIsOpaque; + bool mbIsSprite; + + void SetActivationState (const bool bIsActive); + }; + typedef std::shared_ptr<PaneDescriptor> SharedPaneDescriptor; + typedef ::std::vector<SharedPaneDescriptor> PaneList; + PaneList maPanes; + + void PreparePane ( + const css::uno::Reference<css::drawing::framework::XResourceId>& rxPaneId, + const OUString& rsViewURL, + const OUString& rsTitle, + const OUString& rsAccessibleTitle, + const bool bIsOpaque, + const ViewInitializationFunction& rViewInitialization); + + SharedPaneDescriptor StorePane ( + const rtl::Reference<PresenterPaneBase>& rxPane); + + SharedPaneDescriptor StoreBorderWindow( + const css::uno::Reference<css::drawing::framework::XResourceId>& rxPaneId, + const css::uno::Reference<css::awt::XWindow>& rxBorderWindow); + + SharedPaneDescriptor StoreView ( + const css::uno::Reference<css::drawing::framework::XView>& rxView); + + SharedPaneDescriptor RemovePane ( + const css::uno::Reference<css::drawing::framework::XResourceId>& rxPaneId); + + SharedPaneDescriptor RemoveView ( + const css::uno::Reference<css::drawing::framework::XView>& rxView); + + /** Find the pane whose border window is identical to the given border + window. + */ + SharedPaneDescriptor FindBorderWindow ( + const css::uno::Reference<css::awt::XWindow>& rxBorderWindow); + + /** Find the pane whose border window is identical to the given content + window. + */ + SharedPaneDescriptor FindContentWindow ( + const css::uno::Reference<css::awt::XWindow>& rxBorderWindow); + + /** Find the pane whose pane URL is identical to the given URL string. + */ + SharedPaneDescriptor FindPaneURL (const OUString& rsPaneURL); + + /** Find the pane whose resource id is identical to the given one. + */ + SharedPaneDescriptor FindPaneId (const css::uno::Reference< + css::drawing::framework::XResourceId>& rxPaneId); + + SharedPaneDescriptor FindViewURL (const OUString& rsViewURL); + + OUString GetPaneURLForViewURL (const OUString& rsViewURL); + + void ToTop (const SharedPaneDescriptor& rpDescriptor); + + // XEventListener + + virtual void SAL_CALL disposing ( + const css::lang::EventObject& rEvent) override; + +private: + css::uno::Reference<css::drawing::XPresenterHelper> mxPresenterHelper; +}; + +} // end of namespace ::sdext::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterPaneFactory.cxx b/sdext/source/presenter/PresenterPaneFactory.cxx new file mode 100644 index 000000000..b4aadc771 --- /dev/null +++ b/sdext/source/presenter/PresenterPaneFactory.cxx @@ -0,0 +1,283 @@ +/* -*- 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 "PresenterPaneFactory.hxx" +#include "PresenterController.hxx" +#include "PresenterPane.hxx" +#include "PresenterPaneBorderPainter.hxx" +#include "PresenterPaneContainer.hxx" +#include "PresenterSpritePane.hxx" +#include <com/sun/star/drawing/framework/XControllerManager.hpp> +#include <com/sun/star/lang/XComponent.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::drawing::framework; + +namespace sdext::presenter { + +//===== PresenterPaneFactory ================================================== + +Reference<drawing::framework::XResourceFactory> PresenterPaneFactory::Create ( + const Reference<uno::XComponentContext>& rxContext, + const Reference<frame::XController>& rxController, + const ::rtl::Reference<PresenterController>& rpPresenterController) +{ + rtl::Reference<PresenterPaneFactory> pFactory ( + new PresenterPaneFactory(rxContext,rpPresenterController)); + pFactory->Register(rxController); + return Reference<drawing::framework::XResourceFactory>(pFactory); +} + +PresenterPaneFactory::PresenterPaneFactory ( + const Reference<uno::XComponentContext>& rxContext, + const ::rtl::Reference<PresenterController>& rpPresenterController) + : PresenterPaneFactoryInterfaceBase(m_aMutex), + mxComponentContextWeak(rxContext), + mpPresenterController(rpPresenterController) +{ +} + +void PresenterPaneFactory::Register (const Reference<frame::XController>& rxController) +{ + Reference<XConfigurationController> xCC; + try + { + // Get the configuration controller. + Reference<XControllerManager> xCM (rxController, UNO_QUERY_THROW); + xCC.set(xCM->getConfigurationController()); + mxConfigurationControllerWeak = xCC; + if ( ! xCC.is()) + { + throw RuntimeException(); + } + xCC->addResourceFactory( + "private:resource/pane/Presenter/*", + this); + } + catch (RuntimeException&) + { + OSL_ASSERT(false); + if (xCC.is()) + xCC->removeResourceFactoryForReference(this); + mxConfigurationControllerWeak = WeakReference<XConfigurationController>(); + + throw; + } +} + +PresenterPaneFactory::~PresenterPaneFactory() +{ +} + +void SAL_CALL PresenterPaneFactory::disposing() +{ + Reference<XConfigurationController> xCC (mxConfigurationControllerWeak); + if (xCC.is()) + xCC->removeResourceFactoryForReference(this); + mxConfigurationControllerWeak = WeakReference<XConfigurationController>(); + + // Dispose the panes in the cache. + if (mpResourceCache != nullptr) + { + for (const auto& rxPane : *mpResourceCache) + { + Reference<lang::XComponent> xPaneComponent (rxPane.second, UNO_QUERY); + if (xPaneComponent.is()) + xPaneComponent->dispose(); + } + mpResourceCache.reset(); + } +} + +//----- XPaneFactory ---------------------------------------------------------- + +Reference<XResource> SAL_CALL PresenterPaneFactory::createResource ( + const Reference<XResourceId>& rxPaneId) +{ + ThrowIfDisposed(); + + if ( ! rxPaneId.is()) + return nullptr; + + const OUString sPaneURL (rxPaneId->getResourceURL()); + if (sPaneURL.isEmpty()) + return nullptr; + + if (mpResourceCache != nullptr) + { + // Has the requested resource already been created? + ResourceContainer::const_iterator iResource (mpResourceCache->find(sPaneURL)); + if (iResource != mpResourceCache->end()) + { + // Yes. Mark it as active. + rtl::Reference<PresenterPaneContainer> pPaneContainer( + mpPresenterController->GetPaneContainer()); + PresenterPaneContainer::SharedPaneDescriptor pDescriptor ( + pPaneContainer->FindPaneURL(sPaneURL)); + if (pDescriptor) + { + pDescriptor->SetActivationState(true); + if (pDescriptor->mxBorderWindow.is()) + pDescriptor->mxBorderWindow->setVisible(true); + pPaneContainer->StorePane(pDescriptor->mxPane); + } + + return iResource->second; + } + } + + // No. Create a new one. + Reference<XResource> xResource = CreatePane(rxPaneId); + return xResource; +} + +void SAL_CALL PresenterPaneFactory::releaseResource (const Reference<XResource>& rxResource) +{ + ThrowIfDisposed(); + + if ( ! rxResource.is()) + throw lang::IllegalArgumentException(); + + // Mark the pane as inactive. + rtl::Reference<PresenterPaneContainer> pPaneContainer( + mpPresenterController->GetPaneContainer()); + const OUString sPaneURL (rxResource->getResourceId()->getResourceURL()); + PresenterPaneContainer::SharedPaneDescriptor pDescriptor ( + pPaneContainer->FindPaneURL(sPaneURL)); + if (!pDescriptor) + return; + + pDescriptor->SetActivationState(false); + if (pDescriptor->mxBorderWindow.is()) + pDescriptor->mxBorderWindow->setVisible(false); + + if (mpResourceCache != nullptr) + { + // Store the pane in the cache. + (*mpResourceCache)[sPaneURL] = rxResource; + } + else + { + // Dispose the pane. + Reference<lang::XComponent> xPaneComponent (rxResource, UNO_QUERY); + if (xPaneComponent.is()) + xPaneComponent->dispose(); + } +} + + +Reference<XResource> PresenterPaneFactory::CreatePane ( + const Reference<XResourceId>& rxPaneId) +{ + if ( ! rxPaneId.is()) + return nullptr; + + Reference<XConfigurationController> xCC (mxConfigurationControllerWeak); + if ( ! xCC.is()) + return nullptr; + + Reference<XComponentContext> xContext (mxComponentContextWeak); + if ( ! xContext.is()) + return nullptr; + + Reference<XPane> xParentPane (xCC->getResource(rxPaneId->getAnchor()), UNO_QUERY); + if ( ! xParentPane.is()) + return nullptr; + + try + { + return CreatePane( + rxPaneId, + xParentPane, + rxPaneId->getFullResourceURL().Arguments == "Sprite=1"); + } + catch (Exception&) + { + OSL_ASSERT(false); + } + + return nullptr; +} + +Reference<XResource> PresenterPaneFactory::CreatePane ( + const Reference<XResourceId>& rxPaneId, + const Reference<drawing::framework::XPane>& rxParentPane, + const bool bIsSpritePane) +{ + Reference<XComponentContext> xContext (mxComponentContextWeak); + Reference<lang::XMultiComponentFactory> xFactory ( + xContext->getServiceManager(), UNO_SET_THROW); + + // Create a border window and canvas and store it in the pane + // container. + + // Create the pane. + ::rtl::Reference<PresenterPaneBase> xPane; + if (bIsSpritePane) + { + xPane.set( new PresenterSpritePane(xContext, mpPresenterController)); + } + else + { + xPane.set( new PresenterPane(xContext, mpPresenterController)); + } + + // Supply arguments. + Sequence<Any> aArguments{ Any(rxPaneId), + Any(rxParentPane->getWindow()), + Any(rxParentPane->getCanvas()), + Any(OUString()), + Any(Reference<drawing::framework::XPaneBorderPainter>( + mpPresenterController->GetPaneBorderPainter())), + Any(!bIsSpritePane) }; + xPane->initialize(aArguments); + + // Store pane and canvases and windows in container. + ::rtl::Reference<PresenterPaneContainer> pContainer ( + mpPresenterController->GetPaneContainer()); + PresenterPaneContainer::SharedPaneDescriptor pDescriptor( + pContainer->StoreBorderWindow(rxPaneId, xPane->GetBorderWindow())); + pContainer->StorePane(xPane); + if (pDescriptor) + { + pDescriptor->mbIsSprite = bIsSpritePane; + + // Get the window of the frame and make that visible. + Reference<awt::XWindow> xWindow (pDescriptor->mxBorderWindow, UNO_SET_THROW); + xWindow->setVisible(true); + } + + return Reference<XResource>(static_cast<XWeak*>(xPane.get()), UNO_QUERY_THROW); +} + +void PresenterPaneFactory::ThrowIfDisposed() const +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ( + "PresenterPaneFactory object has already been disposed", + const_cast<uno::XWeak*>(static_cast<const uno::XWeak*>(this))); + } +} + +} // end of namespace sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterPaneFactory.hxx b/sdext/source/presenter/PresenterPaneFactory.hxx new file mode 100644 index 000000000..45f9541c6 --- /dev/null +++ b/sdext/source/presenter/PresenterPaneFactory.hxx @@ -0,0 +1,117 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERPANEFACTORY_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERPANEFACTORY_HXX + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/drawing/framework/XConfigurationController.hpp> +#include <com/sun/star/drawing/framework/XPane.hpp> +#include <com/sun/star/drawing/framework/XResourceFactory.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <rtl/ref.hxx> +#include <map> +#include <memory> + +namespace sdext::presenter { + +class PresenterController; + +typedef ::cppu::WeakComponentImplHelper < + css::drawing::framework::XResourceFactory +> PresenterPaneFactoryInterfaceBase; + +/** The PresenterPaneFactory provides a fixed set of panes. + + In order to make the presenter screen more easily extendable in the + future the set of supported panes could be made extendable on demand. +*/ +class PresenterPaneFactory + : public ::cppu::BaseMutex, + public PresenterPaneFactoryInterfaceBase +{ +public: + static constexpr OUStringLiteral msCurrentSlidePreviewPaneURL + = u"private:resource/pane/Presenter/Pane1"; + static constexpr OUStringLiteral msNextSlidePreviewPaneURL + = u"private:resource/pane/Presenter/Pane2"; + static constexpr OUStringLiteral msNotesPaneURL = u"private:resource/pane/Presenter/Pane3"; + static constexpr OUStringLiteral msToolBarPaneURL = u"private:resource/pane/Presenter/Pane4"; + static constexpr OUStringLiteral msSlideSorterPaneURL + = u"private:resource/pane/Presenter/Pane5"; + + /** Create a new instance of this class and register it as resource + factory in the drawing framework of the given controller. + This registration keeps it alive. When the drawing framework is + shut down and releases its reference to the factory then the factory + is destroyed. + */ + static css::uno::Reference<css::drawing::framework::XResourceFactory> Create ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::frame::XController>& rxController, + const ::rtl::Reference<PresenterController>& rpPresenterController); + virtual ~PresenterPaneFactory() override; + + virtual void SAL_CALL disposing() override; + + // XResourceFactory + + virtual css::uno::Reference<css::drawing::framework::XResource> + SAL_CALL createResource ( + const css::uno::Reference< + css::drawing::framework::XResourceId>& rxPaneId) override; + + virtual void SAL_CALL + releaseResource ( + const css::uno::Reference<css::drawing::framework::XResource>& + rxPane) override; + +private: + css::uno::WeakReference<css::uno::XComponentContext> mxComponentContextWeak; + css::uno::WeakReference<css::drawing::framework::XConfigurationController> + mxConfigurationControllerWeak; + ::rtl::Reference<PresenterController> mpPresenterController; + typedef ::std::map<OUString, css::uno::Reference<css::drawing::framework::XResource> > + ResourceContainer; + std::unique_ptr<ResourceContainer> mpResourceCache; + + PresenterPaneFactory ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const ::rtl::Reference<PresenterController>& rpPresenterController); + + void Register (const css::uno::Reference<css::frame::XController>& rxController); + + css::uno::Reference<css::drawing::framework::XResource> CreatePane ( + const css::uno::Reference<css::drawing::framework::XResourceId>& rxPaneId); + css::uno::Reference<css::drawing::framework::XResource> CreatePane ( + const css::uno::Reference<css::drawing::framework::XResourceId>& rxPaneId, + const css::uno::Reference<css::drawing::framework::XPane>& rxParentPane, + const bool bIsSpritePane); + + /// @throws css::lang::DisposedException + void ThrowIfDisposed() const; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterProtocolHandler.cxx b/sdext/source/presenter/PresenterProtocolHandler.cxx new file mode 100644 index 000000000..efd572ee9 --- /dev/null +++ b/sdext/source/presenter/PresenterProtocolHandler.cxx @@ -0,0 +1,829 @@ +/* -*- 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 <memory> +#include "PresenterProtocolHandler.hxx" +#include "PresenterController.hxx" +#include "PresenterNotesView.hxx" +#include "PresenterPaneContainer.hxx" +#include "PresenterViewFactory.hxx" +#include "PresenterWindowManager.hxx" +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <algorithm> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing::framework; + +namespace sdext::presenter { + +namespace { + class Command + { + public: + virtual ~Command() {} + virtual void Execute() = 0; + virtual bool IsEnabled() const { return true; } + virtual Any GetState() const { return Any(false); } + }; + + class GotoPreviousSlideCommand : public Command + { + public: + explicit GotoPreviousSlideCommand ( + const rtl::Reference<PresenterController>& rpPresenterController); + virtual void Execute() override; + virtual bool IsEnabled() const override; + private: + rtl::Reference<PresenterController> mpPresenterController; + }; + + class GotoNextSlideCommand : public Command + { + public: + explicit GotoNextSlideCommand ( + const rtl::Reference<PresenterController>& rpPresenterController); + virtual void Execute() override; + // The next slide command is always enabled, even when the current slide + // is the last slide: from the last slide it goes to the pause slide, + // and from there it ends the slide show. + virtual bool IsEnabled() const override { return true; } + private: + rtl::Reference<PresenterController> mpPresenterController; + }; + + class GotoNextEffectCommand : public Command + { + public: + explicit GotoNextEffectCommand ( + const rtl::Reference<PresenterController>& rpPresenterController); + virtual void Execute() override; + virtual bool IsEnabled() const override; + private: + rtl::Reference<PresenterController> mpPresenterController; + }; + + class SwitchMonitorCommand : public Command + { + public: + explicit SwitchMonitorCommand ( + const rtl::Reference<PresenterController>& rpPresenterController); + virtual void Execute() override; + private: + rtl::Reference<PresenterController> mpPresenterController; + }; + + class PauseResumeCommand : public Command + { + public: + explicit PauseResumeCommand(const rtl::Reference<PresenterController>& rpPresenterController); + virtual void Execute() override; + virtual Any GetState() const override; + private: + rtl::Reference<PresenterController> mpPresenterController; + }; + + /// This command restarts the presentation timer. + class RestartTimerCommand : public Command + { + public: + explicit RestartTimerCommand(const rtl::Reference<PresenterController>& rpPresenterController); + virtual void Execute() override; + private: + rtl::Reference<PresenterController> mpPresenterController; + }; + + class SetNotesViewCommand : public Command + { + public: + SetNotesViewCommand ( + const bool bOn, + const rtl::Reference<PresenterController>& rpPresenterController); + virtual void Execute() override; + virtual Any GetState() const override; + private: + bool mbOn; + rtl::Reference<PresenterController> mpPresenterController; + }; + + class SetSlideSorterCommand : public Command + { + public: + SetSlideSorterCommand ( + const bool bOn, + const rtl::Reference<PresenterController>& rpPresenterController); + virtual void Execute() override; + virtual Any GetState() const override; + private: + bool mbOn; + rtl::Reference<PresenterController> mpPresenterController; + }; + + class SetHelpViewCommand : public Command + { + public: + SetHelpViewCommand ( + const bool bOn, + const rtl::Reference<PresenterController>& rpPresenterController); + virtual void Execute() override; + virtual Any GetState() const override; + private: + bool mbOn; + rtl::Reference<PresenterController> mpPresenterController; + }; + + class NotesFontSizeCommand : public Command + { + public: + NotesFontSizeCommand( + const rtl::Reference<PresenterController>& rpPresenterController, + const sal_Int32 nSizeChange); + virtual void Execute() override; + virtual Any GetState() const override; + protected: + ::rtl::Reference<PresenterNotesView> GetNotesView() const; + private: + rtl::Reference<PresenterController> mpPresenterController; + const sal_Int32 mnSizeChange; + }; + + class ExitPresenterCommand : public Command + { + public: + explicit ExitPresenterCommand(const rtl::Reference<PresenterController>& rpPresenterController); + virtual void Execute() override; + private: + rtl::Reference<PresenterController> mpPresenterController; + }; + +} // end of anonymous namespace + +namespace { + typedef ::cppu::WeakComponentImplHelper < + css::frame::XDispatch, + css::document::XEventListener + > PresenterDispatchInterfaceBase; +} + +class PresenterProtocolHandler::Dispatch + : protected ::cppu::BaseMutex, + public PresenterDispatchInterfaceBase +{ +public: + /** Create a new Dispatch object. When the given command name + (rsURLPath) is not known then an empty reference is returned. + */ + static Reference<frame::XDispatch> Create ( + const OUString& rsURLPath, + const ::rtl::Reference<PresenterController>& rpPresenterController); + + void SAL_CALL disposing() override; + static Command* CreateCommand ( + const OUString& rsURLPath, + const ::rtl::Reference<PresenterController>& rpPresenterController); + + // XDispatch + virtual void SAL_CALL dispatch( + const css::util::URL& aURL, + const css::uno::Sequence<css::beans::PropertyValue>& rArguments) override; + + virtual void SAL_CALL addStatusListener( + const css::uno::Reference<css::frame::XStatusListener>& rxListener, + const css::util::URL& rURL) override; + + virtual void SAL_CALL removeStatusListener ( + const css::uno::Reference<css::frame::XStatusListener>& rxListener, + const css::util::URL& rURL) override; + + // document::XEventListener + + virtual void SAL_CALL notifyEvent (const css::document::EventObject& rEvent) override; + + // lang::XEventListener + + virtual void SAL_CALL disposing (const css::lang::EventObject& rEvent) override; + +private: + OUString msURLPath; + std::unique_ptr<Command> mpCommand; + ::rtl::Reference<PresenterController> mpPresenterController; + typedef ::std::vector<Reference<frame::XStatusListener> > StatusListenerContainer; + StatusListenerContainer maStatusListenerContainer; + bool mbIsListeningToWindowManager; + + Dispatch ( + const OUString& rsURLPath, + const ::rtl::Reference<PresenterController>& rpPresenterController); + virtual ~Dispatch() override; +}; + + +//===== PresenterProtocolHandler ========================================================= + +PresenterProtocolHandler::PresenterProtocolHandler () + : PresenterProtocolHandlerInterfaceBase(m_aMutex) +{ +} + +PresenterProtocolHandler::~PresenterProtocolHandler() +{ +} + +void SAL_CALL PresenterProtocolHandler::disposing() +{ +} + +//----- XInitialize ----------------------------------------------------------- + +void SAL_CALL PresenterProtocolHandler::initialize (const Sequence<Any>& aArguments) +{ + ThrowIfDisposed(); + if (aArguments.getLength() <= 0) + return; + + try + { + Reference<frame::XFrame> xFrame; + if (aArguments[0] >>= xFrame) + { + mpPresenterController = PresenterController::Instance(xFrame); + } + } + catch (RuntimeException&) + { + OSL_ASSERT(false); + } +} + +OUString PresenterProtocolHandler::getImplementationName() +{ + return "org.libreoffice.comp.PresenterScreenProtocolHandler"; +} + +sal_Bool PresenterProtocolHandler::supportsService(OUString const & ServiceName) +{ + return cppu::supportsService(this, ServiceName); +} + +css::uno::Sequence<OUString> +PresenterProtocolHandler::getSupportedServiceNames() +{ + return { "com.sun.star.frame.ProtocolHandler" }; +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +sdext_PresenterProtocolHandler_get_implementation( + css::uno::XComponentContext* , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new PresenterProtocolHandler()); +} + +//----- XDispatchProvider ----------------------------------------------------- + +Reference<frame::XDispatch> SAL_CALL PresenterProtocolHandler::queryDispatch ( + const css::util::URL& rURL, + const OUString&, + sal_Int32) +{ + ThrowIfDisposed(); + + Reference<frame::XDispatch> xDispatch; + + // tdf#154546 skip dispatch when presenter controller is not set + // mpPresenterController is sometimes unset and this will cause a + // crash when pressing the presenter console's Exchange button. + if (rURL.Protocol == "vnd.org.libreoffice.presenterscreen:" && mpPresenterController.is()) + { + xDispatch.set(Dispatch::Create(rURL.Path, mpPresenterController)); + } + + return xDispatch; +} + +Sequence<Reference<frame::XDispatch> > SAL_CALL PresenterProtocolHandler::queryDispatches( + const Sequence<frame::DispatchDescriptor>&) +{ + ThrowIfDisposed(); + return Sequence<Reference<frame::XDispatch> >(); +} + + +void PresenterProtocolHandler::ThrowIfDisposed() const +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ( + "PresenterProtocolHandler object has already been disposed", + const_cast<uno::XWeak*>(static_cast<const uno::XWeak*>(this))); + } +} + +//===== PresenterProtocolHandler::Dispatch ==================================== + +Reference<frame::XDispatch> PresenterProtocolHandler::Dispatch::Create ( + const OUString& rsURLPath, + const ::rtl::Reference<PresenterController>& rpPresenterController) +{ + ::rtl::Reference<Dispatch> pDispatch (new Dispatch (rsURLPath, rpPresenterController)); + if (pDispatch->mpCommand != nullptr) + return pDispatch; + else + return nullptr; +} + +PresenterProtocolHandler::Dispatch::Dispatch ( + const OUString& rsURLPath, + const ::rtl::Reference<PresenterController>& rpPresenterController) + : PresenterDispatchInterfaceBase(m_aMutex), + msURLPath(rsURLPath), + mpCommand(CreateCommand(rsURLPath, rpPresenterController)), + mpPresenterController(rpPresenterController), + mbIsListeningToWindowManager(false) +{ + if (mpCommand != nullptr) + { + mpPresenterController->GetWindowManager()->AddLayoutListener(this); + mbIsListeningToWindowManager = true; + } +} + +Command* PresenterProtocolHandler::Dispatch::CreateCommand ( + const OUString& rsURLPath, + const ::rtl::Reference<PresenterController>& rpPresenterController) +{ + if (rsURLPath.getLength() <= 5) + return nullptr; + + if (rsURLPath == "CloseNotes") + return new SetNotesViewCommand(false, rpPresenterController); + if (rsURLPath == "CloseSlideSorter") + return new SetSlideSorterCommand(false, rpPresenterController); + if (rsURLPath == "CloseHelp") + return new SetHelpViewCommand(false, rpPresenterController); + if (rsURLPath == "GrowNotesFont") + return new NotesFontSizeCommand(rpPresenterController, +1); + if (rsURLPath == "NextEffect") + return new GotoNextEffectCommand(rpPresenterController); + if (rsURLPath == "NextSlide") + return new GotoNextSlideCommand(rpPresenterController); + if (rsURLPath == "PrevSlide") + return new GotoPreviousSlideCommand(rpPresenterController); + if (rsURLPath == "SwitchMonitor") + return new SwitchMonitorCommand(rpPresenterController); + if (rsURLPath == "PauseResumeTimer") + return new PauseResumeCommand(rpPresenterController); + if (rsURLPath == "RestartTimer") + return new RestartTimerCommand(rpPresenterController); + if (rsURLPath == "ShowNotes") + return new SetNotesViewCommand(true, rpPresenterController); + if (rsURLPath == "ShowSlideSorter") + return new SetSlideSorterCommand(true, rpPresenterController); + if (rsURLPath == "ShowHelp") + return new SetHelpViewCommand(true, rpPresenterController); + if (rsURLPath == "ShrinkNotesFont") + return new NotesFontSizeCommand(rpPresenterController, -1); + if (rsURLPath == "ExitPresenter") + return new ExitPresenterCommand(rpPresenterController); + + return nullptr; +} + +PresenterProtocolHandler::Dispatch::~Dispatch() +{ +} + +void PresenterProtocolHandler::Dispatch::disposing() +{ + if (mbIsListeningToWindowManager) + { + if (mpPresenterController) + mpPresenterController->GetWindowManager()->RemoveLayoutListener(this); + mbIsListeningToWindowManager = false; + } + + msURLPath.clear(); + mpCommand.reset(); +} + +//----- XDispatch ------------------------------------------------------------- + +void SAL_CALL PresenterProtocolHandler::Dispatch::dispatch( + const css::util::URL& rURL, + const css::uno::Sequence<css::beans::PropertyValue>& /*rArguments*/) +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ( + "PresenterProtocolHandler::Dispatch object has already been disposed", + static_cast<uno::XWeak*>(this)); + } + + if (rURL.Protocol != "vnd.org.libreoffice.presenterscreen:" + || rURL.Path != msURLPath) + { + // We can not throw an IllegalArgumentException + throw RuntimeException(); + } + + if (mpCommand != nullptr) + mpCommand->Execute(); +} + +void SAL_CALL PresenterProtocolHandler::Dispatch::addStatusListener( + const css::uno::Reference<css::frame::XStatusListener>& rxListener, + const css::util::URL& rURL) +{ + if (rURL.Path != msURLPath) + throw RuntimeException(); + + maStatusListenerContainer.push_back(rxListener); + + frame::FeatureStateEvent aEvent; + aEvent.FeatureURL = rURL; + aEvent.IsEnabled = mpCommand->IsEnabled(); + aEvent.Requery = false; + aEvent.State = mpCommand->GetState(); + rxListener->statusChanged(aEvent); +} + +void SAL_CALL PresenterProtocolHandler::Dispatch::removeStatusListener ( + const css::uno::Reference<css::frame::XStatusListener>& rxListener, + const css::util::URL& rURL) +{ + if (rURL.Path != msURLPath) + throw RuntimeException(); + + StatusListenerContainer::iterator iListener ( + ::std::find( + maStatusListenerContainer.begin(), + maStatusListenerContainer.end(), + rxListener)); + if (iListener != maStatusListenerContainer.end()) + maStatusListenerContainer.erase(iListener); +} + +//----- document::XEventListener ---------------------------------------------- + +void SAL_CALL PresenterProtocolHandler::Dispatch::notifyEvent ( + const css::document::EventObject&) +{ + mpCommand->GetState(); +} + +//----- lang::XEventListener -------------------------------------------------- + +void SAL_CALL PresenterProtocolHandler::Dispatch::disposing (const css::lang::EventObject&) +{ + mbIsListeningToWindowManager = false; +} + +//===== GotoPreviousSlideCommand ============================================== + +GotoPreviousSlideCommand::GotoPreviousSlideCommand ( + const rtl::Reference<PresenterController>& rpPresenterController) + : mpPresenterController(rpPresenterController) +{ +} + +void GotoPreviousSlideCommand::Execute() +{ + if ( ! mpPresenterController.is()) + return; + + if ( ! mpPresenterController->GetSlideShowController().is()) + return; + + mpPresenterController->GetSlideShowController()->gotoPreviousSlide(); +} + +bool GotoPreviousSlideCommand::IsEnabled() const +{ + if ( ! mpPresenterController.is()) + return false; + + if ( ! mpPresenterController->GetSlideShowController().is()) + return false; + + return mpPresenterController->GetSlideShowController()->getCurrentSlideIndex()>0; +} + +//===== GotoNextEffect ======================================================== + +GotoNextEffectCommand::GotoNextEffectCommand ( + const rtl::Reference<PresenterController>& rpPresenterController) + : mpPresenterController(rpPresenterController) +{ +} + +void GotoNextEffectCommand::Execute() +{ + if ( ! mpPresenterController.is()) + return; + + if ( ! mpPresenterController->GetSlideShowController().is()) + return; + + mpPresenterController->GetSlideShowController()->gotoNextEffect(); +} + +bool GotoNextEffectCommand::IsEnabled() const +{ + if ( ! mpPresenterController.is()) + return false; + + if ( ! mpPresenterController->GetSlideShowController().is()) + return false; + + return ( mpPresenterController->GetSlideShowController()->getNextSlideIndex() < mpPresenterController->GetSlideShowController()->getSlideCount() ); + +} + +//===== GotoNextSlide ========================================================= + +GotoNextSlideCommand::GotoNextSlideCommand ( + const rtl::Reference<PresenterController>& rpPresenterController) + : mpPresenterController(rpPresenterController) +{ +} + +void GotoNextSlideCommand::Execute() +{ + if ( ! mpPresenterController.is()) + return; + + if ( ! mpPresenterController->GetSlideShowController().is()) + return; + + mpPresenterController->GetSlideShowController()->gotoNextSlide(); +} + +//===== SwitchMonitorCommand ============================================== + +SwitchMonitorCommand::SwitchMonitorCommand ( + const rtl::Reference<PresenterController>& rpPresenterController) + : mpPresenterController(rpPresenterController) +{ +} + +void SwitchMonitorCommand::Execute() +{ + mpPresenterController->SwitchMonitors(); +} + +//===== PauseResumeCommand ============================================== + +PauseResumeCommand::PauseResumeCommand (const rtl::Reference<PresenterController>& rpPresenterController) +: mpPresenterController(rpPresenterController) +{ +} + +void PauseResumeCommand::Execute() +{ + if ( ! mpPresenterController.is()) + return; + + ::rtl::Reference<PresenterWindowManager> pWindowManager ( + mpPresenterController->GetWindowManager()); + if ( ! pWindowManager.is()) + return; + + IPresentationTime* pPresentationTime = mpPresenterController->GetPresentationTime(); + if (!pPresentationTime) + return; + + if(pPresentationTime->isPaused()) + { + pPresentationTime->setPauseStatus(false); + pWindowManager->SetPauseState(false); + } + else + { + pPresentationTime->setPauseStatus(true); + pWindowManager->SetPauseState(true); + } +} + +Any PauseResumeCommand::GetState() const +{ + if ( ! mpPresenterController.is()) + return Any(false); + + ::rtl::Reference<PresenterWindowManager> pWindowManager ( + mpPresenterController->GetWindowManager()); + if ( ! pWindowManager.is()) + return Any(false); + + if (IPresentationTime* pPresentationTime = mpPresenterController->GetPresentationTime()) + { + return Any(pPresentationTime->isPaused()); + } + else + return Any(false); +} + +RestartTimerCommand::RestartTimerCommand (const rtl::Reference<PresenterController>& rpPresenterController) +: mpPresenterController(rpPresenterController) +{ +} + +void RestartTimerCommand::Execute() +{ + if ( ! mpPresenterController.is()) + return; + + ::rtl::Reference<PresenterWindowManager> pWindowManager ( + mpPresenterController->GetWindowManager()); + if ( ! pWindowManager.is()) + return; + + if (IPresentationTime* pPresentationTime = mpPresenterController->GetPresentationTime()) + { + //Resets the pause status and restarts the timer + pPresentationTime->setPauseStatus(false); + pWindowManager->SetPauseState(false); + pPresentationTime->restart(); + } +} + +//===== SetNotesViewCommand =================================================== + +SetNotesViewCommand::SetNotesViewCommand ( + const bool bOn, + const rtl::Reference<PresenterController>& rpPresenterController) + : mbOn(bOn), + mpPresenterController(rpPresenterController) +{ +} + +void SetNotesViewCommand::Execute() +{ + if ( ! mpPresenterController.is()) + return; + + ::rtl::Reference<PresenterWindowManager> pWindowManager ( + mpPresenterController->GetWindowManager()); + if ( ! pWindowManager.is()) + return; + + if (mbOn) + pWindowManager->SetViewMode(PresenterWindowManager::VM_Notes); + else + pWindowManager->SetViewMode(PresenterWindowManager::VM_Standard); +} + +Any SetNotesViewCommand::GetState() const +{ + if ( ! mpPresenterController.is()) + return Any(false); + + ::rtl::Reference<PresenterWindowManager> pWindowManager ( + mpPresenterController->GetWindowManager()); + if ( ! pWindowManager.is()) + return Any(false); + + return Any(pWindowManager->GetViewMode() == PresenterWindowManager::VM_Notes); +} + +//===== SetSlideSorterCommand ================================================= + +SetSlideSorterCommand::SetSlideSorterCommand ( + const bool bOn, + const rtl::Reference<PresenterController>& rpPresenterController) + : mbOn(bOn), + mpPresenterController(rpPresenterController) +{ +} + +void SetSlideSorterCommand::Execute() +{ + if ( ! mpPresenterController.is()) + return; + + ::rtl::Reference<PresenterWindowManager> pWindowManager ( + mpPresenterController->GetWindowManager()); + if ( ! pWindowManager.is()) + return; + + pWindowManager->SetSlideSorterState(mbOn); +} + +Any SetSlideSorterCommand::GetState() const +{ + if ( ! mpPresenterController.is()) + return Any(false); + + ::rtl::Reference<PresenterWindowManager> pWindowManager ( + mpPresenterController->GetWindowManager()); + if ( ! pWindowManager.is()) + return Any(false); + + return Any(pWindowManager->GetViewMode()==PresenterWindowManager::VM_SlideOverview); +} + +//===== SetHelpViewCommand =================================================== + +SetHelpViewCommand::SetHelpViewCommand ( + const bool bOn, + const rtl::Reference<PresenterController>& rpPresenterController) + : mbOn(bOn), + mpPresenterController(rpPresenterController) +{ +} + +void SetHelpViewCommand::Execute() +{ + if ( ! mpPresenterController.is()) + return; + + ::rtl::Reference<PresenterWindowManager> pWindowManager ( + mpPresenterController->GetWindowManager()); + if ( ! pWindowManager.is()) + return; + + pWindowManager->SetHelpViewState(mbOn); +} + +Any SetHelpViewCommand::GetState() const +{ + if ( ! mpPresenterController.is()) + return Any(false); + + ::rtl::Reference<PresenterWindowManager> pWindowManager ( + mpPresenterController->GetWindowManager()); + if ( ! pWindowManager.is()) + return Any(false); + + return Any(pWindowManager->GetViewMode()==PresenterWindowManager::VM_Help); +} + +//===== NotesFontSizeCommand ================================================== + +NotesFontSizeCommand::NotesFontSizeCommand( + const rtl::Reference<PresenterController>& rpPresenterController, + const sal_Int32 nSizeChange) + : mpPresenterController(rpPresenterController), + mnSizeChange(nSizeChange) +{ +} + +::rtl::Reference<PresenterNotesView> NotesFontSizeCommand::GetNotesView() const +{ + if (!mpPresenterController) + return nullptr; + + PresenterPaneContainer::SharedPaneDescriptor pDescriptor ( + mpPresenterController->GetPaneContainer()->FindViewURL( + PresenterViewFactory::msNotesViewURL)); + if (!pDescriptor) + return nullptr; + + return dynamic_cast<PresenterNotesView*>(pDescriptor->mxView.get()); +} + +void NotesFontSizeCommand::Execute() +{ + ::rtl::Reference<PresenterNotesView> pView (GetNotesView()); + if (pView.is()) + pView->ChangeFontSize(mnSizeChange); +} + +Any NotesFontSizeCommand::GetState() const +{ + return Any(); +} + +//===== ExitPresenterCommand ================================================== + +ExitPresenterCommand::ExitPresenterCommand (const rtl::Reference<PresenterController>& rpPresenterController) +: mpPresenterController(rpPresenterController) +{ +} + +void ExitPresenterCommand::Execute() +{ + if ( ! mpPresenterController.is()) + return; + + mpPresenterController->ExitPresenter(); +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterProtocolHandler.hxx b/sdext/source/presenter/PresenterProtocolHandler.hxx new file mode 100644 index 000000000..217fb2658 --- /dev/null +++ b/sdext/source/presenter/PresenterProtocolHandler.hxx @@ -0,0 +1,91 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERPROTOCOLHANDLER_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERPROTOCOLHANDLER_HXX + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> +#include <com/sun/star/frame/XDispatchProvider.hpp> +#include <com/sun/star/frame/XDispatch.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/util/XCacheInfo.hpp> +#include <rtl/ref.hxx> + +namespace sdext::presenter { + +typedef ::cppu::WeakComponentImplHelper < + css::lang::XInitialization, + css::lang::XServiceInfo, + css::util::XCacheInfo, + css::frame::XDispatchProvider +> PresenterProtocolHandlerInterfaceBase; + +class PresenterController; + +class PresenterProtocolHandler + : protected ::cppu::BaseMutex, + public PresenterProtocolHandlerInterfaceBase +{ +public: + PresenterProtocolHandler (); + virtual ~PresenterProtocolHandler() override; + + void SAL_CALL disposing() override; + + // XInitialization + + virtual void SAL_CALL initialize( + const css::uno::Sequence<css::uno::Any>& aArguments) override; + + OUString SAL_CALL getImplementationName() override; + + sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override; + + css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + // XDispatchProvider + + virtual css::uno::Reference<css::frame::XDispatch > SAL_CALL + queryDispatch ( + const css::util::URL& aURL, + const OUString& aTargetFrameName, + sal_Int32 nSearchFlags ) override; + + virtual css::uno::Sequence<css::uno::Reference<css::frame::XDispatch> > SAL_CALL + queryDispatches( + const css::uno::Sequence< css::frame::DispatchDescriptor>& rDescriptors) override; + + /// See XCacheInfo::IsCachingAllowed(). + sal_Bool SAL_CALL isCachingAllowed() override { return false; } + +private: + class Dispatch; + ::rtl::Reference<PresenterController> mpPresenterController; + + /// @throws css::lang::DisposedException + void ThrowIfDisposed() const; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterScreen.cxx b/sdext/source/presenter/PresenterScreen.cxx new file mode 100644 index 000000000..a53f28e86 --- /dev/null +++ b/sdext/source/presenter/PresenterScreen.cxx @@ -0,0 +1,801 @@ +/* -*- 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 "PresenterScreen.hxx" +#include "PresenterConfigurationAccess.hxx" +#include "PresenterController.hxx" +#include "PresenterFrameworkObserver.hxx" +#include "PresenterHelper.hxx" +#include "PresenterPaneContainer.hxx" +#include "PresenterPaneFactory.hxx" +#include "PresenterViewFactory.hxx" +#include "PresenterWindowManager.hxx" +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/drawing/framework/XControllerManager.hpp> +#include <com/sun/star/drawing/framework/ResourceId.hpp> +#include <com/sun/star/drawing/framework/ResourceActivationMode.hpp> +#include <com/sun/star/presentation/XPresentation2.hpp> +#include <com/sun/star/presentation/XPresentationSupplier.hpp> +#include <com/sun/star/document/XEventBroadcaster.hpp> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <vcl/svapp.hxx> +#include <sal/log.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::presentation; +using namespace ::com::sun::star::drawing::framework; + +namespace sdext::presenter { + +namespace { + typedef ::cppu::WeakComponentImplHelper < + css::document::XEventListener + > PresenterScreenListenerInterfaceBase; + + /** One instance of a PresenterScreenListener is registered per Impress + document and waits for the full screen slide show to start and to + end. + */ + class PresenterScreenListener + : private ::cppu::BaseMutex, + public PresenterScreenListenerInterfaceBase + { + public: + PresenterScreenListener ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::frame::XModel2>& rxModel); + PresenterScreenListener(const PresenterScreenListener&) = delete; + PresenterScreenListener& operator=(const PresenterScreenListener&) = delete; + + void Initialize(); + virtual void SAL_CALL disposing() override; + + // document::XEventListener + + virtual void SAL_CALL notifyEvent( const css::document::EventObject& Event ) override; + + // XEventListener + + virtual void SAL_CALL disposing ( const css::lang::EventObject& rEvent) override; + + private: + css::uno::Reference<css::frame::XModel2 > mxModel; + css::uno::Reference<css::uno::XComponentContext> mxComponentContext; + rtl::Reference<PresenterScreen> mpPresenterScreen; + }; +} + +//----- XServiceInfo --------------------------------------------------------------- + +Sequence< OUString > SAL_CALL PresenterScreenJob::getSupportedServiceNames() +{ + return { }; +} + +OUString SAL_CALL PresenterScreenJob::getImplementationName() +{ + return "org.libreoffice.comp.PresenterScreenJob"; +} + +sal_Bool SAL_CALL PresenterScreenJob::supportsService(const OUString& aServiceName) +{ + return cppu::supportsService(this, aServiceName); +} + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +sdext_PresenterScreenJob_get_implementation( + css::uno::XComponentContext* context , css::uno::Sequence<css::uno::Any> const&) +{ + return cppu::acquire(new PresenterScreenJob(context)); +} + + +//===== PresenterScreenJob ==================================================== + +PresenterScreenJob::PresenterScreenJob (const Reference<XComponentContext>& rxContext) + : PresenterScreenJobInterfaceBase(m_aMutex), + mxComponentContext(rxContext) +{ +} + +PresenterScreenJob::~PresenterScreenJob() +{ +} + +void SAL_CALL PresenterScreenJob::disposing() +{ + mxComponentContext = nullptr; +} + +//----- XJob ----------------------------------------------------------- + +Any SAL_CALL PresenterScreenJob::execute( + const Sequence< beans::NamedValue >& Arguments ) +{ + Sequence< beans::NamedValue > lEnv; + auto pArg = std::find_if(Arguments.begin(), Arguments.end(), + [](const beans::NamedValue& rArg) { return rArg.Name == "Environment"; }); + if (pArg != Arguments.end()) + pArg->Value >>= lEnv; + + Reference<frame::XModel2> xModel; + auto pProp = std::find_if(std::cbegin(lEnv), std::cend(lEnv), + [](const beans::NamedValue& rProp) { return rProp.Name == "Model"; }); + if (pProp != std::cend(lEnv)) + pProp->Value >>= xModel; + + Reference< XServiceInfo > xInfo( xModel, UNO_QUERY ); + if( xInfo.is() && xInfo->supportsService("com.sun.star.presentation.PresentationDocument") ) + { + // Create a new listener that waits for the full screen presentation + // to start and to end. It takes care of its own lifetime. + ::rtl::Reference<PresenterScreenListener> pListener ( + new PresenterScreenListener(mxComponentContext, xModel)); + pListener->Initialize(); + } + + return Any(); +} + +//===== PresenterScreenListener =============================================== + +namespace { + +PresenterScreenListener::PresenterScreenListener ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::frame::XModel2>& rxModel) + : PresenterScreenListenerInterfaceBase(m_aMutex), + mxModel(rxModel), + mxComponentContext(rxContext) +{ +} + +void PresenterScreenListener::Initialize() +{ + Reference< document::XEventListener > xDocListener(this); + Reference< document::XEventBroadcaster > xDocBroadcaster( mxModel, UNO_QUERY ); + if( xDocBroadcaster.is() ) + xDocBroadcaster->addEventListener(xDocListener); +} + +void SAL_CALL PresenterScreenListener::disposing() +{ + Reference< document::XEventBroadcaster > xDocBroadcaster( mxModel, UNO_QUERY ); + if( xDocBroadcaster.is() ) + xDocBroadcaster->removeEventListener( + Reference<document::XEventListener>(this) ); + + if (mpPresenterScreen.is()) + { + mpPresenterScreen->RequestShutdownPresenterScreen(); + mpPresenterScreen = nullptr; + } +} + +// document::XEventListener + +void SAL_CALL PresenterScreenListener::notifyEvent( const css::document::EventObject& Event ) +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ( + "PresenterScreenListener object has already been disposed", + static_cast<uno::XWeak*>(this)); + } + + if ( Event.EventName == "OnStartPresentation" ) + { + mpPresenterScreen = new PresenterScreen(mxComponentContext, mxModel); + if(PresenterScreen::isPresenterScreenEnabled(mxComponentContext)) + mpPresenterScreen->InitializePresenterScreen(); + } + else if ( Event.EventName == "OnEndPresentation" ) + { + if (mpPresenterScreen.is()) + { + mpPresenterScreen->RequestShutdownPresenterScreen(); + mpPresenterScreen = nullptr; + } + } +} + +// XEventListener + +void SAL_CALL PresenterScreenListener::disposing (const css::lang::EventObject&) +{ + if (mpPresenterScreen.is()) + { + mpPresenterScreen->RequestShutdownPresenterScreen(); + mpPresenterScreen = nullptr; + } +} + +} // end of anonymous namespace + +//===== PresenterScreen ======================================================= + +PresenterScreen::PresenterScreen ( + const Reference<XComponentContext>& rxContext, + const css::uno::Reference<css::frame::XModel2>& rxModel) + : PresenterScreenInterfaceBase(m_aMutex), + mxModel(rxModel), + mxContextWeak(rxContext) +{ +} + +PresenterScreen::~PresenterScreen() +{ +} + +bool PresenterScreen::isPresenterScreenEnabled(const css::uno::Reference<css::uno::XComponentContext>& rxContext) +{ + bool dEnablePresenterScreen=true; + PresenterConfigurationAccess aConfiguration ( + rxContext, + "/org.openoffice.Office.Impress/", + PresenterConfigurationAccess::READ_ONLY); + aConfiguration.GetConfigurationNode("Misc/Start/EnablePresenterScreen") + >>= dEnablePresenterScreen; + return dEnablePresenterScreen; +} +void SAL_CALL PresenterScreen::disposing() +{ + Reference<XConfigurationController> xCC (mxConfigurationControllerWeak); + if (xCC.is() && mxSavedConfiguration.is()) + { + xCC->restoreConfiguration(mxSavedConfiguration); + } + mxConfigurationControllerWeak = Reference<XConfigurationController>(nullptr); + + Reference<lang::XComponent> xViewFactoryComponent (mxViewFactory, UNO_QUERY); + if (xViewFactoryComponent.is()) + xViewFactoryComponent->dispose(); + Reference<lang::XComponent> xPaneFactoryComponent (mxPaneFactory, UNO_QUERY); + if (xPaneFactoryComponent.is()) + xPaneFactoryComponent->dispose(); + + mxModel = nullptr; +} + +//----- XEventListener -------------------------------------------------------- + +void SAL_CALL PresenterScreen::disposing (const lang::EventObject& /*rEvent*/) +{ + RequestShutdownPresenterScreen(); +} + + +void PresenterScreen::InitializePresenterScreen() +{ + try + { + Reference<XComponentContext> xContext (mxContextWeak); + mpPaneContainer = new PresenterPaneContainer(xContext); + + Reference<XPresentationSupplier> xPS ( mxModel, UNO_QUERY_THROW); + Reference<XPresentation2> xPresentation(xPS->getPresentation(), UNO_QUERY_THROW); + Reference<presentation::XSlideShowController> xSlideShowController( xPresentation->getController() ); + + if( !xSlideShowController.is() || !xSlideShowController->isFullScreen() ) + return; + + // find first controller that is not the current controller (the one with the slideshow + mxController = mxModel->getCurrentController(); + Reference< container::XEnumeration > xEnum( mxModel->getControllers() ); + if( xEnum.is() ) + { + while( xEnum->hasMoreElements() ) + { + Reference< frame::XController > xC( xEnum->nextElement(), UNO_QUERY ); + if( xC.is() && (xC != mxController) ) + { + mxController = xC; + break; + } + } + } + // Get the XController from the first argument. + Reference<XControllerManager> xCM(mxController, UNO_QUERY_THROW); + + Reference<XConfigurationController> xCC( xCM->getConfigurationController()); + mxConfigurationControllerWeak = xCC; + + Reference<drawing::framework::XResourceId> xMainPaneId( + GetMainPaneId(xPresentation)); + // An empty reference means that the presenter screen can + // not or must not be displayed. + if ( ! xMainPaneId.is()) + return; + + if (xCC.is() && xContext.is()) + { + // Store the current configuration so that we can restore it when + // the presenter view is deactivated. + mxSavedConfiguration = xCC->getRequestedConfiguration(); + xCC->lock(); + + try + { + // At the moment the presenter controller is displayed in its + // own full screen window that is controlled by the same + // configuration controller as the Impress document from + // which the presentation was started. Therefore the main + // pane is activated additionally to the already existing + // panes and does not replace them. + xCC->requestResourceActivation( + xMainPaneId, + ResourceActivationMode_ADD); + SetupConfiguration(xContext, xMainPaneId); + + mpPresenterController = new PresenterController( + css::uno::WeakReference<css::lang::XEventListener>(this), + xContext, + mxController, + xSlideShowController, + mpPaneContainer, + xMainPaneId); + + // Create pane and view factories and integrate them into the + // drawing framework. + SetupPaneFactory(xContext); + SetupViewFactory(xContext); + + mpPresenterController->GetWindowManager()->RestoreViewMode(); + } + catch (const RuntimeException&) + { + xCC->restoreConfiguration(mxSavedConfiguration); + } + xCC->unlock(); + } + } + catch (const Exception&) + { + } +} + +void PresenterScreen::SwitchMonitors() +{ + try { + Reference<XPresentationSupplier> xPS ( mxModel, UNO_QUERY_THROW); + Reference<XPresentation2> xPresentation(xPS->getPresentation(), UNO_QUERY_THROW); + + // Get the existing presenter console screen, we want to switch the + // presentation to use that instead. + sal_Int32 nNewScreen = GetPresenterScreenNumber (xPresentation); + if (nNewScreen < 0) + return; + + // Adapt that display number to be the 'default' setting of 0 if it matches + sal_Int32 nExternalDisplay = Application::GetDisplayExternalScreen(); + + if (nNewScreen == nExternalDisplay) + nNewScreen = 0; // screen zero is best == the primary display + else + nNewScreen++; // otherwise we store screens offset by one. + + // Set the new presentation display + Reference<beans::XPropertySet> xProperties (xPresentation, UNO_QUERY_THROW); + xProperties->setPropertyValue("Display", Any(nNewScreen)); + } catch (const uno::Exception &) { + } +} + +/** + * Return the real VCL screen number to show the presenter console + * on or -1 to not show anything. + */ +sal_Int32 PresenterScreen::GetPresenterScreenNumber ( + const Reference<presentation::XPresentation2>& rxPresentation) const +{ + sal_Int32 nScreenNumber (0); + try + { + if ( ! rxPresentation.is()) + return -1; + + // Determine the screen on which the full screen presentation is being + // displayed. + sal_Int32 nDisplayNumber (-1); + if ( ! (rxPresentation->getPropertyValue("Display") >>= nDisplayNumber)) + return -1; + if (nDisplayNumber == -1) + { + // The special value -1 indicates that the slide show + // spans all available displays. That leaves no room for + // the presenter screen. + return -1; + } + + SAL_INFO("sdext.presenter", "Display number is " << nDisplayNumber); + + if (nDisplayNumber > 0) + { + nScreenNumber = nDisplayNumber - 1; + } + else if (nDisplayNumber == 0) + { + // A display number value of 0 indicates the primary screen. + // Find out which screen number that is. + nScreenNumber = Application::GetDisplayExternalScreen(); + } + + // We still have to determine the number of screens to decide + // whether the presenter screen may be shown at all. + sal_Int32 nScreenCount = Application::GetScreenCount(); + + if (nScreenCount < 2 || nDisplayNumber > nScreenCount) + { + // There is either only one screen or the full screen + // presentation spans all available screens. The presenter + // screen is shown only when a special flag in the configuration + // is set. + Reference<XComponentContext> xContext (mxContextWeak); + PresenterConfigurationAccess aConfiguration ( + xContext, + "/org.openoffice.Office.PresenterScreen/", + PresenterConfigurationAccess::READ_ONLY); + bool bStartAlways (false); + if (aConfiguration.GetConfigurationNode( + "Presenter/StartAlways") >>= bStartAlways) + { + if (bStartAlways) + return GetPresenterScreenFromScreen(nScreenNumber); + } + return -1; + } + } + catch (const beans::UnknownPropertyException&) + { + OSL_ASSERT(false); + // For some reason we can not access the screen number. Use + // the default instead. + } + SAL_INFO("sdext.presenter", "Get presenter screen for screen " << nScreenNumber); + return GetPresenterScreenFromScreen(nScreenNumber); +} + +sal_Int32 PresenterScreen::GetPresenterScreenFromScreen( sal_Int32 nPresentationScreen ) +{ + // Setup the resource id of the full screen background pane so that + // it is displayed on another screen than the presentation. + sal_Int32 nPresenterScreenNumber (1); + switch (nPresentationScreen) + { + case 0: + nPresenterScreenNumber = 1; + break; + + case 1: + nPresenterScreenNumber = 0; + break; + + default: + SAL_INFO("sdext.presenter", "Warning unexpected, out of bound screen " + "mapped to 0" << nPresentationScreen); + // When the full screen presentation is displayed on a screen + // other than 0 or 1 then place the presenter on the first + // available screen. + nPresenterScreenNumber = 0; + break; + } + return nPresenterScreenNumber; +} + +Reference<drawing::framework::XResourceId> PresenterScreen::GetMainPaneId ( + const Reference<presentation::XPresentation2>& rxPresentation) const +{ + // A negative value means that the presentation spans all available + // displays. That leaves no room for the presenter. + const sal_Int32 nScreen(GetPresenterScreenNumber(rxPresentation)); + if (nScreen < 0) + return nullptr; + + return ResourceId::create( + Reference<XComponentContext>(mxContextWeak), + PresenterHelper::msFullScreenPaneURL + + "?FullScreen=true&ScreenNumber=" + + OUString::number(nScreen)); +} + +void PresenterScreen::RequestShutdownPresenterScreen() +{ + // Restore the configuration that was active before the presenter screen + // has been activated. Now, that the presenter screen is displayed in + // its own top level window this probably not necessary, but one never knows. + Reference<XConfigurationController> xCC (mxConfigurationControllerWeak); + if (xCC.is() && mxSavedConfiguration.is()) + { + xCC->restoreConfiguration(mxSavedConfiguration); + mxSavedConfiguration = nullptr; + } + + if (xCC.is()) + { + // The actual restoration of the configuration takes place + // asynchronously. The view and pane factories can only by disposed + // after that. Therefore, set up a listener and wait for the + // restoration. + rtl::Reference<PresenterScreen> pSelf (this); + PresenterFrameworkObserver::RunOnUpdateEnd( + xCC, + [pSelf](bool){ return pSelf->ShutdownPresenterScreen(); }); + xCC->update(); + } +} + +void PresenterScreen::ShutdownPresenterScreen() +{ + Reference<lang::XComponent> xViewFactoryComponent (mxViewFactory, UNO_QUERY); + if (xViewFactoryComponent.is()) + xViewFactoryComponent->dispose(); + mxViewFactory = nullptr; + + Reference<lang::XComponent> xPaneFactoryComponent (mxPaneFactory, UNO_QUERY); + if (xPaneFactoryComponent.is()) + xPaneFactoryComponent->dispose(); + mxPaneFactory = nullptr; + + if (mpPresenterController) + { + mpPresenterController->dispose(); + mpPresenterController.clear(); + } + mpPaneContainer = new PresenterPaneContainer(Reference<XComponentContext>(mxContextWeak)); +} + +void PresenterScreen::SetupPaneFactory (const Reference<XComponentContext>& rxContext) +{ + try + { + if ( ! mxPaneFactory.is()) + mxPaneFactory = PresenterPaneFactory::Create( + rxContext, + mxController, + mpPresenterController); + } + catch (const RuntimeException&) + { + OSL_ASSERT(false); + } +} + +void PresenterScreen::SetupViewFactory (const Reference<XComponentContext>& rxContext) +{ + try + { + if ( ! mxViewFactory.is()) + mxViewFactory = PresenterViewFactory::Create( + rxContext, + mxController, + mpPresenterController); + } + catch (const RuntimeException&) + { + OSL_ASSERT(false); + } +} + +void PresenterScreen::SetupConfiguration ( + const Reference<XComponentContext>& rxContext, + const Reference<XResourceId>& rxAnchorId) +{ + try + { + PresenterConfigurationAccess aConfiguration ( + rxContext, + "org.openoffice.Office.PresenterScreen", + PresenterConfigurationAccess::READ_ONLY); + maViewDescriptors.clear(); + ProcessViewDescriptions(aConfiguration); + OUString sLayoutName ("DefaultLayout"); + aConfiguration.GetConfigurationNode( + "Presenter/CurrentLayout") >>= sLayoutName; + ProcessLayout(aConfiguration, sLayoutName, rxContext, rxAnchorId); + } + catch (const RuntimeException&) + { + } +} + +void PresenterScreen::ProcessLayout ( + PresenterConfigurationAccess& rConfiguration, + std::u16string_view rsLayoutName, + const Reference<XComponentContext>& rxContext, + const Reference<XResourceId>& rxAnchorId) +{ + try + { + Reference<container::XHierarchicalNameAccess> xLayoutNode ( + rConfiguration.GetConfigurationNode( + OUString::Concat("Presenter/Layouts/")+rsLayoutName), + UNO_QUERY_THROW); + + // Read the parent layout first, if one is referenced. + OUString sParentLayout; + PresenterConfigurationAccess::GetConfigurationNode( + xLayoutNode, + "ParentLayout") >>= sParentLayout; + if (!sParentLayout.isEmpty()) + { + // Prevent infinite recursion. + if (rsLayoutName != sParentLayout) + ProcessLayout(rConfiguration, sParentLayout, rxContext, rxAnchorId); + } + + // Process the actual layout list. + Reference<container::XNameAccess> xList ( + PresenterConfigurationAccess::GetConfigurationNode( + xLayoutNode, + "Layout"), + UNO_QUERY_THROW); + + ::std::vector<OUString> aProperties + { + "PaneURL", + "ViewURL", + "RelativeX", + "RelativeY", + "RelativeWidth", + "RelativeHeight" + }; + PresenterConfigurationAccess::ForAll( + xList, + aProperties, + [this, rxContext, rxAnchorId](std::vector<uno::Any> const& rArgs) + { + this->ProcessComponent(rArgs, rxContext, rxAnchorId); + }); + } + catch (const RuntimeException&) + { + } +} + +void PresenterScreen::ProcessViewDescriptions ( + PresenterConfigurationAccess& rConfiguration) +{ + try + { + Reference<container::XNameAccess> xViewDescriptionsNode ( + rConfiguration.GetConfigurationNode("Presenter/Views"), + UNO_QUERY_THROW); + + ::std::vector<OUString> aProperties + { + "ViewURL", + "Title", + "AccessibleTitle", + "IsOpaque" + }; + PresenterConfigurationAccess::ForAll( + xViewDescriptionsNode, + aProperties, + [this](std::vector<uno::Any> const& rArgs) + { + return this->ProcessViewDescription(rArgs); + }); + } + catch (const RuntimeException&) + { + OSL_ASSERT(false); + } +} + +void PresenterScreen::ProcessComponent ( + const ::std::vector<Any>& rValues, + const Reference<XComponentContext>& rxContext, + const Reference<XResourceId>& rxAnchorId) +{ + if (rValues.size() != 6) + return; + + try + { + OUString sPaneURL; + OUString sViewURL; + double nX = 0; + double nY = 0; + double nWidth = 0; + double nHeight = 0; + rValues[0] >>= sPaneURL; + rValues[1] >>= sViewURL; + rValues[2] >>= nX; + rValues[3] >>= nY; + rValues[4] >>= nWidth; + rValues[5] >>= nHeight; + + if (nX>=0 && nY>=0 && nWidth>0 && nHeight>0) + { + SetupView( + rxContext, + rxAnchorId, + sPaneURL, + sViewURL, + PresenterPaneContainer::ViewInitializationFunction()); + } + } + catch (const Exception&) + { + OSL_ASSERT(false); + } +} + +void PresenterScreen::ProcessViewDescription ( + const ::std::vector<Any>& rValues) +{ + if (rValues.size() != 4) + return; + + try + { + ViewDescriptor aViewDescriptor; + OUString sViewURL; + rValues[0] >>= sViewURL; + rValues[1] >>= aViewDescriptor.msTitle; + rValues[2] >>= aViewDescriptor.msAccessibleTitle; + rValues[3] >>= aViewDescriptor.mbIsOpaque; + if (aViewDescriptor.msAccessibleTitle.isEmpty()) + aViewDescriptor.msAccessibleTitle = aViewDescriptor.msTitle; + maViewDescriptors[sViewURL] = aViewDescriptor; + } + catch (const Exception&) + { + OSL_ASSERT(false); + } +} + +void PresenterScreen::SetupView( + const Reference<XComponentContext>& rxContext, + const Reference<XResourceId>& rxAnchorId, + const OUString& rsPaneURL, + const OUString& rsViewURL, + const PresenterPaneContainer::ViewInitializationFunction& rViewInitialization) +{ + Reference<XConfigurationController> xCC (mxConfigurationControllerWeak); + if (!xCC.is()) + return; + + Reference<XResourceId> xPaneId (ResourceId::createWithAnchor(rxContext,rsPaneURL,rxAnchorId)); + // Look up the view descriptor. + ViewDescriptor aViewDescriptor; + ViewDescriptorContainer::const_iterator iDescriptor (maViewDescriptors.find(rsViewURL)); + if (iDescriptor != maViewDescriptors.end()) + aViewDescriptor = iDescriptor->second; + + // Prepare the pane. + OSL_ASSERT(mpPaneContainer); + mpPaneContainer->PreparePane( + xPaneId, + rsViewURL, + aViewDescriptor.msTitle, + aViewDescriptor.msAccessibleTitle, + aViewDescriptor.mbIsOpaque, + rViewInitialization); +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterScreen.hxx b/sdext/source/presenter/PresenterScreen.hxx new file mode 100644 index 000000000..e696c0dc6 --- /dev/null +++ b/sdext/source/presenter/PresenterScreen.hxx @@ -0,0 +1,227 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERSCREEN_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERSCREEN_HXX + +#include "PresenterConfigurationAccess.hxx" +#include "PresenterPaneContainer.hxx" +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/frame/XModel2.hpp> +#include <com/sun/star/task/XJob.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/drawing/framework/XConfigurationController.hpp> +#include <com/sun/star/presentation/XPresentation2.hpp> +#include <rtl/ref.hxx> + +#include <map> +#include <string_view> + +namespace sdext::presenter { + +class PresenterController; + +typedef ::cppu::WeakComponentImplHelper < + css::task::XJob, css::lang::XServiceInfo + > PresenterScreenJobInterfaceBase; + +/** The PresenterScreenJob service is instantiated every time a document is + created or loaded. In its execute() method it then filters out all + non-Impress documents and creates and registers a new PresenterScreen + object. +*/ +class PresenterScreenJob + : private ::cppu::BaseMutex, + public PresenterScreenJobInterfaceBase +{ +public: + PresenterScreenJob(const PresenterScreenJob&) = delete; + PresenterScreenJob& operator=(const PresenterScreenJob&) = delete; + + virtual void SAL_CALL disposing() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString& ServiceName) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames () override; + + // XJob + virtual css::uno::Any SAL_CALL execute( + const css::uno::Sequence<css::beans::NamedValue >& Arguments) override; + + explicit PresenterScreenJob (const css::uno::Reference<css::uno::XComponentContext>& rxContext); + virtual ~PresenterScreenJob() override; + +private: + css::uno::Reference<css::uno::XComponentContext> mxComponentContext; +}; + +/** This is the bootstrap class of the presenter screen. It is registered + as drawing framework startup service. That means that every drawing + framework instance creates an instance of this class. + + <p>A PresenterScreen object registers itself as listener for drawing + framework configuration changes. It waits for the full screen marker (a + top level resource) to appear in the current configuration. When that + happens the actual presenter screen is initialized. A new + PresenterController is created and takes over the task of controlling + the presenter screen.</p> +*/ +typedef ::cppu::WeakComponentImplHelper < + css::lang::XEventListener + > PresenterScreenInterfaceBase; +class PresenterScreen + : private ::cppu::BaseMutex, + public PresenterScreenInterfaceBase +{ +public: + PresenterScreen ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::frame::XModel2>& rxModel); + virtual ~PresenterScreen() override; + PresenterScreen(const PresenterScreen&) = delete; + PresenterScreen& operator=(const PresenterScreen&) = delete; + + virtual void SAL_CALL disposing() override; + + static bool isPresenterScreenEnabled( + const css::uno::Reference<css::uno::XComponentContext>& rxContext); + /** Make the presenter screen visible. + */ + void InitializePresenterScreen(); + + /** Do not call ShutdownPresenterScreen() directly. Call + RequestShutdownPresenterScreen() instead. It will issue an + asynchronous call to ShutdownPresenterScreen() when that is safe. + */ + void RequestShutdownPresenterScreen(); + + /** Switch / converse monitors between presenter view and slide output + */ + void SwitchMonitors(); + + // XEventListener + + virtual void SAL_CALL disposing ( const css::lang::EventObject& rEvent) override; + +private: + css::uno::Reference<css::frame::XModel2 > mxModel; + css::uno::Reference<css::frame::XController> mxController; + css::uno::WeakReference<css::drawing::framework::XConfigurationController> + mxConfigurationControllerWeak; + css::uno::WeakReference<css::uno::XComponentContext> mxContextWeak; + ::rtl::Reference<PresenterController> mpPresenterController; + css::uno::Reference<css::drawing::framework::XConfiguration> mxSavedConfiguration; + ::rtl::Reference<PresenterPaneContainer> mpPaneContainer; + css::uno::Reference<css::drawing::framework::XResourceFactory> mxPaneFactory; + css::uno::Reference<css::drawing::framework::XResourceFactory> mxViewFactory; + + class ViewDescriptor + { + public: + OUString msTitle; + OUString msAccessibleTitle; + bool mbIsOpaque; + ViewDescriptor() + : mbIsOpaque(false) + { + } + }; + typedef ::std::map<OUString,ViewDescriptor> ViewDescriptorContainer; + ViewDescriptorContainer maViewDescriptors; + + void ShutdownPresenterScreen(); + + /** Create and initialize the factory for presenter view specific panes. + */ + void SetupPaneFactory ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext); + + /** Create and initialize the factory for presenter view specific views. + */ + void SetupViewFactory ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext); + + /** Read the current layout from the configuration and call + ProcessLayout to bring it on to the screen. + */ + void SetupConfiguration ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::drawing::framework::XResourceId>& rxAnchorId); + + /** Read one layout from the configuration and make resource activation + requests to bring it on to the screen. When one layout references a + parent layout then this method calls itself recursively. + */ + void ProcessLayout ( + PresenterConfigurationAccess& rConfiguration, + std::u16string_view rsLayoutName, + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::drawing::framework::XResourceId>& rxAnchorId); + + /** Called by ProcessLayout for a single entry of a Layouts + configuration list. + */ + void ProcessComponent ( + const ::std::vector<css::uno::Any>& rValues, + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::drawing::framework::XResourceId>& rxAnchorId); + + /** Read the view descriptions from the configuration. + */ + void ProcessViewDescriptions ( + PresenterConfigurationAccess& rConfiguration); + + /** Called by ProcessViewDescriptions for a single entry. + */ + void ProcessViewDescription ( + const ::std::vector<css::uno::Any>& rValues); + + void SetupView ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::drawing::framework::XResourceId>& rxAnchorId, + const OUString& rsPaneURL, + const OUString& rsViewURL, + const PresenterPaneContainer::ViewInitializationFunction& rViewInitialization); + + /** Return the built-in screen number on the presentation will normally + display the presenter console. + @return + Returns -1 when the presenter screen can or shall not be + displayed. + */ + sal_Int32 GetPresenterScreenNumber ( + const css::uno::Reference<css::presentation::XPresentation2>& rxPresentation) const; + + static sal_Int32 GetPresenterScreenFromScreen( sal_Int32 nPresentationScreen ); + + /** Create a resource id for the full screen background pane so that it + is displayed on another screen than the full screen presentation. + */ + css::uno::Reference<css::drawing::framework::XResourceId> GetMainPaneId ( + const css::uno::Reference<css::presentation::XPresentation2>& rxPresentation) const; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterScrollBar.cxx b/sdext/source/presenter/PresenterScrollBar.cxx new file mode 100644 index 000000000..a4db80277 --- /dev/null +++ b/sdext/source/presenter/PresenterScrollBar.cxx @@ -0,0 +1,824 @@ +/* -*- 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 "PresenterScrollBar.hxx" +#include "PresenterBitmapContainer.hxx" +#include "PresenterCanvasHelper.hxx" +#include "PresenterGeometryHelper.hxx" +#include "PresenterPaintManager.hxx" +#include "PresenterTimer.hxx" +#include "PresenterUIPainter.hxx" +#include <com/sun/star/awt/PosSize.hpp> +#include <com/sun/star/awt/XWindowPeer.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/XPolyPolygon2D.hpp> + +#include <algorithm> +#include <memory> +#include <math.h> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +const double gnScrollBarGap (10); + +namespace sdext::presenter { + +//===== PresenterScrollBar::MousePressRepeater ================================ + +class PresenterScrollBar::MousePressRepeater + : public std::enable_shared_from_this<MousePressRepeater> +{ +public: + explicit MousePressRepeater (const ::rtl::Reference<PresenterScrollBar>& rpScrollBar); + void Dispose(); + void Start (const PresenterScrollBar::Area& reArea); + void Stop(); + void SetMouseArea (const PresenterScrollBar::Area& reArea); + +private: + void Callback (); + void Execute(); + + sal_Int32 mnMousePressRepeaterTaskId; + ::rtl::Reference<PresenterScrollBar> mpScrollBar; + PresenterScrollBar::Area meMouseArea; +}; + +//===== PresenterScrollBar ==================================================== + +std::weak_ptr<PresenterBitmapContainer> PresenterScrollBar::mpSharedBitmaps; + +PresenterScrollBar::PresenterScrollBar ( + const Reference<XComponentContext>& rxComponentContext, + const Reference<awt::XWindow>& rxParentWindow, + const std::shared_ptr<PresenterPaintManager>& rpPaintManager, + const ::std::function<void (double)>& rThumbMotionListener) + : PresenterScrollBarInterfaceBase(m_aMutex), + mxComponentContext(rxComponentContext), + mpPaintManager(rpPaintManager), + mnThumbPosition(0), + mnTotalSize(0), + mnThumbSize(0), + mnLineHeight(10), + maDragAnchor(-1,-1), + maThumbMotionListener(rThumbMotionListener), + meButtonDownArea(None), + meMouseMoveArea(None), + mbIsNotificationActive(false), + mpMousePressRepeater(std::make_shared<MousePressRepeater>(this)), + mpCanvasHelper(new PresenterCanvasHelper()) +{ + try + { + Reference<lang::XMultiComponentFactory> xFactory (rxComponentContext->getServiceManager()); + if ( ! xFactory.is()) + throw RuntimeException(); + + mxPresenterHelper.set( + xFactory->createInstanceWithContext( + "com.sun.star.comp.Draw.PresenterHelper", + rxComponentContext), + UNO_QUERY_THROW); + + if (mxPresenterHelper.is()) + mxWindow = mxPresenterHelper->createWindow(rxParentWindow, + false, + false, + false, + false); + + // Make the background transparent. The slide show paints its own background. + Reference<awt::XWindowPeer> xPeer (mxWindow, UNO_QUERY_THROW); + xPeer->setBackground(0xff000000); + + mxWindow->setVisible(true); + mxWindow->addWindowListener(this); + mxWindow->addPaintListener(this); + mxWindow->addMouseListener(this); + mxWindow->addMouseMotionListener(this); + } + catch (RuntimeException&) + { + } +} + +PresenterScrollBar::~PresenterScrollBar() +{ +} + +void SAL_CALL PresenterScrollBar::disposing() +{ + mpMousePressRepeater->Dispose(); + + if (mxWindow.is()) + { + mxWindow->removeWindowListener(this); + mxWindow->removePaintListener(this); + mxWindow->removeMouseListener(this); + mxWindow->removeMouseMotionListener(this); + + Reference<lang::XComponent> xComponent = mxWindow; + mxWindow = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + + mpBitmaps.reset(); +} + +void PresenterScrollBar::SetVisible (const bool bIsVisible) +{ + if (mxWindow.is()) + mxWindow->setVisible(bIsVisible); +} + +void PresenterScrollBar::SetPosSize (const css::geometry::RealRectangle2D& rBox) +{ + if (mxWindow.is()) + { + mxWindow->setPosSize( + sal_Int32(floor(rBox.X1)), + sal_Int32(ceil(rBox.Y1)), + sal_Int32(ceil(rBox.X2-rBox.X1)), + sal_Int32(floor(rBox.Y2-rBox.Y1)), + awt::PosSize::POSSIZE); + UpdateBorders(); + } +} + +void PresenterScrollBar::SetThumbPosition ( + double nPosition, + const bool bAsynchronousUpdate) +{ + nPosition = ValidateThumbPosition(nPosition); + + if (nPosition == mnThumbPosition || mbIsNotificationActive) + return; + + mnThumbPosition = nPosition; + + UpdateBorders(); + Repaint(GetRectangle(Total), bAsynchronousUpdate); + + mbIsNotificationActive = true; + try + { + maThumbMotionListener(mnThumbPosition); + } + catch (Exception&) + { + } + mbIsNotificationActive = false; +} + + +void PresenterScrollBar::SetTotalSize (const double nTotalSize) +{ + if (mnTotalSize != nTotalSize) + { + mnTotalSize = nTotalSize + 1; + UpdateBorders(); + Repaint(GetRectangle(Total), false); + } +} + +void PresenterScrollBar::SetThumbSize (const double nThumbSize) +{ + OSL_ASSERT(nThumbSize>=0); + if (mnThumbSize != nThumbSize) + { + mnThumbSize = nThumbSize; + UpdateBorders(); + Repaint(GetRectangle(Total), false); + } +} + + +void PresenterScrollBar::SetLineHeight (const double nLineHeight) +{ + mnLineHeight = nLineHeight; +} + + +void PresenterScrollBar::SetCanvas (const Reference<css::rendering::XCanvas>& rxCanvas) +{ + if (mxCanvas == rxCanvas) + return; + + mxCanvas = rxCanvas; + if (!mxCanvas.is()) + return; + + if (mpBitmaps == nullptr) + { + mpBitmaps = mpSharedBitmaps.lock(); + if (!mpBitmaps) + { + try + { + mpBitmaps = std::make_shared<PresenterBitmapContainer>( + "PresenterScreenSettings/ScrollBar/Bitmaps", + std::shared_ptr<PresenterBitmapContainer>(), + mxComponentContext, + mxCanvas); + mpSharedBitmaps = mpBitmaps; + } + catch(Exception&) + { + OSL_ASSERT(false); + } + } + UpdateBitmaps(); + UpdateBorders(); + } + + Repaint(GetRectangle(Total), false); +} + +void PresenterScrollBar::SetBackground (const SharedBitmapDescriptor& rpBackgroundBitmap) +{ + mpBackgroundBitmap = rpBackgroundBitmap; +} + +void PresenterScrollBar::CheckValues() +{ + mnThumbPosition = ValidateThumbPosition(mnThumbPosition); +} + +double PresenterScrollBar::ValidateThumbPosition (double nPosition) +{ + if (nPosition + mnThumbSize > mnTotalSize) + nPosition = mnTotalSize - mnThumbSize; + if (nPosition < 0) + nPosition = 0; + return nPosition; +} + +void PresenterScrollBar::Paint ( + const awt::Rectangle& rUpdateBox) +{ + if ( ! mxCanvas.is() || ! mxWindow.is()) + { + OSL_ASSERT(mxCanvas.is()); + OSL_ASSERT(mxWindow.is()); + return; + } + + if (PresenterGeometryHelper::AreRectanglesDisjoint (rUpdateBox, mxWindow->getPosSize())) + return; + + PaintBackground(rUpdateBox); + PaintComposite(rUpdateBox, PagerUp, + mpPagerStartDescriptor, mpPagerCenterDescriptor, SharedBitmapDescriptor()); + PaintComposite(rUpdateBox, PagerDown, + SharedBitmapDescriptor(), mpPagerCenterDescriptor, mpPagerEndDescriptor); + PaintComposite(rUpdateBox, Thumb, + mpThumbStartDescriptor, mpThumbCenterDescriptor, mpThumbEndDescriptor); + PaintBitmap(rUpdateBox, PrevButton, mpPrevButtonDescriptor); + PaintBitmap(rUpdateBox, NextButton, mpNextButtonDescriptor); + + Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY); + if (xSpriteCanvas.is()) + xSpriteCanvas->updateScreen(false); +} + +//----- XWindowListener ------------------------------------------------------- + +void SAL_CALL PresenterScrollBar::windowResized (const css::awt::WindowEvent&) {} + +void SAL_CALL PresenterScrollBar::windowMoved (const css::awt::WindowEvent&) {} + +void SAL_CALL PresenterScrollBar::windowShown (const css::lang::EventObject&) {} + +void SAL_CALL PresenterScrollBar::windowHidden (const css::lang::EventObject&) {} + +//----- XPaintListener -------------------------------------------------------- + +void SAL_CALL PresenterScrollBar::windowPaint (const css::awt::PaintEvent& rEvent) +{ + if (mxWindow.is()) + { + awt::Rectangle aRepaintBox (rEvent.UpdateRect); + const awt::Rectangle aWindowBox (mxWindow->getPosSize()); + aRepaintBox.X += aWindowBox.X; + aRepaintBox.Y += aWindowBox.Y; + Paint(aRepaintBox); + + Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY); + if (xSpriteCanvas.is()) + xSpriteCanvas->updateScreen(false); + } +} + +//----- XMouseListener -------------------------------------------------------- + +void SAL_CALL PresenterScrollBar::mousePressed (const css::awt::MouseEvent& rEvent) +{ + maDragAnchor.X = rEvent.X; + maDragAnchor.Y = rEvent.Y; + meButtonDownArea = GetArea(rEvent.X, rEvent.Y); + + mpMousePressRepeater->Start(meButtonDownArea); +} + +void SAL_CALL PresenterScrollBar::mouseReleased (const css::awt::MouseEvent&) +{ + mpMousePressRepeater->Stop(); + + if (mxPresenterHelper.is()) + mxPresenterHelper->releaseMouse(mxWindow); +} + +void SAL_CALL PresenterScrollBar::mouseEntered (const css::awt::MouseEvent&) {} + +void SAL_CALL PresenterScrollBar::mouseExited (const css::awt::MouseEvent&) +{ + if (meMouseMoveArea != None) + { + const Area eOldMouseMoveArea (meMouseMoveArea); + meMouseMoveArea = None; + Repaint(GetRectangle(eOldMouseMoveArea), true); + } + meButtonDownArea = None; + meMouseMoveArea = None; + + mpMousePressRepeater->Stop(); +} + +//----- XMouseMotionListener -------------------------------------------------- + +void SAL_CALL PresenterScrollBar::mouseMoved (const css::awt::MouseEvent& rEvent) +{ + const Area eArea (GetArea(rEvent.X, rEvent.Y)); + if (eArea != meMouseMoveArea) + { + const Area eOldMouseMoveArea (meMouseMoveArea); + meMouseMoveArea = eArea; + if (eOldMouseMoveArea != None) + Repaint(GetRectangle(eOldMouseMoveArea), meMouseMoveArea==None); + if (meMouseMoveArea != None) + Repaint(GetRectangle(meMouseMoveArea), true); + } + mpMousePressRepeater->SetMouseArea(eArea); +} + +void SAL_CALL PresenterScrollBar::mouseDragged (const css::awt::MouseEvent& rEvent) +{ + if (meButtonDownArea != Thumb) + return; + + mpMousePressRepeater->Stop(); + + if (mxPresenterHelper.is()) + mxPresenterHelper->captureMouse(mxWindow); + + const double nDragDistance (GetDragDistance(rEvent.X,rEvent.Y)); + UpdateDragAnchor(nDragDistance); + if (nDragDistance != 0) + { + SetThumbPosition(mnThumbPosition + nDragDistance, false); + } +} + +//----- lang::XEventListener -------------------------------------------------- + +void SAL_CALL PresenterScrollBar::disposing (const css::lang::EventObject& rEvent) +{ + if (rEvent.Source == mxWindow) + mxWindow = nullptr; +} + + +geometry::RealRectangle2D const & PresenterScrollBar::GetRectangle (const Area eArea) const +{ + OSL_ASSERT(eArea>=0 && eArea<AreaCount); + + return maBox[eArea]; +} + +void PresenterScrollBar::Repaint ( + const geometry::RealRectangle2D& rBox, + const bool bAsynchronousUpdate) +{ + if (mpPaintManager != nullptr) + mpPaintManager->Invalidate( + mxWindow, + PresenterGeometryHelper::ConvertRectangle(rBox), + bAsynchronousUpdate); +} + +void PresenterScrollBar::PaintBackground( + const css::awt::Rectangle& rUpdateBox) +{ + if (!mpBackgroundBitmap) + return; + + const awt::Rectangle aWindowBox (mxWindow->getPosSize()); + mpCanvasHelper->Paint( + mpBackgroundBitmap, + mxCanvas, + rUpdateBox, + aWindowBox, + awt::Rectangle()); +} + +void PresenterScrollBar::PaintBitmap( + const css::awt::Rectangle& rUpdateBox, + const Area eArea, + const SharedBitmapDescriptor& rpBitmaps) +{ + const geometry::RealRectangle2D aLocalBox (GetRectangle(eArea)); + const awt::Rectangle aWindowBox (mxWindow->getPosSize()); + geometry::RealRectangle2D aBox (aLocalBox); + aBox.X1 += aWindowBox.X; + aBox.Y1 += aWindowBox.Y; + aBox.X2 += aWindowBox.X; + aBox.Y2 += aWindowBox.Y; + + Reference<rendering::XBitmap> xBitmap (GetBitmap(eArea,rpBitmaps)); + + if (!xBitmap.is()) + return; + + Reference<rendering::XPolyPolygon2D> xClipPolygon ( + PresenterGeometryHelper::CreatePolygon( + PresenterGeometryHelper::Intersection(rUpdateBox, + PresenterGeometryHelper::ConvertRectangle(aBox)), + mxCanvas->getDevice())); + + const rendering::ViewState aViewState ( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + xClipPolygon); + + const geometry::IntegerSize2D aBitmapSize (xBitmap->getSize()); + rendering::RenderState aRenderState ( + geometry::AffineMatrix2D( + 1,0,aBox.X1 + (aBox.X2-aBox.X1 - aBitmapSize.Width)/2, + 0,1,aBox.Y1 + (aBox.Y2-aBox.Y1 - aBitmapSize.Height)/2), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + + mxCanvas->drawBitmap( + xBitmap, + aViewState, + aRenderState); +} + +PresenterScrollBar::Area PresenterScrollBar::GetArea (const double nX, const double nY) const +{ + const geometry::RealPoint2D aPoint(nX, nY); + + if (PresenterGeometryHelper::IsInside(GetRectangle(Pager), aPoint)) + { + if (PresenterGeometryHelper::IsInside(GetRectangle(Thumb), aPoint)) + return Thumb; + else if (PresenterGeometryHelper::IsInside(GetRectangle(PagerUp), aPoint)) + return PagerUp; + else if (PresenterGeometryHelper::IsInside(GetRectangle(PagerDown), aPoint)) + return PagerDown; + } + else if (PresenterGeometryHelper::IsInside(GetRectangle(PrevButton), aPoint)) + return PrevButton; + else if (PresenterGeometryHelper::IsInside(GetRectangle(NextButton), aPoint)) + return NextButton; + + return None; +} + +void PresenterScrollBar::UpdateWidthOrHeight ( + sal_Int32& rSize, + const SharedBitmapDescriptor& rpDescriptor) +{ + if (rpDescriptor) + { + Reference<rendering::XBitmap> xBitmap (rpDescriptor->GetNormalBitmap()); + if (xBitmap.is()) + { + const geometry::IntegerSize2D aBitmapSize (xBitmap->getSize()); + const sal_Int32 nBitmapSize = static_cast<sal_Int32>(GetMinor(aBitmapSize.Width, aBitmapSize.Height)); + if (nBitmapSize > rSize) + rSize = nBitmapSize; + } + } +} + +css::uno::Reference<css::rendering::XBitmap> PresenterScrollBar::GetBitmap ( + const Area eArea, + const SharedBitmapDescriptor& rpBitmaps) const +{ + if (!rpBitmaps) + return nullptr; + else + return rpBitmaps->GetBitmap(GetBitmapMode(eArea)); +} + +PresenterBitmapContainer::BitmapDescriptor::Mode PresenterScrollBar::GetBitmapMode ( + const Area eArea) const +{ + if (IsDisabled(eArea)) + return PresenterBitmapContainer::BitmapDescriptor::Disabled; + else if (eArea == meMouseMoveArea) + return PresenterBitmapContainer::BitmapDescriptor::MouseOver; + else + return PresenterBitmapContainer::BitmapDescriptor::Normal; +} + +bool PresenterScrollBar::IsDisabled (const Area eArea) const +{ + OSL_ASSERT(eArea>=0 && eArea<AreaCount); + + return ! maEnabledState[eArea]; +} + +//===== PresenterVerticalScrollBar ============================================ + +PresenterVerticalScrollBar::PresenterVerticalScrollBar ( + const Reference<XComponentContext>& rxComponentContext, + const Reference<awt::XWindow>& rxParentWindow, + const std::shared_ptr<PresenterPaintManager>& rpPaintManager, + const ::std::function<void (double)>& rThumbMotionListener) + : PresenterScrollBar(rxComponentContext, rxParentWindow, rpPaintManager, rThumbMotionListener), + mnScrollBarWidth(0) +{ +} + +PresenterVerticalScrollBar::~PresenterVerticalScrollBar() +{ +} + +double PresenterVerticalScrollBar::GetDragDistance (const sal_Int32, const sal_Int32 nY) const +{ + const double nDistance (nY - maDragAnchor.Y); + if (nDistance == 0) + return 0; + else + { + const awt::Rectangle aWindowBox (mxWindow->getPosSize()); + const double nBarWidth (aWindowBox.Width); + const double nPagerHeight (aWindowBox.Height - 2*nBarWidth); + const double nDragDistance (mnTotalSize / nPagerHeight * nDistance); + if (nDragDistance + mnThumbPosition < 0) + return -mnThumbPosition; + else if (mnThumbPosition + nDragDistance > mnTotalSize-mnThumbSize) + return mnTotalSize-mnThumbSize-mnThumbPosition; + else + return nDragDistance; + } +} + +void PresenterVerticalScrollBar::UpdateDragAnchor (const double nDragDistance) +{ + const awt::Rectangle aWindowBox (mxWindow->getPosSize()); + const double nBarWidth (aWindowBox.Width); + const double nPagerHeight (aWindowBox.Height - 2*nBarWidth); + maDragAnchor.Y += nDragDistance * nPagerHeight / mnTotalSize; +} + +sal_Int32 PresenterVerticalScrollBar::GetSize() const +{ + return mnScrollBarWidth; +} + +double PresenterVerticalScrollBar::GetMinor (const double nX, const double) const +{ + return nX; +} + +void PresenterVerticalScrollBar::UpdateBorders() +{ + const awt::Rectangle aWindowBox (mxWindow->getPosSize()); + double nBottom = aWindowBox.Height; + + if (mpNextButtonDescriptor) + { + Reference<rendering::XBitmap> xBitmap (mpNextButtonDescriptor->GetNormalBitmap()); + if (xBitmap.is()) + { + geometry::IntegerSize2D aSize (xBitmap->getSize()); + maBox[NextButton] = geometry::RealRectangle2D( + 0, nBottom - aSize.Height, aWindowBox.Width, nBottom); + nBottom -= aSize.Height + gnScrollBarGap; + } + } + if (mpPrevButtonDescriptor) + { + Reference<rendering::XBitmap> xBitmap (mpPrevButtonDescriptor->GetNormalBitmap()); + if (xBitmap.is()) + { + geometry::IntegerSize2D aSize (xBitmap->getSize()); + maBox[PrevButton] = geometry::RealRectangle2D( + 0, nBottom - aSize.Height, aWindowBox.Width, nBottom); + nBottom -= aSize.Height + gnScrollBarGap; + } + } + const double nPagerHeight (nBottom); + maBox[Pager] = geometry::RealRectangle2D( + 0,0, aWindowBox.Width, nBottom); + if (mnTotalSize < 1) + { + maBox[Thumb] = maBox[Pager]; + + // Set up the enabled/disabled states. + maEnabledState[PrevButton] = false; + maEnabledState[PagerUp] = false; + maEnabledState[NextButton] = false; + maEnabledState[PagerDown] = false; + maEnabledState[Thumb] = false; + } + else + { + const double nThumbSize = ::std::min(mnThumbSize,mnTotalSize); + const double nThumbPosition = ::std::clamp(mnThumbPosition, 0.0, mnTotalSize - nThumbSize); + maBox[Thumb] = geometry::RealRectangle2D( + 0, nThumbPosition / mnTotalSize * nPagerHeight, + aWindowBox.Width, + (nThumbPosition+nThumbSize) / mnTotalSize * nPagerHeight); + + // Set up the enabled/disabled states. + maEnabledState[PrevButton] = nThumbPosition>0; + maEnabledState[PagerUp] = nThumbPosition>0; + maEnabledState[NextButton] = nThumbPosition+nThumbSize < mnTotalSize; + maEnabledState[PagerDown] = nThumbPosition+nThumbSize < mnTotalSize; + maEnabledState[Thumb] = nThumbSize < mnTotalSize; + } + maBox[PagerUp] = geometry::RealRectangle2D( + maBox[Pager].X1, maBox[Pager].Y1, maBox[Pager].X2, maBox[Thumb].Y1-1); + maBox[PagerDown] = geometry::RealRectangle2D( + maBox[Pager].X1, maBox[Thumb].Y2+1, maBox[Pager].X2, maBox[Pager].Y2); + maBox[Total] = PresenterGeometryHelper::Union( + PresenterGeometryHelper::Union(maBox[PrevButton], maBox[NextButton]), + maBox[Pager]); +} + +void PresenterVerticalScrollBar::UpdateBitmaps() +{ + if (mpBitmaps == nullptr) + return; + + mpPrevButtonDescriptor = mpBitmaps->GetBitmap("Up"); + mpNextButtonDescriptor = mpBitmaps->GetBitmap("Down"); + mpPagerStartDescriptor = mpBitmaps->GetBitmap("PagerTop"); + mpPagerCenterDescriptor = mpBitmaps->GetBitmap("PagerVertical"); + mpPagerEndDescriptor = mpBitmaps->GetBitmap("PagerBottom"); + mpThumbStartDescriptor = mpBitmaps->GetBitmap("ThumbTop"); + mpThumbCenterDescriptor = mpBitmaps->GetBitmap("ThumbVertical"); + mpThumbEndDescriptor = mpBitmaps->GetBitmap("ThumbBottom"); + + mnScrollBarWidth = 0; + UpdateWidthOrHeight(mnScrollBarWidth, mpPrevButtonDescriptor); + UpdateWidthOrHeight(mnScrollBarWidth, mpNextButtonDescriptor); + UpdateWidthOrHeight(mnScrollBarWidth, mpPagerStartDescriptor); + UpdateWidthOrHeight(mnScrollBarWidth, mpPagerCenterDescriptor); + UpdateWidthOrHeight(mnScrollBarWidth, mpPagerEndDescriptor); + UpdateWidthOrHeight(mnScrollBarWidth, mpThumbStartDescriptor); + UpdateWidthOrHeight(mnScrollBarWidth, mpThumbCenterDescriptor); + UpdateWidthOrHeight(mnScrollBarWidth, mpThumbEndDescriptor); + if (mnScrollBarWidth == 0) + mnScrollBarWidth = 20; +} + +void PresenterVerticalScrollBar::PaintComposite( + const css::awt::Rectangle& rUpdateBox, + const Area eArea, + const SharedBitmapDescriptor& rpStartBitmaps, + const SharedBitmapDescriptor& rpCenterBitmaps, + const SharedBitmapDescriptor& rpEndBitmaps) +{ + const awt::Rectangle aWindowBox (mxWindow->getPosSize()); + geometry::RealRectangle2D aBox (GetRectangle(eArea)); + aBox.X1 += aWindowBox.X; + aBox.Y1 += aWindowBox.Y; + aBox.X2 += aWindowBox.X; + aBox.Y2 += aWindowBox.Y; + + // Get bitmaps and sizes. + + PresenterUIPainter::PaintVerticalBitmapComposite( + mxCanvas, + rUpdateBox, + (eArea == Thumb + ? PresenterGeometryHelper::ConvertRectangleWithConstantSize(aBox) + : PresenterGeometryHelper::ConvertRectangle(aBox)), + GetBitmap(eArea, rpStartBitmaps), + GetBitmap(eArea, rpCenterBitmaps), + GetBitmap(eArea, rpEndBitmaps)); +} + +//===== PresenterScrollBar::MousePressRepeater ================================ + +PresenterScrollBar::MousePressRepeater::MousePressRepeater ( + const ::rtl::Reference<PresenterScrollBar>& rpScrollBar) + : mnMousePressRepeaterTaskId(PresenterTimer::NotAValidTaskId), + mpScrollBar(rpScrollBar), + meMouseArea(PresenterScrollBar::None) +{ +} + +void PresenterScrollBar::MousePressRepeater::Dispose() +{ + Stop(); + mpScrollBar = nullptr; +} + +void PresenterScrollBar::MousePressRepeater::Start (const PresenterScrollBar::Area& reArea) +{ + meMouseArea = reArea; + + if (mnMousePressRepeaterTaskId == PresenterTimer::NotAValidTaskId) + { + // Execute key press operation at least this one time. + Execute(); + + // Schedule repeated executions. + auto pThis(shared_from_this()); + mnMousePressRepeaterTaskId = PresenterTimer::ScheduleRepeatedTask ( + mpScrollBar->GetComponentContext(), + [pThis] (TimeValue const&) { return pThis->Callback(); }, + 500000000, + 250000000); + } + else + { + // There is already an active repeating task. + } +} + +void PresenterScrollBar::MousePressRepeater::Stop() +{ + if (mnMousePressRepeaterTaskId != PresenterTimer::NotAValidTaskId) + { + const sal_Int32 nTaskId (mnMousePressRepeaterTaskId); + mnMousePressRepeaterTaskId = PresenterTimer::NotAValidTaskId; + PresenterTimer::CancelTask(nTaskId); + } +} + +void PresenterScrollBar::MousePressRepeater::SetMouseArea(const PresenterScrollBar::Area& reArea) +{ + if (meMouseArea != reArea) + { + if (mnMousePressRepeaterTaskId != PresenterTimer::NotAValidTaskId) + { + Stop(); + } + } +} + +void PresenterScrollBar::MousePressRepeater::Callback () +{ + if (!mpScrollBar) + { + Stop(); + return; + } + + Execute(); +} + +void PresenterScrollBar::MousePressRepeater::Execute() +{ + const double nThumbPosition (mpScrollBar->GetThumbPosition()); + switch (meMouseArea) + { + case PrevButton: + mpScrollBar->SetThumbPosition(nThumbPosition - mpScrollBar->GetLineHeight(), true); + break; + + case NextButton: + mpScrollBar->SetThumbPosition(nThumbPosition + mpScrollBar->GetLineHeight(), true); + break; + + case PagerUp: + mpScrollBar->SetThumbPosition(nThumbPosition - mpScrollBar->GetThumbSize()*0.8, true); + break; + + case PagerDown: + mpScrollBar->SetThumbPosition(nThumbPosition + mpScrollBar->GetThumbSize()*0.8, true); + break; + + default: + break; + } +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterScrollBar.hxx b/sdext/source/presenter/PresenterScrollBar.hxx new file mode 100644 index 000000000..b131c8a43 --- /dev/null +++ b/sdext/source/presenter/PresenterScrollBar.hxx @@ -0,0 +1,257 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERSCROLLBAR_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERSCROLLBAR_HXX + +#include "PresenterBitmapContainer.hxx" +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/drawing/XPresenterHelper.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> + +#include <functional> +#include <memory> + +namespace sdext::presenter { + +class PresenterCanvasHelper; +class PresenterPaintManager; + +typedef ::cppu::WeakComponentImplHelper < + css::awt::XWindowListener, + css::awt::XPaintListener, + css::awt::XMouseListener, + css::awt::XMouseMotionListener +> PresenterScrollBarInterfaceBase; + +/** Base class of horizontal and vertical scroll bars. +*/ +class PresenterScrollBar + : private ::cppu::BaseMutex, + public PresenterScrollBarInterfaceBase +{ +public: + virtual ~PresenterScrollBar() override; + PresenterScrollBar(const PresenterScrollBar&) = delete; + PresenterScrollBar& operator=(const PresenterScrollBar&) = delete; + + virtual void SAL_CALL disposing() override; + + css::uno::Reference<css::uno::XComponentContext> const& + GetComponentContext() const { return mxComponentContext; } + + void SetVisible (const bool bIsVisible); + + /** Set the bounding box of the scroll bar. + */ + void SetPosSize (const css::geometry::RealRectangle2D& rBox); + + /** Set the position of the movable thumb. + @param nPosition + A value between 0 and the last value given to SetTotalSize() + minus the last value given to SetThumbSize(). + */ + void SetThumbPosition ( + double nPosition, + const bool bAsynchronousRepaint); + + double GetThumbPosition() const { return mnThumbPosition;} + + /** Set the upper border of the slider range. + */ + void SetTotalSize (const double nTotalSize); + + /** Set the size of the movable thumb. + @param nThumbSize + A value not larger than the last value given to SetTotalSize(). + */ + void SetThumbSize (const double nThumbSize); + double GetThumbSize() const { return mnThumbSize;} + + void SetLineHeight (const double nLineHeight); + double GetLineHeight() const { return mnLineHeight;} + + /** Set the canvas that is used for painting the scroll bar. + */ + void SetCanvas (const css::uno::Reference<css::rendering::XCanvas>& rxCanvas); + + void SetBackground (const SharedBitmapDescriptor& rpBackgroundBitmap); + + /** Call this after changing total size or thumb position or size to + move the thumb to a valid position. + */ + void CheckValues(); + + /** On some occasions it is necessary to trigger the painting of a + scrollbar from the outside. + */ + void Paint ( + const css::awt::Rectangle& rUpdateBox); + + virtual sal_Int32 GetSize() const = 0; + + // XWindowListener + + virtual void SAL_CALL windowResized (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowMoved (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowShown (const css::lang::EventObject& rEvent) override; + + virtual void SAL_CALL windowHidden (const css::lang::EventObject& rEvent) override; + + // XPaintListener + + virtual void SAL_CALL windowPaint (const css::awt::PaintEvent& rEvent) override; + + // XMouseListener + + virtual void SAL_CALL mousePressed (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseReleased (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseEntered (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseExited (const css::awt::MouseEvent& rEvent) override; + + // XMouseMotionListener + + virtual void SAL_CALL mouseMoved (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseDragged (const css::awt::MouseEvent& rEvent) override; + + // lang::XEventListener + virtual void SAL_CALL disposing (const css::lang::EventObject& rEvent) override; + + enum Area { Total, Pager, Thumb, PagerUp, PagerDown, PrevButton, NextButton, None, + AreaCount = None }; + +protected: + css::uno::Reference<css::uno::XComponentContext> mxComponentContext; + css::uno::Reference<css::awt::XWindow> mxWindow; + css::uno::Reference<css::rendering::XCanvas> mxCanvas; + css::uno::Reference<css::drawing::XPresenterHelper> mxPresenterHelper; + std::shared_ptr<PresenterPaintManager> mpPaintManager; + double mnThumbPosition; + double mnTotalSize; + double mnThumbSize; + double mnLineHeight; + css::geometry::RealPoint2D maDragAnchor; + ::std::function<void (double)> maThumbMotionListener; + Area meButtonDownArea; + Area meMouseMoveArea; + css::geometry::RealRectangle2D maBox[AreaCount]; + bool mbIsNotificationActive; + static std::weak_ptr<PresenterBitmapContainer> mpSharedBitmaps; + std::shared_ptr<PresenterBitmapContainer> mpBitmaps; + SharedBitmapDescriptor mpPrevButtonDescriptor; + SharedBitmapDescriptor mpNextButtonDescriptor; + SharedBitmapDescriptor mpPagerStartDescriptor; + SharedBitmapDescriptor mpPagerCenterDescriptor; + SharedBitmapDescriptor mpPagerEndDescriptor; + SharedBitmapDescriptor mpThumbStartDescriptor; + SharedBitmapDescriptor mpThumbCenterDescriptor; + SharedBitmapDescriptor mpThumbEndDescriptor; + bool maEnabledState[AreaCount]; + + css::geometry::RealRectangle2D const & GetRectangle (const Area eArea) const; + virtual double GetDragDistance (const sal_Int32 nX, const sal_Int32 nY) const = 0; + virtual void UpdateDragAnchor (const double nDragDistance) = 0; + virtual double GetMinor (const double nX, const double nY) const = 0; + virtual void UpdateBorders() = 0; + virtual void UpdateBitmaps() = 0; + virtual void PaintComposite( + const css::awt::Rectangle& rRepaintBox, + const Area eArea, + const SharedBitmapDescriptor& rpStartBitmaps, + const SharedBitmapDescriptor& rpCenterBitmaps, + const SharedBitmapDescriptor& rpEndBitmaps) = 0; + + PresenterScrollBar ( + const css::uno::Reference<css::uno::XComponentContext>& rxComponentContext, + const css::uno::Reference<css::awt::XWindow>& rxParentWindow, + const std::shared_ptr<PresenterPaintManager>& rpPaintManager, + const ::std::function<void (double)>& rThumbMotionListener); + + void Repaint ( + const css::geometry::RealRectangle2D& rBox, + const bool bAsynchronous); + void PaintBackground ( + const css::awt::Rectangle& rRepaintBox); + void PaintBitmap( + const css::awt::Rectangle& rRepaintBox, + const Area eArea, + const SharedBitmapDescriptor& rpBitmaps); + void UpdateWidthOrHeight (sal_Int32& rSize, + const SharedBitmapDescriptor& rpDescriptor); + css::uno::Reference<css::rendering::XBitmap> GetBitmap ( + const Area eArea, + const SharedBitmapDescriptor& rpBitmaps) const; + PresenterBitmapContainer::BitmapDescriptor::Mode GetBitmapMode ( + const Area eArea) const; + bool IsDisabled (const Area eArea) const; + double ValidateThumbPosition (double nPosition); + +private: + class MousePressRepeater; + std::shared_ptr<MousePressRepeater> mpMousePressRepeater; + SharedBitmapDescriptor mpBackgroundBitmap; + std::unique_ptr<PresenterCanvasHelper> mpCanvasHelper; + + Area GetArea (const double nX, const double nY) const; +}; + +/** A vertical scroll bar. +*/ +class PresenterVerticalScrollBar : public PresenterScrollBar +{ +public: + PresenterVerticalScrollBar ( + const css::uno::Reference<css::uno::XComponentContext>& rxComponentContext, + const css::uno::Reference<css::awt::XWindow>& rxParentWindow, + const std::shared_ptr<PresenterPaintManager>& rpPaintManager, + const ::std::function<void (double)>& rThumbMotionListener); + virtual ~PresenterVerticalScrollBar() override; + virtual sal_Int32 GetSize() const override; + +protected: + virtual double GetDragDistance (const sal_Int32 nX, const sal_Int32 nY) const override; + virtual void UpdateDragAnchor (const double nDragDistance) override; + virtual double GetMinor (const double nX, const double nY) const override; + virtual void UpdateBorders() override; + virtual void UpdateBitmaps() override; + virtual void PaintComposite( + const css::awt::Rectangle& rRepaintBox, + const Area eArea, + const SharedBitmapDescriptor& rpStartBitmaps, + const SharedBitmapDescriptor& rpCenterBitmaps, + const SharedBitmapDescriptor& rpEndBitmaps) override; + +private: + sal_Int32 mnScrollBarWidth; +}; + +} // end of namespace ::sdext::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterSlidePreview.cxx b/sdext/source/presenter/PresenterSlidePreview.cxx new file mode 100644 index 000000000..9de4c8820 --- /dev/null +++ b/sdext/source/presenter/PresenterSlidePreview.cxx @@ -0,0 +1,352 @@ +/* -*- 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 "PresenterSlidePreview.hxx" +#include "PresenterCanvasHelper.hxx" +#include "PresenterGeometryHelper.hxx" +#include "PresenterPaintManager.hxx" +#include "PresenterBitmapContainer.hxx" +#include <com/sun/star/awt/XWindowPeer.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing::framework; + +namespace +{ + // Use a super sample factor greater than 1 to achieve a poor mans + // antialiasing effect for slide previews. + const sal_Int16 gnSuperSampleFactor = 2; +} + +namespace sdext::presenter { + +//===== PresenterSlidePreview ================================================= + +PresenterSlidePreview::PresenterSlidePreview ( + const Reference<XComponentContext>& rxContext, + const Reference<XResourceId>& rxViewId, + const Reference<XPane>& rxAnchorPane, + const ::rtl::Reference<PresenterController>& rpPresenterController) + : PresenterSlidePreviewInterfaceBase(m_aMutex), + mpPresenterController(rpPresenterController), + mxViewId(rxViewId), + mnSlideAspectRatio(28.0 / 21.0) +{ + if ( ! rxContext.is() + || ! rxViewId.is() + || ! rxAnchorPane.is() + || ! rpPresenterController.is()) + { + throw RuntimeException( + "PresenterSlidePreview can not be constructed due to empty argument", + static_cast<XWeak*>(this)); + } + + mxWindow = rxAnchorPane->getWindow(); + mxCanvas = rxAnchorPane->getCanvas(); + + if (mxWindow.is()) + { + mxWindow->addWindowListener(this); + mxWindow->addPaintListener(this); + + Reference<awt::XWindowPeer> xPeer (mxWindow, UNO_QUERY); + if (xPeer.is()) + xPeer->setBackground(util::Color(0xff000000)); + + mxWindow->setVisible(true); + } + + if (mpPresenterController) + mnSlideAspectRatio = mpPresenterController->GetSlideAspectRatio(); + + Reference<lang::XMultiComponentFactory> xFactory = rxContext->getServiceManager(); + if (xFactory.is()) + mxPreviewRenderer.set( + xFactory->createInstanceWithContext( + "com.sun.star.drawing.SlideRenderer", + rxContext), + UNO_QUERY); + mpBitmaps = std::make_shared<PresenterBitmapContainer>( + "PresenterScreenSettings/ScrollBar/Bitmaps", + std::shared_ptr<PresenterBitmapContainer>(), + rxContext, + mxCanvas); + Resize(); +} + +PresenterSlidePreview::~PresenterSlidePreview() +{ +} + +void SAL_CALL PresenterSlidePreview::disposing() +{ + if (mxWindow.is()) + { + mxWindow->removeWindowListener(this); + mxWindow->removePaintListener(this); + mxWindow = nullptr; + mxCanvas = nullptr; + } + + Reference<lang::XComponent> xComponent (mxPreviewRenderer, UNO_QUERY); + if (xComponent.is()) + xComponent->dispose(); +} + +//----- XResourceId ----------------------------------------------------------- + +Reference<XResourceId> SAL_CALL PresenterSlidePreview::getResourceId() +{ + return mxViewId; +} + +sal_Bool SAL_CALL PresenterSlidePreview::isAnchorOnly() +{ + return false; +} + +//----- XWindowListener ------------------------------------------------------- + +void SAL_CALL PresenterSlidePreview::windowResized (const awt::WindowEvent&) +{ + ThrowIfDisposed(); + ::osl::MutexGuard aGuard (::osl::Mutex::getGlobalMutex()); + Resize(); +} + +void SAL_CALL PresenterSlidePreview::windowMoved (const awt::WindowEvent&) {} + +void SAL_CALL PresenterSlidePreview::windowShown (const lang::EventObject&) +{ + ThrowIfDisposed(); + ::osl::MutexGuard aGuard (::osl::Mutex::getGlobalMutex()); + Resize(); +} + +void SAL_CALL PresenterSlidePreview::windowHidden (const lang::EventObject&) {} + +//----- XPaintListener -------------------------------------------------------- + +void SAL_CALL PresenterSlidePreview::windowPaint (const awt::PaintEvent& rEvent) +{ + ThrowIfDisposed(); + + ::osl::MutexGuard aGuard (::osl::Mutex::getGlobalMutex()); + if (mxWindow.is()) + Paint(awt::Rectangle( + rEvent.UpdateRect.X, + rEvent.UpdateRect.Y, + rEvent.UpdateRect.Width, + rEvent.UpdateRect.Height)); +} + +//----- lang::XEventListener -------------------------------------------------- + +void SAL_CALL PresenterSlidePreview::disposing (const lang::EventObject& rEvent) +{ + if (rEvent.Source == mxWindow) + { + mxWindow = nullptr; + mxCanvas = nullptr; + mxPreview = nullptr; + } +} + +//----- XDrawView ------------------------------------------------------------- + +void SAL_CALL PresenterSlidePreview::setCurrentPage (const Reference<drawing::XDrawPage>& rxSlide) +{ + ThrowIfDisposed(); + ::osl::MutexGuard aGuard (::osl::Mutex::getGlobalMutex()); + SetSlide(rxSlide); +} + +Reference<drawing::XDrawPage> SAL_CALL PresenterSlidePreview::getCurrentPage() +{ + ThrowIfDisposed(); + return nullptr; +} + + +void PresenterSlidePreview::SetSlide (const Reference<drawing::XDrawPage>& rxPage) +{ + mxCurrentSlide = rxPage; + mxPreview = nullptr; + + // The preview is not transparent, therefore only this window, not its + // parent, has to be invalidated. + mpPresenterController->GetPaintManager()->Invalidate(mxWindow); +} + +void PresenterSlidePreview::Paint (const awt::Rectangle& rBoundingBox) +{ + if ( ! mxWindow.is()) + return; + if ( ! mxCanvas.is()) + return; + if ( ! mxPreviewRenderer.is()) + return; + + // Make sure that a preview in the correct size exists. + awt::Rectangle aWindowBox (mxWindow->getPosSize()); + + bool bCustomAnimation = false; + bool bTransition = false; + if( mxCurrentSlide.is() ) + { + bCustomAnimation = PresenterController::HasCustomAnimation(mxCurrentSlide); + bTransition = PresenterController::HasTransition(mxCurrentSlide); + } + + if ( ! mxPreview.is() && mxCurrentSlide.is()) + { + // Create a new preview bitmap. + mxPreview = mxPreviewRenderer->createPreviewForCanvas( + mxCurrentSlide, + awt::Size(aWindowBox.Width, aWindowBox.Height), + gnSuperSampleFactor, + mxCanvas); + } + + // Determine the bounding box of the preview. + awt::Rectangle aPreviewBox; + if (mxPreview.is()) + { + const geometry::IntegerSize2D aPreviewSize (mxPreview->getSize()); + aPreviewBox = awt::Rectangle( + (aWindowBox.Width - aPreviewSize.Width)/2, + (aWindowBox.Height - aPreviewSize.Height)/2, + aPreviewSize.Width, + aPreviewSize.Height); + } + else + { + if (mnSlideAspectRatio > 0) + { + const awt::Size aPreviewSize (mxPreviewRenderer->calculatePreviewSize( + mnSlideAspectRatio,awt::Size(aWindowBox.Width, aWindowBox.Height))); + aPreviewBox = awt::Rectangle( + (aWindowBox.Width - aPreviewSize.Width)/2, + (aWindowBox.Height - aPreviewSize.Height)/2, + aPreviewSize.Width, + aPreviewSize.Height); + } + } + + // Paint the background. + mpPresenterController->GetCanvasHelper()->Paint( + mpPresenterController->GetViewBackground(mxViewId->getResourceURL()), + mxCanvas, + rBoundingBox, + awt::Rectangle(0,0,aWindowBox.Width,aWindowBox.Height), + aPreviewBox); + + // Paint the preview. + const rendering::ViewState aViewState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr); + + Sequence<double> aBackgroundColor(4); + rendering::RenderState aRenderState ( + geometry::AffineMatrix2D(1, 0, aPreviewBox.X, 0, 1, aPreviewBox.Y), + nullptr, + aBackgroundColor, + rendering::CompositeOperation::SOURCE); + PresenterCanvasHelper::SetDeviceColor(aRenderState, 0x00000000); + if (mxPreview.is()) + { + mxCanvas->drawBitmap(mxPreview, aViewState, aRenderState); + if( bTransition ) + { + const awt::Rectangle aTransitionPreviewBox(5, aWindowBox.Height-20, 0, 0); + SharedBitmapDescriptor aTransitionDescriptor = mpBitmaps->GetBitmap("Transition"); + Reference<rendering::XBitmap> xTransitionIcon (aTransitionDescriptor->GetNormalBitmap()); + rendering::RenderState aTransitionRenderState ( + geometry::AffineMatrix2D(1, 0, aTransitionPreviewBox.X, 0, 1, aTransitionPreviewBox.Y), + nullptr, + aBackgroundColor, + rendering::CompositeOperation::SOURCE); + mxCanvas->drawBitmap(xTransitionIcon, aViewState, aTransitionRenderState); + } + if( bCustomAnimation ) + { + const awt::Rectangle aAnimationPreviewBox(5, aWindowBox.Height-40, 0, 0); + SharedBitmapDescriptor aAnimationDescriptor = mpBitmaps->GetBitmap("Animation"); + Reference<rendering::XBitmap> xAnimationIcon (aAnimationDescriptor->GetNormalBitmap()); + rendering::RenderState aAnimationRenderState ( + geometry::AffineMatrix2D(1, 0, aAnimationPreviewBox.X, 0, 1, aAnimationPreviewBox.Y), + nullptr, + aBackgroundColor, + rendering::CompositeOperation::SOURCE); + mxCanvas->drawBitmap(xAnimationIcon, aViewState, aAnimationRenderState); + } + } + else + { + if (mnSlideAspectRatio > 0) + { + Reference<rendering::XPolyPolygon2D> xPolygon ( + PresenterGeometryHelper::CreatePolygon(aPreviewBox, mxCanvas->getDevice())); + if (xPolygon.is()) + mxCanvas->fillPolyPolygon(xPolygon, aViewState, aRenderState); + } + } + + Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY); + if (xSpriteCanvas.is()) + xSpriteCanvas->updateScreen(false); +} + +void PresenterSlidePreview::Resize() +{ + if (mxPreviewRenderer.is() && mxPreview.is()) + { + const awt::Rectangle aWindowBox (mxWindow->getPosSize()); + const awt::Size aNewPreviewSize (mxPreviewRenderer->calculatePreviewSize( + mnSlideAspectRatio, + awt::Size(aWindowBox.Width, aWindowBox.Height))); + const geometry::IntegerSize2D aPreviewSize (mxPreview->getSize()); + if (aNewPreviewSize.Width==aPreviewSize.Width + && aNewPreviewSize.Height==aPreviewSize.Height) + { + // The size of the window may have changed but the preview would + // be painted in the same size (but not necessarily at the same + // position.) + return; + } + } + SetSlide(mxCurrentSlide); +} + +void PresenterSlidePreview::ThrowIfDisposed() +{ + if (PresenterSlidePreviewInterfaceBase::rBHelper.bDisposed || PresenterSlidePreviewInterfaceBase::rBHelper.bInDispose) + { + throw lang::DisposedException ( + "PresenterSlidePreview object has already been disposed", + static_cast<uno::XWeak*>(this)); + } +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterSlidePreview.hxx b/sdext/source/presenter/PresenterSlidePreview.hxx new file mode 100644 index 000000000..85107693a --- /dev/null +++ b/sdext/source/presenter/PresenterSlidePreview.hxx @@ -0,0 +1,145 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERSLIDEPREVIEW_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERSLIDEPREVIEW_HXX + +#include "PresenterController.hxx" + +#include <com/sun/star/awt/XPaintListener.hpp> +#include <com/sun/star/awt/XWindowListener.hpp> +#include <com/sun/star/drawing/XDrawPage.hpp> +#include <com/sun/star/drawing/XDrawView.hpp> +#include <com/sun/star/drawing/XSlideRenderer.hpp> +#include <com/sun/star/drawing/framework/XPane.hpp> +#include <com/sun/star/drawing/framework/XView.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <rtl/ref.hxx> + +namespace sdext::presenter { + +typedef ::cppu::WeakComponentImplHelper < + css::drawing::framework::XView, + css::drawing::XDrawView, + css::awt::XPaintListener, + css::awt::XWindowListener +> PresenterSlidePreviewInterfaceBase; + +/** Static preview of a slide. Typically used for the preview of the next + slide. + This implementation shows a preview of the slide given to the + setCurrentSlide. For showing the next slide the PresenterViewFactory + uses a derived class that overrides the setCurrentSlide() method. +*/ +class PresenterSlidePreview + : private ::cppu::BaseMutex, + public PresenterSlidePreviewInterfaceBase +{ +public: + PresenterSlidePreview ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId, + const css::uno::Reference<css::drawing::framework::XPane>& rxAnchorPane, + const ::rtl::Reference<PresenterController>& rpPresenterController); + virtual ~PresenterSlidePreview() override; + PresenterSlidePreview(const PresenterSlidePreview&) = delete; + PresenterSlidePreview& operator=(const PresenterSlidePreview&) = delete; + virtual void SAL_CALL disposing() override; + + // XResourceId + + virtual css::uno::Reference<css::drawing::framework::XResourceId> SAL_CALL getResourceId() override; + + virtual sal_Bool SAL_CALL isAnchorOnly() override; + + // XWindowListener + + virtual void SAL_CALL windowResized (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowMoved (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowShown (const css::lang::EventObject& rEvent) override; + + virtual void SAL_CALL windowHidden (const css::lang::EventObject& rEvent) override; + + // XPaintListener + + virtual void SAL_CALL windowPaint (const css::awt::PaintEvent& rEvent) override; + + // lang::XEventListener + virtual void SAL_CALL disposing (const css::lang::EventObject& rEvent) override; + + // XDrawView + + virtual void SAL_CALL setCurrentPage ( + const css::uno::Reference<css::drawing::XDrawPage>& rxSlide) override; + + virtual css::uno::Reference<css::drawing::XDrawPage> SAL_CALL getCurrentPage() override; + +protected: + ::rtl::Reference<PresenterController> mpPresenterController; + +private: + css::uno::Reference<css::drawing::framework::XResourceId> mxViewId; + css::uno::Reference<css::drawing::XSlideRenderer> mxPreviewRenderer; + + /** This Image holds the preview of the current slide. After resize + requests the image may be empty. This results eventually in a call + to ProvideSlide() in order to created a preview in the correct new + size. + */ + css::uno::Reference<css::rendering::XBitmap> mxPreview; + std::shared_ptr<PresenterBitmapContainer> mpBitmaps; + + /** The current slide for which a preview is displayed. This may or + may not be the same as the current slide of the PresenterView. + */ + css::uno::Reference<css::drawing::XDrawPage> mxCurrentSlide; + double mnSlideAspectRatio; + + css::uno::Reference<css::awt::XWindow> mxWindow; + css::uno::Reference<css::rendering::XCanvas> mxCanvas; + + /** Set the given slide as the current slide of the called PresenterSlidePreview + object. + */ + void SetSlide (const css::uno::Reference<css::drawing::XDrawPage>& rxPage); + + /** Paint the preview of the current slide centered in the window of the + anchor pane. + */ + void Paint (const css::awt::Rectangle& rBoundingBox); + + /** React to a resize of the anchor pane. + */ + void Resize(); + + /** @throws css::lang::DisposedException when the object has already been + disposed. + */ + void ThrowIfDisposed(); +}; + +} // end of namespace ::sd::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterSlideShowView.cxx b/sdext/source/presenter/PresenterSlideShowView.cxx new file mode 100644 index 000000000..5ca0ee630 --- /dev/null +++ b/sdext/source/presenter/PresenterSlideShowView.cxx @@ -0,0 +1,953 @@ + +/* -*- 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 "PresenterSlideShowView.hxx" +#include "PresenterCanvasHelper.hxx" +#include "PresenterGeometryHelper.hxx" +#include "PresenterHelper.hxx" +#include "PresenterPaneContainer.hxx" +#include <com/sun/star/awt/InvalidateStyle.hpp> +#include <com/sun/star/awt/PosSize.hpp> +#include <com/sun/star/awt/Pointer.hpp> +#include <com/sun/star/awt/Toolkit.hpp> +#include <com/sun/star/awt/WindowAttribute.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/awt/XWindowPeer.hpp> +#include <com/sun/star/drawing/XPresenterHelper.hpp> +#include <com/sun/star/drawing/framework/XControllerManager.hpp> +#include <com/sun/star/drawing/framework/XConfigurationController.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/TextDirection.hpp> +#include <com/sun/star/rendering/TexturingMode.hpp> +#include <osl/mutex.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing::framework; + +namespace sdext::presenter { + +//===== PresenterSlideShowView ================================================ + +PresenterSlideShowView::PresenterSlideShowView ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId, + const css::uno::Reference<css::frame::XController>& rxController, + const ::rtl::Reference<PresenterController>& rpPresenterController) + : PresenterSlideShowViewInterfaceBase(m_aMutex), + mxComponentContext(rxContext), + mpPresenterController(rpPresenterController), + mxViewId(rxViewId), + mxController(rxController), + mxSlideShowController(PresenterHelper::GetSlideShowController(rxController)), + mbIsViewAdded(false), + mnPageAspectRatio(28.0/21.0), + maBroadcaster(m_aMutex), + mbIsForcedPaintPending(false), + mbIsPaintPending(true), + mbIsEndSlideVisible(false) +{ + if (mpPresenterController) + { + mnPageAspectRatio = mpPresenterController->GetSlideAspectRatio(); + mpBackground = mpPresenterController->GetViewBackground(mxViewId->getResourceURL()); + } +} + +void PresenterSlideShowView::LateInit() +{ + mxSlideShow.set( mxSlideShowController->getSlideShow(), UNO_SET_THROW); + Reference<lang::XComponent> xSlideShowComponent (mxSlideShow, UNO_QUERY); + xSlideShowComponent->addEventListener(static_cast<awt::XWindowListener*>(this)); + + Reference<lang::XMultiComponentFactory> xFactory ( + mxComponentContext->getServiceManager(), UNO_SET_THROW); + mxPresenterHelper.set (xFactory->createInstanceWithContext( + "com.sun.star.comp.Draw.PresenterHelper", + mxComponentContext), + UNO_QUERY_THROW); + + // Use view id and controller to retrieve window and canvas from + // configuration controller. + Reference<XControllerManager> xCM (mxController, UNO_QUERY_THROW); + Reference<XConfigurationController> xCC (xCM->getConfigurationController()); + + if (xCC.is()) + { + mxTopPane.set(xCC->getResource(mxViewId->getAnchor()->getAnchor()), UNO_QUERY); + + Reference<XPane> xPane (xCC->getResource(mxViewId->getAnchor()), UNO_QUERY_THROW); + + mxWindow = xPane->getWindow(); + mxCanvas = xPane->getCanvas(); + + if (mxWindow.is()) + { + mxWindow->addPaintListener(this); + mxWindow->addWindowListener(this); + } + + // The window does not have to paint a background. We do + // that ourself. + Reference<awt::XWindowPeer> xPeer (mxWindow, UNO_QUERY); + if (xPeer.is()) + xPeer->setBackground(util::Color(0xff000000)); + } + + // Create a window for the actual slide show view. It is places + // centered and with maximal size inside the pane. + mxViewWindow = CreateViewWindow(mxWindow); + + mxViewCanvas = CreateViewCanvas(mxViewWindow); + + if (mxViewWindow.is()) + { + // Register listeners at window. + mxViewWindow->addPaintListener(this); + mxViewWindow->addMouseListener(this); + mxViewWindow->addMouseMotionListener(this); + } + + if (mxViewWindow.is()) + Resize(); + + if (mxWindow.is()) + mxWindow->setVisible(true); + + // Add the new slide show view to the slide show. + if (mxSlideShow.is() && ! mbIsViewAdded) + { + impl_addAndConfigureView(); + mbIsViewAdded = true; + } + + // Read text for one past last slide. + PresenterConfigurationAccess aConfiguration ( + mxComponentContext, + PresenterConfigurationAccess::msPresenterScreenRootName, + PresenterConfigurationAccess::READ_ONLY); + aConfiguration.GetConfigurationNode( + "Presenter/Views/CurrentSlidePreview/" + "Strings/ClickToExitPresentationText/String") + >>= msClickToExitPresentationText; + aConfiguration.GetConfigurationNode( + "Presenter/Views/CurrentSlidePreview/" + "Strings/ClickToExitPresentationTitle/String") + >>= msClickToExitPresentationTitle; +} + +PresenterSlideShowView::~PresenterSlideShowView() +{ +} + +void PresenterSlideShowView::disposing() +{ + // Tell all listeners that we are disposed. + lang::EventObject aEvent; + aEvent.Source = static_cast<XWeak*>(this); + + ::cppu::OInterfaceContainerHelper* pIterator + = maBroadcaster.getContainer(cppu::UnoType<lang::XEventListener>::get()); + if (pIterator != nullptr) + pIterator->disposeAndClear(aEvent); + + // Do this for + // XPaintListener, XModifyListener,XMouseListener,XMouseMotionListener,XWindowListener? + + if (mxWindow.is()) + { + mxWindow->removePaintListener(this); + mxWindow->removeMouseListener(this); + mxWindow->removeMouseMotionListener(this); + mxWindow->removeWindowListener(this); + mxWindow = nullptr; + } + mxSlideShowController = nullptr; + mxSlideShow = nullptr; + if (mxViewCanvas.is()) + { + Reference<XComponent> xComponent (mxViewCanvas, UNO_QUERY); + mxViewCanvas = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + if (mxViewWindow.is()) + { + Reference<XComponent> xComponent = mxViewWindow; + mxViewWindow = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + if (mxPointer.is()) + { + Reference<XComponent> xComponent (mxPointer, UNO_QUERY); + mxPointer = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + if (mxBackgroundPolygon1.is()) + { + Reference<XComponent> xComponent (mxBackgroundPolygon1, UNO_QUERY); + mxBackgroundPolygon1 = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + if (mxBackgroundPolygon2.is()) + { + Reference<XComponent> xComponent (mxBackgroundPolygon2, UNO_QUERY); + mxBackgroundPolygon2 = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + + mxComponentContext = nullptr; + mpPresenterController = nullptr; + mxViewId = nullptr; + mxController = nullptr; + mxCanvas = nullptr; + mpBackground.reset(); + msClickToExitPresentationText.clear(); + msClickToExitPresentationTitle.clear(); + msTitleTemplate.clear(); + mxCurrentSlide = nullptr; +} + +//----- XDrawView ------------------------------------------------------------- + +void SAL_CALL PresenterSlideShowView::setCurrentPage ( + const css::uno::Reference<css::drawing::XDrawPage>& rxSlide) +{ + mxCurrentSlide = rxSlide; + if (mpPresenterController + && mxSlideShowController.is() + && ! mpPresenterController->GetCurrentSlide().is() + && ! mxSlideShowController->isPaused()) + { + mbIsEndSlideVisible = true; + Reference<awt::XWindowPeer> xPeer (mxViewWindow, UNO_QUERY); + if (xPeer.is()) + xPeer->invalidate(awt::InvalidateStyle::NOTRANSPARENT); + + // For the end slide we use a special title, without the (n of m) + // part. Save the title template for the case that the user goes + // backwards. + PresenterPaneContainer::SharedPaneDescriptor pDescriptor ( + mpPresenterController->GetPaneContainer()->FindViewURL(mxViewId->getResourceURL())); + if (pDescriptor) + { + msTitleTemplate = pDescriptor->msTitleTemplate; + pDescriptor->msTitleTemplate = msClickToExitPresentationTitle; + mpPresenterController->UpdatePaneTitles(); + } + } + else if (mbIsEndSlideVisible) + { + mbIsEndSlideVisible = false; + + // Restore the title template. + PresenterPaneContainer::SharedPaneDescriptor pDescriptor ( + mpPresenterController->GetPaneContainer()->FindViewURL(mxViewId->getResourceURL())); + if (pDescriptor) + { + pDescriptor->msTitleTemplate = msTitleTemplate; + pDescriptor->msTitle.clear(); + mpPresenterController->UpdatePaneTitles(); + } + } +} + +css::uno::Reference<css::drawing::XDrawPage> SAL_CALL PresenterSlideShowView::getCurrentPage() +{ + return mxCurrentSlide; +} + +//----- CachablePresenterView ------------------------------------------------- + +void PresenterSlideShowView::ReleaseView() +{ + if (mxSlideShow.is() && mbIsViewAdded) + { + mxSlideShow->removeView(this); + mbIsViewAdded = false; + } +} + +//----- XSlideShowView -------------------------------------------------------- + +Reference<rendering::XSpriteCanvas> SAL_CALL PresenterSlideShowView::getCanvas() +{ + ThrowIfDisposed(); + + return Reference<rendering::XSpriteCanvas>(mxViewCanvas, UNO_QUERY); +} + +void SAL_CALL PresenterSlideShowView::clear() +{ + ThrowIfDisposed(); + mbIsForcedPaintPending = false; + mbIsPaintPending = false; + + if (!(mxViewCanvas.is() && mxViewWindow.is())) + return; + + // Create a polygon for the window outline. + awt::Rectangle aViewWindowBox (mxViewWindow->getPosSize()); + Reference<rendering::XPolyPolygon2D> xPolygon (PresenterGeometryHelper::CreatePolygon( + awt::Rectangle(0,0, aViewWindowBox.Width,aViewWindowBox.Height), + mxViewCanvas->getDevice())); + + rendering::ViewState aViewState ( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr); + double const aColor[4] = {0,0,0,0}; + rendering::RenderState aRenderState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr, + Sequence<double>(aColor,4), + rendering::CompositeOperation::SOURCE); + mxViewCanvas->fillPolyPolygon(xPolygon, aViewState, aRenderState); +} + +geometry::AffineMatrix2D SAL_CALL PresenterSlideShowView::getTransformation() +{ + ThrowIfDisposed(); + + if (mxViewWindow.is()) + { + // When the mbIsInModifyNotification is set then a slightly modified + // version of the transformation is returned in order to get past + // optimizations the avoid updates when the transformation is + // unchanged (when the window size changes then due to the constant + // aspect ratio the size of the preview may remain the same while + // the position changes. The position, however, is represented by + // the position of the view window. This transformation is given + // relative to the view window and therefore does not contain the + // position.) + const awt::Rectangle aWindowBox = mxViewWindow->getPosSize(); + return geometry::AffineMatrix2D( + aWindowBox.Width-1, 0, 0, + 0, aWindowBox.Height-1, 0); + } + else + { + return geometry::AffineMatrix2D(1,0,0, 0,1,0); + } +} + +geometry::IntegerSize2D SAL_CALL PresenterSlideShowView::getTranslationOffset() +{ + ThrowIfDisposed(); + return geometry::IntegerSize2D(0,0); +} + +void SAL_CALL PresenterSlideShowView::addTransformationChangedListener( + const Reference<util::XModifyListener>& rxListener) +{ + ThrowIfDisposed(); + maBroadcaster.addListener( + cppu::UnoType<util::XModifyListener>::get(), + rxListener); +} + +void SAL_CALL PresenterSlideShowView::removeTransformationChangedListener( + const Reference<util::XModifyListener>& rxListener) +{ + ThrowIfDisposed(); + maBroadcaster.removeListener( + cppu::UnoType<util::XModifyListener>::get(), + rxListener); +} + +void SAL_CALL PresenterSlideShowView::addPaintListener( + const Reference<awt::XPaintListener>& rxListener) +{ + ThrowIfDisposed(); + maBroadcaster.addListener( + cppu::UnoType<awt::XPaintListener>::get(), + rxListener); +} + +void SAL_CALL PresenterSlideShowView::removePaintListener( + const Reference<awt::XPaintListener>& rxListener) +{ + ThrowIfDisposed(); + maBroadcaster.removeListener( + cppu::UnoType<awt::XPaintListener>::get(), + rxListener); +} + +void SAL_CALL PresenterSlideShowView::addMouseListener( + const Reference<awt::XMouseListener>& rxListener) +{ + ThrowIfDisposed(); + maBroadcaster.addListener( + cppu::UnoType<awt::XMouseListener>::get(), + rxListener); +} + +void SAL_CALL PresenterSlideShowView::removeMouseListener( + const Reference<awt::XMouseListener>& rxListener) +{ + ThrowIfDisposed(); + maBroadcaster.removeListener( + cppu::UnoType<awt::XMouseListener>::get(), + rxListener); +} + +void SAL_CALL PresenterSlideShowView::addMouseMotionListener( + const Reference<awt::XMouseMotionListener>& rxListener) +{ + ThrowIfDisposed(); + maBroadcaster.addListener( + cppu::UnoType<awt::XMouseMotionListener>::get(), + rxListener); +} + +void SAL_CALL PresenterSlideShowView::removeMouseMotionListener( + const Reference<awt::XMouseMotionListener>& rxListener) +{ + ThrowIfDisposed(); + maBroadcaster.removeListener( + cppu::UnoType<awt::XMouseMotionListener>::get(), + rxListener); +} + +void SAL_CALL PresenterSlideShowView::setMouseCursor(::sal_Int16 nPointerShape) +{ + ThrowIfDisposed(); + + // Create a pointer when it does not yet exist. + if ( ! mxPointer.is()) + { + mxPointer = awt::Pointer::create(mxComponentContext); + } + + // Set the pointer to the given shape and the window(peer) to the + // pointer. + Reference<awt::XWindowPeer> xPeer (mxViewWindow, UNO_QUERY); + if (mxPointer.is() && xPeer.is()) + { + mxPointer->setType(nPointerShape); + xPeer->setPointer(mxPointer); + } +} + +awt::Rectangle SAL_CALL PresenterSlideShowView::getCanvasArea( ) +{ + if( mxViewWindow.is() && mxTopPane.is() ) + return mxPresenterHelper->getWindowExtentsRelative( mxViewWindow, mxTopPane->getWindow() ); + + awt::Rectangle aRectangle; + + aRectangle.X = aRectangle.Y = aRectangle.Width = aRectangle.Height = 0; + + return aRectangle; +} + +//----- lang::XEventListener -------------------------------------------------- + +void SAL_CALL PresenterSlideShowView::disposing (const lang::EventObject& rEvent) +{ + if (rEvent.Source == mxViewWindow) + mxViewWindow = nullptr; + else if (rEvent.Source == mxSlideShow) + mxSlideShow = nullptr; +} + +//----- XPaintListener -------------------------------------------------------- + +void SAL_CALL PresenterSlideShowView::windowPaint (const awt::PaintEvent& rEvent) +{ + // Deactivated views must not be painted. + if ( ! mbIsPresenterViewActive) + return; + + awt::Rectangle aViewWindowBox (mxViewWindow->getPosSize()); + if (aViewWindowBox.Width <= 0 || aViewWindowBox.Height <= 0) + return; + + if (rEvent.Source == mxWindow) + PaintOuterWindow(rEvent.UpdateRect); + else if (mbIsEndSlideVisible) + PaintEndSlide(rEvent.UpdateRect); + else + PaintInnerWindow(rEvent); +} + +//----- XMouseListener -------------------------------------------------------- + +void SAL_CALL PresenterSlideShowView::mousePressed (const awt::MouseEvent& rEvent) +{ + awt::MouseEvent aEvent (rEvent); + aEvent.Source = static_cast<XWeak*>(this); + ::cppu::OInterfaceContainerHelper* pIterator + = maBroadcaster.getContainer(cppu::UnoType<awt::XMouseListener>::get()); + if (pIterator != nullptr) + { + pIterator->notifyEach(&awt::XMouseListener::mousePressed, aEvent); + } + + // Only when the end slide is displayed we forward the mouse event to + // the PresenterController so that it switches to the next slide and + // ends the presentation. + if (mbIsEndSlideVisible) + if (mpPresenterController) + mpPresenterController->HandleMouseClick(rEvent); +} + +void SAL_CALL PresenterSlideShowView::mouseReleased (const awt::MouseEvent& rEvent) +{ + awt::MouseEvent aEvent (rEvent); + aEvent.Source = static_cast<XWeak*>(this); + ::cppu::OInterfaceContainerHelper* pIterator + = maBroadcaster.getContainer(cppu::UnoType<awt::XMouseListener>::get()); + if (pIterator != nullptr) + { + pIterator->notifyEach(&awt::XMouseListener::mouseReleased, aEvent); + } +} + +void SAL_CALL PresenterSlideShowView::mouseEntered (const awt::MouseEvent& rEvent) +{ + awt::MouseEvent aEvent (rEvent); + aEvent.Source = static_cast<XWeak*>(this); + ::cppu::OInterfaceContainerHelper* pIterator + = maBroadcaster.getContainer(cppu::UnoType<awt::XMouseListener>::get()); + if (pIterator != nullptr) + { + pIterator->notifyEach(&awt::XMouseListener::mouseEntered, aEvent); + } +} + +void SAL_CALL PresenterSlideShowView::mouseExited (const awt::MouseEvent& rEvent) +{ + awt::MouseEvent aEvent (rEvent); + aEvent.Source = static_cast<XWeak*>(this); + ::cppu::OInterfaceContainerHelper* pIterator + = maBroadcaster.getContainer(cppu::UnoType<awt::XMouseListener>::get()); + if (pIterator != nullptr) + { + pIterator->notifyEach(&awt::XMouseListener::mouseExited, aEvent); + } +} + +//----- XMouseMotionListener -------------------------------------------------- + +void SAL_CALL PresenterSlideShowView::mouseDragged (const awt::MouseEvent& rEvent) +{ + awt::MouseEvent aEvent (rEvent); + aEvent.Source = static_cast<XWeak*>(this); + ::cppu::OInterfaceContainerHelper* pIterator + = maBroadcaster.getContainer(cppu::UnoType<awt::XMouseMotionListener>::get()); + if (pIterator != nullptr) + { + pIterator->notifyEach(&awt::XMouseMotionListener::mouseDragged, aEvent); + } +} + +void SAL_CALL PresenterSlideShowView::mouseMoved (const awt::MouseEvent& rEvent) +{ + awt::MouseEvent aEvent (rEvent); + aEvent.Source = static_cast<XWeak*>(this); + ::cppu::OInterfaceContainerHelper* pIterator + = maBroadcaster.getContainer(cppu::UnoType<awt::XMouseMotionListener>::get()); + if (pIterator != nullptr) + { + pIterator->notifyEach(&awt::XMouseMotionListener::mouseMoved, aEvent); + } +} + +//----- XWindowListener ------------------------------------------------------- + +void SAL_CALL PresenterSlideShowView::windowResized (const awt::WindowEvent&) +{ + ThrowIfDisposed(); + ::osl::MutexGuard aGuard (::osl::Mutex::getGlobalMutex()); + + Resize(); +} + +void SAL_CALL PresenterSlideShowView::windowMoved (const awt::WindowEvent&) +{ + if ( ! mbIsPaintPending) + mbIsForcedPaintPending = true; +} + +void SAL_CALL PresenterSlideShowView::windowShown (const lang::EventObject&) +{ + Resize(); +} + +void SAL_CALL PresenterSlideShowView::windowHidden (const lang::EventObject&) {} + +//----- XView ----------------------------------------------------------------- + +Reference<XResourceId> SAL_CALL PresenterSlideShowView::getResourceId() +{ + return mxViewId; +} + +sal_Bool SAL_CALL PresenterSlideShowView::isAnchorOnly() +{ + return false; +} + +//----- CachablePresenterView ------------------------------------------------- + +void PresenterSlideShowView::ActivatePresenterView() +{ + if (mxSlideShow.is() && ! mbIsViewAdded) + { + impl_addAndConfigureView(); + mbIsViewAdded = true; + } +} + +void PresenterSlideShowView::DeactivatePresenterView() +{ + if (mxSlideShow.is() && mbIsViewAdded) + { + mxSlideShow->removeView(this); + mbIsViewAdded = false; + } +} + + +void PresenterSlideShowView::PaintOuterWindow (const awt::Rectangle& rRepaintBox) +{ + if ( ! mxCanvas.is()) + return; + + if (!mpBackground) + return; + + const rendering::ViewState aViewState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + PresenterGeometryHelper::CreatePolygon(rRepaintBox, mxCanvas->getDevice())); + + rendering::RenderState aRenderState ( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + + Reference<rendering::XBitmap> xBackgroundBitmap (mpBackground->GetNormalBitmap()); + if (xBackgroundBitmap.is()) + { + const geometry::IntegerSize2D aBitmapSize(xBackgroundBitmap->getSize()); + Sequence<rendering::Texture> aTextures + { + { + geometry::AffineMatrix2D( aBitmapSize.Width,0,0, 0,aBitmapSize.Height,0), + 1, + 0, + xBackgroundBitmap, + nullptr, + nullptr, + rendering::StrokeAttributes(), + rendering::TexturingMode::REPEAT, + rendering::TexturingMode::REPEAT + } + }; + + if (mxBackgroundPolygon1.is()) + mxCanvas->fillTexturedPolyPolygon( + mxBackgroundPolygon1, + aViewState, + aRenderState, + aTextures); + if (mxBackgroundPolygon2.is()) + mxCanvas->fillTexturedPolyPolygon( + mxBackgroundPolygon2, + aViewState, + aRenderState, + aTextures); + } + else + { + PresenterCanvasHelper::SetDeviceColor(aRenderState, mpBackground->maReplacementColor); + + if (mxBackgroundPolygon1.is()) + mxCanvas->fillPolyPolygon(mxBackgroundPolygon1, aViewState, aRenderState); + if (mxBackgroundPolygon2.is()) + mxCanvas->fillPolyPolygon(mxBackgroundPolygon2, aViewState, aRenderState); + } +} + +void PresenterSlideShowView::PaintEndSlide (const awt::Rectangle& rRepaintBox) +{ + if ( ! mxCanvas.is()) + return; + + const rendering::ViewState aViewState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + PresenterGeometryHelper::CreatePolygon(rRepaintBox, mxCanvas->getDevice())); + + rendering::RenderState aRenderState ( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + PresenterCanvasHelper::SetDeviceColor(aRenderState, util::Color(0x00000000)); + mxCanvas->fillPolyPolygon( + PresenterGeometryHelper::CreatePolygon(mxViewWindow->getPosSize(), mxCanvas->getDevice()), + aViewState, + aRenderState); + + do + { + if (!mpPresenterController) + break; + std::shared_ptr<PresenterTheme> pTheme (mpPresenterController->GetTheme()); + if (pTheme == nullptr) + break; + + const OUString sViewStyle (pTheme->GetStyleName(mxViewId->getResourceURL())); + PresenterTheme::SharedFontDescriptor pFont (pTheme->GetFont(sViewStyle)); + if (!pFont) + break; + + /// this is responsible of the " presentation exit " text inside the slide windows + PresenterCanvasHelper::SetDeviceColor(aRenderState, util::Color(0x00ffffff)); + aRenderState.AffineTransform.m02 = 20; + aRenderState.AffineTransform.m12 = 40; + const rendering::StringContext aContext ( + msClickToExitPresentationText, 0, msClickToExitPresentationText.getLength()); + pFont->PrepareFont(mxCanvas); + const Reference<rendering::XTextLayout> xLayout ( + pFont->mxFont->createTextLayout(aContext,rendering::TextDirection::WEAK_LEFT_TO_RIGHT,0)); + mxCanvas->drawTextLayout( + xLayout, + aViewState, + aRenderState); + } + while (false); + + // Finally, in double buffered environments, request the changes to be + // made visible. + Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY); + if (xSpriteCanvas.is()) + xSpriteCanvas->updateScreen(true); +} + +void PresenterSlideShowView::PaintInnerWindow (const awt::PaintEvent& rEvent) +{ + // Forward window paint to listeners. + awt::PaintEvent aEvent (rEvent); + aEvent.Source = static_cast<XWeak*>(this); + ::cppu::OInterfaceContainerHelper* pIterator + = maBroadcaster.getContainer(cppu::UnoType<awt::XPaintListener>::get()); + if (pIterator != nullptr) + { + pIterator->notifyEach(&awt::XPaintListener::windowPaint, aEvent); + } + + /** The slide show relies on the back buffer of the canvas not being + modified. With a shared canvas there are times when that can not be + guaranteed. + */ + if (mbIsForcedPaintPending && mxSlideShow.is() && mbIsViewAdded) + { + mxSlideShow->removeView(this); + impl_addAndConfigureView(); + } + + // Finally, in double buffered environments, request the changes to be + // made visible. + Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY); + if (xSpriteCanvas.is()) + xSpriteCanvas->updateScreen(true); +} + +Reference<awt::XWindow> PresenterSlideShowView::CreateViewWindow ( + const Reference<awt::XWindow>& rxParentWindow) const +{ + Reference<awt::XWindow> xViewWindow; + try + { + Reference<lang::XMultiComponentFactory> xFactory (mxComponentContext->getServiceManager()); + if ( ! xFactory.is()) + return xViewWindow; + + Reference<awt::XToolkit2> xToolkit = awt::Toolkit::create(mxComponentContext); + awt::WindowDescriptor aWindowDescriptor ( + awt::WindowClass_CONTAINER, + OUString(), + Reference<awt::XWindowPeer>(rxParentWindow,UNO_QUERY_THROW), + -1, // parent index not available + awt::Rectangle(0,0,10,10), + awt::WindowAttribute::SIZEABLE + | awt::WindowAttribute::MOVEABLE + | awt::WindowAttribute::NODECORATION); + xViewWindow.set( xToolkit->createWindow(aWindowDescriptor),UNO_QUERY_THROW); + + // Make the background transparent. The slide show paints its own background. + Reference<awt::XWindowPeer> xPeer (xViewWindow, UNO_QUERY_THROW); + xPeer->setBackground(0xff000000); + + xViewWindow->setVisible(true); + } + catch (RuntimeException&) + { + } + return xViewWindow; +} + +Reference<rendering::XCanvas> PresenterSlideShowView::CreateViewCanvas ( + const Reference<awt::XWindow>& rxViewWindow) const +{ + // Create a canvas for the view window. + return mxPresenterHelper->createSharedCanvas( + Reference<rendering::XSpriteCanvas>(mxTopPane->getCanvas(), UNO_QUERY), + mxTopPane->getWindow(), + mxTopPane->getCanvas(), + mxTopPane->getWindow(), + rxViewWindow); +} + +void PresenterSlideShowView::Resize() +{ + if ( ! mxWindow.is() || ! mxViewWindow.is()) + return; + + const awt::Rectangle aWindowBox (mxWindow->getPosSize()); + if (aWindowBox.Height > 0) + { + awt::Rectangle aViewWindowBox; + const double nWindowAspectRatio ( + double(aWindowBox.Width) / double(aWindowBox.Height)); + if (nWindowAspectRatio > mnPageAspectRatio) + { + // Slides will be painted with the full parent window height. + aViewWindowBox.Width = sal_Int32(aWindowBox.Height * mnPageAspectRatio + 0.5); + aViewWindowBox.Height = aWindowBox.Height; + aViewWindowBox.X = (aWindowBox.Width - aViewWindowBox.Width) / 2; + aViewWindowBox.Y = 0; + } + else + { + // Slides will be painted with the full parent window width. + aViewWindowBox.Width = aWindowBox.Width; + aViewWindowBox.Height = sal_Int32(aWindowBox.Width / mnPageAspectRatio + 0.5); + aViewWindowBox.X = 0; + aViewWindowBox.Y = (aWindowBox.Height - aViewWindowBox.Height) / 2; + } + mxViewWindow->setPosSize( + aViewWindowBox.X, + aViewWindowBox.Y, + aViewWindowBox.Width, + aViewWindowBox.Height, + awt::PosSize::POSSIZE); + } + + // Clear the background polygon so that on the next paint it is created + // for the new size. + CreateBackgroundPolygons(); + + // Notify listeners that the transformation that maps the view into the + // window has changed. + lang::EventObject aEvent (static_cast<XWeak*>(this)); + ::cppu::OInterfaceContainerHelper* pIterator + = maBroadcaster.getContainer(cppu::UnoType<util::XModifyListener>::get()); + if (pIterator != nullptr) + { + pIterator->notifyEach(&util::XModifyListener::modified, aEvent); + } + + // Due to constant aspect ratio resizing may lead a preview that changes + // its position but not its size. This invalidates the back buffer and + // we have to enforce a complete repaint. + if ( ! mbIsPaintPending) + mbIsForcedPaintPending = true; +} + +void PresenterSlideShowView::CreateBackgroundPolygons() +{ + const awt::Rectangle aWindowBox (mxWindow->getPosSize()); + const awt::Rectangle aViewWindowBox (mxViewWindow->getPosSize()); + if (aWindowBox.Height == aViewWindowBox.Height && aWindowBox.Width == aViewWindowBox.Width) + { + mxBackgroundPolygon1 = nullptr; + mxBackgroundPolygon2 = nullptr; + } + else if (aWindowBox.Height == aViewWindowBox.Height) + { + // Paint two boxes to the left and right of the view window. + mxBackgroundPolygon1 = PresenterGeometryHelper::CreatePolygon( + awt::Rectangle( + 0, + 0, + aViewWindowBox.X, + aWindowBox.Height), + mxCanvas->getDevice()); + mxBackgroundPolygon2 = PresenterGeometryHelper::CreatePolygon( + awt::Rectangle( + aViewWindowBox.X + aViewWindowBox.Width, + 0, + aWindowBox.Width - aViewWindowBox.X - aViewWindowBox.Width, + aWindowBox.Height), + mxCanvas->getDevice()); + } + else + { + // Paint two boxes above and below the view window. + mxBackgroundPolygon1 = PresenterGeometryHelper::CreatePolygon( + awt::Rectangle( + 0, + 0, + aWindowBox.Width, + aViewWindowBox.Y), + mxCanvas->getDevice()); + mxBackgroundPolygon2 = PresenterGeometryHelper::CreatePolygon( + awt::Rectangle( + 0, + aViewWindowBox.Y + aViewWindowBox.Height, + aWindowBox.Width, + aWindowBox.Height - aViewWindowBox.Y - aViewWindowBox.Height), + mxCanvas->getDevice()); + } +} + +void PresenterSlideShowView::ThrowIfDisposed() +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ( + "PresenterSlideShowView object has already been disposed", + static_cast<uno::XWeak*>(this)); + } +} + +void PresenterSlideShowView::impl_addAndConfigureView() +{ + Reference<presentation::XSlideShowView> xView (this); + mxSlideShow->addView(xView); + // Prevent embedded sounds being played twice at the same time by + // disabling sound for the new slide show view. + beans::PropertyValue aProperty; + aProperty.Name = "IsSoundEnabled"; + Sequence<Any> aValues{ Any(xView), Any(false) }; + aProperty.Value <<= aValues; + mxSlideShow->setProperty(aProperty); +} + +} // end of namespace ::sd::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterSlideShowView.hxx b/sdext/source/presenter/PresenterSlideShowView.hxx new file mode 100644 index 000000000..d009888f9 --- /dev/null +++ b/sdext/source/presenter/PresenterSlideShowView.hxx @@ -0,0 +1,241 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERSLIDESHOWVIEW_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERSLIDESHOWVIEW_HXX + +#include "PresenterViewFactory.hxx" +#include <com/sun/star/presentation/XSlideShowView.hpp> +#include <com/sun/star/awt/XPaintListener.hpp> +#include <com/sun/star/awt/XMouseListener.hpp> +#include <com/sun/star/awt/XMouseMotionListener.hpp> +#include <com/sun/star/awt/XPointer.hpp> +#include <com/sun/star/awt/XWindowListener.hpp> +#include <com/sun/star/drawing/XDrawView.hpp> +#include <com/sun/star/drawing/framework/XPane.hpp> +#include <com/sun/star/drawing/framework/XResourceId.hpp> +#include <com/sun/star/drawing/framework/XView.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/presentation/XSlideShowController.hpp> +#include <com/sun/star/rendering/XPolyPolygon2D.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> + +namespace sdext::presenter { + +typedef cppu::WeakComponentImplHelper< + css::presentation::XSlideShowView, + css::awt::XPaintListener, + css::awt::XMouseListener, + css::awt::XMouseMotionListener, + css::awt::XWindowListener, + css::drawing::framework::XView, + css::drawing::XDrawView + > PresenterSlideShowViewInterfaceBase; + +/** Life view in a secondary window of a full screen slide show. +*/ +class PresenterSlideShowView + : protected ::cppu::BaseMutex, + public PresenterSlideShowViewInterfaceBase, + public CachablePresenterView +{ +public: + PresenterSlideShowView ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId, + const css::uno::Reference<css::frame::XController>& rxController, + const ::rtl::Reference<PresenterController>& rpPresenterController); + virtual ~PresenterSlideShowView() override; + PresenterSlideShowView(const PresenterSlideShowView&) = delete; + PresenterSlideShowView& operator=(const PresenterSlideShowView&) = delete; + + void LateInit(); + virtual void SAL_CALL disposing() override; + + // CachablePresenterView + + virtual void ReleaseView() override; + + // XSlideShowView + + virtual css::uno::Reference< + css::rendering::XSpriteCanvas > SAL_CALL getCanvas() override; + + virtual void SAL_CALL clear() override; + + virtual css::geometry::AffineMatrix2D SAL_CALL getTransformation() override; + + virtual css::geometry::IntegerSize2D SAL_CALL getTranslationOffset() override; + + virtual void SAL_CALL addTransformationChangedListener( + const css::uno::Reference< + css::util::XModifyListener >& xListener) override; + + virtual void SAL_CALL removeTransformationChangedListener( + const css::uno::Reference< + css::util::XModifyListener >& xListener) override; + + virtual void SAL_CALL addPaintListener( + const css::uno::Reference< + css::awt::XPaintListener >& xListener) override; + + virtual void SAL_CALL removePaintListener( + const css::uno::Reference< + css::awt::XPaintListener >& xListener) override; + + virtual void SAL_CALL addMouseListener( + const css::uno::Reference< + css::awt::XMouseListener >& xListener) override; + + virtual void SAL_CALL removeMouseListener( + const css::uno::Reference< + css::awt::XMouseListener >& xListener) override; + + virtual void SAL_CALL addMouseMotionListener( + const css::uno::Reference< + css::awt::XMouseMotionListener >& xListener) override; + + virtual void SAL_CALL removeMouseMotionListener( + const css::uno::Reference< + css::awt::XMouseMotionListener >& xListener) override; + + virtual void SAL_CALL setMouseCursor(::sal_Int16 nPointerShape) override; + + virtual css::awt::Rectangle SAL_CALL getCanvasArea( ) override; + + // lang::XEventListener + virtual void SAL_CALL disposing (const css::lang::EventObject& rEvent) override; + + // XPaintListener + virtual void SAL_CALL windowPaint (const css::awt::PaintEvent& rEvent) override; + + // XMouseListener + virtual void SAL_CALL mousePressed (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseReleased (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseEntered (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseExited (const css::awt::MouseEvent& rEvent) override; + + // XMouseMotionListener + + virtual void SAL_CALL mouseDragged (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseMoved (const css::awt::MouseEvent& rEvent) override; + + // XWindowListener + + virtual void SAL_CALL windowResized (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowMoved (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowShown (const css::lang::EventObject& rEvent) override; + + virtual void SAL_CALL windowHidden (const css::lang::EventObject& rEvent) override; + + // XView + + virtual css::uno::Reference<css::drawing::framework::XResourceId> SAL_CALL + getResourceId() override; + + virtual sal_Bool SAL_CALL isAnchorOnly() override; + + // XDrawView + + virtual void SAL_CALL setCurrentPage ( + const css::uno::Reference<css::drawing::XDrawPage>& rxSlide) override; + + virtual css::uno::Reference<css::drawing::XDrawPage> SAL_CALL getCurrentPage() override; + + // CachablePresenterView + + virtual void ActivatePresenterView() override; + + virtual void DeactivatePresenterView() override; + +private: + css::uno::Reference<css::uno::XComponentContext> mxComponentContext; + ::rtl::Reference<PresenterController> mpPresenterController; + css::uno::Reference<css::drawing::framework::XResourceId> mxViewId; + css::uno::Reference<css::frame::XController> mxController; + css::uno::Reference<css::presentation::XSlideShowController> mxSlideShowController; + css::uno::Reference<css::presentation::XSlideShow> mxSlideShow; + css::uno::Reference<css::rendering::XCanvas> mxCanvas; + css::uno::Reference<css::rendering::XCanvas> mxViewCanvas; + css::uno::Reference<css::awt::XPointer> mxPointer; + css::uno::Reference<css::awt::XWindow> mxWindow; + css::uno::Reference<css::awt::XWindow> mxViewWindow; + css::uno::Reference<css::drawing::framework::XPane> mxTopPane; + css::uno::Reference<css::drawing::XPresenterHelper> mxPresenterHelper; + css::uno::Reference<css::rendering::XPolyPolygon2D> mxBackgroundPolygon1; + css::uno::Reference<css::rendering::XPolyPolygon2D> mxBackgroundPolygon2; + bool mbIsViewAdded; + + /** Aspect ratio of the current slide. + */ + double mnPageAspectRatio; + + /** This broadcast helper is used to notify listeners registered to a + SlideShowView object. + */ + ::cppu::OBroadcastHelper maBroadcaster; + + SharedBitmapDescriptor mpBackground; + + bool mbIsForcedPaintPending; + bool mbIsPaintPending; + OUString msClickToExitPresentationText; + OUString msClickToExitPresentationTitle; + OUString msTitleTemplate; + bool mbIsEndSlideVisible; + css::uno::Reference<css::drawing::XDrawPage> mxCurrentSlide; + + /** Create the window into which the slide show will render its + content. This window has the correct aspect ratio and is displayed centered + and as large as possible in its parent window. + */ + css::uno::Reference<css::awt::XWindow> CreateViewWindow ( + const css::uno::Reference<css::awt::XWindow>& rxParentWindow) const; + css::uno::Reference<css::rendering::XCanvas> CreateViewCanvas ( + const css::uno::Reference<css::awt::XWindow>& rxWindow) const; + + void Resize(); + + void PaintOuterWindow (const css::awt::Rectangle& rRepaintBox); + void PaintInnerWindow (const css::awt::PaintEvent& rEvent); + void PaintEndSlide (const css::awt::Rectangle& rRepaintBox); + + void CreateBackgroundPolygons(); + + /** @throws css::lang::DisposedException when the object has already been + disposed. + */ + void ThrowIfDisposed(); + + void impl_addAndConfigureView(); +}; + +} // end of namespace ::sd::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterSlideSorter.cxx b/sdext/source/presenter/PresenterSlideSorter.cxx new file mode 100644 index 000000000..f89061ef6 --- /dev/null +++ b/sdext/source/presenter/PresenterSlideSorter.cxx @@ -0,0 +1,1929 @@ +/* -*- 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 <vcl/settings.hxx> + +#include "PresenterSlideSorter.hxx" +#include "PresenterButton.hxx" +#include "PresenterCanvasHelper.hxx" +#include "PresenterGeometryHelper.hxx" +#include "PresenterPaintManager.hxx" +#include "PresenterPaneBase.hxx" +#include "PresenterScrollBar.hxx" +#include "PresenterUIPainter.hxx" +#include "PresenterWindowManager.hxx" +#include <com/sun/star/drawing/framework/XConfigurationController.hpp> +#include <com/sun/star/drawing/framework/XControllerManager.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/TextDirection.hpp> +#include <algorithm> +#include <math.h> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing::framework; + +namespace { + const sal_Int32 gnVerticalGap (10); + const sal_Int32 gnVerticalBorder (10); + const sal_Int32 gnHorizontalGap (10); + const sal_Int32 gnHorizontalBorder (10); + + const double gnMinimalPreviewWidth (200); + const double gnPreferredPreviewWidth (300); + const double gnMaximalPreviewWidth (400); + const sal_Int32 gnPreferredColumnCount (6); + const double gnMinimalHorizontalPreviewGap(15); + const double gnPreferredHorizontalPreviewGap(25); + const double gnMaximalHorizontalPreviewGap(50); + const double gnPreferredVerticalPreviewGap(25); + + const sal_Int32 gnHorizontalLabelBorder (3); + const sal_Int32 gnHorizontalLabelPadding (5); + + const sal_Int32 gnVerticalButtonPadding (gnVerticalGap); +} + +namespace sdext::presenter { + +namespace { + sal_Int32 round (const double nValue) { return sal::static_int_cast<sal_Int32>(0.5 + nValue); } + sal_Int32 floor (const double nValue) { return sal::static_int_cast<sal_Int32>(nValue); } +} + +//===== PresenterSlideSorter::Layout ========================================== + +class PresenterSlideSorter::Layout +{ +public: + explicit Layout (const ::rtl::Reference<PresenterScrollBar>& rpVerticalScrollBar); + + void Update (const geometry::RealRectangle2D& rBoundingBox, const double nSlideAspectRatio); + void SetupVisibleArea(); + void UpdateScrollBars(); + bool IsScrollBarNeeded (const sal_Int32 nSlideCount); + geometry::RealPoint2D GetLocalPosition (const geometry::RealPoint2D& rWindowPoint) const; + geometry::RealPoint2D GetWindowPosition(const geometry::RealPoint2D& rLocalPoint) const; + sal_Int32 GetColumn (const geometry::RealPoint2D& rLocalPoint) const; + sal_Int32 GetRow (const geometry::RealPoint2D& rLocalPoint, + const bool bReturnInvalidValue = false) const; + sal_Int32 GetSlideIndexForPosition (const css::geometry::RealPoint2D& rPoint) const; + css::geometry::RealPoint2D GetPoint ( + const sal_Int32 nSlideIndex, + const sal_Int32 nRelativeHorizontalPosition, + const sal_Int32 nRelativeVerticalPosition) const; + css::awt::Rectangle GetBoundingBox (const sal_Int32 nSlideIndex) const; + void ForAllVisibleSlides (const ::std::function<void (sal_Int32)>& rAction); + sal_Int32 GetFirstVisibleSlideIndex() const; + sal_Int32 GetLastVisibleSlideIndex() const; + bool SetHorizontalOffset (const double nOffset); + bool SetVerticalOffset (const double nOffset); + + css::geometry::RealRectangle2D maBoundingBox; + css::geometry::IntegerSize2D maPreviewSize; + sal_Int32 mnHorizontalOffset; + sal_Int32 mnVerticalOffset; + sal_Int32 mnHorizontalGap; + sal_Int32 mnVerticalGap; + sal_Int32 mnHorizontalBorder; + sal_Int32 mnVerticalBorder; + sal_Int32 mnRowCount; + sal_Int32 mnColumnCount; + sal_Int32 mnSlideCount; + sal_Int32 mnFirstVisibleColumn; + sal_Int32 mnLastVisibleColumn; + sal_Int32 mnFirstVisibleRow; + sal_Int32 mnLastVisibleRow; + +private: + ::rtl::Reference<PresenterScrollBar> mpVerticalScrollBar; + + sal_Int32 GetIndex (const sal_Int32 nRow, const sal_Int32 nColumn) const; + sal_Int32 GetRow (const sal_Int32 nSlideIndex) const; + sal_Int32 GetColumn (const sal_Int32 nSlideIndex) const; +}; + +//==== PresenterSlideSorter::MouseOverManager ================================= + +class PresenterSlideSorter::MouseOverManager +{ +public: + MouseOverManager ( + const Reference<container::XIndexAccess>& rxSlides, + const std::shared_ptr<PresenterTheme>& rpTheme, + const Reference<awt::XWindow>& rxInvalidateTarget, + const std::shared_ptr<PresenterPaintManager>& rpPaintManager); + MouseOverManager(const MouseOverManager&) = delete; + MouseOverManager& operator=(const MouseOverManager&) = delete; + + void Paint ( + const sal_Int32 nSlideIndex, + const Reference<rendering::XCanvas>& rxCanvas, + const Reference<rendering::XPolyPolygon2D>& rxClip); + + void SetSlide ( + const sal_Int32 nSlideIndex, + const awt::Rectangle& rBox); + +private: + Reference<rendering::XCanvas> mxCanvas; + const Reference<container::XIndexAccess> mxSlides; + SharedBitmapDescriptor mpLeftLabelBitmap; + SharedBitmapDescriptor mpCenterLabelBitmap; + SharedBitmapDescriptor mpRightLabelBitmap; + PresenterTheme::SharedFontDescriptor mpFont; + sal_Int32 mnSlideIndex; + awt::Rectangle maSlideBoundingBox; + OUString msText; + Reference<rendering::XBitmap> mxBitmap; + Reference<awt::XWindow> mxInvalidateTarget; + std::shared_ptr<PresenterPaintManager> mpPaintManager; + + void SetCanvas ( + const Reference<rendering::XCanvas>& rxCanvas); + /** Create a bitmap that shows the given text and is not wider than the + given maximal width. + */ + Reference<rendering::XBitmap> CreateBitmap ( + const OUString& rsText, + const sal_Int32 nMaximalWidth) const; + void Invalidate(); + geometry::IntegerSize2D CalculateLabelSize ( + const OUString& rsText) const; + OUString GetFittingText (const OUString& rsText, const double nMaximalWidth) const; + void PaintButtonBackground ( + const Reference<rendering::XCanvas>& rxCanvas, + const geometry::IntegerSize2D& rSize) const; +}; + +//==== PresenterSlideSorter::CurrentSlideFrameRenderer ======================== + +class PresenterSlideSorter::CurrentSlideFrameRenderer +{ +public: + CurrentSlideFrameRenderer ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas); + + void PaintCurrentSlideFrame ( + const awt::Rectangle& rSlideBoundingBox, + const Reference<rendering::XCanvas>& rxCanvas, + const geometry::RealRectangle2D& rClipBox); + + /** Enlarge the given rectangle to include the current slide indicator. + */ + awt::Rectangle GetBoundingBox ( + const awt::Rectangle& rSlideBoundingBox); + +private: + SharedBitmapDescriptor mpTopLeft; + SharedBitmapDescriptor mpTop; + SharedBitmapDescriptor mpTopRight; + SharedBitmapDescriptor mpLeft; + SharedBitmapDescriptor mpRight; + SharedBitmapDescriptor mpBottomLeft; + SharedBitmapDescriptor mpBottom; + SharedBitmapDescriptor mpBottomRight; + sal_Int32 mnTopFrameSize; + sal_Int32 mnLeftFrameSize; + sal_Int32 mnRightFrameSize; + sal_Int32 mnBottomFrameSize; + + static void PaintBitmapOnce( + const css::uno::Reference<css::rendering::XBitmap>& rxBitmap, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const Reference<rendering::XPolyPolygon2D>& rxClip, + const double nX, + const double nY); + static void PaintBitmapTiled( + const css::uno::Reference<css::rendering::XBitmap>& rxBitmap, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const geometry::RealRectangle2D& rClipBox, + const double nX, + const double nY, + const double nWidth, + const double nHeight); +}; + +//===== PresenterSlideSorter ================================================== + +PresenterSlideSorter::PresenterSlideSorter ( + const Reference<uno::XComponentContext>& rxContext, + const Reference<XResourceId>& rxViewId, + const Reference<frame::XController>& rxController, + const ::rtl::Reference<PresenterController>& rpPresenterController) + : PresenterSlideSorterInterfaceBase(m_aMutex), + mxComponentContext(rxContext), + mxViewId(rxViewId), + mpPresenterController(rpPresenterController), + mxSlideShowController(mpPresenterController->GetSlideShowController()), + mbIsLayoutPending(true), + mnSlideIndexMousePressed(-1), + mnCurrentSlideIndex(-1), + mnSeparatorY(0), + maSeparatorColor(0x00ffffff) +{ + if ( ! rxContext.is() + || ! rxViewId.is() + || ! rxController.is() + || ! rpPresenterController) + { + throw lang::IllegalArgumentException(); + } + + if ( ! mxSlideShowController.is()) + throw RuntimeException(); + + try + { + // Get pane and window. + Reference<XControllerManager> xCM (rxController, UNO_QUERY_THROW); + Reference<XConfigurationController> xCC ( + xCM->getConfigurationController(), UNO_SET_THROW); + Reference<lang::XMultiComponentFactory> xFactory ( + mxComponentContext->getServiceManager(), UNO_SET_THROW); + + mxPane.set(xCC->getResource(rxViewId->getAnchor()), UNO_QUERY_THROW); + mxWindow = mxPane->getWindow(); + + // Add window listener. + mxWindow->addWindowListener(this); + mxWindow->addPaintListener(this); + mxWindow->addMouseListener(this); + mxWindow->addMouseMotionListener(this); + mxWindow->setVisible(true); + + // Remember the current slide. + mnCurrentSlideIndex = mxSlideShowController->getCurrentSlideIndex(); + + // Create the scroll bar. + mpVerticalScrollBar.set( + new PresenterVerticalScrollBar( + rxContext, + mxWindow, + mpPresenterController->GetPaintManager(), + [this] (double const offset) { return this->SetVerticalOffset(offset); })); + + mpCloseButton = PresenterButton::Create( + rxContext, + mpPresenterController, + mpPresenterController->GetTheme(), + mxWindow, + mxCanvas, + "SlideSorterCloser"); + + if (mpPresenterController->GetTheme() != nullptr) + { + PresenterTheme::SharedFontDescriptor pFont ( + mpPresenterController->GetTheme()->GetFont("ButtonFont")); + if (pFont) + maSeparatorColor = pFont->mnColor; + } + + // Create the layout. + mpLayout = std::make_shared<Layout>(mpVerticalScrollBar); + + // Create the preview cache. + mxPreviewCache.set( + xFactory->createInstanceWithContext( + "com.sun.star.drawing.PresenterPreviewCache", + mxComponentContext), + UNO_QUERY_THROW); + Reference<container::XIndexAccess> xSlides (mxSlideShowController, UNO_QUERY); + mxPreviewCache->setDocumentSlides(xSlides, rxController->getModel()); + mxPreviewCache->addPreviewCreationNotifyListener(this); + if (xSlides.is()) + { + mpLayout->mnSlideCount = xSlides->getCount(); + } + + // Create the mouse over manager. + mpMouseOverManager.reset(new MouseOverManager( + Reference<container::XIndexAccess>(mxSlideShowController, UNO_QUERY), + mpPresenterController->GetTheme(), + mxWindow, + mpPresenterController->GetPaintManager())); + + // Listen for changes of the current slide. + Reference<beans::XPropertySet> xControllerProperties (rxController, UNO_QUERY_THROW); + xControllerProperties->addPropertyChangeListener( + "CurrentPage", + this); + + // Move the current slide in the center of the window. + const awt::Rectangle aCurrentSlideBBox (mpLayout->GetBoundingBox(mnCurrentSlideIndex)); + const awt::Rectangle aWindowBox (mxWindow->getPosSize()); + SetHorizontalOffset(aCurrentSlideBBox.X - aWindowBox.Width/2.0); + } + catch (RuntimeException&) + { + disposing(); + throw; + } +} + +PresenterSlideSorter::~PresenterSlideSorter() +{ +} + +void SAL_CALL PresenterSlideSorter::disposing() +{ + mxComponentContext = nullptr; + mxViewId = nullptr; + mxPane = nullptr; + + if (mpVerticalScrollBar.is()) + { + Reference<lang::XComponent> xComponent = mpVerticalScrollBar; + mpVerticalScrollBar = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + if (mpCloseButton.is()) + { + Reference<lang::XComponent> xComponent = mpCloseButton; + mpCloseButton = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + + if (mxCanvas.is()) + { + Reference<lang::XComponent> xComponent (mxCanvas, UNO_QUERY); + if (xComponent.is()) + xComponent->removeEventListener(static_cast<awt::XWindowListener*>(this)); + mxCanvas = nullptr; + } + mpPresenterController = nullptr; + mxSlideShowController = nullptr; + mpLayout.reset(); + mpMouseOverManager.reset(); + + if (mxPreviewCache.is()) + { + mxPreviewCache->removePreviewCreationNotifyListener(this); + + Reference<XComponent> xComponent (mxPreviewCache, UNO_QUERY); + mxPreviewCache = nullptr; + if (xComponent.is()) + xComponent->dispose(); + } + + if (mxWindow.is()) + { + mxWindow->removeWindowListener(this); + mxWindow->removePaintListener(this); + mxWindow->removeMouseListener(this); + mxWindow->removeMouseMotionListener(this); + } +} + +//----- lang::XEventListener -------------------------------------------------- + +void SAL_CALL PresenterSlideSorter::disposing (const lang::EventObject& rEventObject) +{ + if (rEventObject.Source == mxWindow) + { + mxWindow = nullptr; + dispose(); + } + else if (rEventObject.Source == mxPreviewCache) + { + mxPreviewCache = nullptr; + dispose(); + } + else if (rEventObject.Source == mxCanvas) + { + mxCanvas = nullptr; + mbIsLayoutPending = true; + + mpPresenterController->GetPaintManager()->Invalidate(mxWindow); + } +} + +//----- XWindowListener ------------------------------------------------------- + +void SAL_CALL PresenterSlideSorter::windowResized (const awt::WindowEvent&) +{ + ThrowIfDisposed(); + mbIsLayoutPending = true; + mpPresenterController->GetPaintManager()->Invalidate(mxWindow); +} + +void SAL_CALL PresenterSlideSorter::windowMoved (const awt::WindowEvent&) +{ + ThrowIfDisposed(); +} + +void SAL_CALL PresenterSlideSorter::windowShown (const lang::EventObject&) +{ + ThrowIfDisposed(); + mbIsLayoutPending = true; + mpPresenterController->GetPaintManager()->Invalidate(mxWindow); +} + +void SAL_CALL PresenterSlideSorter::windowHidden (const lang::EventObject&) +{ + ThrowIfDisposed(); +} + +//----- XPaintListener -------------------------------------------------------- + +void SAL_CALL PresenterSlideSorter::windowPaint (const css::awt::PaintEvent& rEvent) +{ + // Deactivated views must not be painted. + if ( ! mbIsPresenterViewActive) + return; + + Paint(rEvent.UpdateRect); + + Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY); + if (xSpriteCanvas.is()) + xSpriteCanvas->updateScreen(false); +} + +//----- XMouseListener -------------------------------------------------------- + +void SAL_CALL PresenterSlideSorter::mousePressed (const css::awt::MouseEvent& rEvent) +{ + css::awt::MouseEvent rTemp =rEvent; + /// check whether RTL interface or not + if(AllSettings::GetLayoutRTL()){ + awt::Rectangle aBox = mxWindow->getPosSize(); + rTemp.X=aBox.Width-rEvent.X; + } + const geometry::RealPoint2D aPosition(rTemp.X, rEvent.Y); + mnSlideIndexMousePressed = mpLayout->GetSlideIndexForPosition(aPosition); +} + +void SAL_CALL PresenterSlideSorter::mouseReleased (const css::awt::MouseEvent& rEvent) +{ + css::awt::MouseEvent rTemp =rEvent; + /// check whether RTL interface or not + if(AllSettings::GetLayoutRTL()){ + awt::Rectangle aBox = mxWindow->getPosSize(); + rTemp.X=aBox.Width-rEvent.X; + } + const geometry::RealPoint2D aPosition(rTemp.X, rEvent.Y); + const sal_Int32 nSlideIndex (mpLayout->GetSlideIndexForPosition(aPosition)); + + if (nSlideIndex != mnSlideIndexMousePressed || mnSlideIndexMousePressed < 0) + return; + + switch (rEvent.ClickCount) + { + case 1: + default: + GotoSlide(nSlideIndex); + break; + + case 2: + OSL_ASSERT(mpPresenterController); + OSL_ASSERT(mpPresenterController->GetWindowManager()); + mpPresenterController->GetWindowManager()->SetSlideSorterState(false); + GotoSlide(nSlideIndex); + break; + } +} + +void SAL_CALL PresenterSlideSorter::mouseEntered (const css::awt::MouseEvent&) {} + +void SAL_CALL PresenterSlideSorter::mouseExited (const css::awt::MouseEvent&) +{ + mnSlideIndexMousePressed = -1; + if (mpMouseOverManager != nullptr) + mpMouseOverManager->SetSlide(mnSlideIndexMousePressed, awt::Rectangle(0,0,0,0)); +} + +//----- XMouseMotionListener -------------------------------------------------- + +void SAL_CALL PresenterSlideSorter::mouseMoved (const css::awt::MouseEvent& rEvent) +{ + if (mpMouseOverManager == nullptr) + return; + + css::awt::MouseEvent rTemp =rEvent; + /// check whether RTL interface or not + if(AllSettings::GetLayoutRTL()){ + awt::Rectangle aBox = mxWindow->getPosSize(); + rTemp.X=aBox.Width-rEvent.X; + } + const geometry::RealPoint2D aPosition(rTemp.X, rEvent.Y); + sal_Int32 nSlideIndex (mpLayout->GetSlideIndexForPosition(aPosition)); + + if (nSlideIndex < 0) + { + mnSlideIndexMousePressed = -1; + mpMouseOverManager->SetSlide(nSlideIndex, awt::Rectangle(0,0,0,0)); + } + else + { + mpMouseOverManager->SetSlide( + nSlideIndex, + mpLayout->GetBoundingBox(nSlideIndex)); + } +} + +void SAL_CALL PresenterSlideSorter::mouseDragged (const css::awt::MouseEvent&) {} + +//----- XResourceId ----------------------------------------------------------- + +Reference<XResourceId> SAL_CALL PresenterSlideSorter::getResourceId() +{ + ThrowIfDisposed(); + return mxViewId; +} + +sal_Bool SAL_CALL PresenterSlideSorter::isAnchorOnly() +{ + return false; +} + +//----- XPropertyChangeListener ----------------------------------------------- + +void SAL_CALL PresenterSlideSorter::propertyChange ( + const css::beans::PropertyChangeEvent&) +{} + +//----- XSlidePreviewCacheListener -------------------------------------------- + +void SAL_CALL PresenterSlideSorter::notifyPreviewCreation ( + sal_Int32 nSlideIndex) +{ + OSL_ASSERT(mpLayout != nullptr); + + awt::Rectangle aBBox (mpLayout->GetBoundingBox(nSlideIndex)); + mpPresenterController->GetPaintManager()->Invalidate(mxWindow, aBBox, true); +} + +//----- XDrawView ------------------------------------------------------------- + +void SAL_CALL PresenterSlideSorter::setCurrentPage (const Reference<drawing::XDrawPage>&) +{ + ThrowIfDisposed(); + ::osl::MutexGuard aGuard (::osl::Mutex::getGlobalMutex()); + + if (!mxSlideShowController.is()) + return; + + const sal_Int32 nNewCurrentSlideIndex (mxSlideShowController->getCurrentSlideIndex()); + if (nNewCurrentSlideIndex == mnCurrentSlideIndex) + return; + + mnCurrentSlideIndex = nNewCurrentSlideIndex; + + // Request a repaint of the previous current slide to hide its + // current slide indicator. + mpPresenterController->GetPaintManager()->Invalidate( + mxWindow, + maCurrentSlideFrameBoundingBox); + + // Request a repaint of the new current slide to show its + // current slide indicator. + maCurrentSlideFrameBoundingBox = mpCurrentSlideFrameRenderer->GetBoundingBox( + mpLayout->GetBoundingBox(mnCurrentSlideIndex)); + mpPresenterController->GetPaintManager()->Invalidate( + mxWindow, + maCurrentSlideFrameBoundingBox); +} + +Reference<drawing::XDrawPage> SAL_CALL PresenterSlideSorter::getCurrentPage() +{ + ThrowIfDisposed(); + return nullptr; +} + + +void PresenterSlideSorter::UpdateLayout() +{ + if ( ! mxWindow.is()) + return; + + mbIsLayoutPending = false; + + const awt::Rectangle aWindowBox (mxWindow->getPosSize()); + sal_Int32 nLeftBorderWidth (aWindowBox.X); + + // Get border width. + PresenterPaneContainer::SharedPaneDescriptor pPane ( + mpPresenterController->GetPaneContainer()->FindViewURL( + mxViewId->getResourceURL())); + do + { + if (!pPane) + break; + if ( ! pPane->mxPane.is()) + break; + + Reference<drawing::framework::XPaneBorderPainter> xBorderPainter ( + pPane->mxPane->GetPaneBorderPainter()); + if ( ! xBorderPainter.is()) + break; + xBorderPainter->addBorder ( + mxViewId->getAnchor()->getResourceURL(), + awt::Rectangle(0, 0, aWindowBox.Width, aWindowBox.Height), + drawing::framework::BorderType_INNER_BORDER); + } + while(false); + + // Place vertical separator. + mnSeparatorY = aWindowBox.Height - mpCloseButton->GetSize().Height - gnVerticalButtonPadding; + + PlaceCloseButton(pPane, aWindowBox, nLeftBorderWidth); + + geometry::RealRectangle2D aUpperBox( + gnHorizontalBorder, + gnVerticalBorder, + aWindowBox.Width - 2*gnHorizontalBorder, + mnSeparatorY - gnVerticalGap); + + // Determine whether the scroll bar has to be displayed. + aUpperBox = PlaceScrollBars(aUpperBox); + + mpLayout->Update(aUpperBox, GetSlideAspectRatio()); + mpLayout->SetupVisibleArea(); + mpLayout->UpdateScrollBars(); + + // Tell the preview cache about some of the values. + mxPreviewCache->setPreviewSize(mpLayout->maPreviewSize); + mxPreviewCache->setVisibleRange( + mpLayout->GetFirstVisibleSlideIndex(), + mpLayout->GetLastVisibleSlideIndex()); + + // Clear the frame polygon so that it is re-created on the next paint. + mxPreviewFrame = nullptr; +} + +geometry::RealRectangle2D PresenterSlideSorter::PlaceScrollBars ( + const geometry::RealRectangle2D& rUpperBox) +{ + mpLayout->Update(rUpperBox, GetSlideAspectRatio()); + bool bIsScrollBarNeeded (false); + Reference<container::XIndexAccess> xSlides (mxSlideShowController, UNO_QUERY_THROW); + bIsScrollBarNeeded = mpLayout->IsScrollBarNeeded(xSlides->getCount()); + if (mpVerticalScrollBar) + { + if (bIsScrollBarNeeded) + { + if(AllSettings::GetLayoutRTL()) + { + mpVerticalScrollBar->SetPosSize(geometry::RealRectangle2D( + rUpperBox.X1, + rUpperBox.Y1, + rUpperBox.X1 + mpVerticalScrollBar->GetSize(), + rUpperBox.Y2)); + mpVerticalScrollBar->SetVisible(true); + // Reduce area covered by the scroll bar from the available + // space. + return geometry::RealRectangle2D( + rUpperBox.X1 + gnHorizontalGap + mpVerticalScrollBar->GetSize(), + rUpperBox.Y1, + rUpperBox.X2, + rUpperBox.Y2); + } + else + { + // if it's not RTL place vertical scroll bar at right border. + mpVerticalScrollBar->SetPosSize(geometry::RealRectangle2D( + rUpperBox.X2 - mpVerticalScrollBar->GetSize(), + rUpperBox.Y1, + rUpperBox.X2, + rUpperBox.Y2)); + mpVerticalScrollBar->SetVisible(true); + // Reduce area covered by the scroll bar from the available + // space. + return geometry::RealRectangle2D( + rUpperBox.X1, + rUpperBox.Y1, + rUpperBox.X2 - mpVerticalScrollBar->GetSize() - gnHorizontalGap, + rUpperBox.Y2); + } + } + else + mpVerticalScrollBar->SetVisible(false); + } + return rUpperBox; +} + +void PresenterSlideSorter::PlaceCloseButton ( + const PresenterPaneContainer::SharedPaneDescriptor& rpPane, + const awt::Rectangle& rCenterBox, + const sal_Int32 nLeftBorderWidth) +{ + // Place button. When the callout is near the center then the button is + // centered over the callout. Otherwise it is centered with respect to + // the whole window. + sal_Int32 nCloseButtonCenter (rCenterBox.Width/2); + if (rpPane && rpPane->mxPane.is()) + { + const sal_Int32 nCalloutCenter (-nLeftBorderWidth); + const sal_Int32 nDistanceFromWindowCenter (abs(nCalloutCenter - rCenterBox.Width/2)); + const sal_Int32 nButtonWidth (mpCloseButton->GetSize().Width); + const static sal_Int32 nMaxDistanceForCalloutCentering (nButtonWidth * 2); + if (nDistanceFromWindowCenter < nMaxDistanceForCalloutCentering) + { + if (nCalloutCenter < nButtonWidth/2) + nCloseButtonCenter = nButtonWidth/2; + else if (nCalloutCenter > rCenterBox.Width-nButtonWidth/2) + nCloseButtonCenter = rCenterBox.Width-nButtonWidth/2; + else + nCloseButtonCenter = nCalloutCenter; + } + } + mpCloseButton->SetCenter(geometry::RealPoint2D( + nCloseButtonCenter, + rCenterBox.Height - mpCloseButton->GetSize().Height/ 2)); +} + +void PresenterSlideSorter::ClearBackground ( + const Reference<rendering::XCanvas>& rxCanvas, + const awt::Rectangle& rUpdateBox) +{ + OSL_ASSERT(rxCanvas.is()); + + const awt::Rectangle aWindowBox (mxWindow->getPosSize()); + mpPresenterController->GetCanvasHelper()->Paint( + mpPresenterController->GetViewBackground(mxViewId->getResourceURL()), + rxCanvas, + rUpdateBox, + awt::Rectangle(0,0,aWindowBox.Width,aWindowBox.Height), + awt::Rectangle()); +} + +double PresenterSlideSorter::GetSlideAspectRatio() const +{ + double nSlideAspectRatio (28.0/21.0); + + try + { + Reference<container::XIndexAccess> xSlides(mxSlideShowController, UNO_QUERY_THROW); + if (mxSlideShowController.is() && xSlides->getCount()>0) + { + Reference<beans::XPropertySet> xProperties(xSlides->getByIndex(0),UNO_QUERY_THROW); + sal_Int32 nWidth (28000); + sal_Int32 nHeight (21000); + if ((xProperties->getPropertyValue("Width") >>= nWidth) + && (xProperties->getPropertyValue("Height") >>= nHeight) + && nHeight > 0) + { + nSlideAspectRatio = double(nWidth) / double(nHeight); + } + } + } + catch (RuntimeException&) + { + OSL_ASSERT(false); + } + + return nSlideAspectRatio; +} + +Reference<rendering::XBitmap> PresenterSlideSorter::GetPreview (const sal_Int32 nSlideIndex) +{ + if (nSlideIndex < 0 || nSlideIndex>=mpLayout->mnSlideCount) + return nullptr; + else if (mxPane.is()) + return mxPreviewCache->getSlidePreview(nSlideIndex, mxPane->getCanvas()); + else + return nullptr; +} + +void PresenterSlideSorter::PaintPreview ( + const Reference<rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rUpdateBox, + const sal_Int32 nSlideIndex) +{ + OSL_ASSERT(rxCanvas.is()); + + geometry::IntegerSize2D aSize (mpLayout->maPreviewSize); + + if (PresenterGeometryHelper::AreRectanglesDisjoint( + rUpdateBox, + mpLayout->GetBoundingBox(nSlideIndex))) + { + return; + } + + Reference<rendering::XBitmap> xPreview (GetPreview(nSlideIndex)); + bool isRTL = AllSettings::GetLayoutRTL(); + + const geometry::RealPoint2D aTopLeft ( + mpLayout->GetWindowPosition( + mpLayout->GetPoint(nSlideIndex, isRTL?1:-1, -1))); + + PresenterBitmapContainer aContainer ( + "PresenterScreenSettings/ScrollBar/Bitmaps", + std::shared_ptr<PresenterBitmapContainer>(), + mxComponentContext, + rxCanvas); + Reference<container::XIndexAccess> xIndexAccess(mxSlideShowController, UNO_QUERY); + Reference<drawing::XDrawPage> xPage( xIndexAccess->getByIndex(nSlideIndex), UNO_QUERY); + bool bTransition = PresenterController::HasTransition(xPage); + bool bCustomAnimation = PresenterController::HasCustomAnimation(xPage); + + // Create clip rectangle as intersection of the current update area and + // the bounding box of all previews. + geometry::RealRectangle2D aBoundingBox (mpLayout->maBoundingBox); + aBoundingBox.Y2 += 1; + const geometry::RealRectangle2D aClipBox ( + PresenterGeometryHelper::Intersection( + PresenterGeometryHelper::ConvertRectangle(rUpdateBox), + aBoundingBox)); + Reference<rendering::XPolyPolygon2D> xClip ( + PresenterGeometryHelper::CreatePolygon(aClipBox, rxCanvas->getDevice())); + + const rendering::ViewState aViewState (geometry::AffineMatrix2D(1,0,0, 0,1,0), xClip); + + rendering::RenderState aRenderState ( + geometry::AffineMatrix2D( + 1, 0, aTopLeft.X, + 0, 1, aTopLeft.Y), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + + // Emphasize the current slide. + if (nSlideIndex == mnCurrentSlideIndex) + { + if (mpCurrentSlideFrameRenderer != nullptr) + { + const awt::Rectangle aSlideBoundingBox( + sal::static_int_cast<sal_Int32>(0.5 + aTopLeft.X), + sal::static_int_cast<sal_Int32>(0.5 + aTopLeft.Y), + aSize.Width, + aSize.Height); + maCurrentSlideFrameBoundingBox + = mpCurrentSlideFrameRenderer->GetBoundingBox(aSlideBoundingBox); + mpCurrentSlideFrameRenderer->PaintCurrentSlideFrame ( + aSlideBoundingBox, + mxCanvas, + aClipBox); + } + } + + // Paint the preview. + if (xPreview.is()) + { + aSize = xPreview->getSize(); + if (aSize.Width > 0 && aSize.Height > 0) + { + rxCanvas->drawBitmap(xPreview, aViewState, aRenderState); + if( bCustomAnimation ) + { + const awt::Rectangle aAnimationPreviewBox(aTopLeft.X+3, aTopLeft.Y+aSize.Height-40, 0, 0); + SharedBitmapDescriptor aAnimationDescriptor = aContainer.GetBitmap("Animation"); + Reference<rendering::XBitmap> xAnimationIcon (aAnimationDescriptor->GetNormalBitmap()); + rendering::RenderState aAnimationRenderState ( + geometry::AffineMatrix2D( + 1, 0, aAnimationPreviewBox.X, + 0, 1, aAnimationPreviewBox.Y), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + rxCanvas->drawBitmap(xAnimationIcon, aViewState, aAnimationRenderState); + } + if( bTransition ) + { + const awt::Rectangle aTransitionPreviewBox(aTopLeft.X+3, aTopLeft.Y+aSize.Height-20, 0, 0); + SharedBitmapDescriptor aTransitionDescriptor = aContainer.GetBitmap("Transition"); + Reference<rendering::XBitmap> xTransitionIcon (aTransitionDescriptor->GetNormalBitmap()); + rendering::RenderState aTransitionRenderState ( + geometry::AffineMatrix2D( + 1, 0, aTransitionPreviewBox.X, + 0, 1, aTransitionPreviewBox.Y), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + rxCanvas->drawBitmap(xTransitionIcon, aViewState, aTransitionRenderState); + } + } + } + + // Create a polygon that is used to paint a frame around previews. Its + // coordinates are chosen in the local coordinate system of a preview. + if ( ! mxPreviewFrame.is()) + mxPreviewFrame = PresenterGeometryHelper::CreatePolygon( + awt::Rectangle(-1, -1, aSize.Width+2, aSize.Height+2), + rxCanvas->getDevice()); + + // Paint a border around the preview. + if (mxPreviewFrame.is()) + { + const util::Color aFrameColor (0x00000000); + PresenterCanvasHelper::SetDeviceColor(aRenderState, aFrameColor); + rxCanvas->drawPolyPolygon(mxPreviewFrame, aViewState, aRenderState); + } + + // Paint mouse over effect. + mpMouseOverManager->Paint(nSlideIndex, mxCanvas, xClip); +} + +void PresenterSlideSorter::Paint (const awt::Rectangle& rUpdateBox) +{ + const bool bCanvasChanged ( ! mxCanvas.is()); + if ( ! ProvideCanvas()) + return; + + if (mpLayout->mnRowCount<=0 || mpLayout->mnColumnCount<=0) + { + OSL_ASSERT(mpLayout->mnRowCount>0 || mpLayout->mnColumnCount>0); + return; + } + + ClearBackground(mxCanvas, rUpdateBox); + + // Give the canvas to the controls. + if (bCanvasChanged) + { + if (mpVerticalScrollBar.is()) + mpVerticalScrollBar->SetCanvas(mxCanvas); + if (mpCloseButton.is()) + mpCloseButton->SetCanvas(mxCanvas, mxWindow); + } + + // Now that the controls have a canvas we can do the layouting. + if (mbIsLayoutPending) + UpdateLayout(); + + // Paint the horizontal separator. + rendering::RenderState aRenderState (geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr, Sequence<double>(4), rendering::CompositeOperation::SOURCE); + PresenterCanvasHelper::SetDeviceColor(aRenderState, maSeparatorColor); + mxCanvas->drawLine( + geometry::RealPoint2D(0, mnSeparatorY), + geometry::RealPoint2D(mxWindow->getPosSize().Width, mnSeparatorY), + rendering::ViewState(geometry::AffineMatrix2D(1,0,0, 0,1,0), nullptr), + aRenderState); + + // Paint the slides. + if ( ! PresenterGeometryHelper::AreRectanglesDisjoint( + rUpdateBox, + PresenterGeometryHelper::ConvertRectangle(mpLayout->maBoundingBox))) + { + mpLayout->ForAllVisibleSlides( + [this, &rUpdateBox] (sal_Int32 const nIndex) { + return this->PaintPreview(this->mxCanvas, rUpdateBox, nIndex); + }); + } + + Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY); + if (xSpriteCanvas.is()) + xSpriteCanvas->updateScreen(false); +} + +void PresenterSlideSorter::SetHorizontalOffset (const double nXOffset) +{ + if (mpLayout->SetHorizontalOffset(nXOffset)) + { + mxPreviewCache->setVisibleRange( + mpLayout->GetFirstVisibleSlideIndex(), + mpLayout->GetLastVisibleSlideIndex()); + + mpPresenterController->GetPaintManager()->Invalidate(mxWindow); + } +} + +void PresenterSlideSorter::SetVerticalOffset (const double nYOffset) +{ + if (mpLayout->SetVerticalOffset(nYOffset)) + { + mxPreviewCache->setVisibleRange( + mpLayout->GetFirstVisibleSlideIndex(), + mpLayout->GetLastVisibleSlideIndex()); + + mpPresenterController->GetPaintManager()->Invalidate(mxWindow); + } +} + +void PresenterSlideSorter::GotoSlide (const sal_Int32 nSlideIndex) +{ + mxSlideShowController->gotoSlideIndex(nSlideIndex); +} + +bool PresenterSlideSorter::ProvideCanvas() +{ + if ( ! mxCanvas.is()) + { + if (mxPane.is()) + mxCanvas = mxPane->getCanvas(); + + // Register as event listener so that we are informed when the + // canvas is disposed (and we have to fetch another one). + Reference<lang::XComponent> xComponent (mxCanvas, UNO_QUERY); + if (xComponent.is()) + xComponent->addEventListener(static_cast<awt::XWindowListener*>(this)); + + mpCurrentSlideFrameRenderer = + std::make_shared<CurrentSlideFrameRenderer>(mxComponentContext, mxCanvas); + } + return mxCanvas.is(); +} + +void PresenterSlideSorter::ThrowIfDisposed() +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ( + "PresenterSlideSorter has been already disposed", + static_cast<uno::XWeak*>(this)); + } +} + +//===== PresenterSlideSorter::Layout ========================================== + +PresenterSlideSorter::Layout::Layout ( + const ::rtl::Reference<PresenterScrollBar>& rpVerticalScrollBar) + : mnHorizontalOffset(0), + mnVerticalOffset(0), + mnHorizontalGap(0), + mnVerticalGap(0), + mnHorizontalBorder(0), + mnVerticalBorder(0), + mnRowCount(1), + mnColumnCount(1), + mnSlideCount(0), + mnFirstVisibleColumn(-1), + mnLastVisibleColumn(-1), + mnFirstVisibleRow(-1), + mnLastVisibleRow(-1), + mpVerticalScrollBar(rpVerticalScrollBar) +{ +} + +void PresenterSlideSorter::Layout::Update ( + const geometry::RealRectangle2D& rBoundingBox, + const double nSlideAspectRatio) +{ + maBoundingBox = rBoundingBox; + + mnHorizontalBorder = gnHorizontalBorder; + mnVerticalBorder = gnVerticalBorder; + + const double nWidth (rBoundingBox.X2 - rBoundingBox.X1 - 2*mnHorizontalBorder); + const double nHeight (rBoundingBox.Y2 - rBoundingBox.Y1 - 2*mnVerticalBorder); + if (nWidth<=0 || nHeight<=0) + return; + + double nPreviewWidth; + + // Determine column count, preview width, and horizontal gap (borders + // are half the gap). Try to use the preferred values. Try more to + // stay in the valid intervals. This last constraint may be not + // fulfilled in some cases. + const double nElementWidth = nWidth / gnPreferredColumnCount; + if (nElementWidth < gnMinimalPreviewWidth + gnMinimalHorizontalPreviewGap) + { + // The preferred column count is too large. + // Can we use the preferred preview width? + if (nWidth - gnMinimalHorizontalPreviewGap >= gnPreferredPreviewWidth) + { + // Yes. + nPreviewWidth = gnPreferredPreviewWidth; + mnColumnCount = floor((nWidth+gnPreferredHorizontalPreviewGap) + / (nPreviewWidth+gnPreferredHorizontalPreviewGap)); + mnHorizontalGap = round((nWidth - mnColumnCount*nPreviewWidth) / mnColumnCount); + } + else + { + // No. Set the column count to 1 and adapt preview width and + // gap. + mnColumnCount = 1; + mnHorizontalGap = floor(gnMinimalHorizontalPreviewGap); + if (nWidth - gnMinimalHorizontalPreviewGap >= gnPreferredPreviewWidth) + nPreviewWidth = nWidth - gnMinimalHorizontalPreviewGap; + else + nPreviewWidth = ::std::max(gnMinimalPreviewWidth, nWidth-mnHorizontalGap); + } + } + else if (nElementWidth > gnMaximalPreviewWidth + gnMaximalHorizontalPreviewGap) + { + // The preferred column count is too small. + nPreviewWidth = gnPreferredPreviewWidth; + mnColumnCount = floor((nWidth+gnPreferredHorizontalPreviewGap) + / (nPreviewWidth+gnPreferredHorizontalPreviewGap)); + mnHorizontalGap = round((nWidth - mnColumnCount*nPreviewWidth) / mnColumnCount); + } + else + { + // The preferred column count is possible. Determine gap and + // preview width. + mnColumnCount = gnPreferredColumnCount; + if (nElementWidth - gnPreferredPreviewWidth < gnMinimalHorizontalPreviewGap) + { + // Use the minimal gap and adapt the preview width. + mnHorizontalGap = floor(gnMinimalHorizontalPreviewGap); + nPreviewWidth = (nWidth - mnColumnCount*mnHorizontalGap) / mnColumnCount; + } + else if (nElementWidth - gnPreferredPreviewWidth <= gnMaximalHorizontalPreviewGap) + { + // Use the maximal gap and adapt the preview width. + mnHorizontalGap = round(gnMaximalHorizontalPreviewGap); + nPreviewWidth = (nWidth - mnColumnCount*mnHorizontalGap) / mnColumnCount; + } + else + { + // Use the preferred preview width and adapt the gap. + nPreviewWidth = gnPreferredPreviewWidth; + mnHorizontalGap = round((nWidth - mnColumnCount*nPreviewWidth) / mnColumnCount); + } + } + + // Now determine the row count, preview height, and vertical gap. + const double nPreviewHeight = nPreviewWidth / nSlideAspectRatio; + mnRowCount = ::std::max( + sal_Int32(1), + sal_Int32(ceil((nHeight+gnPreferredVerticalPreviewGap) + / (nPreviewHeight + gnPreferredVerticalPreviewGap)))); + mnVerticalGap = round(gnPreferredVerticalPreviewGap); + + maPreviewSize = geometry::IntegerSize2D(floor(nPreviewWidth), floor(nPreviewHeight)); + + // Reset the offset. + mnVerticalOffset = 0; + mnHorizontalOffset = round(-(nWidth + - mnColumnCount*maPreviewSize.Width + - (mnColumnCount-1)*mnHorizontalGap) + / 2); +} + +void PresenterSlideSorter::Layout::SetupVisibleArea() +{ + geometry::RealPoint2D aPoint (GetLocalPosition( + geometry::RealPoint2D(maBoundingBox.X1, maBoundingBox.Y1))); + mnFirstVisibleColumn = 0; + mnFirstVisibleRow = ::std::max(sal_Int32(0), GetRow(aPoint)); + + aPoint = GetLocalPosition(geometry::RealPoint2D( maBoundingBox.X2, maBoundingBox.Y2)); + mnLastVisibleColumn = mnColumnCount - 1; + mnLastVisibleRow = GetRow(aPoint, true); +} + +bool PresenterSlideSorter::Layout::IsScrollBarNeeded (const sal_Int32 nSlideCount) +{ + geometry::RealPoint2D aBottomRight = GetPoint( + mnColumnCount * (GetRow(nSlideCount)+1) - 1, +1, +1); + return aBottomRight.X > maBoundingBox.X2-maBoundingBox.X1 + || aBottomRight.Y > maBoundingBox.Y2-maBoundingBox.Y1; +} + +geometry::RealPoint2D PresenterSlideSorter::Layout::GetLocalPosition( + const geometry::RealPoint2D& rWindowPoint) const +{ + if(AllSettings::GetLayoutRTL()) + { + return css::geometry::RealPoint2D( + -rWindowPoint.X + maBoundingBox.X2 + mnHorizontalOffset, + rWindowPoint.Y - maBoundingBox.Y1 + mnVerticalOffset); + } + else + { + return css::geometry::RealPoint2D( + rWindowPoint.X - maBoundingBox.X1 + mnHorizontalOffset, + rWindowPoint.Y - maBoundingBox.Y1 + mnVerticalOffset); + } +} + +geometry::RealPoint2D PresenterSlideSorter::Layout::GetWindowPosition( + const geometry::RealPoint2D& rLocalPoint) const +{ + if(AllSettings::GetLayoutRTL()) + { + return css::geometry::RealPoint2D( + -rLocalPoint.X + mnHorizontalOffset + maBoundingBox.X2, + rLocalPoint.Y - mnVerticalOffset + maBoundingBox.Y1); + } + else + { + return css::geometry::RealPoint2D( + rLocalPoint.X - mnHorizontalOffset + maBoundingBox.X1, + rLocalPoint.Y - mnVerticalOffset + maBoundingBox.Y1); + } +} + +sal_Int32 PresenterSlideSorter::Layout::GetColumn ( + const css::geometry::RealPoint2D& rLocalPoint) const +{ + const sal_Int32 nColumn(floor( + (rLocalPoint.X + mnHorizontalGap/2.0) / (maPreviewSize.Width+mnHorizontalGap))); + if (nColumn>=mnFirstVisibleColumn && nColumn<=mnLastVisibleColumn) + { + return nColumn; + } + else + return -1; +} + +sal_Int32 PresenterSlideSorter::Layout::GetRow ( + const css::geometry::RealPoint2D& rLocalPoint, + const bool bReturnInvalidValue) const +{ + const sal_Int32 nRow (floor( + (rLocalPoint.Y + mnVerticalGap/2.0) / (maPreviewSize.Height+mnVerticalGap))); + if (bReturnInvalidValue + || (nRow>=mnFirstVisibleRow && nRow<=mnLastVisibleRow)) + { + return nRow; + } + else + return -1; +} + +sal_Int32 PresenterSlideSorter::Layout::GetSlideIndexForPosition ( + const css::geometry::RealPoint2D& rWindowPoint) const +{ + if ( ! PresenterGeometryHelper::IsInside(maBoundingBox, rWindowPoint)) + return -1; + + const css::geometry::RealPoint2D aLocalPosition (GetLocalPosition(rWindowPoint)); + const sal_Int32 nColumn (GetColumn(aLocalPosition)); + const sal_Int32 nRow (GetRow(aLocalPosition)); + + if (nColumn < 0 || nRow < 0) + return -1; + else + { + sal_Int32 nIndex (GetIndex(nRow, nColumn)); + if (nIndex >= mnSlideCount) + return -1; + else + return nIndex; + } +} + +geometry::RealPoint2D PresenterSlideSorter::Layout::GetPoint ( + const sal_Int32 nSlideIndex, + const sal_Int32 nRelativeHorizontalPosition, + const sal_Int32 nRelativeVerticalPosition) const +{ + sal_Int32 nColumn (GetColumn(nSlideIndex)); + sal_Int32 nRow (GetRow(nSlideIndex)); + + geometry::RealPoint2D aPosition ( + mnHorizontalBorder + nColumn*(maPreviewSize.Width+mnHorizontalGap), + mnVerticalBorder + nRow*(maPreviewSize.Height+mnVerticalGap)); + + if (nRelativeHorizontalPosition >= 0) + { + if (nRelativeHorizontalPosition > 0) + aPosition.X += maPreviewSize.Width; + else + aPosition.X += maPreviewSize.Width / 2.0; + } + if (nRelativeVerticalPosition >= 0) + { + if (nRelativeVerticalPosition > 0) + aPosition.Y += maPreviewSize.Height; + else + aPosition.Y += maPreviewSize.Height / 2.0; + } + + return aPosition; +} + +awt::Rectangle PresenterSlideSorter::Layout::GetBoundingBox (const sal_Int32 nSlideIndex) const +{ + bool isRTL = AllSettings::GetLayoutRTL(); + const geometry::RealPoint2D aWindowPosition(GetWindowPosition(GetPoint(nSlideIndex, isRTL?1:-1, -1))); + return PresenterGeometryHelper::ConvertRectangle( + geometry::RealRectangle2D( + aWindowPosition.X, + aWindowPosition.Y, + aWindowPosition.X + maPreviewSize.Width, + aWindowPosition.Y + maPreviewSize.Height)); +} + +void PresenterSlideSorter::Layout::ForAllVisibleSlides( + const ::std::function<void (sal_Int32)>& rAction) +{ + for (sal_Int32 nRow=mnFirstVisibleRow; nRow<=mnLastVisibleRow; ++nRow) + { + for (sal_Int32 nColumn=mnFirstVisibleColumn; nColumn<=mnLastVisibleColumn; ++nColumn) + { + const sal_Int32 nSlideIndex (GetIndex(nRow, nColumn)); + if (nSlideIndex >= mnSlideCount) + return; + rAction(nSlideIndex); + } + } +} + +sal_Int32 PresenterSlideSorter::Layout::GetFirstVisibleSlideIndex() const +{ + return GetIndex(mnFirstVisibleRow, mnFirstVisibleColumn); +} + +sal_Int32 PresenterSlideSorter::Layout::GetLastVisibleSlideIndex() const +{ + return ::std::min( + GetIndex(mnLastVisibleRow, mnLastVisibleColumn), + mnSlideCount); +} + +bool PresenterSlideSorter::Layout::SetHorizontalOffset (const double nOffset) +{ + if (mnHorizontalOffset != nOffset) + { + mnHorizontalOffset = round(nOffset); + SetupVisibleArea(); + UpdateScrollBars(); + return true; + } + else + return false; +} + +bool PresenterSlideSorter::Layout::SetVerticalOffset (const double nOffset) +{ + if (mnVerticalOffset != nOffset) + { + mnVerticalOffset = round(nOffset); + SetupVisibleArea(); + UpdateScrollBars(); + return true; + } + else + return false; +} + +void PresenterSlideSorter::Layout::UpdateScrollBars() +{ + sal_Int32 nTotalRowCount = sal_Int32(ceil(double(mnSlideCount) / double(mnColumnCount))); + + if (mpVerticalScrollBar) + { + mpVerticalScrollBar->SetTotalSize( + nTotalRowCount * maPreviewSize.Height + + (nTotalRowCount-1) * mnVerticalGap + + 2*mnVerticalGap); + mpVerticalScrollBar->SetThumbPosition(mnVerticalOffset, false); + mpVerticalScrollBar->SetThumbSize(maBoundingBox.Y2 - maBoundingBox.Y1 + 1); + mpVerticalScrollBar->SetLineHeight(maPreviewSize.Height); + } + + // No place yet for the vertical scroll bar. +} + +sal_Int32 PresenterSlideSorter::Layout::GetIndex ( + const sal_Int32 nRow, + const sal_Int32 nColumn) const +{ + return nRow * mnColumnCount + nColumn; +} + +sal_Int32 PresenterSlideSorter::Layout::GetRow (const sal_Int32 nSlideIndex) const +{ + return nSlideIndex / mnColumnCount; +} + +sal_Int32 PresenterSlideSorter::Layout::GetColumn (const sal_Int32 nSlideIndex) const +{ + return nSlideIndex % mnColumnCount; +} + +//===== PresenterSlideSorter::MouseOverManager ================================ + +PresenterSlideSorter::MouseOverManager::MouseOverManager ( + const Reference<container::XIndexAccess>& rxSlides, + const std::shared_ptr<PresenterTheme>& rpTheme, + const Reference<awt::XWindow>& rxInvalidateTarget, + const std::shared_ptr<PresenterPaintManager>& rpPaintManager) + : mxSlides(rxSlides), + mnSlideIndex(-1), + mxInvalidateTarget(rxInvalidateTarget), + mpPaintManager(rpPaintManager) +{ + if (rpTheme != nullptr) + { + std::shared_ptr<PresenterBitmapContainer> pBitmaps (rpTheme->GetBitmapContainer()); + if (pBitmaps != nullptr) + { + mpLeftLabelBitmap = pBitmaps->GetBitmap("LabelLeft"); + mpCenterLabelBitmap = pBitmaps->GetBitmap("LabelCenter"); + mpRightLabelBitmap = pBitmaps->GetBitmap("LabelRight"); + } + + mpFont = rpTheme->GetFont("SlideSorterLabelFont"); + } +} + +void PresenterSlideSorter::MouseOverManager::Paint ( + const sal_Int32 nSlideIndex, + const Reference<rendering::XCanvas>& rxCanvas, + const Reference<rendering::XPolyPolygon2D>& rxClip) +{ + if (nSlideIndex != mnSlideIndex) + return; + + if (mxCanvas != rxCanvas) + SetCanvas(rxCanvas); + if (rxCanvas == nullptr) + return; + + if ( ! mxBitmap.is()) + mxBitmap = CreateBitmap(msText, maSlideBoundingBox.Width); + if (!mxBitmap.is()) + return; + + geometry::IntegerSize2D aSize (mxBitmap->getSize()); + const double nXOffset (maSlideBoundingBox.X + + (maSlideBoundingBox.Width - aSize.Width) / 2.0); + const double nYOffset (maSlideBoundingBox.Y + + (maSlideBoundingBox.Height - aSize.Height) / 2.0); + rxCanvas->drawBitmap( + mxBitmap, + rendering::ViewState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + rxClip), + rendering::RenderState( + geometry::AffineMatrix2D(1,0,nXOffset, 0,1,nYOffset), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE)); +} + +void PresenterSlideSorter::MouseOverManager::SetCanvas ( + const Reference<rendering::XCanvas>& rxCanvas) +{ + mxCanvas = rxCanvas; + if (mpFont) + mpFont->PrepareFont(mxCanvas); +} + +void PresenterSlideSorter::MouseOverManager::SetSlide ( + const sal_Int32 nSlideIndex, + const awt::Rectangle& rBox) +{ + if (mnSlideIndex == nSlideIndex) + return; + + mnSlideIndex = -1; + Invalidate(); + + maSlideBoundingBox = rBox; + mnSlideIndex = nSlideIndex; + + if (nSlideIndex >= 0) + { + if (mxSlides) + { + msText.clear(); + + Reference<beans::XPropertySet> xSlideProperties(mxSlides->getByIndex(nSlideIndex), UNO_QUERY); + if (xSlideProperties.is()) + xSlideProperties->getPropertyValue("LinkDisplayName") >>= msText; + + if (msText.isEmpty()) + msText = "Slide " + OUString::number(nSlideIndex + 1); + } + } + else + { + msText.clear(); + } + mxBitmap = nullptr; + + Invalidate(); +} + +Reference<rendering::XBitmap> PresenterSlideSorter::MouseOverManager::CreateBitmap ( + const OUString& rsText, + const sal_Int32 nMaximalWidth) const +{ + if ( ! mxCanvas.is()) + return nullptr; + + if (!mpFont || !mpFont->mxFont.is()) + return nullptr; + + // Long text has to be shortened. + const OUString sText (GetFittingText(rsText, nMaximalWidth + - 2*gnHorizontalLabelBorder + - 2*gnHorizontalLabelPadding)); + + // Determine the size of the label. Its height is defined by the + // bitmaps that are used to paints its background. The width is defined + // by the text. + geometry::IntegerSize2D aLabelSize (CalculateLabelSize(sText)); + + // Create a new bitmap that will contain the complete label. + Reference<rendering::XBitmap> xBitmap ( + mxCanvas->getDevice()->createCompatibleAlphaBitmap(aLabelSize)); + + if ( ! xBitmap.is()) + return nullptr; + + Reference<rendering::XBitmapCanvas> xBitmapCanvas (xBitmap, UNO_QUERY); + if ( ! xBitmapCanvas.is()) + return nullptr; + + // Paint the background. + PaintButtonBackground(xBitmapCanvas, aLabelSize); + + // Paint the text. + if (!sText.isEmpty()) + { + + const rendering::StringContext aContext (sText, 0, sText.getLength()); + const Reference<rendering::XTextLayout> xLayout (mpFont->mxFont->createTextLayout( + aContext, rendering::TextDirection::WEAK_LEFT_TO_RIGHT,0)); + const geometry::RealRectangle2D aTextBBox (xLayout->queryTextBounds()); + + const double nXOffset = (aLabelSize.Width - aTextBBox.X2 + aTextBBox.X1) / 2; + const double nYOffset = aLabelSize.Height + - (aLabelSize.Height - aTextBBox.Y2 + aTextBBox.Y1)/2 - aTextBBox.Y2; + + const rendering::ViewState aViewState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr); + + rendering::RenderState aRenderState ( + geometry::AffineMatrix2D(1,0,nXOffset, 0,1,nYOffset), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor); + + xBitmapCanvas->drawTextLayout ( + xLayout, + aViewState, + aRenderState); + } + + return xBitmap; +} + +OUString PresenterSlideSorter::MouseOverManager::GetFittingText ( + const OUString& rsText, + const double nMaximalWidth) const +{ + const double nTextWidth ( + PresenterCanvasHelper::GetTextSize(mpFont->mxFont, rsText).Width); + if (nTextWidth > nMaximalWidth) + { + // Text is too wide. Shorten it by removing characters from the end + // and replacing them by ellipses. + + // Guess a start value of the final string length. + double nBestWidth (0); + OUString sBestCandidate; + sal_Int32 nLength (round(rsText.getLength() * nMaximalWidth / nTextWidth)); + static const OUStringLiteral sEllipses (u"..."); + while (true) + { + const OUString sCandidate (rsText.subView(0,nLength) + sEllipses); + const double nWidth ( + PresenterCanvasHelper::GetTextSize(mpFont->mxFont, sCandidate).Width); + if (nWidth > nMaximalWidth) + { + // Candidate still too wide, shorten it. + nLength -= 1; + if (nLength <= 0) + break; + } + else if (nWidth < nMaximalWidth) + { + // Candidate short enough. + if (nWidth > nBestWidth) + { + // Best length so far. + sBestCandidate = sCandidate; + nBestWidth = nWidth; + nLength += 1; + if (nLength >= rsText.getLength()) + break; + } + else + break; + } + else + { + // Candidate is exactly as long as it may be. Use it + // without looking any further. + sBestCandidate = sCandidate; + break; + } + } + return sBestCandidate; + } + else + return rsText; +} + +geometry::IntegerSize2D PresenterSlideSorter::MouseOverManager::CalculateLabelSize ( + const OUString& rsText) const +{ + // Height is specified by the label bitmaps. + sal_Int32 nHeight (32); + if (mpCenterLabelBitmap) + { + Reference<rendering::XBitmap> xBitmap (mpCenterLabelBitmap->GetNormalBitmap()); + if (xBitmap.is()) + nHeight = xBitmap->getSize().Height; + } + + // Width is specified by text width and maximal width. + const geometry::RealSize2D aTextSize ( + PresenterCanvasHelper::GetTextSize(mpFont->mxFont, rsText)); + + const sal_Int32 nWidth (round(aTextSize.Width + 2*gnHorizontalLabelPadding)); + + return geometry::IntegerSize2D(nWidth, nHeight); +} + +void PresenterSlideSorter::MouseOverManager::PaintButtonBackground ( + const Reference<rendering::XCanvas>& rxCanvas, + const geometry::IntegerSize2D& rSize) const +{ + // Get the bitmaps for painting the label background. + Reference<rendering::XBitmap> xLeftLabelBitmap; + if (mpLeftLabelBitmap) + xLeftLabelBitmap = mpLeftLabelBitmap->GetNormalBitmap(); + + Reference<rendering::XBitmap> xCenterLabelBitmap; + if (mpCenterLabelBitmap) + xCenterLabelBitmap = mpCenterLabelBitmap->GetNormalBitmap(); + + Reference<rendering::XBitmap> xRightLabelBitmap; + if (mpRightLabelBitmap) + xRightLabelBitmap = mpRightLabelBitmap->GetNormalBitmap(); + + PresenterUIPainter::PaintHorizontalBitmapComposite ( + rxCanvas, + awt::Rectangle(0,0, rSize.Width,rSize.Height), + awt::Rectangle(0,0, rSize.Width,rSize.Height), + xLeftLabelBitmap, + xCenterLabelBitmap, + xRightLabelBitmap); +} + +void PresenterSlideSorter::MouseOverManager::Invalidate() +{ + if (mpPaintManager != nullptr) + mpPaintManager->Invalidate(mxInvalidateTarget, maSlideBoundingBox, true); +} + +//===== PresenterSlideSorter::CurrentSlideFrameRenderer ======================= + +PresenterSlideSorter::CurrentSlideFrameRenderer::CurrentSlideFrameRenderer ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas) + : mnTopFrameSize(0), + mnLeftFrameSize(0), + mnRightFrameSize(0), + mnBottomFrameSize(0) +{ + PresenterConfigurationAccess aConfiguration ( + rxContext, + "/org.openoffice.Office.PresenterScreen/", + PresenterConfigurationAccess::READ_ONLY); + Reference<container::XHierarchicalNameAccess> xBitmaps ( + aConfiguration.GetConfigurationNode( + "PresenterScreenSettings/SlideSorter/CurrentSlideBorderBitmaps"), + UNO_QUERY); + if ( ! xBitmaps.is()) + return; + + PresenterBitmapContainer aContainer ( + "PresenterScreenSettings/SlideSorter/CurrentSlideBorderBitmaps", + std::shared_ptr<PresenterBitmapContainer>(), + rxContext, + rxCanvas); + + mpTopLeft = aContainer.GetBitmap("TopLeft"); + mpTop = aContainer.GetBitmap("Top"); + mpTopRight = aContainer.GetBitmap("TopRight"); + mpLeft = aContainer.GetBitmap("Left"); + mpRight = aContainer.GetBitmap("Right"); + mpBottomLeft = aContainer.GetBitmap("BottomLeft"); + mpBottom = aContainer.GetBitmap("Bottom"); + mpBottomRight = aContainer.GetBitmap("BottomRight"); + + // Determine size of frame. + if (mpTop) + mnTopFrameSize = mpTop->mnHeight; + if (mpLeft) + mnLeftFrameSize = mpLeft->mnWidth; + if (mpRight) + mnRightFrameSize = mpRight->mnWidth; + if (mpBottom) + mnBottomFrameSize = mpBottom->mnHeight; + + if (mpTopLeft) + { + mnTopFrameSize = ::std::max(mnTopFrameSize, mpTopLeft->mnHeight); + mnLeftFrameSize = ::std::max(mnLeftFrameSize, mpTopLeft->mnWidth); + } + if (mpTopRight) + { + mnTopFrameSize = ::std::max(mnTopFrameSize, mpTopRight->mnHeight); + mnRightFrameSize = ::std::max(mnRightFrameSize, mpTopRight->mnWidth); + } + if (mpBottomLeft) + { + mnLeftFrameSize = ::std::max(mnLeftFrameSize, mpBottomLeft->mnWidth); + mnBottomFrameSize = ::std::max(mnBottomFrameSize, mpBottomLeft->mnHeight); + } + if (mpBottomRight) + { + mnRightFrameSize = ::std::max(mnRightFrameSize, mpBottomRight->mnWidth); + mnBottomFrameSize = ::std::max(mnBottomFrameSize, mpBottomRight->mnHeight); + } +} + +void PresenterSlideSorter::CurrentSlideFrameRenderer::PaintCurrentSlideFrame ( + const awt::Rectangle& rSlideBoundingBox, + const Reference<rendering::XCanvas>& rxCanvas, + const geometry::RealRectangle2D& rClipBox) +{ + if ( ! rxCanvas.is()) + return; + + const Reference<rendering::XPolyPolygon2D> xClip ( + PresenterGeometryHelper::CreatePolygon(rClipBox, rxCanvas->getDevice())); + + if (mpTop) + { + PaintBitmapTiled( + mpTop->GetNormalBitmap(), + rxCanvas, + rClipBox, + rSlideBoundingBox.X, + rSlideBoundingBox.Y - mpTop->mnHeight, + rSlideBoundingBox.Width, + mpTop->mnHeight); + } + if (mpLeft) + { + PaintBitmapTiled( + mpLeft->GetNormalBitmap(), + rxCanvas, + rClipBox, + rSlideBoundingBox.X - mpLeft->mnWidth, + rSlideBoundingBox.Y, + mpLeft->mnWidth, + rSlideBoundingBox.Height); + } + if (mpRight) + { + PaintBitmapTiled( + mpRight->GetNormalBitmap(), + rxCanvas, + rClipBox, + rSlideBoundingBox.X + rSlideBoundingBox.Width, + rSlideBoundingBox.Y, + mpRight->mnWidth, + rSlideBoundingBox.Height); + } + if (mpBottom) + { + PaintBitmapTiled( + mpBottom->GetNormalBitmap(), + rxCanvas, + rClipBox, + rSlideBoundingBox.X, + rSlideBoundingBox.Y + rSlideBoundingBox.Height, + rSlideBoundingBox.Width, + mpBottom->mnHeight); + } + if (mpTopLeft) + { + PaintBitmapOnce( + mpTopLeft->GetNormalBitmap(), + rxCanvas, + xClip, + rSlideBoundingBox.X - mpTopLeft->mnWidth, + rSlideBoundingBox.Y - mpTopLeft->mnHeight); + } + if (mpTopRight) + { + PaintBitmapOnce( + mpTopRight->GetNormalBitmap(), + rxCanvas, + xClip, + rSlideBoundingBox.X + rSlideBoundingBox.Width, + rSlideBoundingBox.Y - mpTopLeft->mnHeight); + } + if (mpBottomLeft) + { + PaintBitmapOnce( + mpBottomLeft->GetNormalBitmap(), + rxCanvas, + xClip, + rSlideBoundingBox.X - mpBottomLeft->mnWidth, + rSlideBoundingBox.Y + rSlideBoundingBox.Height); + } + if (mpBottomRight) + { + PaintBitmapOnce( + mpBottomRight->GetNormalBitmap(), + rxCanvas, + xClip, + rSlideBoundingBox.X + rSlideBoundingBox.Width, + rSlideBoundingBox.Y + rSlideBoundingBox.Height); + } +} + +awt::Rectangle PresenterSlideSorter::CurrentSlideFrameRenderer::GetBoundingBox ( + const awt::Rectangle& rSlideBoundingBox) +{ + return awt::Rectangle( + rSlideBoundingBox.X - mnLeftFrameSize, + rSlideBoundingBox.Y - mnTopFrameSize, + rSlideBoundingBox.Width + mnLeftFrameSize + mnRightFrameSize, + rSlideBoundingBox.Height + mnTopFrameSize + mnBottomFrameSize); +} + +void PresenterSlideSorter::CurrentSlideFrameRenderer::PaintBitmapOnce( + const css::uno::Reference<css::rendering::XBitmap>& rxBitmap, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const Reference<rendering::XPolyPolygon2D>& rxClip, + const double nX, + const double nY) +{ + OSL_ASSERT(rxCanvas.is()); + if ( ! rxBitmap.is()) + return; + + const rendering::ViewState aViewState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + rxClip); + + const rendering::RenderState aRenderState ( + geometry::AffineMatrix2D( + 1, 0, nX, + 0, 1, nY), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + + rxCanvas->drawBitmap( + rxBitmap, + aViewState, + aRenderState); +} + +void PresenterSlideSorter::CurrentSlideFrameRenderer::PaintBitmapTiled( + const css::uno::Reference<css::rendering::XBitmap>& rxBitmap, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const geometry::RealRectangle2D& rClipBox, + const double nX0, + const double nY0, + const double nWidth, + const double nHeight) +{ + OSL_ASSERT(rxCanvas.is()); + if ( ! rxBitmap.is()) + return; + + geometry::IntegerSize2D aSize (rxBitmap->getSize()); + + const rendering::ViewState aViewState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + PresenterGeometryHelper::CreatePolygon( + PresenterGeometryHelper::Intersection( + rClipBox, + geometry::RealRectangle2D(nX0,nY0,nX0+nWidth,nY0+nHeight)), + rxCanvas->getDevice())); + + rendering::RenderState aRenderState ( + geometry::AffineMatrix2D( + 1, 0, nX0, + 0, 1, nY0), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + + const double nX1 = nX0 + nWidth; + const double nY1 = nY0 + nHeight; + for (double nY=nY0; nY<nY1; nY+=aSize.Height) + for (double nX=nX0; nX<nX1; nX+=aSize.Width) + { + aRenderState.AffineTransform.m02 = nX; + aRenderState.AffineTransform.m12 = nY; + rxCanvas->drawBitmap( + rxBitmap, + aViewState, + aRenderState); + } +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterSlideSorter.hxx b/sdext/source/presenter/PresenterSlideSorter.hxx new file mode 100644 index 000000000..807bc4399 --- /dev/null +++ b/sdext/source/presenter/PresenterSlideSorter.hxx @@ -0,0 +1,189 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERSLIDESORTER_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERSLIDESORTER_HXX + +#include <memory> +#include "PresenterController.hxx" +#include "PresenterPaneContainer.hxx" +#include "PresenterViewFactory.hxx" +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <com/sun/star/awt/XPaintListener.hpp> +#include <com/sun/star/awt/XWindowListener.hpp> +#include <com/sun/star/beans/XPropertyChangeListener.hpp> +#include <com/sun/star/drawing/XDrawView.hpp> +#include <com/sun/star/drawing/XSlidePreviewCache.hpp> +#include <com/sun/star/drawing/framework/XView.hpp> +#include <com/sun/star/drawing/framework/XResourceId.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/geometry/RealRectangle2D.hpp> +#include <com/sun/star/rendering/XPolyPolygon2D.hpp> + +namespace sdext::presenter { + +class PresenterButton; +class PresenterScrollBar; + +typedef cppu::WeakComponentImplHelper< + css::drawing::framework::XView, + css::awt::XWindowListener, + css::awt::XPaintListener, + css::beans::XPropertyChangeListener, + css::drawing::XSlidePreviewCacheListener, + css::awt::XMouseListener, + css::awt::XMouseMotionListener, + css::drawing::XDrawView + > PresenterSlideSorterInterfaceBase; + +/** A simple slide sorter for the presenter screen. It uses a preview cache + to create the slide previews. Painting is done via a canvas. +*/ +class PresenterSlideSorter + : private ::cppu::BaseMutex, + public PresenterSlideSorterInterfaceBase, + public CachablePresenterView +{ +public: + PresenterSlideSorter ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId, + const css::uno::Reference<css::frame::XController>& rxController, + const ::rtl::Reference<PresenterController>& rpPresenterController); + virtual ~PresenterSlideSorter() override; + + virtual void SAL_CALL disposing() override; + + // lang::XEventListener + + virtual void SAL_CALL + disposing (const css::lang::EventObject& rEventObject) override; + + // XWindowListener + + virtual void SAL_CALL windowResized (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowMoved (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowShown (const css::lang::EventObject& rEvent) override; + + virtual void SAL_CALL windowHidden (const css::lang::EventObject& rEvent) override; + + // XPaintListener + + virtual void SAL_CALL windowPaint (const css::awt::PaintEvent& rEvent) override; + + // XMouseListener + + virtual void SAL_CALL mousePressed (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseReleased (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseEntered (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseExited (const css::awt::MouseEvent& rEvent) override; + + // XMouseMotionListener + + virtual void SAL_CALL mouseMoved (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseDragged (const css::awt::MouseEvent& rEvent) override; + + // XResourceId + + virtual css::uno::Reference<css::drawing::framework::XResourceId> SAL_CALL getResourceId() override; + + virtual sal_Bool SAL_CALL isAnchorOnly() override; + + // XPropertyChangeListener + + virtual void SAL_CALL propertyChange ( + const css::beans::PropertyChangeEvent& rEvent) override; + + // XSlidePreviewCacheListener + + virtual void SAL_CALL notifyPreviewCreation ( + sal_Int32 nSlideIndex) override; + + // XDrawView + + virtual void SAL_CALL setCurrentPage ( + const css::uno::Reference<css::drawing::XDrawPage>& rxSlide) override; + + virtual css::uno::Reference<css::drawing::XDrawPage> SAL_CALL getCurrentPage() override; + +private: + css::uno::Reference<css::uno::XComponentContext> mxComponentContext; + css::uno::Reference<css::drawing::framework::XResourceId> mxViewId; + css::uno::Reference<css::drawing::framework::XPane> mxPane; + css::uno::Reference<css::rendering::XCanvas> mxCanvas; + css::uno::Reference<css::awt::XWindow> mxWindow; + ::rtl::Reference<PresenterController> mpPresenterController; + css::uno::Reference<css::presentation::XSlideShowController> mxSlideShowController; + css::uno::Reference<css::drawing::XSlidePreviewCache> mxPreviewCache; + bool mbIsLayoutPending; + class Layout; + std::shared_ptr<Layout> mpLayout; + ::rtl::Reference<PresenterScrollBar> mpVerticalScrollBar; + ::rtl::Reference<PresenterButton> mpCloseButton; + class MouseOverManager; + std::unique_ptr<MouseOverManager> mpMouseOverManager; + sal_Int32 mnSlideIndexMousePressed; + sal_Int32 mnCurrentSlideIndex; + sal_Int32 mnSeparatorY; + css::util::Color maSeparatorColor; + css::awt::Rectangle maCurrentSlideFrameBoundingBox; + class CurrentSlideFrameRenderer; + std::shared_ptr<CurrentSlideFrameRenderer> mpCurrentSlideFrameRenderer; + css::uno::Reference<css::rendering::XPolyPolygon2D> mxPreviewFrame; + + void UpdateLayout(); + css::geometry::RealRectangle2D PlaceScrollBars ( + const css::geometry::RealRectangle2D& rUpperBox); + void PlaceCloseButton ( + const PresenterPaneContainer::SharedPaneDescriptor& rpPane, + const css::awt::Rectangle& rCenterBox, + const sal_Int32 nLeftFrameWidth); + void ClearBackground ( + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rRedrawArea); + double GetSlideAspectRatio() const; + css::uno::Reference<css::rendering::XBitmap> GetPreview (const sal_Int32 nSlideIndex); + void PaintPreview ( + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rUpdateBox, + const sal_Int32 nSlideIndex); + void Paint (const css::awt::Rectangle& rUpdateBox); + void SetHorizontalOffset (const double nXOffset); + void SetVerticalOffset (const double nYOffset); + void GotoSlide (const sal_Int32 nSlideIndex); + bool ProvideCanvas(); + + /** @throws css::lang::DisposedException when the object has already been + disposed. + */ + void ThrowIfDisposed(); +}; + +} // end of namespace ::sdext::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterSprite.cxx b/sdext/source/presenter/PresenterSprite.cxx new file mode 100644 index 000000000..0f7c8f829 --- /dev/null +++ b/sdext/source/presenter/PresenterSprite.cxx @@ -0,0 +1,163 @@ +/* -*- 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 "PresenterSprite.hxx" + +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/RenderState.hpp> +#include <com/sun/star/rendering/ViewState.hpp> + +using namespace ::com::sun::star; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::UNO_QUERY; + +namespace sdext::presenter { + +PresenterSprite::PresenterSprite() + : maSize(0,0), + maLocation(0,0), + mbIsVisible(false) +{ +} + +PresenterSprite::~PresenterSprite() +{ + if (mxSprite.is()) + { + mxSprite->hide(); + Reference<lang::XComponent> xComponent (mxSprite, UNO_QUERY); + if (xComponent.is()) + xComponent->dispose(); + mxSprite = nullptr; + } +} + +void PresenterSprite::SetFactory ( + const css::uno::Reference<css::rendering::XSpriteCanvas>& rxSpriteFactory) +{ + if (mxSpriteFactory != rxSpriteFactory) + { + DisposeSprite(); + mxSpriteFactory = rxSpriteFactory; + if (mbIsVisible) + ProvideSprite(); + } +} + +css::uno::Reference<css::rendering::XCanvas> PresenterSprite::GetCanvas() +{ + ProvideSprite(); + if (mxSprite.is()) + return mxSprite->getContentCanvas(); + else + return nullptr; +} + +void PresenterSprite::Show() +{ + mbIsVisible = true; + if (mxSprite.is()) + mxSprite->show(); + else + ProvideSprite(); +} + +void PresenterSprite::Hide() +{ + mbIsVisible = false; + if (mxSprite.is()) + mxSprite->hide(); +} + +void PresenterSprite::Resize (const css::geometry::RealSize2D& rSize) +{ + maSize = rSize; + if (mxSprite.is()) + DisposeSprite(); + if (mbIsVisible) + ProvideSprite(); +} + +void PresenterSprite::MoveTo (const css::geometry::RealPoint2D& rLocation) +{ + maLocation = rLocation; + if (mxSprite.is()) + mxSprite->move( + maLocation, + rendering::ViewState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr), + rendering::RenderState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr, + uno::Sequence<double>(4), + rendering::CompositeOperation::SOURCE) + ); +} + +void PresenterSprite::Update() +{ + if (mxSpriteFactory.is()) + mxSpriteFactory->updateScreen(false); +} + +void PresenterSprite::ProvideSprite() +{ + if ( !(! mxSprite.is() + && mxSpriteFactory.is() + && maSize.Width>0 + && maSize.Height>0)) + return; + + mxSprite = mxSpriteFactory->createCustomSprite(maSize); + if (!mxSprite.is()) + return; + + mxSprite->move(maLocation, + rendering::ViewState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr), + rendering::RenderState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr, + uno::Sequence<double>(4), + rendering::CompositeOperation::SOURCE) + ); + mxSprite->setAlpha(1.0); + mxSprite->setPriority(0); + if (mbIsVisible) + mxSprite->show(); +} + +void PresenterSprite::DisposeSprite() +{ + if (mxSprite.is()) + { + mxSprite->hide(); + Reference<lang::XComponent> xComponent (mxSprite, UNO_QUERY); + if (xComponent.is()) + xComponent->dispose(); + mxSprite = nullptr; + } +} + +} //end of namespace sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterSprite.hxx b/sdext/source/presenter/PresenterSprite.hxx new file mode 100644 index 000000000..b550ec0a8 --- /dev/null +++ b/sdext/source/presenter/PresenterSprite.hxx @@ -0,0 +1,73 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERSPRITE_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERSPRITE_HXX + +#include <com/sun/star/rendering/XCustomSprite.hpp> +#include <com/sun/star/rendering/XSpriteCanvas.hpp> + +namespace sdext::presenter +{ +/** A wrapper around a css::rendering::XCustomSprite that allows + not only setting values like size, location, and transformation but also + provides read access to them. + It also handles the showing and hiding of a sprite. This includes not + to show the sprite when its size is not yet defined (results in a crash) + and hiding a sprite before disposing it (results in zombie sprites.) +*/ +class PresenterSprite final +{ +public: + PresenterSprite(); + ~PresenterSprite(); + PresenterSprite(const PresenterSprite&) = delete; + PresenterSprite& operator=(const PresenterSprite&) = delete; + + /** The given sprite canvas is used as factory to create the sprite that + is wrapped by objects of this class. + It is also used to call updateScreen() at (wrapped by the Update() method). + */ + void SetFactory(const css::uno::Reference<css::rendering::XSpriteCanvas>& rxSpriteFactory); + + css::uno::Reference<css::rendering::XCanvas> GetCanvas(); + + void Show(); + void Hide(); + + void Resize(const css::geometry::RealSize2D& rSize); + void MoveTo(const css::geometry::RealPoint2D& rLocation); + + void Update(); + +private: + css::uno::Reference<css::rendering::XSpriteCanvas> mxSpriteFactory; + css::uno::Reference<css::rendering::XCustomSprite> mxSprite; + css::geometry::RealSize2D maSize; + css::geometry::RealPoint2D maLocation; + bool mbIsVisible; + + void ProvideSprite(); + void DisposeSprite(); +}; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterSpritePane.cxx b/sdext/source/presenter/PresenterSpritePane.cxx new file mode 100644 index 000000000..452e633a2 --- /dev/null +++ b/sdext/source/presenter/PresenterSpritePane.cxx @@ -0,0 +1,172 @@ +/* -*- 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 "PresenterSpritePane.hxx" +#include <com/sun/star/lang/XMultiComponentFactory.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing::framework; + +namespace sdext::presenter { + +//===== PresenterSpritePane ========================================================= + +PresenterSpritePane::PresenterSpritePane (const Reference<XComponentContext>& rxContext, + const ::rtl::Reference<PresenterController>& rpPresenterController) + : PresenterPaneBase(rxContext, rpPresenterController), + mpSprite(std::make_shared<PresenterSprite>()) +{ + Reference<lang::XMultiComponentFactory> xFactory ( + mxComponentContext->getServiceManager(), UNO_SET_THROW); + mxPresenterHelper.set( + xFactory->createInstanceWithContext( + "com.sun.star.comp.Draw.PresenterHelper", + mxComponentContext), + UNO_QUERY_THROW); +} + +PresenterSpritePane::~PresenterSpritePane() +{ +} + +void PresenterSpritePane::disposing() +{ + mpSprite->SetFactory(nullptr); + mxParentCanvas = nullptr; + PresenterPaneBase::disposing(); +} + +//----- XPane ----------------------------------------------------------------- + +Reference<awt::XWindow> SAL_CALL PresenterSpritePane::getWindow() +{ + ThrowIfDisposed(); + return mxContentWindow; +} + +Reference<rendering::XCanvas> SAL_CALL PresenterSpritePane::getCanvas() +{ + ThrowIfDisposed(); + + if ( ! mxContentCanvas.is()) + UpdateCanvases(); + + return mxContentCanvas; +} + +//----- XWindowListener ------------------------------------------------------- + +void SAL_CALL PresenterSpritePane::windowResized (const awt::WindowEvent& rEvent) +{ + PresenterPaneBase::windowResized(rEvent); + + mpSprite->Resize(geometry::RealSize2D(rEvent.Width, rEvent.Height)); + LayoutContextWindow(); + UpdateCanvases(); +} + +void SAL_CALL PresenterSpritePane::windowMoved (const awt::WindowEvent& rEvent) +{ + PresenterPaneBase::windowMoved(rEvent); + + awt::Rectangle aBox ( + mxPresenterHelper->getWindowExtentsRelative(mxBorderWindow, mxParentWindow)); + mpSprite->MoveTo(geometry::RealPoint2D(aBox.X, aBox.Y)); + mpSprite->Update(); +} + +void SAL_CALL PresenterSpritePane::windowShown (const lang::EventObject& rEvent) +{ + PresenterPaneBase::windowShown(rEvent); + + mpSprite->Show(); + ToTop(); + + if (mxContentWindow.is()) + { + LayoutContextWindow(); + mxContentWindow->setVisible(true); + } +} + +void SAL_CALL PresenterSpritePane::windowHidden (const lang::EventObject& rEvent) +{ + PresenterPaneBase::windowHidden(rEvent); + + mpSprite->Hide(); + if (mxContentWindow.is()) + mxContentWindow->setVisible(false); +} + +//----- XPaintListener -------------------------------------------------------- + +void SAL_CALL PresenterSpritePane::windowPaint (const awt::PaintEvent&) +{ + ThrowIfDisposed(); + + /* + Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxParentCanvas, UNO_QUERY); + if (xSpriteCanvas.is()) + xSpriteCanvas->updateScreen(sal_False); + */ +} + + +void PresenterSpritePane::UpdateCanvases() +{ + Reference<XComponent> xContentCanvasComponent (mxContentCanvas, UNO_QUERY); + if (xContentCanvasComponent.is()) + xContentCanvasComponent->dispose(); + + // The border canvas is the content canvas of the sprite. + mxBorderCanvas = mpSprite->GetCanvas(); + + // The content canvas is a wrapper of the border canvas. + if (mxBorderCanvas.is()) + mxContentCanvas = mxPresenterHelper->createSharedCanvas( + mxParentCanvas, + mxParentWindow, + mxBorderCanvas, + mxBorderWindow, + mxContentWindow); + + const awt::Rectangle aWindowBox (mxBorderWindow->getPosSize()); + PaintBorder(awt::Rectangle(0,0,aWindowBox.Width,aWindowBox.Height)); +} + +void PresenterSpritePane::CreateCanvases ( + const css::uno::Reference<css::rendering::XSpriteCanvas>& rxParentCanvas) +{ + OSL_ASSERT(!mxParentCanvas.is() || mxParentCanvas==rxParentCanvas); + mxParentCanvas = rxParentCanvas; + + mpSprite->SetFactory(mxParentCanvas); + if (mxBorderWindow.is()) + { + const awt::Rectangle aBorderBox (mxBorderWindow->getPosSize()); + mpSprite->Resize(geometry::RealSize2D(aBorderBox.Width, aBorderBox.Height)); + } + + UpdateCanvases(); +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterSpritePane.hxx b/sdext/source/presenter/PresenterSpritePane.hxx new file mode 100644 index 000000000..1c2c923b9 --- /dev/null +++ b/sdext/source/presenter/PresenterSpritePane.hxx @@ -0,0 +1,79 @@ +/* -*- 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 . + */ + +#pragma once + +#include "PresenterPaneBase.hxx" +#include "PresenterSprite.hxx" +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XSpriteCanvas.hpp> +#include <rtl/ref.hxx> +#include <memory> + +namespace sdext::presenter +{ +/** Use a sprite to display the contents and the border of a pane. Windows + are still used to define the locations and sizes of both the border and + the pane content. Note that every resize results in a disposed canvas. + Therefore call getCanvas in every repaint or at least after every resize. +*/ +class PresenterSpritePane : public PresenterPaneBase +{ +public: + PresenterSpritePane(const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const ::rtl::Reference<PresenterController>& rpPresenterController); + virtual ~PresenterSpritePane() override; + + virtual void SAL_CALL disposing() override; + + using PresenterPaneBase::disposing; + + // XPane + + virtual css::uno::Reference<css::awt::XWindow> SAL_CALL getWindow() override; + + virtual css::uno::Reference<css::rendering::XCanvas> SAL_CALL getCanvas() override; + + // XWindowListener + + virtual void SAL_CALL windowResized(const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowMoved(const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowShown(const css::lang::EventObject& rEvent) override; + + virtual void SAL_CALL windowHidden(const css::lang::EventObject& rEvent) override; + + // XPaintListener + + virtual void SAL_CALL windowPaint(const css::awt::PaintEvent& rEvent) override; + +private: + css::uno::Reference<css::rendering::XSpriteCanvas> mxParentCanvas; + std::shared_ptr<PresenterSprite> mpSprite; + + virtual void CreateCanvases( + const css::uno::Reference<css::rendering::XSpriteCanvas>& rxParentCanvas) override; + void UpdateCanvases(); +}; + +} // end of namespace ::sd::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterTextView.cxx b/sdext/source/presenter/PresenterTextView.cxx new file mode 100644 index 000000000..d83229b88 --- /dev/null +++ b/sdext/source/presenter/PresenterTextView.cxx @@ -0,0 +1,1192 @@ +/* -*- 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 "PresenterTextView.hxx" +#include "PresenterCanvasHelper.hxx" +#include "PresenterGeometryHelper.hxx" +#include "PresenterTimer.hxx" + +#include <algorithm> +#include <cmath> +#include <numeric> + +#include <com/sun/star/accessibility/AccessibleTextType.hpp> +#include <com/sun/star/container/XEnumerationAccess.hpp> +#include <com/sun/star/i18n/BreakIterator.hpp> +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> +#include <com/sun/star/i18n/ScriptDirection.hpp> +#include <com/sun/star/i18n/WordType.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/TextDirection.hpp> +#include <com/sun/star/text/WritingMode2.hpp> +#include <o3tl/safeint.hxx> +#include <tools/diagnose_ex.h> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; + +const sal_Int64 CaretBlinkInterval = 500 * 1000 * 1000; + +//#define SHOW_CHARACTER_BOXES + +namespace { + sal_Int32 Signum (const sal_Int32 nValue) + { + if (nValue < 0) + return -1; + else if (nValue > 0) + return +1; + else + return 0; + } +} + +namespace sdext::presenter { + +//===== PresenterTextView ===================================================== + +PresenterTextView::PresenterTextView ( + const Reference<XComponentContext>& rxContext, + const Reference<rendering::XCanvas>& rxCanvas, + const ::std::function<void (const css::awt::Rectangle&)>& rInvalidator) + : mxCanvas(rxCanvas), + maLocation(0,0), + maSize(0,0), + mpCaret(std::make_shared<PresenterTextCaret>( + rxContext, + [this] (sal_Int32 const nParagraphIndex, sal_Int32 const nCharacterIndex) + { return this->GetCaretBounds(nParagraphIndex, nCharacterIndex); }, + rInvalidator)), + mnLeftOffset(0), + mnTopOffset(0), + mbIsFormatPending(false) +{ + Reference<lang::XMultiComponentFactory> xFactory = + rxContext->getServiceManager(); + if ( ! xFactory.is()) + return; + + // Create the break iterator that we use to break text into lines. + mxBreakIterator = i18n::BreakIterator::create(rxContext); + + // Create the script type detector that is used to split paragraphs into + // portions of the same text direction. + mxScriptTypeDetector.set( + xFactory->createInstanceWithContext( + "com.sun.star.i18n.ScriptTypeDetector", + rxContext), + UNO_QUERY_THROW); +} + +void PresenterTextView::SetText (const Reference<text::XText>& rxText) +{ + maParagraphs.clear(); + + Reference<container::XEnumerationAccess> xParagraphAccess (rxText, UNO_QUERY); + if ( ! xParagraphAccess.is()) + return; + + Reference<container::XEnumeration> xParagraphs = + xParagraphAccess->createEnumeration(); + if ( ! xParagraphs.is()) + return; + + if ( ! mpFont || ! mpFont->PrepareFont(mxCanvas)) + return; + + sal_Int32 nCharacterCount (0); + while (xParagraphs->hasMoreElements()) + { + SharedPresenterTextParagraph pParagraph = std::make_shared<PresenterTextParagraph>( + maParagraphs.size(), + mxBreakIterator, + mxScriptTypeDetector, + Reference<text::XTextRange>(xParagraphs->nextElement(), UNO_QUERY), + mpCaret); + pParagraph->SetupCellArray(mpFont); + pParagraph->SetCharacterOffset(nCharacterCount); + nCharacterCount += pParagraph->GetCharacterCount(); + maParagraphs.push_back(pParagraph); + } + + if (mpCaret) + mpCaret->HideCaret(); + + RequestFormat(); +} + +void PresenterTextView::SetTextChangeBroadcaster ( + const ::std::function<void ()>& rBroadcaster) +{ + maTextChangeBroadcaster = rBroadcaster; +} + +void PresenterTextView::SetLocation (const css::geometry::RealPoint2D& rLocation) +{ + maLocation = rLocation; + + for (auto& rxParagraph : maParagraphs) + { + rxParagraph->SetOrigin( + maLocation.X - mnLeftOffset, + maLocation.Y - mnTopOffset); + } +} + +void PresenterTextView::SetSize (const css::geometry::RealSize2D& rSize) +{ + maSize = rSize; + RequestFormat(); +} + +double PresenterTextView::GetTotalTextHeight() +{ + if (mbIsFormatPending) + { + if ( ! mpFont->PrepareFont(mxCanvas)) + return 0; + Format(); + } + + return std::accumulate(maParagraphs.begin(), maParagraphs.end(), double(0), + [](const double& nTotalHeight, const SharedPresenterTextParagraph& rxParagraph) { + return nTotalHeight + rxParagraph->GetTotalTextHeight(); + }); +} + +void PresenterTextView::SetFont (const PresenterTheme::SharedFontDescriptor& rpFont) +{ + mpFont = rpFont; + RequestFormat(); +} + +void PresenterTextView::SetOffset( + const double nLeft, + const double nTop) +{ + mnLeftOffset = nLeft; + mnTopOffset = nTop; + + // Trigger an update of the text origin stored at the individual paragraphs. + SetLocation(maLocation); +} + +void PresenterTextView::MoveCaret ( + const sal_Int32 nDistance, + const sal_Int16 nTextType) +{ + if ( ! mpCaret) + return; + + // When the caret has not been visible yet then move it to the beginning + // of the text. + if (mpCaret->GetParagraphIndex() < 0) + { + mpCaret->SetPosition(0,0); + return; + } + + sal_Int32 nParagraphIndex (mpCaret->GetParagraphIndex()); + sal_Int32 nCharacterIndex (mpCaret->GetCharacterIndex()); + switch (nTextType) + { + default: + case AccessibleTextType::CHARACTER: + nCharacterIndex += nDistance; + break; + + case AccessibleTextType::WORD: + { + sal_Int32 nRemainingDistance (nDistance); + while (nRemainingDistance != 0) + { + SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex)); + if (pParagraph) + { + const sal_Int32 nDelta (Signum(nDistance)); + nCharacterIndex = pParagraph->GetWordBoundary(nCharacterIndex, nDelta); + if (nCharacterIndex < 0) + { + // Go to previous or next paragraph. + nParagraphIndex += nDelta; + if (nParagraphIndex < 0) + { + nParagraphIndex = 0; + nCharacterIndex = 0; + nRemainingDistance = 0; + } + else if (o3tl::make_unsigned(nParagraphIndex) >= maParagraphs.size()) + { + nParagraphIndex = maParagraphs.size()-1; + pParagraph = GetParagraph(nParagraphIndex); + if (pParagraph) + nCharacterIndex = pParagraph->GetCharacterCount(); + nRemainingDistance = 0; + } + else + { + nRemainingDistance -= nDelta; + + // Move caret one character to the end of + // the previous or the start of the next paragraph. + pParagraph = GetParagraph(nParagraphIndex); + if (pParagraph) + { + if (nDistance<0) + nCharacterIndex = pParagraph->GetCharacterCount(); + else + nCharacterIndex = 0; + } + } + } + else + nRemainingDistance -= nDelta; + } + else + break; + } + break; + } + } + + // Move the caret to the new position. + mpCaret->SetPosition(nParagraphIndex, nCharacterIndex); +} + +void PresenterTextView::Paint ( + const css::awt::Rectangle& rUpdateBox) +{ + if ( ! mxCanvas.is()) + return; + if ( ! mpFont->PrepareFont(mxCanvas)) + return; + + if (mbIsFormatPending) + Format(); + + // Setup the clipping rectangle. Horizontally we make it a little + // larger to allow characters (and the caret) to stick out of their + // bounding boxes. This can happen on some characters (like the + // uppercase J) for typographical reasons. + const sal_Int32 nAdditionalLeftBorder (10); + const sal_Int32 nAdditionalRightBorder (5); + double nX (maLocation.X - mnLeftOffset); + double nY (maLocation.Y - mnTopOffset); + const sal_Int32 nClipLeft (::std::max( + PresenterGeometryHelper::Round(maLocation.X)-nAdditionalLeftBorder, rUpdateBox.X)); + const sal_Int32 nClipTop (::std::max( + PresenterGeometryHelper::Round(maLocation.Y), rUpdateBox.Y)); + const sal_Int32 nClipRight (::std::min( + PresenterGeometryHelper::Round(maLocation.X+maSize.Width)+nAdditionalRightBorder, rUpdateBox.X+rUpdateBox.Width)); + const sal_Int32 nClipBottom (::std::min( + PresenterGeometryHelper::Round(maLocation.Y+maSize.Height), rUpdateBox.Y+rUpdateBox.Height)); + if (nClipLeft>=nClipRight || nClipTop>=nClipBottom) + return; + + const awt::Rectangle aClipBox( + nClipLeft, + nClipTop, + nClipRight - nClipLeft, + nClipBottom - nClipTop); + Reference<rendering::XPolyPolygon2D> xClipPolygon ( + PresenterGeometryHelper::CreatePolygon(aClipBox, mxCanvas->getDevice())); + + const rendering::ViewState aViewState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + xClipPolygon); + + rendering::RenderState aRenderState ( + geometry::AffineMatrix2D(1,0,nX, 0,1,nY), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor); + + for (const auto& rxParagraph : maParagraphs) + { + rxParagraph->Paint( + mxCanvas, + maSize, + mpFont, + aViewState, + aRenderState, + mnTopOffset, + nClipTop, + nClipBottom); + } + + aRenderState.AffineTransform.m02 = 0; + aRenderState.AffineTransform.m12 = 0; + +#ifdef SHOW_CHARACTER_BOXES + PresenterCanvasHelper::SetDeviceColor(aRenderState, 0x00808080); + for (sal_Int32 nParagraphIndex(0), nParagraphCount(GetParagraphCount()); + nParagraphIndex<nParagraphCount; + ++nParagraphIndex) + { + const SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex)); + if ( ! pParagraph) + continue; + for (sal_Int32 nCharacterIndex(0),nCharacterCount(pParagraph->GetCharacterCount()); + nCharacterIndex<nCharacterCount; ++nCharacterIndex) + { + const awt::Rectangle aBox (pParagraph->GetCharacterBounds(nCharacterIndex, false)); + mxCanvas->drawPolyPolygon ( + PresenterGeometryHelper::CreatePolygon( + aBox, + mxCanvas->getDevice()), + aViewState, + aRenderState); + } + } + PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor); +#endif + + if (mpCaret && mpCaret->IsVisible()) + { + mxCanvas->fillPolyPolygon ( + PresenterGeometryHelper::CreatePolygon( + mpCaret->GetBounds(), + mxCanvas->getDevice()), + aViewState, + aRenderState); + } +} + +const SharedPresenterTextCaret& PresenterTextView::GetCaret() const +{ + return mpCaret; +} + +awt::Rectangle PresenterTextView::GetCaretBounds ( + sal_Int32 nParagraphIndex, + const sal_Int32 nCharacterIndex) const +{ + SharedPresenterTextParagraph pParagraph (GetParagraph(nParagraphIndex)); + + if (pParagraph) + return pParagraph->GetCharacterBounds(nCharacterIndex, true); + else + return awt::Rectangle(0,0,0,0); +} + +//----- private --------------------------------------------------------------- + +void PresenterTextView::RequestFormat() +{ + mbIsFormatPending = true; +} + +void PresenterTextView::Format() +{ + mbIsFormatPending = false; + + double nY (0); + for (const auto& rxParagraph : maParagraphs) + { + rxParagraph->Format(nY, maSize.Width, mpFont); + nY += rxParagraph->GetTotalTextHeight(); + } + + if (maTextChangeBroadcaster) + maTextChangeBroadcaster(); +} + +sal_Int32 PresenterTextView::GetParagraphCount() const +{ + return maParagraphs.size(); +} + +SharedPresenterTextParagraph PresenterTextView::GetParagraph ( + const sal_Int32 nParagraphIndex) const +{ + if (nParagraphIndex < 0) + return SharedPresenterTextParagraph(); + else if (o3tl::make_unsigned(nParagraphIndex)>=maParagraphs.size()) + return SharedPresenterTextParagraph(); + else + return maParagraphs[nParagraphIndex]; +} + +//===== PresenterTextParagraph ================================================ + +PresenterTextParagraph::PresenterTextParagraph ( + const sal_Int32 nParagraphIndex, + const Reference<i18n::XBreakIterator>& rxBreakIterator, + const Reference<i18n::XScriptTypeDetector>& rxScriptTypeDetector, + const Reference<text::XTextRange>& rxTextRange, + const SharedPresenterTextCaret& rpCaret) + : mnParagraphIndex(nParagraphIndex), + mpCaret(rpCaret), + mxBreakIterator(rxBreakIterator), + mxScriptTypeDetector(rxScriptTypeDetector), + mnVerticalOffset(0), + mnXOrigin(0), + mnYOrigin(0), + mnWidth(0), + mnAscent(0), + mnDescent(0), + mnLineHeight(-1), + mnWritingMode (text::WritingMode2::LR_TB), + mnCharacterOffset(0) +{ + if (!rxTextRange.is()) + return; + + Reference<beans::XPropertySet> xProperties (rxTextRange, UNO_QUERY); + try + { + xProperties->getPropertyValue("WritingMode") >>= mnWritingMode; + } + catch(beans::UnknownPropertyException&) + { + // Ignore the exception. Use the default value. + } + + msParagraphText = rxTextRange->getString(); +} + +void PresenterTextParagraph::Paint ( + const Reference<rendering::XCanvas>& rxCanvas, + const geometry::RealSize2D& rSize, + const PresenterTheme::SharedFontDescriptor& rpFont, + const rendering::ViewState& rViewState, + rendering::RenderState& rRenderState, + const double nTopOffset, + const double nClipTop, + const double nClipBottom) +{ + if (mnLineHeight <= 0) + return; + + sal_Int8 nTextDirection (GetTextDirection()); + + const double nSavedM12 (rRenderState.AffineTransform.m12); + + if ( ! IsTextReferencePointLeft()) + rRenderState.AffineTransform.m02 += rSize.Width; + +#ifdef SHOW_CHARACTER_BOXES + for (sal_Int32 nIndex=0,nCount=maLines.size(); + nIndex<nCount; + ++nIndex) + { + Line& rLine (maLines[nIndex]); + rLine.ProvideLayoutedLine(msParagraphText, rpFont, nTextDirection); + } +#endif + + for (sal_Int32 nIndex=0,nCount=maLines.size(); + nIndex<nCount; + ++nIndex, rRenderState.AffineTransform.m12 += mnLineHeight) + { + Line& rLine (maLines[nIndex]); + + // Paint only visible lines. + const double nLineTop = rLine.mnBaseLine - mnAscent - nTopOffset; + if (nLineTop + mnLineHeight< nClipTop) + continue; + else if (nLineTop > nClipBottom) + break; + rLine.ProvideLayoutedLine(msParagraphText, rpFont, nTextDirection); + + rRenderState.AffineTransform.m12 = nSavedM12 + rLine.mnBaseLine; + + rxCanvas->drawTextLayout ( + rLine.mxLayoutedLine, + rViewState, + rRenderState); + } + rRenderState.AffineTransform.m12 = nSavedM12; + + if ( ! IsTextReferencePointLeft()) + rRenderState.AffineTransform.m02 -= rSize.Width; +} + +void PresenterTextParagraph::Format ( + const double nY, + const double nWidth, + const PresenterTheme::SharedFontDescriptor& rpFont) +{ + // Make sure that the text view is in a valid and sane state. + if ( ! mxBreakIterator.is() || ! mxScriptTypeDetector.is()) + return; + if (nWidth<=0) + return; + if ( ! rpFont || ! rpFont->mxFont.is()) + return; + + sal_Int32 nPosition (0); + + mnWidth = nWidth; + maLines.clear(); + mnLineHeight = 0; + mnAscent = 0; + mnDescent = 0; + mnVerticalOffset = nY; + maWordBoundaries.clear(); + maWordBoundaries.push_back(0); + + const rendering::FontMetrics aMetrics (rpFont->mxFont->getFontMetrics()); + mnAscent = aMetrics.Ascent; + mnDescent = aMetrics.Descent; + mnLineHeight = aMetrics.Ascent + aMetrics.Descent + aMetrics.ExternalLeading; + nPosition = 0; + i18n::Boundary aCurrentLine(0,0); + while (true) + { + const i18n::Boundary aWordBoundary = mxBreakIterator->nextWord( + msParagraphText, + nPosition, + lang::Locale(), + i18n::WordType::ANYWORD_IGNOREWHITESPACES); + AddWord(nWidth, aCurrentLine, aWordBoundary.startPos, rpFont); + + // Remember the new word boundary for caret travelling by words. + // Prevent duplicates. + if (aWordBoundary.startPos > maWordBoundaries.back()) + maWordBoundaries.push_back(aWordBoundary.startPos); + + if (aWordBoundary.endPos>aWordBoundary.startPos) + AddWord(nWidth, aCurrentLine, aWordBoundary.endPos, rpFont); + + if (aWordBoundary.startPos<0 || aWordBoundary.endPos<0) + break; + if (nPosition >= aWordBoundary.endPos) + break; + nPosition = aWordBoundary.endPos; + } + + if (aCurrentLine.endPos>aCurrentLine.startPos) + AddLine(aCurrentLine); + +} + +sal_Int32 PresenterTextParagraph::GetWordBoundary( + const sal_Int32 nLocalCharacterIndex, + const sal_Int32 nDistance) +{ + OSL_ASSERT(nDistance==-1 || nDistance==+1); + + if (nLocalCharacterIndex < 0) + { + // The caller asked for the start or end position of the paragraph. + if (nDistance < 0) + return 0; + else + return GetCharacterCount(); + } + + sal_Int32 nIndex (0); + for (sal_Int32 nCount (maWordBoundaries.size()); nIndex<nCount; ++nIndex) + { + if (maWordBoundaries[nIndex] >= nLocalCharacterIndex) + { + // When inside the word (not at its start or end) then + // first move to the start or end before going the previous or + // next word. + if (maWordBoundaries[nIndex] > nLocalCharacterIndex) + if (nDistance > 0) + --nIndex; + break; + } + } + + nIndex += nDistance; + + if (nIndex < 0) + return -1; + else if (o3tl::make_unsigned(nIndex)>=maWordBoundaries.size()) + return -1; + else + return maWordBoundaries[nIndex]; +} + +sal_Int32 PresenterTextParagraph::GetCaretPosition() const +{ + if (mpCaret && mpCaret->GetParagraphIndex()==mnParagraphIndex) + return mpCaret->GetCharacterIndex(); + else + return -1; +} + +void PresenterTextParagraph::SetCaretPosition (const sal_Int32 nPosition) const +{ + if (mpCaret && mpCaret->GetParagraphIndex()==mnParagraphIndex) + return mpCaret->SetPosition(mnParagraphIndex, nPosition); +} + +void PresenterTextParagraph::SetOrigin (const double nXOrigin, const double nYOrigin) +{ + mnXOrigin = nXOrigin; + mnYOrigin = nYOrigin; +} + +awt::Point PresenterTextParagraph::GetRelativeLocation() const +{ + return awt::Point( + sal_Int32(mnXOrigin), + sal_Int32(mnYOrigin + mnVerticalOffset)); +} + +awt::Size PresenterTextParagraph::GetSize() const +{ + return awt::Size( + sal_Int32(mnWidth), + sal_Int32(GetTotalTextHeight())); +} + +void PresenterTextParagraph::AddWord ( + const double nWidth, + i18n::Boundary& rCurrentLine, + const sal_Int32 nWordBoundary, + const PresenterTheme::SharedFontDescriptor& rpFont) +{ + sal_Int32 nLineStart (0); + if ( ! maLines.empty()) + nLineStart = rCurrentLine.startPos; + + const OUString sLineCandidate ( + msParagraphText.copy(nLineStart, nWordBoundary-nLineStart)); + + css::geometry::RealRectangle2D aLineBox ( + PresenterCanvasHelper::GetTextBoundingBox ( + rpFont->mxFont, + sLineCandidate, + mnWritingMode)); + const double nLineWidth (aLineBox.X2 - aLineBox.X1); + + if (nLineWidth >= nWidth) + { + // Add new line with a single word (so far). + AddLine(rCurrentLine); + } + rCurrentLine.endPos = nWordBoundary; +} + +void PresenterTextParagraph::AddLine ( + i18n::Boundary& rCurrentLine) +{ + Line aLine (rCurrentLine.startPos, rCurrentLine.endPos); + + // Find the start and end of the line with respect to cells. + if (!maLines.empty()) + { + aLine.mnLineStartCellIndex = maLines.back().mnLineEndCellIndex; + aLine.mnBaseLine = maLines.back().mnBaseLine + mnLineHeight; + } + else + { + aLine.mnLineStartCellIndex = 0; + aLine.mnBaseLine = mnVerticalOffset + mnAscent; + } + sal_Int32 nCellIndex (aLine.mnLineStartCellIndex); + double nWidth (0); + for ( ; nCellIndex<sal_Int32(maCells.size()); ++nCellIndex) + { + const Cell& rCell (maCells[nCellIndex]); + if (rCell.mnCharacterIndex+rCell.mnCharacterCount > aLine.mnLineEndCharacterIndex) + break; + nWidth += rCell.mnCellWidth; + } + aLine.mnLineEndCellIndex = nCellIndex; + aLine.mnWidth = nWidth; + + maLines.push_back(aLine); + + rCurrentLine.startPos = rCurrentLine.endPos; +} + +double PresenterTextParagraph::GetTotalTextHeight() const +{ + return maLines.size() * mnLineHeight; +} + +void PresenterTextParagraph::SetCharacterOffset (const sal_Int32 nCharacterOffset) +{ + mnCharacterOffset = nCharacterOffset; +} + +sal_Int32 PresenterTextParagraph::GetCharacterCount() const +{ + return msParagraphText.getLength(); +} + +sal_Unicode PresenterTextParagraph::GetCharacter ( + const sal_Int32 nGlobalCharacterIndex) const +{ + if (nGlobalCharacterIndex<mnCharacterOffset + || nGlobalCharacterIndex>=mnCharacterOffset+msParagraphText.getLength()) + { + return sal_Unicode(); + } + else + { + return msParagraphText[nGlobalCharacterIndex - mnCharacterOffset]; + } +} + +const OUString& PresenterTextParagraph::GetText() const +{ + return msParagraphText; +} + +TextSegment PresenterTextParagraph::GetTextSegment ( + const sal_Int32 nOffset, + const sal_Int32 nIndex, + const sal_Int16 nTextType) const +{ + switch(nTextType) + { + case AccessibleTextType::PARAGRAPH: + return TextSegment( + msParagraphText, + mnCharacterOffset, + mnCharacterOffset+msParagraphText.getLength()); + + case AccessibleTextType::SENTENCE: + if (mxBreakIterator.is()) + { + const sal_Int32 nStart (mxBreakIterator->beginOfSentence( + msParagraphText, nIndex-mnCharacterOffset, lang::Locale())); + const sal_Int32 nEnd (mxBreakIterator->endOfSentence( + msParagraphText, nIndex-mnCharacterOffset, lang::Locale())); + if (nStart < nEnd) + return TextSegment( + msParagraphText.copy(nStart, nEnd-nStart), + nStart+mnCharacterOffset, + nEnd+mnCharacterOffset); + } + break; + + case AccessibleTextType::WORD: + if (mxBreakIterator.is()) + return GetWordTextSegment(nOffset, nIndex); + break; + + case AccessibleTextType::LINE: + { + auto iLine = std::find_if(maLines.begin(), maLines.end(), + [nIndex](const Line& rLine) { return nIndex < rLine.mnLineEndCharacterIndex; }); + if (iLine != maLines.end()) + { + return TextSegment( + msParagraphText.copy( + iLine->mnLineStartCharacterIndex, + iLine->mnLineEndCharacterIndex - iLine->mnLineStartCharacterIndex), + iLine->mnLineStartCharacterIndex, + iLine->mnLineEndCharacterIndex); + } + } + break; + + // Handle GLYPH and ATTRIBUTE_RUN like CHARACTER because we can not + // do better at the moment. + case AccessibleTextType::CHARACTER: + case AccessibleTextType::GLYPH: + case AccessibleTextType::ATTRIBUTE_RUN: + return CreateTextSegment(nIndex+nOffset, nIndex+nOffset+1); + } + + return TextSegment(OUString(), 0,0); +} + +TextSegment PresenterTextParagraph::GetWordTextSegment ( + const sal_Int32 nOffset, + const sal_Int32 nIndex) const +{ + sal_Int32 nCurrentOffset (nOffset); + sal_Int32 nCurrentIndex (nIndex); + + i18n::Boundary aWordBoundary; + if (nCurrentOffset == 0) + aWordBoundary = mxBreakIterator->getWordBoundary( + msParagraphText, + nIndex, + lang::Locale(), + i18n::WordType::ANYWORD_IGNOREWHITESPACES, + true); + else if (nCurrentOffset < 0) + { + while (nCurrentOffset<0 && nCurrentIndex>0) + { + aWordBoundary = mxBreakIterator->previousWord( + msParagraphText, + nCurrentIndex, + lang::Locale(), + i18n::WordType::ANYWORD_IGNOREWHITESPACES); + nCurrentIndex = aWordBoundary.startPos; + ++nCurrentOffset; + } + } + else + { + while (nCurrentOffset>0 && nCurrentIndex<=GetCharacterCount()) + { + aWordBoundary = mxBreakIterator->nextWord( + msParagraphText, + nCurrentIndex, + lang::Locale(), + i18n::WordType::ANYWORD_IGNOREWHITESPACES); + nCurrentIndex = aWordBoundary.endPos; + --nCurrentOffset; + } + } + + return CreateTextSegment(aWordBoundary.startPos, aWordBoundary.endPos); +} + +TextSegment PresenterTextParagraph::CreateTextSegment ( + sal_Int32 nStartIndex, + sal_Int32 nEndIndex) const +{ + if (nEndIndex <= nStartIndex) + return TextSegment( + OUString(), + nStartIndex, + nEndIndex); + else + return TextSegment( + msParagraphText.copy(nStartIndex, nEndIndex-nStartIndex), + nStartIndex, + nEndIndex); +} + +awt::Rectangle PresenterTextParagraph::GetCharacterBounds ( + sal_Int32 nGlobalCharacterIndex, + const bool bCaretBox) +{ + // Find the line that contains the requested character and accumulate + // the previous line heights. + double nX (mnXOrigin); + double nY (mnYOrigin + mnVerticalOffset + mnAscent); + const sal_Int8 nTextDirection (GetTextDirection()); + for (sal_Int32 nLineIndex=0,nLineCount=maLines.size(); + nLineIndex<nLineCount; + ++nLineIndex, nY+=mnLineHeight) + { + Line& rLine (maLines[nLineIndex]); + // Skip lines before the indexed character. + if (nGlobalCharacterIndex >= rLine.mnLineEndCharacterIndex) + // When in the last line then allow the index past the last char. + if (nLineIndex<nLineCount-1) + continue; + + rLine.ProvideCellBoxes(); + + const sal_Int32 nCellIndex (nGlobalCharacterIndex - rLine.mnLineStartCharacterIndex); + + // The cell bounding box is defined relative to the origin of + // the current line. Therefore we have to add the absolute + // position of the line. + geometry::RealRectangle2D rCellBox (rLine.maCellBoxes[ + ::std::min(nCellIndex, rLine.maCellBoxes.getLength()-1)]); + + double nLeft = nX + rCellBox.X1; + double nRight = nX + rCellBox.X2; + if (nTextDirection == rendering::TextDirection::WEAK_RIGHT_TO_LEFT) + { + const double nOldRight (nRight); + nRight = rLine.mnWidth - nLeft; + nLeft = rLine.mnWidth - nOldRight; + } + double nTop = nY - mnAscent; + double nBottom; + if (bCaretBox) + { + nBottom = nTop + mnLineHeight; + if (nCellIndex >= rLine.maCellBoxes.getLength()) + nLeft = nRight-2; + if (nLeft < nX) + nLeft = nX; + nRight = nLeft+2; + } + else + { + nBottom = nTop + mnAscent + mnDescent; + } + const sal_Int32 nX1 = sal_Int32(floor(nLeft)); + const sal_Int32 nY1 = sal_Int32(floor(nTop)); + const sal_Int32 nX2 = sal_Int32(ceil(nRight)); + const sal_Int32 nY2 = sal_Int32(ceil(nBottom)); + + return awt::Rectangle(nX1,nY1,nX2-nX1+1,nY2-nY1+1); + } + + // We are still here. That means that the given index lies past the + // last character in the paragraph. + // Return an empty box that lies past the last character. Better than nothing. + return awt::Rectangle(sal_Int32(nX+0.5), sal_Int32(nY+0.5), 0, 0); +} + +sal_Int8 PresenterTextParagraph::GetTextDirection() const +{ + // Find first portion that has a non-neutral text direction. + sal_Int32 nPosition (0); + sal_Int32 nTextLength (msParagraphText.getLength()); + while (nPosition < nTextLength) + { + const sal_Int16 nScriptDirection ( + mxScriptTypeDetector->getScriptDirection( + msParagraphText, nPosition, i18n::ScriptDirection::NEUTRAL)); + switch (nScriptDirection) + { + case i18n::ScriptDirection::NEUTRAL: + // continue looping. + break; + case i18n::ScriptDirection::LEFT_TO_RIGHT: + return rendering::TextDirection::WEAK_LEFT_TO_RIGHT; + + case i18n::ScriptDirection::RIGHT_TO_LEFT: + return rendering::TextDirection::WEAK_RIGHT_TO_LEFT; + } + + nPosition = mxScriptTypeDetector->endOfScriptDirection( + msParagraphText, nPosition, nScriptDirection); + } + + // All text in paragraph is neutral. Fall back on writing mode taken + // from the XText (which may not be properly initialized.) + sal_Int8 nTextDirection(rendering::TextDirection::WEAK_LEFT_TO_RIGHT); + switch(mnWritingMode) + { + case text::WritingMode2::LR_TB: + nTextDirection = rendering::TextDirection::WEAK_LEFT_TO_RIGHT; + break; + + case text::WritingMode2::RL_TB: + nTextDirection = rendering::TextDirection::WEAK_RIGHT_TO_LEFT; + break; + + default: + case text::WritingMode2::TB_RL: + case text::WritingMode2::TB_LR: + // Can not handle this. Use default and hope for the best. + break; + } + return nTextDirection; +} + +bool PresenterTextParagraph::IsTextReferencePointLeft() const +{ + return mnWritingMode != text::WritingMode2::RL_TB; +} + +void PresenterTextParagraph::SetupCellArray ( + const PresenterTheme::SharedFontDescriptor& rpFont) +{ + maCells.clear(); + + if ( ! rpFont || ! rpFont->mxFont.is()) + return; + + sal_Int32 nPosition (0); + sal_Int32 nIndex (0); + const sal_Int32 nTextLength (msParagraphText.getLength()); + const sal_Int8 nTextDirection (GetTextDirection()); + while (nPosition < nTextLength) + { + const sal_Int32 nNewPosition (mxBreakIterator->nextCharacters( + msParagraphText, + nPosition, + lang::Locale(), + i18n::CharacterIteratorMode::SKIPCELL, + 1, + nIndex)); + + rendering::StringContext aContext (msParagraphText, nPosition, nNewPosition-nPosition); + Reference<rendering::XTextLayout> xLayout ( + rpFont->mxFont->createTextLayout(aContext, nTextDirection, 0)); + css::geometry::RealRectangle2D aCharacterBox (xLayout->queryTextBounds()); + + maCells.emplace_back( + nPosition, + nNewPosition-nPosition, + aCharacterBox.X2-aCharacterBox.X1); + + nPosition = nNewPosition; + } +} + +//===== PresenterTextCaret ================================================---- + +PresenterTextCaret::PresenterTextCaret ( + uno::Reference<uno::XComponentContext> const& xContext, + const ::std::function<css::awt::Rectangle (const sal_Int32,const sal_Int32)>& rCharacterBoundsAccess, + const ::std::function<void (const css::awt::Rectangle&)>& rInvalidator) + : m_xContext(xContext) + , mnParagraphIndex(-1), + mnCharacterIndex(-1), + mnCaretBlinkTaskId(0), + mbIsCaretVisible(false), + maCharacterBoundsAccess(rCharacterBoundsAccess), + maInvalidator(rInvalidator) +{ +} + +PresenterTextCaret::~PresenterTextCaret() +{ + try + { + HideCaret(); + } + catch (uno::Exception const&) + { + TOOLS_WARN_EXCEPTION("sdext.presenter", "unexpected exception in ~PresenterTextCaret"); + } +} + +void PresenterTextCaret::ShowCaret() +{ + if (mnCaretBlinkTaskId == 0) + { + mnCaretBlinkTaskId = PresenterTimer::ScheduleRepeatedTask ( + m_xContext, + [this] (TimeValue const&) { return this->InvertCaret(); }, + CaretBlinkInterval, + CaretBlinkInterval); + } + mbIsCaretVisible = true; +} + +void PresenterTextCaret::HideCaret() +{ + if (mnCaretBlinkTaskId != 0) + { + PresenterTimer::CancelTask(mnCaretBlinkTaskId); + mnCaretBlinkTaskId = 0; + } + mbIsCaretVisible = false; + // Reset the caret position. + mnParagraphIndex = -1; + mnCharacterIndex = -1; +} + + +void PresenterTextCaret::SetPosition ( + const sal_Int32 nParagraphIndex, + const sal_Int32 nCharacterIndex) +{ + if (mnParagraphIndex == nParagraphIndex + && mnCharacterIndex == nCharacterIndex) + return; + + if (mnParagraphIndex >= 0) + maInvalidator(maCaretBounds); + + const sal_Int32 nOldParagraphIndex (mnParagraphIndex); + const sal_Int32 nOldCharacterIndex (mnCharacterIndex); + mnParagraphIndex = nParagraphIndex; + mnCharacterIndex = nCharacterIndex; + maCaretBounds = maCharacterBoundsAccess(mnParagraphIndex, mnCharacterIndex); + if (mnParagraphIndex >= 0) + ShowCaret(); + else + HideCaret(); + + if (mnParagraphIndex >= 0) + maInvalidator(maCaretBounds); + + if (maBroadcaster) + maBroadcaster( + nOldParagraphIndex, + nOldCharacterIndex, + mnParagraphIndex, + mnCharacterIndex); +} + + +void PresenterTextCaret::SetCaretMotionBroadcaster ( + const ::std::function<void (sal_Int32,sal_Int32,sal_Int32,sal_Int32)>& rBroadcaster) +{ + maBroadcaster = rBroadcaster; +} + +const css::awt::Rectangle& PresenterTextCaret::GetBounds() const +{ + return maCaretBounds; +} + +void PresenterTextCaret::InvertCaret() +{ + mbIsCaretVisible = !mbIsCaretVisible; + if (mnParagraphIndex >= 0) + maInvalidator(maCaretBounds); +} + +//===== PresenterTextParagraph::Cell ========================================== + +PresenterTextParagraph::Cell::Cell ( + const sal_Int32 nCharacterIndex, + const sal_Int32 nCharacterCount, + const double nCellWidth) + : mnCharacterIndex(nCharacterIndex), + mnCharacterCount(nCharacterCount), + mnCellWidth(nCellWidth) +{ +} + +//===== PresenterTextParagraph::Line ========================================== + +PresenterTextParagraph::Line::Line ( + const sal_Int32 nLineStartCharacterIndex, + const sal_Int32 nLineEndCharacterIndex) + : mnLineStartCharacterIndex(nLineStartCharacterIndex), + mnLineEndCharacterIndex(nLineEndCharacterIndex), + mnLineStartCellIndex(-1), mnLineEndCellIndex(-1), + mnBaseLine(0), mnWidth(0) +{ +} + +void PresenterTextParagraph::Line::ProvideCellBoxes() +{ + if ( mnLineStartCharacterIndex < mnLineEndCharacterIndex && !maCellBoxes.hasElements() ) + { + if (mxLayoutedLine.is()) + maCellBoxes = mxLayoutedLine->queryInkMeasures(); + else + { + OSL_ASSERT(mxLayoutedLine.is()); + } + } +} + +void PresenterTextParagraph::Line::ProvideLayoutedLine ( + const OUString& rsParagraphText, + const PresenterTheme::SharedFontDescriptor& rpFont, + const sal_Int8 nTextDirection) +{ + if ( ! mxLayoutedLine.is()) + { + const rendering::StringContext aContext ( + rsParagraphText, + mnLineStartCharacterIndex, + mnLineEndCharacterIndex - mnLineStartCharacterIndex); + + mxLayoutedLine = rpFont->mxFont->createTextLayout( + aContext, + nTextDirection, + 0); + } +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterTextView.hxx b/sdext/source/presenter/PresenterTextView.hxx new file mode 100644 index 000000000..a732978e6 --- /dev/null +++ b/sdext/source/presenter/PresenterTextView.hxx @@ -0,0 +1,279 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERTEXTVIEW_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERTEXTVIEW_HXX + +#include "PresenterTheme.hxx" +#include <com/sun/star/accessibility/TextSegment.hpp> +#include <com/sun/star/awt/Point.hpp> +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/geometry/RealPoint2D.hpp> +#include <com/sun/star/geometry/RealSize2D.hpp> +#include <com/sun/star/i18n/XBreakIterator.hpp> +#include <com/sun/star/i18n/XScriptTypeDetector.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/text/XText.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> + +#include <functional> + +namespace sdext::presenter { + +class PresenterTextCaret +{ +public: + PresenterTextCaret ( + css::uno::Reference<css::uno::XComponentContext> const& xContext, + const ::std::function<css::awt::Rectangle (const sal_Int32,const sal_Int32)>& + rCharacterBoundsAccess, + const ::std::function<void (const css::awt::Rectangle&)>& + rInvalidator); + ~PresenterTextCaret(); + + void ShowCaret(); + void HideCaret(); + + sal_Int32 GetParagraphIndex() const { return mnParagraphIndex;} + sal_Int32 GetCharacterIndex() const { return mnCharacterIndex;} + void SetPosition ( + const sal_Int32 nParagraphIndex, + const sal_Int32 nCharacterIndex); + + bool IsVisible() const { return mbIsCaretVisible;} + + /** Set a (possibly empty) functor that broadcasts changes of the caret + position. This is used when a PresenterTextView object is set at + the accessibility object so that accessibility events can be sent + when the caret changes position. + */ + void SetCaretMotionBroadcaster ( + const ::std::function<void (sal_Int32,sal_Int32,sal_Int32,sal_Int32)>& rBroadcaster); + + const css::awt::Rectangle& GetBounds() const; + +private: + css::uno::Reference<css::uno::XComponentContext> const& m_xContext; + sal_Int32 mnParagraphIndex; + sal_Int32 mnCharacterIndex; + sal_Int32 mnCaretBlinkTaskId; + bool mbIsCaretVisible; + const ::std::function<css::awt::Rectangle (const sal_Int32,const sal_Int32)> maCharacterBoundsAccess; + const ::std::function<void (const css::awt::Rectangle&)> maInvalidator; + ::std::function<void (sal_Int32,sal_Int32,sal_Int32,sal_Int32)> maBroadcaster; + css::awt::Rectangle maCaretBounds; + + void InvertCaret(); +}; +typedef std::shared_ptr<PresenterTextCaret> SharedPresenterTextCaret; + +//===== PresenterTextParagraph ================================================ + +class PresenterTextParagraph +{ +public: + PresenterTextParagraph ( + const sal_Int32 nParagraphIndex, + const css::uno::Reference<css::i18n::XBreakIterator>& rxBreakIterator, + const css::uno::Reference<css::i18n::XScriptTypeDetector>& rxScriptTypeDetector, + const css::uno::Reference<css::text::XTextRange>& rxTextRange, + const SharedPresenterTextCaret& rpCaret); + + void Paint ( + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::geometry::RealSize2D& rSize, + const PresenterTheme::SharedFontDescriptor& rpFont, + const css::rendering::ViewState& rViewState, + css::rendering::RenderState& rRenderState, + const double nTopOffset, + const double nClipTop, + const double nClipBottom); + + double GetTotalTextHeight() const; + + void SetCharacterOffset (const sal_Int32 nCharacterOffset); + sal_Int32 GetCharacterCount() const; + sal_Unicode GetCharacter (const sal_Int32 nGlobalCharacterIndex) const; + const OUString& GetText() const; + css::accessibility::TextSegment GetTextSegment ( + const sal_Int32 nOffset, + const sal_Int32 nGlobalCharacterIndex, + const sal_Int16 nTextType) const; + css::accessibility::TextSegment GetWordTextSegment ( + const sal_Int32 nOffset, + const sal_Int32 nIndex) const; + css::accessibility::TextSegment CreateTextSegment ( + sal_Int32 nStartIndex, + sal_Int32 nEndIndex) const; + css::awt::Rectangle GetCharacterBounds ( + sal_Int32 nGlobalCharacterIndex, + const bool bCaretBox); + void SetupCellArray ( + const PresenterTheme::SharedFontDescriptor& rpFont); + void Format ( + const double nY, + const double nWidth, + const PresenterTheme::SharedFontDescriptor& rpFont); + sal_Int32 GetWordBoundary( + const sal_Int32 nLocalCharacterIndex, + const sal_Int32 nDistance); + sal_Int32 GetCaretPosition() const; + void SetCaretPosition (const sal_Int32 nPosition) const; + void SetOrigin (const double nXOrigin, const double nYOrigin); + css::awt::Point GetRelativeLocation() const; + css::awt::Size GetSize() const; + +private: + OUString msParagraphText; + const sal_Int32 mnParagraphIndex; + SharedPresenterTextCaret mpCaret; + + /** A portion of a string that encodes one unicode cell. It describes + number of characters in the unicode string that make up the cell and its + width in pixel (with respect to some configuration that is stored + externally or implicitly). + */ + class Cell + { + public: + Cell (const sal_Int32 nCharacterIndex, const sal_Int32 nCharacterCount, const double nCellWidth); + sal_Int32 mnCharacterIndex; + sal_Int32 mnCharacterCount; + double mnCellWidth; + }; + + class Line + { + public: + Line (const sal_Int32 nLineStartCharacterIndex, const sal_Int32 nLineEndCharacterIndex); + sal_Int32 mnLineStartCharacterIndex; + sal_Int32 mnLineEndCharacterIndex; + sal_Int32 mnLineStartCellIndex; + sal_Int32 mnLineEndCellIndex; + css::uno::Reference<css::rendering::XTextLayout> mxLayoutedLine; + double mnBaseLine; + double mnWidth; + css::uno::Sequence<css::geometry::RealRectangle2D> maCellBoxes; + + void ProvideLayoutedLine ( + const OUString& rsParagraphText, + const PresenterTheme::SharedFontDescriptor& rpFont, + const sal_Int8 nTextDirection); + void ProvideCellBoxes(); + }; + + css::uno::Reference<css::i18n::XBreakIterator> mxBreakIterator; + css::uno::Reference<css::i18n::XScriptTypeDetector> mxScriptTypeDetector; + ::std::vector<Line> maLines; + ::std::vector<sal_Int32> maWordBoundaries; + // Offset of the top of the paragraph with respect to the origin of the + // whole text (specified by mnXOrigin and mnYOrigin). + double mnVerticalOffset; + double mnXOrigin; + double mnYOrigin; + double mnWidth; + double mnAscent; + double mnDescent; + double mnLineHeight; + sal_Int8 mnWritingMode; + /// The index of the first character in this paragraph with respect to + /// the whole text. + sal_Int32 mnCharacterOffset; + ::std::vector<Cell> maCells; + + void AddWord ( + const double nWidth, + css::i18n::Boundary& rCurrentLine, + const sal_Int32 nWordBoundary, + const PresenterTheme::SharedFontDescriptor& rpFont); + void AddLine ( + css::i18n::Boundary& rCurrentLine); + sal_Int8 GetTextDirection() const; + bool IsTextReferencePointLeft() const; +}; +typedef std::shared_ptr<PresenterTextParagraph> SharedPresenterTextParagraph; + +/** A simple text view that paints text onto a given canvas. +*/ +class PresenterTextView +{ +public: + + PresenterTextView ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const ::std::function<void (const css::awt::Rectangle&)>& rInvalidator); + void SetText (const css::uno::Reference<css::text::XText>& rxText); + void SetTextChangeBroadcaster(const ::std::function<void ()>& rBroadcaster); + + void SetLocation (const css::geometry::RealPoint2D& rLocation); + void SetSize (const css::geometry::RealSize2D& rSize); + double GetTotalTextHeight(); + + void SetFont (const PresenterTheme::SharedFontDescriptor& rpFont); + + void SetOffset ( + const double nLeft, + const double nTop); + + /** Move the caret forward or backward by character or by word. + @param nDistance + Should be either -1 or +1 to move caret backwards or forwards, + respectively. + @param nTextType + Valid values are the + css::accessibility::AccessibleTextType constants. + */ + void MoveCaret ( + const sal_Int32 nDistance, + const sal_Int16 nTextType); + + void Paint (const css::awt::Rectangle& rUpdateBox); + + const SharedPresenterTextCaret& GetCaret() const; + + sal_Int32 GetParagraphCount() const; + SharedPresenterTextParagraph GetParagraph (const sal_Int32 nParagraphIndex) const; + +private: + css::uno::Reference<css::rendering::XCanvas> mxCanvas; + css::uno::Reference<css::i18n::XBreakIterator> mxBreakIterator; + css::uno::Reference<css::i18n::XScriptTypeDetector> mxScriptTypeDetector; + css::geometry::RealPoint2D maLocation; + css::geometry::RealSize2D maSize; + PresenterTheme::SharedFontDescriptor mpFont; + ::std::vector<SharedPresenterTextParagraph> maParagraphs; + SharedPresenterTextCaret mpCaret; + double mnLeftOffset; + double mnTopOffset; + bool mbIsFormatPending; + ::std::function<void ()> maTextChangeBroadcaster; + + void RequestFormat(); + void Format(); + css::awt::Rectangle GetCaretBounds ( + const sal_Int32 nParagraphIndex, + const sal_Int32 nCharacterIndex) const; +}; + +} // end of namespace ::sdext::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterTheme.cxx b/sdext/source/presenter/PresenterTheme.cxx new file mode 100644 index 000000000..c84747d0f --- /dev/null +++ b/sdext/source/presenter/PresenterTheme.cxx @@ -0,0 +1,1060 @@ +/* -*- 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 "PresenterTheme.hxx" +#include "PresenterBitmapContainer.hxx" +#include "PresenterCanvasHelper.hxx" +#include "PresenterConfigurationAccess.hxx" +#include <com/sun/star/drawing/XPresenterHelper.hpp> +#include <com/sun/star/rendering/PanoseWeight.hpp> +#include <osl/diagnose.h> +#include <map> +#include <numeric> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::std; + +namespace sdext::presenter { + +namespace { + +class BorderSize +{ +public: + const static sal_Int32 mnInvalidValue = -10000; + + BorderSize() : mnLeft(mnInvalidValue), + mnTop(mnInvalidValue), + mnRight(mnInvalidValue), + mnBottom(mnInvalidValue) {} + + sal_Int32 mnLeft; + sal_Int32 mnTop; + sal_Int32 mnRight; + sal_Int32 mnBottom; + + vector<sal_Int32> ToVector() + { + return + { + mnLeft == mnInvalidValue ? 0 : mnLeft, + mnTop == mnInvalidValue ? 0 : mnTop, + mnRight == mnInvalidValue ? 0 : mnRight, + mnBottom == mnInvalidValue ? 0 : mnBottom + }; + }; + + void Merge (const BorderSize& rBorderSize) + { + if (mnLeft == mnInvalidValue) + mnLeft = rBorderSize.mnLeft; + if (mnTop == mnInvalidValue) + mnTop = rBorderSize.mnTop; + if (mnRight == mnInvalidValue) + mnRight = rBorderSize.mnRight; + if (mnBottom == mnInvalidValue) + mnBottom = rBorderSize.mnBottom; + } +}; + +/** Reading a theme from the configurations is done in various classes. The + ReadContext gives access to frequently used objects and functions to make + the configuration handling easier. +*/ +class ReadContext +{ +public: + Reference<XComponentContext> mxComponentContext; + Reference<rendering::XCanvas> mxCanvas; + Reference<drawing::XPresenterHelper> mxPresenterHelper; + + ReadContext ( + const Reference<XComponentContext>& rxContext, + const Reference<rendering::XCanvas>& rxCanvas); + + /** Read data describing a font from the node that can be reached from + the given root via the given path. + @param rsFontPath + May be empty. + */ + static PresenterTheme::SharedFontDescriptor ReadFont ( + const css::uno::Reference<css::container::XHierarchicalNameAccess>& rxTheme, + const PresenterTheme::SharedFontDescriptor& rpDefault); + static PresenterTheme::SharedFontDescriptor ReadFont ( + const Reference<beans::XPropertySet>& rxFontProperties, + const PresenterTheme::SharedFontDescriptor& rpDefault); + + std::shared_ptr<PresenterTheme::Theme> ReadTheme ( + PresenterConfigurationAccess& rConfiguration, + const OUString& rsThemeName); + + static BorderSize ReadBorderSize (const Reference<container::XNameAccess>& rxNode); + +private: + static Any GetByName ( + const Reference<container::XNameAccess>& rxNode, + const OUString& rsName); +}; + +/** A PaneStyle describes how a pane is rendered. +*/ +class PaneStyle +{ +public: + PaneStyle(); + + SharedBitmapDescriptor GetBitmap (const OUString& sBitmapName) const; + + OUString msStyleName; + std::shared_ptr<PaneStyle> mpParentStyle; + PresenterTheme::SharedFontDescriptor mpFont; + BorderSize maInnerBorderSize; + BorderSize maOuterBorderSize; + std::shared_ptr<PresenterBitmapContainer> mpBitmaps; + + PresenterTheme::SharedFontDescriptor GetFont() const; +}; + +typedef std::shared_ptr<PaneStyle> SharedPaneStyle; + +class PaneStyleContainer +{ +private: + ::std::vector<SharedPaneStyle> mStyles; + +public: + void Read ( + const ReadContext& rReadContext, + const Reference<container::XHierarchicalNameAccess>& rThemeRoot); + + SharedPaneStyle GetPaneStyle (const OUString& rsStyleName) const; + +private: + void ProcessPaneStyle ( + ReadContext const & rReadContext, + const ::std::vector<css::uno::Any>& rValues); +}; + +/** A ViewStyle describes how a view is displayed. +*/ +class ViewStyle +{ +public: + ViewStyle(); + + SharedBitmapDescriptor GetBitmap (std::u16string_view sBitmapName) const; + + PresenterTheme::SharedFontDescriptor GetFont() const; + + OUString msStyleName; + std::shared_ptr<ViewStyle> mpParentStyle; + PresenterTheme::SharedFontDescriptor mpFont; + SharedBitmapDescriptor mpBackground; +}; + +typedef std::shared_ptr<ViewStyle> SharedViewStyle; + +class ViewStyleContainer +{ +private: + ::std::vector<SharedViewStyle> mStyles; + +public: + void Read ( + const ReadContext& rReadContext, + const Reference<container::XHierarchicalNameAccess>& rThemeRoot); + + SharedViewStyle GetViewStyle (const OUString& rsStyleName) const; + +private: + void ProcessViewStyle( + ReadContext const & rReadContext, + const Reference<beans::XPropertySet>& rxProperties); +}; + +class StyleAssociationContainer +{ +public: + void Read ( + const Reference<container::XHierarchicalNameAccess>& rThemeRoot); + + OUString GetStyleName (const OUString& rsResourceName) const; + +private: + typedef map<OUString, OUString> StyleAssociations; + StyleAssociations maStyleAssociations; + + void ProcessStyleAssociation( + const ::std::vector<css::uno::Any>& rValues); +}; + +} // end of anonymous namespace + +class PresenterTheme::Theme +{ +public: + Theme ( + const Reference<container::XHierarchicalNameAccess>& rThemeRoot, + const OUString& rsNodeName); + + void Read ( + PresenterConfigurationAccess& rConfiguration, + ReadContext& rReadContext); + + OUString msConfigurationNodeName; + std::shared_ptr<Theme> mpParentTheme; + SharedBitmapDescriptor mpBackground; + PaneStyleContainer maPaneStyles; + ViewStyleContainer maViewStyles; + StyleAssociationContainer maStyleAssociations; + Reference<container::XHierarchicalNameAccess> mxThemeRoot; + std::shared_ptr<PresenterBitmapContainer> mpIconContainer; + typedef map<OUString,SharedFontDescriptor> FontContainer; + FontContainer maFontContainer; + + SharedPaneStyle GetPaneStyle (const OUString& rsStyleName) const; + SharedViewStyle GetViewStyle (const OUString& rsStyleName) const; + +private: + void ProcessFont( + const OUString& rsKey, + const Reference<beans::XPropertySet>& rxProperties); +}; + +//===== PresenterTheme ======================================================== + +PresenterTheme::PresenterTheme ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas) + : mxContext(rxContext), + mxCanvas(rxCanvas) +{ + mpTheme = ReadTheme(); +} + +PresenterTheme::~PresenterTheme() +{ +} + +std::shared_ptr<PresenterTheme::Theme> PresenterTheme::ReadTheme() +{ + ReadContext aReadContext(mxContext, mxCanvas); + + PresenterConfigurationAccess aConfiguration ( + mxContext, + "/org.openoffice.Office.PresenterScreen/", + PresenterConfigurationAccess::READ_ONLY); + + return aReadContext.ReadTheme(aConfiguration, OUString()); +} + +bool PresenterTheme::HasCanvas() const +{ + return mxCanvas.is(); +} + +void PresenterTheme::ProvideCanvas (const Reference<rendering::XCanvas>& rxCanvas) +{ + if ( ! mxCanvas.is() && rxCanvas.is()) + { + mxCanvas = rxCanvas; + ReadTheme(); + } +} + +OUString PresenterTheme::GetStyleName (const OUString& rsResourceURL) const +{ + OUString sStyleName; + std::shared_ptr<Theme> pTheme (mpTheme); + while (sStyleName.isEmpty() && pTheme != nullptr) + { + sStyleName = pTheme->maStyleAssociations.GetStyleName(rsResourceURL); + pTheme = pTheme->mpParentTheme; + } + return sStyleName; +} + +::std::vector<sal_Int32> PresenterTheme::GetBorderSize ( + const OUString& rsStyleName, + const bool bOuter) const +{ + OSL_ASSERT(mpTheme != nullptr); + + SharedPaneStyle pPaneStyle (mpTheme->GetPaneStyle(rsStyleName)); + if (pPaneStyle) + if (bOuter) + return pPaneStyle->maOuterBorderSize.ToVector(); + else + return pPaneStyle->maInnerBorderSize.ToVector(); + else + { + return ::std::vector<sal_Int32>(4,0); + } +} + +PresenterTheme::SharedFontDescriptor PresenterTheme::ReadFont ( + const Reference<container::XHierarchicalNameAccess>& rxNode, + const PresenterTheme::SharedFontDescriptor& rpDefault) +{ + return ReadContext::ReadFont(rxNode, rpDefault); +} + +bool PresenterTheme::ConvertToColor ( + const Any& rColorSequence, + sal_uInt32& rColor) +{ + Sequence<sal_Int8> aByteSequence; + if (rColorSequence >>= aByteSequence) + { + rColor = std::accumulate(std::cbegin(aByteSequence), std::cend(aByteSequence), sal_uInt32(0), + [](const sal_uInt32 nRes, const sal_uInt8 nByte) { return (nRes << 8) | nByte; }); + return true; + } + else + return false; +} + +std::shared_ptr<PresenterConfigurationAccess> PresenterTheme::GetNodeForViewStyle ( + const OUString& rsStyleName) const +{ + if (mpTheme == nullptr) + return std::shared_ptr<PresenterConfigurationAccess>(); + + // Open configuration for writing. + auto pConfiguration = std::make_shared<PresenterConfigurationAccess>( + mxContext, + "/org.openoffice.Office.PresenterScreen/", + PresenterConfigurationAccess::READ_WRITE); + + // Get configuration node for the view style container of the current + // theme. + if (pConfiguration->GoToChild( OUString( + "Presenter/Themes/" + mpTheme->msConfigurationNodeName + "/ViewStyles"))) + { + pConfiguration->GoToChild( + [&rsStyleName] (OUString const&, uno::Reference<beans::XPropertySet> const& xProps) + { + return PresenterConfigurationAccess::IsStringPropertyEqual( + rsStyleName, "StyleName", xProps); + }); + } + return pConfiguration; +} + +SharedBitmapDescriptor PresenterTheme::GetBitmap ( + const OUString& rsStyleName, + const OUString& rsBitmapName) const +{ + if (mpTheme != nullptr) + { + if (rsStyleName.isEmpty()) + { + if (rsBitmapName == "Background") + { + std::shared_ptr<Theme> pTheme (mpTheme); + while (pTheme != nullptr && !pTheme->mpBackground) + pTheme = pTheme->mpParentTheme; + if (pTheme != nullptr) + return pTheme->mpBackground; + else + return SharedBitmapDescriptor(); + } + } + else + { + SharedPaneStyle pPaneStyle (mpTheme->GetPaneStyle(rsStyleName)); + if (pPaneStyle) + { + SharedBitmapDescriptor pBitmap (pPaneStyle->GetBitmap(rsBitmapName)); + if (pBitmap) + return pBitmap; + } + + SharedViewStyle pViewStyle (mpTheme->GetViewStyle(rsStyleName)); + if (pViewStyle) + { + SharedBitmapDescriptor pBitmap (pViewStyle->GetBitmap(rsBitmapName)); + if (pBitmap) + return pBitmap; + } + } + } + + return SharedBitmapDescriptor(); +} + +SharedBitmapDescriptor PresenterTheme::GetBitmap ( + const OUString& rsBitmapName) const +{ + if (mpTheme != nullptr) + { + if (rsBitmapName == "Background") + { + std::shared_ptr<Theme> pTheme (mpTheme); + while (pTheme != nullptr && !pTheme->mpBackground) + pTheme = pTheme->mpParentTheme; + if (pTheme != nullptr) + return pTheme->mpBackground; + else + return SharedBitmapDescriptor(); + } + else + { + if (mpTheme->mpIconContainer != nullptr) + return mpTheme->mpIconContainer->GetBitmap(rsBitmapName); + } + } + + return SharedBitmapDescriptor(); +} + +std::shared_ptr<PresenterBitmapContainer> PresenterTheme::GetBitmapContainer() const +{ + if (mpTheme != nullptr) + return mpTheme->mpIconContainer; + else + return std::shared_ptr<PresenterBitmapContainer>(); +} + +PresenterTheme::SharedFontDescriptor PresenterTheme::GetFont ( + const OUString& rsStyleName) const +{ + if (mpTheme != nullptr) + { + SharedPaneStyle pPaneStyle (mpTheme->GetPaneStyle(rsStyleName)); + if (pPaneStyle) + return pPaneStyle->GetFont(); + + SharedViewStyle pViewStyle (mpTheme->GetViewStyle(rsStyleName)); + if (pViewStyle) + return pViewStyle->GetFont(); + + std::shared_ptr<Theme> pTheme (mpTheme); + while (pTheme != nullptr) + { + Theme::FontContainer::const_iterator iFont (pTheme->maFontContainer.find(rsStyleName)); + if (iFont != pTheme->maFontContainer.end()) + return iFont->second; + + pTheme = pTheme->mpParentTheme; + } + } + + return SharedFontDescriptor(); +} + +//===== FontDescriptor ======================================================== + +PresenterTheme::FontDescriptor::FontDescriptor ( + const std::shared_ptr<FontDescriptor>& rpDescriptor) + : mnSize(12), + mnColor(0x00000000), + msAnchor(OUString("Left")), + mnXOffset(0), + mnYOffset(0) +{ + if (rpDescriptor != nullptr) + { + msFamilyName = rpDescriptor->msFamilyName; + msStyleName = rpDescriptor->msStyleName; + mnSize = rpDescriptor->mnSize; + mnColor = rpDescriptor->mnColor; + msAnchor = rpDescriptor->msAnchor; + mnXOffset = rpDescriptor->mnXOffset; + mnYOffset = rpDescriptor->mnYOffset; + } +} + +bool PresenterTheme::FontDescriptor::PrepareFont ( + const Reference<rendering::XCanvas>& rxCanvas) +{ + if (mxFont.is()) + return true; + + if ( ! rxCanvas.is()) + return false; + + const double nCellSize (GetCellSizeForDesignSize(rxCanvas, mnSize)); + mxFont = CreateFont(rxCanvas, nCellSize); + + return mxFont.is(); +} + +Reference<rendering::XCanvasFont> PresenterTheme::FontDescriptor::CreateFont ( + const Reference<rendering::XCanvas>& rxCanvas, + const double nCellSize) const +{ + rendering::FontRequest aFontRequest; + aFontRequest.FontDescription.FamilyName = msFamilyName; + if (msFamilyName.isEmpty()) + aFontRequest.FontDescription.FamilyName = "Tahoma"; + aFontRequest.FontDescription.StyleName = msStyleName; + aFontRequest.CellSize = nCellSize; + + // Make an attempt at translating the style name(s)into a corresponding + // font description. + if (msStyleName == "Bold") + aFontRequest.FontDescription.FontDescription.Weight = rendering::PanoseWeight::HEAVY; + + return rxCanvas->createFont( + aFontRequest, + Sequence<beans::PropertyValue>(), + geometry::Matrix2D(1,0,0,1)); +} + +double PresenterTheme::FontDescriptor::GetCellSizeForDesignSize ( + const Reference<rendering::XCanvas>& rxCanvas, + const double nDesignSize) const +{ + // Use the given design size as initial value in calculating the cell + // size. + double nCellSize (nDesignSize); + + if ( ! rxCanvas.is()) + { + // We need the canvas to do the conversion. Return the design size, + // it is the our best guess in this circumstance. + return nDesignSize; + } + + Reference<rendering::XCanvasFont> xFont (CreateFont(rxCanvas, nCellSize)); + if ( ! xFont.is()) + return nDesignSize; + + geometry::RealRectangle2D aBox (PresenterCanvasHelper::GetTextBoundingBox (xFont, "X")); + + const double nAscent (-aBox.Y1); + //tdf#112408 + if (nAscent == 0) + return nDesignSize; + const double nDescent (aBox.Y2); + const double nScale = (nAscent+nDescent) / nAscent; + return nDesignSize * nScale; +} + +//===== Theme ================================================================= + +PresenterTheme::Theme::Theme ( + const Reference<container::XHierarchicalNameAccess>& rxThemeRoot, + const OUString& rsNodeName) + : msConfigurationNodeName(rsNodeName), + maPaneStyles(), + maViewStyles(), + maStyleAssociations(), + mxThemeRoot(rxThemeRoot) +{ +} + +void PresenterTheme::Theme::Read ( + PresenterConfigurationAccess& rConfiguration, + ReadContext& rReadContext) +{ + // Parent theme name. + OUString sParentThemeName; + if ((PresenterConfigurationAccess::GetConfigurationNode(mxThemeRoot, "ParentTheme") + >>= sParentThemeName) + && !sParentThemeName.isEmpty()) + { + mpParentTheme = rReadContext.ReadTheme(rConfiguration, sParentThemeName); + } + + // Background. + mpBackground = PresenterBitmapContainer::LoadBitmap( + mxThemeRoot, + "Background", + rReadContext.mxPresenterHelper, + rReadContext.mxCanvas, + SharedBitmapDescriptor()); + + // Style associations. + maStyleAssociations.Read(mxThemeRoot); + + // Pane styles. + maPaneStyles.Read(rReadContext, mxThemeRoot); + + // View styles. + maViewStyles.Read(rReadContext, mxThemeRoot); + + // Read bitmaps. + mpIconContainer = std::make_shared<PresenterBitmapContainer>( + Reference<container::XNameAccess>( + PresenterConfigurationAccess::GetConfigurationNode(mxThemeRoot, "Bitmaps"), UNO_QUERY), + mpParentTheme != nullptr ? mpParentTheme->mpIconContainer + : std::shared_ptr<PresenterBitmapContainer>(), + rReadContext.mxComponentContext, rReadContext.mxCanvas); + + // Read fonts. + Reference<container::XNameAccess> xFontNode( + PresenterConfigurationAccess::GetConfigurationNode(mxThemeRoot, "Fonts"), + UNO_QUERY); + PresenterConfigurationAccess::ForAll( + xFontNode, + [this] (OUString const& rKey, uno::Reference<beans::XPropertySet> const& xProps) + { + return this->ProcessFont(rKey, xProps); + }); +} + +SharedPaneStyle PresenterTheme::Theme::GetPaneStyle (const OUString& rsStyleName) const +{ + SharedPaneStyle pPaneStyle (maPaneStyles.GetPaneStyle(rsStyleName)); + if (pPaneStyle) + return pPaneStyle; + else if (mpParentTheme != nullptr) + return mpParentTheme->GetPaneStyle(rsStyleName); + else + return SharedPaneStyle(); +} + +SharedViewStyle PresenterTheme::Theme::GetViewStyle (const OUString& rsStyleName) const +{ + SharedViewStyle pViewStyle (maViewStyles.GetViewStyle(rsStyleName)); + if (pViewStyle) + return pViewStyle; + else if (mpParentTheme != nullptr) + return mpParentTheme->GetViewStyle(rsStyleName); + else + return SharedViewStyle(); +} + +void PresenterTheme::Theme::ProcessFont( + const OUString& rsKey, + const Reference<beans::XPropertySet>& rxProperties) +{ + maFontContainer[rsKey] = ReadContext::ReadFont(rxProperties, SharedFontDescriptor()); +} + +namespace { + +//===== ReadContext =========================================================== + +ReadContext::ReadContext ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const Reference<rendering::XCanvas>& rxCanvas) + : mxComponentContext(rxContext), + mxCanvas(rxCanvas) +{ + Reference<lang::XMultiComponentFactory> xFactory (rxContext->getServiceManager()); + if (xFactory.is()) + { + mxPresenterHelper.set( + xFactory->createInstanceWithContext( + "com.sun.star.comp.Draw.PresenterHelper", + rxContext), + UNO_QUERY_THROW); + } +} + +PresenterTheme::SharedFontDescriptor ReadContext::ReadFont ( + const Reference<container::XHierarchicalNameAccess>& rxNode, + const PresenterTheme::SharedFontDescriptor& rpDefault) +{ + if ( ! rxNode.is()) + return PresenterTheme::SharedFontDescriptor(); + + try + { + Reference<container::XHierarchicalNameAccess> xFont ( + PresenterConfigurationAccess::GetConfigurationNode( + rxNode, + /*rsFontPath*/""), + UNO_QUERY_THROW); + + Reference<beans::XPropertySet> xProperties (xFont, UNO_QUERY_THROW); + return ReadFont(xProperties, rpDefault); + } + catch (Exception&) + { + OSL_ASSERT(false); + } + + return PresenterTheme::SharedFontDescriptor(); +} + +PresenterTheme::SharedFontDescriptor ReadContext::ReadFont ( + const Reference<beans::XPropertySet>& rxProperties, + const PresenterTheme::SharedFontDescriptor& rpDefault) +{ + auto pDescriptor = std::make_shared<PresenterTheme::FontDescriptor>(rpDefault); + + PresenterConfigurationAccess::GetProperty(rxProperties, "FamilyName") >>= pDescriptor->msFamilyName; + PresenterConfigurationAccess::GetProperty(rxProperties, "Style") >>= pDescriptor->msStyleName; + PresenterConfigurationAccess::GetProperty(rxProperties, "Size") >>= pDescriptor->mnSize; + PresenterTheme::ConvertToColor( + PresenterConfigurationAccess::GetProperty(rxProperties, "Color"), + pDescriptor->mnColor); + PresenterConfigurationAccess::GetProperty(rxProperties, "Anchor") >>= pDescriptor->msAnchor; + PresenterConfigurationAccess::GetProperty(rxProperties, "XOffset") >>= pDescriptor->mnXOffset; + PresenterConfigurationAccess::GetProperty(rxProperties, "YOffset") >>= pDescriptor->mnYOffset; + + return pDescriptor; +} + +Any ReadContext::GetByName ( + const Reference<container::XNameAccess>& rxNode, + const OUString& rsName) +{ + OSL_ASSERT(rxNode.is()); + if (rxNode->hasByName(rsName)) + return rxNode->getByName(rsName); + else + return Any(); +} + +std::shared_ptr<PresenterTheme::Theme> ReadContext::ReadTheme ( + PresenterConfigurationAccess& rConfiguration, + const OUString& rsThemeName) +{ + std::shared_ptr<PresenterTheme::Theme> pTheme; + + OUString sCurrentThemeName (rsThemeName); + if (sCurrentThemeName.isEmpty()) + { + // No theme name given. Look up the CurrentTheme property. + rConfiguration.GetConfigurationNode("Presenter/CurrentTheme") >>= sCurrentThemeName; + if (sCurrentThemeName.isEmpty()) + { + // Still no name. Use "DefaultTheme". + sCurrentThemeName = "DefaultTheme"; + } + } + + Reference<container::XNameAccess> xThemes ( + rConfiguration.GetConfigurationNode("Presenter/Themes"), + UNO_QUERY); + if (xThemes.is()) + { + // Iterate over all themes and search the one with the given name. + const Sequence<OUString> aKeys (xThemes->getElementNames()); + for (const OUString& rsKey : aKeys) + { + Reference<container::XHierarchicalNameAccess> xTheme ( + xThemes->getByName(rsKey), UNO_QUERY); + if (xTheme.is()) + { + OUString sThemeName; + PresenterConfigurationAccess::GetConfigurationNode(xTheme, "ThemeName") + >>= sThemeName; + if (sThemeName == sCurrentThemeName) + { + pTheme = std::make_shared<PresenterTheme::Theme>(xTheme,rsKey); + break; + } + } + } + } + + if (pTheme != nullptr) + { + pTheme->Read(rConfiguration, *this); + } + + return pTheme; +} + +BorderSize ReadContext::ReadBorderSize (const Reference<container::XNameAccess>& rxNode) +{ + BorderSize aBorderSize; + + if (rxNode.is()) + { + GetByName(rxNode, "Left") >>= aBorderSize.mnLeft; + GetByName(rxNode, "Top") >>= aBorderSize.mnTop; + GetByName(rxNode, "Right") >>= aBorderSize.mnRight; + GetByName(rxNode, "Bottom") >>= aBorderSize.mnBottom; + } + + return aBorderSize; +} + +//===== PaneStyleContainer ==================================================== + +void PaneStyleContainer::Read ( + const ReadContext& rReadContext, + const Reference<container::XHierarchicalNameAccess>& rxThemeRoot) +{ + Reference<container::XNameAccess> xPaneStyleList ( + PresenterConfigurationAccess::GetConfigurationNode( + rxThemeRoot, + "PaneStyles"), + UNO_QUERY); + if (!xPaneStyleList.is()) + return; + + ::std::vector<OUString> aProperties; + aProperties.reserve(6); + aProperties.emplace_back("StyleName"); + aProperties.emplace_back("ParentStyle"); + aProperties.emplace_back("TitleFont"); + aProperties.emplace_back("InnerBorderSize"); + aProperties.emplace_back("OuterBorderSize"); + aProperties.emplace_back("BorderBitmapList"); + PresenterConfigurationAccess::ForAll( + xPaneStyleList, + aProperties, + [this, &rReadContext] (std::vector<uno::Any> const& rValues) + { + return this->ProcessPaneStyle(rReadContext, rValues); + }); +} + +void PaneStyleContainer::ProcessPaneStyle( + ReadContext const & rReadContext, + const ::std::vector<Any>& rValues) +{ + if (rValues.size() != 6) + return; + + auto pStyle = std::make_shared<PaneStyle>(); + + rValues[0] >>= pStyle->msStyleName; + + OUString sParentStyleName; + if (rValues[1] >>= sParentStyleName) + { + // Find parent style. + auto iStyle = std::find_if(mStyles.begin(), mStyles.end(), + [&sParentStyleName](const SharedPaneStyle& rxStyle) { return rxStyle->msStyleName == sParentStyleName; }); + if (iStyle != mStyles.end()) + pStyle->mpParentStyle = *iStyle; + } + + Reference<container::XHierarchicalNameAccess> xFontNode (rValues[2], UNO_QUERY); + pStyle->mpFont = ReadContext::ReadFont( + xFontNode, PresenterTheme::SharedFontDescriptor()); + + Reference<container::XNameAccess> xInnerBorderSizeNode (rValues[3], UNO_QUERY); + pStyle->maInnerBorderSize = ReadContext::ReadBorderSize(xInnerBorderSizeNode); + Reference<container::XNameAccess> xOuterBorderSizeNode (rValues[4], UNO_QUERY); + pStyle->maOuterBorderSize = ReadContext::ReadBorderSize(xOuterBorderSizeNode); + + if (pStyle->mpParentStyle != nullptr) + { + pStyle->maInnerBorderSize.Merge(pStyle->mpParentStyle->maInnerBorderSize); + pStyle->maOuterBorderSize.Merge(pStyle->mpParentStyle->maOuterBorderSize); + } + + if (rReadContext.mxCanvas.is()) + { + Reference<container::XNameAccess> xBitmapsNode (rValues[5], UNO_QUERY); + pStyle->mpBitmaps = std::make_shared<PresenterBitmapContainer>( + xBitmapsNode, + pStyle->mpParentStyle != nullptr ? pStyle->mpParentStyle->mpBitmaps + : std::shared_ptr<PresenterBitmapContainer>(), + rReadContext.mxComponentContext, rReadContext.mxCanvas, + rReadContext.mxPresenterHelper); + } + + mStyles.push_back(pStyle); +} + +SharedPaneStyle PaneStyleContainer::GetPaneStyle (const OUString& rsStyleName) const +{ + auto iStyle = std::find_if(mStyles.begin(), mStyles.end(), + [&rsStyleName](const SharedPaneStyle& rxStyle) { return rxStyle->msStyleName == rsStyleName; }); + if (iStyle != mStyles.end()) + return *iStyle; + return SharedPaneStyle(); +} + +//===== PaneStyle ============================================================= + +PaneStyle::PaneStyle() +{ +} + +SharedBitmapDescriptor PaneStyle::GetBitmap (const OUString& rsBitmapName) const +{ + if (mpBitmaps != nullptr) + { + SharedBitmapDescriptor pBitmap = mpBitmaps->GetBitmap(rsBitmapName); + if (pBitmap) + return pBitmap; + } + + if (mpParentStyle != nullptr) + return mpParentStyle->GetBitmap(rsBitmapName); + else + return SharedBitmapDescriptor(); +} + +PresenterTheme::SharedFontDescriptor PaneStyle::GetFont() const +{ + if (mpFont) + return mpFont; + else if (mpParentStyle != nullptr) + return mpParentStyle->GetFont(); + else + return PresenterTheme::SharedFontDescriptor(); +} + +//===== ViewStyleContainer ==================================================== + +void ViewStyleContainer::Read ( + const ReadContext& rReadContext, + const Reference<container::XHierarchicalNameAccess>& rxThemeRoot) +{ + Reference<container::XNameAccess> xViewStyleList ( + PresenterConfigurationAccess::GetConfigurationNode( + rxThemeRoot, + "ViewStyles"), + UNO_QUERY); + if (xViewStyleList.is()) + { + PresenterConfigurationAccess::ForAll( + xViewStyleList, + [this, &rReadContext] (OUString const&, uno::Reference<beans::XPropertySet> const& xProps) + { + return this->ProcessViewStyle(rReadContext, xProps); + }); + } +} + +void ViewStyleContainer::ProcessViewStyle( + ReadContext const & rReadContext, + const Reference<beans::XPropertySet>& rxProperties) +{ + auto pStyle = std::make_shared<ViewStyle>(); + + PresenterConfigurationAccess::GetProperty(rxProperties, "StyleName") + >>= pStyle->msStyleName; + + OUString sParentStyleName; + if (PresenterConfigurationAccess::GetProperty(rxProperties, "ParentStyle") + >>= sParentStyleName) + { + // Find parent style. + auto iStyle = std::find_if(mStyles.begin(), mStyles.end(), + [&sParentStyleName](const SharedViewStyle& rxStyle) { return rxStyle->msStyleName == sParentStyleName; }); + if (iStyle != mStyles.end()) + { + pStyle->mpParentStyle = *iStyle; + pStyle->mpFont = (*iStyle)->mpFont; + pStyle->mpBackground = (*iStyle)->mpBackground; + } + } + + Reference<container::XHierarchicalNameAccess> xFontNode ( + PresenterConfigurationAccess::GetProperty(rxProperties, "Font"), UNO_QUERY); + PresenterTheme::SharedFontDescriptor pFont ( + ReadContext::ReadFont(xFontNode, PresenterTheme::SharedFontDescriptor())); + if (pFont) + pStyle->mpFont = pFont; + + Reference<container::XHierarchicalNameAccess> xBackgroundNode ( + PresenterConfigurationAccess::GetProperty(rxProperties, "Background"), + UNO_QUERY); + SharedBitmapDescriptor pBackground (PresenterBitmapContainer::LoadBitmap( + xBackgroundNode, + OUString(), + rReadContext.mxPresenterHelper, + rReadContext.mxCanvas, + SharedBitmapDescriptor())); + if (pBackground && pBackground->GetNormalBitmap().is()) + pStyle->mpBackground = pBackground; + + mStyles.push_back(pStyle); +} + +SharedViewStyle ViewStyleContainer::GetViewStyle (const OUString& rsStyleName) const +{ + auto iStyle = std::find_if(mStyles.begin(), mStyles.end(), + [&rsStyleName](const SharedViewStyle& rxStyle) { return rxStyle->msStyleName == rsStyleName; }); + if (iStyle != mStyles.end()) + return *iStyle; + return SharedViewStyle(); +} + +//===== ViewStyle ============================================================= + +ViewStyle::ViewStyle() +{ +} + +SharedBitmapDescriptor ViewStyle::GetBitmap (std::u16string_view rsBitmapName) const +{ + if (rsBitmapName == u"Background") + return mpBackground; + else + return SharedBitmapDescriptor(); +} + +PresenterTheme::SharedFontDescriptor ViewStyle::GetFont() const +{ + if (mpFont) + return mpFont; + else if (mpParentStyle != nullptr) + return mpParentStyle->GetFont(); + else + return PresenterTheme::SharedFontDescriptor(); +} + +//===== StyleAssociationContainer ============================================= + +void StyleAssociationContainer::Read ( + const Reference<container::XHierarchicalNameAccess>& rxThemeRoot) +{ + Reference<container::XNameAccess> xStyleAssociationList ( + PresenterConfigurationAccess::GetConfigurationNode( + rxThemeRoot, + "StyleAssociations"), + UNO_QUERY); + if (!xStyleAssociationList.is()) + return; + + ::std::vector<OUString> aProperties { "ResourceURL", "StyleName" }; + PresenterConfigurationAccess::ForAll( + xStyleAssociationList, + aProperties, + [this] (std::vector<uno::Any> const& rValues) + { + return this->ProcessStyleAssociation(rValues); + }); +} + +OUString StyleAssociationContainer::GetStyleName (const OUString& rsResourceName) const +{ + StyleAssociations::const_iterator iAssociation (maStyleAssociations.find(rsResourceName)); + if (iAssociation != maStyleAssociations.end()) + return iAssociation->second; + else + return OUString(); +} + +void StyleAssociationContainer::ProcessStyleAssociation( + const ::std::vector<Any>& rValues) +{ + if (rValues.size() != 2) + return; + + OUString sResourceURL; + OUString sStyleName; + if ((rValues[0] >>= sResourceURL) + && (rValues[1] >>= sStyleName)) + { + maStyleAssociations[sResourceURL] = sStyleName; + } +} + +} // end of anonymous namespace + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterTheme.hxx b/sdext/source/presenter/PresenterTheme.hxx new file mode 100644 index 000000000..97fff4842 --- /dev/null +++ b/sdext/source/presenter/PresenterTheme.hxx @@ -0,0 +1,134 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERTHEME_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERTHEME_HXX + +#include "PresenterBitmapContainer.hxx" +#include "PresenterConfigurationAccess.hxx" +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XCanvasFont.hpp> +#include <memory> + +namespace sdext::presenter { + +/** A theme is a set of properties describing fonts, colors, and bitmaps to be used to draw + background, pane borders, and view content. + + At the moment the properties can be accessed via the getPropertyValue() method. + + For a resource URL of a pane or a view you get the name of the + associated PaneStyle or ViewStyle. + + For the name of pane or view style suffixed with and underscore and the + name of configuration property, and maybe additionally suffixed by + another underscore and sub property name you get the associated + property. + + Example: you want to access the top left bitmap of a pane border + (simplified code): + + OUString sStyleName = getPropertyValue("private:resource/pane/Presenter/Pane1"); + XBitmap xBitmap = getPropertyValue(sStyleName + "_TopLeftBitmap"); + + For the offset of the bitmap you can call + Point aOffset = getPropertyValue(sStyleName + "_TopLeftOffset"); + + This is work in progress. +*/ +class PresenterTheme +{ +public: + PresenterTheme ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas); + ~PresenterTheme(); + + bool HasCanvas() const; + void ProvideCanvas (const css::uno::Reference<css::rendering::XCanvas>& rxCanvas); + + OUString GetStyleName (const OUString& rsResourceURL) const; + ::std::vector<sal_Int32> GetBorderSize ( + const OUString& rsStyleName, + const bool bOuter) const; + + class Theme; + class FontDescriptor + { + public: + explicit FontDescriptor (const std::shared_ptr<FontDescriptor>& rpDescriptor); + + OUString msFamilyName; + OUString msStyleName; + sal_Int32 mnSize; + sal_uInt32 mnColor; + OUString msAnchor; + sal_Int32 mnXOffset; + sal_Int32 mnYOffset; + css::uno::Reference<css::rendering::XCanvasFont> mxFont; + + bool PrepareFont (const css::uno::Reference<css::rendering::XCanvas>& rxCanvas); + + private: + css::uno::Reference<css::rendering::XCanvasFont> CreateFont ( + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const double nCellSize) const; + double GetCellSizeForDesignSize ( + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const double nDesignSize) const; + }; + typedef std::shared_ptr<FontDescriptor> SharedFontDescriptor; + + SharedBitmapDescriptor GetBitmap ( + const OUString& rsStyleName, + const OUString& rsBitmapName) const; + + SharedBitmapDescriptor GetBitmap ( + const OUString& rsBitmapName) const; + + std::shared_ptr<PresenterBitmapContainer> GetBitmapContainer() const; + + SharedFontDescriptor GetFont ( + const OUString& rsStyleName) const; + + static SharedFontDescriptor ReadFont ( + const css::uno::Reference<css::container::XHierarchicalNameAccess>& rxNode, + const SharedFontDescriptor& rDefaultFount); + + static bool ConvertToColor ( + const css::uno::Any& rColorSequence, + sal_uInt32& rColor); + + std::shared_ptr<PresenterConfigurationAccess> GetNodeForViewStyle ( + const OUString& rsStyleName) const; + +private: + css::uno::Reference<css::uno::XComponentContext> mxContext; + std::shared_ptr<Theme> mpTheme; + css::uno::Reference<css::rendering::XCanvas> mxCanvas; + + std::shared_ptr<Theme> ReadTheme(); +}; + +} // end of namespace ::sd::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterTimer.cxx b/sdext/source/presenter/PresenterTimer.cxx new file mode 100644 index 000000000..b03df035c --- /dev/null +++ b/sdext/source/presenter/PresenterTimer.cxx @@ -0,0 +1,571 @@ +/* -*- 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 "PresenterTimer.hxx" + +#include <com/sun/star/lang/XMultiComponentFactory.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/frame/XTerminateListener.hpp> + +#include <osl/thread.hxx> +#include <osl/conditn.hxx> + +#include <algorithm> +#include <memory> +#include <mutex> +#include <set> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace sdext::presenter { + +namespace { +class TimerTask +{ +public: + TimerTask ( + const PresenterTimer::Task& rTask, + const TimeValue& rDueTime, + const sal_Int64 nRepeatInterval, + const sal_Int32 nTaskId); + + PresenterTimer::Task maTask; + TimeValue maDueTime; + const sal_Int64 mnRepeatInterval; + const sal_Int32 mnTaskId; + bool mbIsCanceled; +}; + +typedef std::shared_ptr<TimerTask> SharedTimerTask; + +class TimerTaskComparator +{ +public: + bool operator() (const SharedTimerTask& rpTask1, const SharedTimerTask& rpTask2) const + { + return rpTask1->maDueTime.Seconds < rpTask2->maDueTime.Seconds + || (rpTask1->maDueTime.Seconds == rpTask2->maDueTime.Seconds + && rpTask1->maDueTime.Nanosec < rpTask2->maDueTime.Nanosec); + } +}; + +/** Queue all scheduled tasks and process them when their time has come. +*/ +class TimerScheduler + : public std::enable_shared_from_this<TimerScheduler>, + public ::osl::Thread +{ +public: + static std::shared_ptr<TimerScheduler> Instance( + uno::Reference<uno::XComponentContext> const& xContext); + static SharedTimerTask CreateTimerTask ( + const PresenterTimer::Task& rTask, + const TimeValue& rDueTime, + const sal_Int64 nRepeatInterval); + + void ScheduleTask (const SharedTimerTask& rpTask); + void CancelTask (const sal_Int32 nTaskId); + + static bool GetCurrentTime (TimeValue& rCurrentTime); + static sal_Int64 GetTimeDifference ( + const TimeValue& rTargetTime, + const TimeValue& rCurrentTime); + static void ConvertToTimeValue ( + TimeValue& rTimeValue, + const sal_Int64 nTimeDifference); + static sal_Int64 ConvertFromTimeValue ( + const TimeValue& rTimeValue); + + static void NotifyTermination(); +#if !defined NDEBUG + static bool HasInstance() { return mpInstance != nullptr; } +#endif + +private: + static std::shared_ptr<TimerScheduler> mpInstance; + static std::mutex maInstanceMutex; + std::shared_ptr<TimerScheduler> mpLateDestroy; // for clean exit + static sal_Int32 mnTaskId; + + std::mutex maTaskContainerMutex; + typedef ::std::set<SharedTimerTask,TimerTaskComparator> TaskContainer; + TaskContainer maScheduledTasks; + std::mutex maCurrentTaskMutex; + SharedTimerTask mpCurrentTask; + ::osl::Condition m_Shutdown; + + TimerScheduler( + uno::Reference<uno::XComponentContext> const& xContext); +public: + virtual void SAL_CALL run() override; + virtual void SAL_CALL onTerminated() override { mpLateDestroy.reset(); } +}; + +class TerminateListener + : public ::cppu::WeakImplHelper<frame::XTerminateListener> +{ + virtual ~TerminateListener() override + { + assert(!TimerScheduler::HasInstance()); + } + + virtual void SAL_CALL disposing(lang::EventObject const&) override + { + } + + virtual void SAL_CALL queryTermination(lang::EventObject const&) override + { + } + + virtual void SAL_CALL notifyTermination(lang::EventObject const&) override + { + TimerScheduler::NotifyTermination(); + } +}; + +} // end of anonymous namespace + +//===== PresenterTimer ======================================================== + +sal_Int32 PresenterTimer::ScheduleRepeatedTask ( + const uno::Reference<uno::XComponentContext>& xContext, + const Task& rTask, + const sal_Int64 nDelay, + const sal_Int64 nInterval) +{ + assert(xContext.is()); + TimeValue aCurrentTime; + if (TimerScheduler::GetCurrentTime(aCurrentTime)) + { + TimeValue aDueTime; + TimerScheduler::ConvertToTimeValue( + aDueTime, + TimerScheduler::ConvertFromTimeValue (aCurrentTime) + nDelay); + SharedTimerTask pTask (TimerScheduler::CreateTimerTask(rTask, aDueTime, nInterval)); + TimerScheduler::Instance(xContext)->ScheduleTask(pTask); + return pTask->mnTaskId; + } + + return NotAValidTaskId; +} + +void PresenterTimer::CancelTask (const sal_Int32 nTaskId) +{ + auto const pInstance(TimerScheduler::Instance(nullptr)); + if (pInstance) + { + pInstance->CancelTask(nTaskId); + } +} + +//===== TimerScheduler ======================================================== + +std::shared_ptr<TimerScheduler> TimerScheduler::mpInstance; +std::mutex TimerScheduler::maInstanceMutex; +sal_Int32 TimerScheduler::mnTaskId = PresenterTimer::NotAValidTaskId; + +std::shared_ptr<TimerScheduler> TimerScheduler::Instance( + uno::Reference<uno::XComponentContext> const& xContext) +{ + std::scoped_lock aGuard (maInstanceMutex); + if (mpInstance == nullptr) + { + if (!xContext.is()) + return nullptr; + mpInstance.reset(new TimerScheduler(xContext)); + mpInstance->create(); + } + return mpInstance; +} + +TimerScheduler::TimerScheduler( + uno::Reference<uno::XComponentContext> const& xContext) +{ + uno::Reference<frame::XDesktop> const xDesktop( + frame::Desktop::create(xContext)); + uno::Reference<frame::XTerminateListener> const xListener( + new TerminateListener); + // assuming the desktop can take ownership + xDesktop->addTerminateListener(xListener); +} + +SharedTimerTask TimerScheduler::CreateTimerTask ( + const PresenterTimer::Task& rTask, + const TimeValue& rDueTime, + const sal_Int64 nRepeatInterval) +{ + return std::make_shared<TimerTask>(rTask, rDueTime, nRepeatInterval, ++mnTaskId); +} + +void TimerScheduler::ScheduleTask (const SharedTimerTask& rpTask) +{ + if (!rpTask) + return; + if (rpTask->mbIsCanceled) + return; + + { + std::scoped_lock aTaskGuard (maTaskContainerMutex); + maScheduledTasks.insert(rpTask); + } +} + +void TimerScheduler::CancelTask (const sal_Int32 nTaskId) +{ + // Set of scheduled tasks is sorted after their due times, not their + // task ids. Therefore we have to do a linear search for the task to + // cancel. + { + std::scoped_lock aGuard (maTaskContainerMutex); + auto iTask = std::find_if(maScheduledTasks.begin(), maScheduledTasks.end(), + [nTaskId](const SharedTimerTask& rxTask) { return rxTask->mnTaskId == nTaskId; }); + if (iTask != maScheduledTasks.end()) + maScheduledTasks.erase(iTask); + } + + // The task that is to be canceled may be currently about to be + // processed. Mark it with a flag that a) prevents a repeating task + // from being scheduled again and b) tries to prevent its execution. + { + std::scoped_lock aGuard (maCurrentTaskMutex); + if (mpCurrentTask + && mpCurrentTask->mnTaskId == nTaskId) + mpCurrentTask->mbIsCanceled = true; + } + + // Let the main-loop cleanup in its own time +} + +void TimerScheduler::NotifyTermination() +{ + std::shared_ptr<TimerScheduler> const pInstance(TimerScheduler::mpInstance); + if (!pInstance) + { + return; + } + + { + std::scoped_lock aGuard(pInstance->maTaskContainerMutex); + pInstance->maScheduledTasks.clear(); + } + + { + std::scoped_lock aGuard(pInstance->maCurrentTaskMutex); + if (pInstance->mpCurrentTask) + { + pInstance->mpCurrentTask->mbIsCanceled = true; + } + } + + pInstance->m_Shutdown.set(); + + // rhbz#1425304 join thread before shutdown + pInstance->join(); +} + +void SAL_CALL TimerScheduler::run() +{ + osl_setThreadName("sdext::presenter::TimerScheduler"); + + while (true) + { + // Get the current time. + TimeValue aCurrentTime; + if ( ! GetCurrentTime(aCurrentTime)) + { + // We can not get the current time and thus can not schedule anything. + break; + } + + // Restrict access to the maScheduledTasks member to one, mutex + // guarded, block. + SharedTimerTask pTask; + sal_Int64 nDifference = 0; + { + std::scoped_lock aGuard (maTaskContainerMutex); + + // There are no more scheduled task. Leave this loop, function and + // live of the TimerScheduler. + if (maScheduledTasks.empty()) + break; + + nDifference = GetTimeDifference( + (*maScheduledTasks.begin())->maDueTime, + aCurrentTime); + if (nDifference <= 0) + { + pTask = *maScheduledTasks.begin(); + maScheduledTasks.erase(maScheduledTasks.begin()); + } + } + + // Acquire a reference to the current task. + { + std::scoped_lock aGuard (maCurrentTaskMutex); + mpCurrentTask = pTask; + } + + if (!pTask) + { + // Wait until the first task becomes due. + TimeValue aTimeValue; + ConvertToTimeValue(aTimeValue, nDifference); + // wait on condition variable, so the thread can be stopped + m_Shutdown.wait(&aTimeValue); + } + else + { + // Execute task. + if (pTask->maTask && !pTask->mbIsCanceled) + { + pTask->maTask(aCurrentTime); + + // Re-schedule repeating tasks. + if (pTask->mnRepeatInterval > 0) + { + ConvertToTimeValue( + pTask->maDueTime, + ConvertFromTimeValue(pTask->maDueTime) + + pTask->mnRepeatInterval); + ScheduleTask(pTask); + } + } + + } + + // Release reference to the current task. + { + std::scoped_lock aGuard (maCurrentTaskMutex); + mpCurrentTask.reset(); + } + } + + // While holding maInstanceMutex + std::scoped_lock aInstance( maInstanceMutex ); + mpLateDestroy = mpInstance; + mpInstance.reset(); +} + +bool TimerScheduler::GetCurrentTime (TimeValue& rCurrentTime) +{ + TimeValue aSystemTime; + if (osl_getSystemTime(&aSystemTime)) + return osl_getLocalTimeFromSystemTime(&aSystemTime, &rCurrentTime); + return false; +} + +sal_Int64 TimerScheduler::GetTimeDifference ( + const TimeValue& rTargetTime, + const TimeValue& rCurrentTime) +{ + return ConvertFromTimeValue(rTargetTime) - ConvertFromTimeValue(rCurrentTime); +} + +void TimerScheduler::ConvertToTimeValue ( + TimeValue& rTimeValue, + const sal_Int64 nTimeDifference) +{ + rTimeValue.Seconds = sal::static_int_cast<sal_Int32>(nTimeDifference / 1000000000L); + rTimeValue.Nanosec = sal::static_int_cast<sal_Int32>(nTimeDifference % 1000000000L); +} + +sal_Int64 TimerScheduler::ConvertFromTimeValue ( + const TimeValue& rTimeValue) +{ + return sal_Int64(rTimeValue.Seconds) * 1000000000L + rTimeValue.Nanosec; +} + +//===== TimerTask ============================================================= + +namespace { + +TimerTask::TimerTask ( + const PresenterTimer::Task& rTask, + const TimeValue& rDueTime, + const sal_Int64 nRepeatInterval, + const sal_Int32 nTaskId) + : maTask(rTask), + maDueTime(rDueTime), + mnRepeatInterval(nRepeatInterval), + mnTaskId(nTaskId), + mbIsCanceled(false) +{ +} + +} // end of anonymous namespace + +//===== PresenterTimer ======================================================== + +::rtl::Reference<PresenterClockTimer> PresenterClockTimer::mpInstance; + +::rtl::Reference<PresenterClockTimer> PresenterClockTimer::Instance ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext) +{ + ::osl::MutexGuard aSolarGuard (::osl::Mutex::getGlobalMutex()); + + ::rtl::Reference<PresenterClockTimer> pTimer; + if (mpInstance.is()) + { + pTimer = mpInstance; + } + if ( ! pTimer.is()) + { + pTimer.set(new PresenterClockTimer(rxContext)); + mpInstance = pTimer; + } + return pTimer; +} + +PresenterClockTimer::PresenterClockTimer (const Reference<XComponentContext>& rxContext) + : PresenterClockTimerInterfaceBase(m_aMutex), + maDateTime(), + mnTimerTaskId(PresenterTimer::NotAValidTaskId), + mbIsCallbackPending(false), + m_xContext(rxContext) +{ + assert(m_xContext.is()); + Reference<lang::XMultiComponentFactory> xFactory = + rxContext->getServiceManager(); + if (xFactory.is()) + mxRequestCallback.set( + xFactory->createInstanceWithContext( + "com.sun.star.awt.AsyncCallback", + rxContext), + UNO_QUERY_THROW); +} + +PresenterClockTimer::~PresenterClockTimer() +{ + if (mnTimerTaskId != PresenterTimer::NotAValidTaskId) + { + PresenterTimer::CancelTask(mnTimerTaskId); + mnTimerTaskId = PresenterTimer::NotAValidTaskId; + } + + Reference<lang::XComponent> xComponent (mxRequestCallback, UNO_QUERY); + if (xComponent.is()) + xComponent->dispose(); + mxRequestCallback = nullptr; +} + +void PresenterClockTimer::AddListener (const SharedListener& rListener) +{ + osl::MutexGuard aGuard (maMutex); + + maListeners.push_back(rListener); + + // Create a timer task when the first listener is added. + if (mnTimerTaskId==PresenterTimer::NotAValidTaskId) + { + mnTimerTaskId = PresenterTimer::ScheduleRepeatedTask( + m_xContext, + [this] (TimeValue const& rTime) { return this->CheckCurrentTime(rTime); }, + 0, + 250000000 /*ns*/); + } +} + +void PresenterClockTimer::RemoveListener (const SharedListener& rListener) +{ + osl::MutexGuard aGuard (maMutex); + + ListenerContainer::iterator iListener (::std::find( + maListeners.begin(), + maListeners.end(), + rListener)); + if (iListener != maListeners.end()) + maListeners.erase(iListener); + if (maListeners.empty()) + { + // We have no more clients and therefore are not interested in time changes. + if (mnTimerTaskId != PresenterTimer::NotAValidTaskId) + { + PresenterTimer::CancelTask(mnTimerTaskId); + mnTimerTaskId = PresenterTimer::NotAValidTaskId; + } + mpInstance = nullptr; + } +} + +oslDateTime PresenterClockTimer::GetCurrentTime() +{ + TimeValue aCurrentTime; + TimerScheduler::GetCurrentTime(aCurrentTime); + oslDateTime aDateTime; + osl_getDateTimeFromTimeValue(&aCurrentTime, &aDateTime); + return aDateTime; +} + +void PresenterClockTimer::CheckCurrentTime (const TimeValue& rCurrentTime) +{ + css::uno::Reference<css::awt::XRequestCallback> xRequestCallback; + css::uno::Reference<css::awt::XCallback> xCallback; + { + osl::MutexGuard aGuard (maMutex); + + TimeValue aCurrentTime (rCurrentTime); + oslDateTime aDateTime; + if (osl_getDateTimeFromTimeValue(&aCurrentTime, &aDateTime)) + { + if (aDateTime.Seconds != maDateTime.Seconds + || aDateTime.Minutes != maDateTime.Minutes + || aDateTime.Hours != maDateTime.Hours) + { + // The displayed part of the current time has changed. + // Prepare to call the listeners. + maDateTime = aDateTime; + + // Schedule notification of listeners. + if (mxRequestCallback.is() && ! mbIsCallbackPending) + { + mbIsCallbackPending = true; + xRequestCallback = mxRequestCallback; + xCallback = this; + } + } + } + } + if (xRequestCallback.is() && xCallback.is()) + xRequestCallback->addCallback(xCallback, Any()); +} + +//----- XCallback ------------------------------------------------------------- + +void SAL_CALL PresenterClockTimer::notify (const css::uno::Any&) +{ + ListenerContainer aListenerCopy; + + { + osl::MutexGuard aGuard (maMutex); + + mbIsCallbackPending = false; + + aListenerCopy = maListeners; + } + + for (const auto& rxListener : aListenerCopy) + { + rxListener->TimeHasChanged(maDateTime); + } +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterTimer.hxx b/sdext/source/presenter/PresenterTimer.hxx new file mode 100644 index 000000000..1c2b6530b --- /dev/null +++ b/sdext/source/presenter/PresenterTimer.hxx @@ -0,0 +1,122 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERTIMER_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERTIMER_HXX + +#include <com/sun/star/awt/XCallback.hpp> +#include <com/sun/star/awt/XRequestCallback.hpp> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <osl/mutex.hxx> +#include <osl/time.h> +#include <rtl/ref.hxx> +#include <sal/types.h> + +#include <functional> +#include <memory> +#include <vector> + +namespace com::sun::star::uno { class XComponentContext; } + +namespace sdext::presenter { + +/** The timer allows tasks to be scheduled for execution at a specified time + in the future. +*/ +class PresenterTimer +{ +public: + /** A task is called with the current time. + */ + typedef ::std::function<void (const TimeValue&)> Task; + + static const sal_Int32 NotAValidTaskId = 0; + + /** Schedule a task to be executed repeatedly. The task is executed the + first time after nFirst nano-seconds (1000000000 corresponds to one + second). After that task is executed in intervals that are + nInterval ns long until CancelTask is called. + */ + static sal_Int32 ScheduleRepeatedTask ( + const css::uno::Reference<css::uno::XComponentContext>& xContext, + const Task& rTask, + const sal_Int64 nFirst, + const sal_Int64 nInterval); + + static void CancelTask (const sal_Int32 nTaskId); +}; + +typedef cppu::WeakComponentImplHelper< + css::awt::XCallback + > PresenterClockTimerInterfaceBase; + +/** A timer that calls its listeners, typically clocks, every second to + update their current time value. +*/ +class PresenterClockTimer + : protected ::cppu::BaseMutex, + public PresenterClockTimerInterfaceBase +{ +public: + class Listener { + public: + virtual void TimeHasChanged (const oslDateTime& rCurrentTime) = 0; + + protected: + ~Listener() {} + }; + typedef std::shared_ptr<Listener> SharedListener; + + static ::rtl::Reference<PresenterClockTimer> Instance ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext); + + void AddListener (const SharedListener& rListener); + void RemoveListener (const SharedListener& rListener); + + static oslDateTime GetCurrentTime(); + + // XCallback + + virtual void SAL_CALL notify (const css::uno::Any& rUserData) override; + +private: + static ::rtl::Reference<PresenterClockTimer> mpInstance; + + ::osl::Mutex maMutex; + typedef ::std::vector<SharedListener> ListenerContainer; + ListenerContainer maListeners; + oslDateTime maDateTime; + sal_Int32 mnTimerTaskId; + bool mbIsCallbackPending; + css::uno::Reference<css::awt::XRequestCallback> mxRequestCallback; + const css::uno::Reference<css::uno::XComponentContext> m_xContext; + + PresenterClockTimer ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext); + virtual ~PresenterClockTimer() override; + + void CheckCurrentTime (const TimeValue& rCurrentTime); +}; + +} // end of namespace ::sdext::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterToolBar.cxx b/sdext/source/presenter/PresenterToolBar.cxx new file mode 100644 index 000000000..75a4b7500 --- /dev/null +++ b/sdext/source/presenter/PresenterToolBar.cxx @@ -0,0 +1,2015 @@ +/* -*- 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 <vcl/settings.hxx> +#include "PresenterToolBar.hxx" + +#include "PresenterBitmapContainer.hxx" +#include "PresenterCanvasHelper.hxx" +#include "PresenterGeometryHelper.hxx" +#include "PresenterPaintManager.hxx" +#include "PresenterTimer.hxx" +#include "PresenterWindowManager.hxx" + +#include <cppuhelper/compbase.hxx> +#include <com/sun/star/awt/XWindowPeer.hpp> +#include <com/sun/star/drawing/framework/XControllerManager.hpp> +#include <com/sun/star/drawing/framework/XConfigurationController.hpp> +#include <com/sun/star/drawing/framework/XPane.hpp> +#include <com/sun/star/geometry/AffineMatrix2D.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/RenderState.hpp> +#include <com/sun/star/rendering/TextDirection.hpp> +#include <com/sun/star/rendering/ViewState.hpp> +#include <com/sun/star/rendering/XSpriteCanvas.hpp> +#include <com/sun/star/util/Color.hpp> +#include <rtl/ustrbuf.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing::framework; + +namespace sdext::presenter { + +const sal_Int32 gnGapSize (20); + +namespace { + + class Text + { + public: + Text(); + Text ( + const OUString& rsText, + const PresenterTheme::SharedFontDescriptor& rpFont); + + void SetText (const OUString& rsText); + const OUString& GetText() const; + const PresenterTheme::SharedFontDescriptor& GetFont() const; + + void Paint ( + const Reference<rendering::XCanvas>& rxCanvas, + const rendering::ViewState& rViewState, + const awt::Rectangle& rBoundingBox); + + geometry::RealRectangle2D GetBoundingBox ( + const Reference<rendering::XCanvas>& rxCanvas); + + private: + OUString msText; + PresenterTheme::SharedFontDescriptor mpFont; + }; + + class ElementMode + { + public: + ElementMode(); + ElementMode(const ElementMode&) = delete; + ElementMode& operator=(const ElementMode&) = delete; + + SharedBitmapDescriptor mpIcon; + OUString msAction; + Text maText; + + void ReadElementMode ( + const Reference<beans::XPropertySet>& rxProperties, + const OUString& rsModeName, + std::shared_ptr<ElementMode> const & rpDefaultMode, + ::sdext::presenter::PresenterToolBar::Context const & rContext); + }; + typedef std::shared_ptr<ElementMode> SharedElementMode; + +} // end of anonymous namespace + +class PresenterToolBar::Context +{ +public: + Context() = default; + Context(const Context&) = delete; + Context& operator=(const Context&) = delete; + Reference<drawing::XPresenterHelper> mxPresenterHelper; + css::uno::Reference<css::rendering::XCanvas> mxCanvas; +}; + +//===== PresenterToolBar::Element ============================================= + +namespace { + typedef cppu::WeakComponentImplHelper< + css::document::XEventListener, + css::frame::XStatusListener + > ElementInterfaceBase; + + class Element + : private ::cppu::BaseMutex, + public ElementInterfaceBase + { + public: + explicit Element (const ::rtl::Reference<PresenterToolBar>& rpToolBar); + Element(const Element&) = delete; + Element& operator=(const Element&) = delete; + + virtual void SAL_CALL disposing() override; + + virtual void SetModes ( + const SharedElementMode& rpNormalMode, + const SharedElementMode& rpMouseOverMode, + const SharedElementMode& rpSelectedMode, + const SharedElementMode& rpDisabledMode, + const SharedElementMode& rpMouseOverSelectedMode); + void CurrentSlideHasChanged(); + void SetLocation (const awt::Point& rLocation); + void SetSize (const geometry::RealSize2D& rSize); + virtual void Paint ( + const Reference<rendering::XCanvas>& rxCanvas, + const rendering::ViewState& rViewState) = 0; + awt::Size const & GetBoundingSize ( + const Reference<rendering::XCanvas>& rxCanvas); + awt::Rectangle GetBoundingBox() const; + virtual bool SetState (const bool bIsOver, const bool bIsPressed); + void Invalidate (const bool bSynchronous); + bool IsOutside (const awt::Rectangle& rBox); + virtual bool IsFilling() const; + void UpdateState(); + + // lang::XEventListener + + virtual void SAL_CALL disposing (const css::lang::EventObject& rEvent) override; + + // document::XEventListener + + virtual void SAL_CALL notifyEvent (const css::document::EventObject& rEvent) override; + + // frame::XStatusListener + + virtual void SAL_CALL statusChanged (const css::frame::FeatureStateEvent& rEvent) override; + + protected: + ::rtl::Reference<PresenterToolBar> mpToolBar; + awt::Point maLocation; + awt::Size maSize; + SharedElementMode mpNormal; + SharedElementMode mpMouseOver; + SharedElementMode mpSelected; + SharedElementMode mpDisabled; + SharedElementMode mpMouseOverSelected; + SharedElementMode mpMode; + bool mbIsOver; + bool mbIsPressed; + bool mbIsSelected; + + virtual awt::Size CreateBoundingSize ( + const Reference<rendering::XCanvas>& rxCanvas) = 0; + + bool IsEnabled() const { return mbIsEnabled;} + private: + bool mbIsEnabled; + }; + +} // end of anonymous namespace + +class PresenterToolBar::ElementContainerPart + : public ::std::vector<rtl::Reference<Element> > +{ +}; + +//===== Button ================================================================ + +namespace { + + class Button : public Element + { + public: + static ::rtl::Reference<Element> Create ( + const ::rtl::Reference<PresenterToolBar>& rpToolBar); + + virtual void SAL_CALL disposing() override; + + virtual void Paint ( + const Reference<rendering::XCanvas>& rxCanvas, + const rendering::ViewState& rViewState) override; + + // lang::XEventListener + + virtual void SAL_CALL disposing (const css::lang::EventObject& rEvent) override; + + protected: + virtual awt::Size CreateBoundingSize ( + const Reference<rendering::XCanvas>& rxCanvas) override; + + private: + bool mbIsListenerRegistered; + + Button (const ::rtl::Reference<PresenterToolBar>& rpToolBar); + void Initialize(); + void PaintIcon ( + const Reference<rendering::XCanvas>& rxCanvas, + const sal_Int32 nTextHeight, + const rendering::ViewState& rViewState); + PresenterBitmapDescriptor::Mode GetMode() const; + }; + +//===== Label ================================================================= + + class Label : public Element + { + public: + explicit Label (const ::rtl::Reference<PresenterToolBar>& rpToolBar); + + void SetText (const OUString& rsText); + virtual void Paint ( + const Reference<rendering::XCanvas>& rxCanvas, + const rendering::ViewState& rViewState) override; + virtual bool SetState (const bool bIsOver, const bool bIsPressed) override; + + protected: + virtual awt::Size CreateBoundingSize ( + const Reference<rendering::XCanvas>& rxCanvas) override; + }; + +// Some specialized controls. + + class TimeFormatter + { + public: + static OUString FormatTime (const oslDateTime& rTime); + }; + + class TimeLabel : public Label + { + public: + void ConnectToTimer(); + virtual void TimeHasChanged (const oslDateTime& rCurrentTime) = 0; + protected: + explicit TimeLabel(const ::rtl::Reference<PresenterToolBar>& rpToolBar); + using Element::disposing; + virtual void SAL_CALL disposing() override; + private: + class Listener : public PresenterClockTimer::Listener + { + public: + explicit Listener (const ::rtl::Reference<TimeLabel>& rxLabel) + : mxLabel(rxLabel) {} + virtual ~Listener() {} + virtual void TimeHasChanged (const oslDateTime& rCurrentTime) override + { if (mxLabel.is()) mxLabel->TimeHasChanged(rCurrentTime); } + private: + ::rtl::Reference<TimeLabel> mxLabel; + }; + std::shared_ptr<PresenterClockTimer::Listener> mpListener; + }; + + class CurrentTimeLabel : public TimeLabel + { + public: + static ::rtl::Reference<Element> Create ( + const ::rtl::Reference<PresenterToolBar>& rpToolBar); + virtual void SetModes ( + const SharedElementMode& rpNormalMode, + const SharedElementMode& rpMouseOverMode, + const SharedElementMode& rpSelectedMode, + const SharedElementMode& rpDisabledMode, + const SharedElementMode& rpMouseOverSelectedMode) override; + private: + CurrentTimeLabel (const ::rtl::Reference<PresenterToolBar>& rpToolBar); + virtual ~CurrentTimeLabel() override; + virtual void TimeHasChanged (const oslDateTime& rCurrentTime) override; + }; + + class PresentationTimeLabel : public TimeLabel, public IPresentationTime + { + public: + static ::rtl::Reference<Element> Create ( + const ::rtl::Reference<PresenterToolBar>& rpToolBar); + virtual void SetModes ( + const SharedElementMode& rpNormalMode, + const SharedElementMode& rpMouseOverMode, + const SharedElementMode& rpSelectedMode, + const SharedElementMode& rpDisabledMode, + const SharedElementMode& rpMouseOverSelectedMode) override; + virtual void restart() override; + virtual bool isPaused() override; + virtual void setPauseStatus(const bool pauseStatus) override; + const TimeValue& getPauseTimeValue() const; + void setPauseTimeValue(const TimeValue pauseTime); + private: + TimeValue maStartTimeValue; + TimeValue pauseTimeValue; + PresentationTimeLabel (const ::rtl::Reference<PresenterToolBar>& rpToolBar); + bool paused; + virtual ~PresentationTimeLabel() override; + virtual void TimeHasChanged (const oslDateTime& rCurrentTime) override; + }; + + class VerticalSeparator : public Element + { + public: + explicit VerticalSeparator (const ::rtl::Reference<PresenterToolBar>& rpToolBar); + virtual void Paint ( + const Reference<rendering::XCanvas>& rxCanvas, + const rendering::ViewState& rViewState) override; + virtual bool IsFilling() const override; + + protected: + virtual awt::Size CreateBoundingSize ( + const Reference<rendering::XCanvas>& rxCanvas) override; + }; + + class HorizontalSeparator : public Element + { + public: + explicit HorizontalSeparator (const ::rtl::Reference<PresenterToolBar>& rpToolBar); + virtual void Paint ( + const Reference<rendering::XCanvas>& rxCanvas, + const rendering::ViewState& rViewState) override; + virtual bool IsFilling() const override; + + protected: + virtual awt::Size CreateBoundingSize ( + const Reference<rendering::XCanvas>& rxCanvas) override; + }; +} // end of anonymous namespace + +//===== PresenterToolBar ====================================================== + +PresenterToolBar::PresenterToolBar ( + const Reference<XComponentContext>& rxContext, + const css::uno::Reference<css::awt::XWindow>& rxWindow, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const ::rtl::Reference<PresenterController>& rpPresenterController, + const Anchor eAnchor) + : PresenterToolBarInterfaceBase(m_aMutex), + mxComponentContext(rxContext), + mxWindow(rxWindow), + mxCanvas(rxCanvas), + mpPresenterController(rpPresenterController), + mbIsLayoutPending(false), + meAnchor(eAnchor) +{ +} + +void PresenterToolBar::Initialize ( + const OUString& rsConfigurationPath) +{ + try + { + CreateControls(rsConfigurationPath); + + if (mxWindow.is()) + { + mxWindow->addWindowListener(this); + mxWindow->addPaintListener(this); + mxWindow->addMouseListener(this); + mxWindow->addMouseMotionListener(this); + + Reference<awt::XWindowPeer> xPeer (mxWindow, UNO_QUERY); + if (xPeer.is()) + xPeer->setBackground(util::Color(0xff000000)); + + mxWindow->setVisible(true); + } + + mxSlideShowController = mpPresenterController->GetSlideShowController(); + UpdateSlideNumber(); + mbIsLayoutPending = true; + } + catch (RuntimeException&) + { + mpCurrentContainerPart.reset(); + maElementContainer.clear(); + throw; + } +} + +PresenterToolBar::~PresenterToolBar() +{ +} + +void SAL_CALL PresenterToolBar::disposing() +{ + if (mxWindow.is()) + { + mxWindow->removeWindowListener(this); + mxWindow->removePaintListener(this); + mxWindow->removeMouseListener(this); + mxWindow->removeMouseMotionListener(this); + mxWindow = nullptr; + } + + // Dispose tool bar elements. + for (const auto& rxPart : maElementContainer) + { + OSL_ASSERT(rxPart != nullptr); + for (const rtl::Reference<Element>& pElement : *rxPart) + { + if (pElement) + { + Reference<lang::XComponent> xComponent = pElement; + if (xComponent.is()) + xComponent->dispose(); + } + } + } + + mpCurrentContainerPart.reset(); + maElementContainer.clear(); +} + +void PresenterToolBar::InvalidateArea ( + const awt::Rectangle& rRepaintBox, + const bool bSynchronous) +{ + std::shared_ptr<PresenterPaintManager> xManager(mpPresenterController->GetPaintManager()); + if (!xManager) + return; + xManager->Invalidate( + mxWindow, + rRepaintBox, + bSynchronous); +} + +void PresenterToolBar::RequestLayout() +{ + mbIsLayoutPending = true; + + std::shared_ptr<PresenterPaintManager> xManager(mpPresenterController->GetPaintManager()); + if (!xManager) + return; + + xManager->Invalidate(mxWindow); +} + +geometry::RealSize2D const & PresenterToolBar::GetMinimalSize() +{ + if (mbIsLayoutPending) + Layout(mxCanvas); + return maMinimalSize; +} + +const ::rtl::Reference<PresenterController>& PresenterToolBar::GetPresenterController() const +{ + return mpPresenterController; +} + +const Reference<XComponentContext>& PresenterToolBar::GetComponentContext() const +{ + return mxComponentContext; +} + +//----- lang::XEventListener ------------------------------------------------- + +void SAL_CALL PresenterToolBar::disposing (const lang::EventObject& rEventObject) +{ + if (rEventObject.Source == mxWindow) + mxWindow = nullptr; +} + +//----- XWindowListener ------------------------------------------------------- + +void SAL_CALL PresenterToolBar::windowResized (const awt::WindowEvent&) +{ + mbIsLayoutPending = true; +} + +void SAL_CALL PresenterToolBar::windowMoved (const awt::WindowEvent&) {} + +void SAL_CALL PresenterToolBar::windowShown (const lang::EventObject&) +{ + mbIsLayoutPending = true; +} + +void SAL_CALL PresenterToolBar::windowHidden (const lang::EventObject&) {} + +//----- XPaintListener -------------------------------------------------------- +void SAL_CALL PresenterToolBar::windowPaint (const css::awt::PaintEvent& rEvent) +{ + if ( ! mxCanvas.is()) + return; + + if ( ! mbIsPresenterViewActive) + return; + + const rendering::ViewState aViewState ( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + PresenterGeometryHelper::CreatePolygon(rEvent.UpdateRect, mxCanvas->getDevice())); + + if (mbIsLayoutPending) + Layout(mxCanvas); + + Paint(rEvent.UpdateRect, aViewState); + + // Make the back buffer visible. + Reference<rendering::XSpriteCanvas> xSpriteCanvas (mxCanvas, UNO_QUERY); + if (xSpriteCanvas.is()) + xSpriteCanvas->updateScreen(false); +} + +//----- XMouseListener -------------------------------------------------------- +void SAL_CALL PresenterToolBar::mousePressed (const css::awt::MouseEvent& rEvent) +{ + ThrowIfDisposed(); + CheckMouseOver(rEvent, true, true); +} + +void SAL_CALL PresenterToolBar::mouseReleased (const css::awt::MouseEvent& rEvent) +{ + ThrowIfDisposed(); + CheckMouseOver(rEvent, true); +} + +void SAL_CALL PresenterToolBar::mouseEntered (const css::awt::MouseEvent& rEvent) +{ + ThrowIfDisposed(); + CheckMouseOver(rEvent, true); +} + +void SAL_CALL PresenterToolBar::mouseExited (const css::awt::MouseEvent& rEvent) +{ + ThrowIfDisposed(); + CheckMouseOver(rEvent, false); + } + +//----- XMouseMotionListener -------------------------------------------------- + +void SAL_CALL PresenterToolBar::mouseMoved (const css::awt::MouseEvent& rEvent) +{ + ThrowIfDisposed(); + CheckMouseOver(rEvent, true); + } + +void SAL_CALL PresenterToolBar::mouseDragged (const css::awt::MouseEvent&) +{ + ThrowIfDisposed(); +} + +//----- XDrawView ------------------------------------------------------------- + +void SAL_CALL PresenterToolBar::setCurrentPage (const Reference<drawing::XDrawPage>& rxSlide) +{ + if (rxSlide != mxCurrentSlide) + { + mxCurrentSlide = rxSlide; + UpdateSlideNumber(); + } +} + +Reference<drawing::XDrawPage> SAL_CALL PresenterToolBar::getCurrentPage() +{ + return mxCurrentSlide; +} + + +void PresenterToolBar::CreateControls ( + const OUString& rsConfigurationPath) +{ + if ( ! mxWindow.is()) + return; + + // Expand the macro in the bitmap file names. + PresenterConfigurationAccess aConfiguration ( + mxComponentContext, + "/org.openoffice.Office.PresenterScreen/", + PresenterConfigurationAccess::READ_ONLY); + + mpCurrentContainerPart = std::make_shared<ElementContainerPart>(); + maElementContainer.clear(); + maElementContainer.push_back(mpCurrentContainerPart); + + Reference<container::XHierarchicalNameAccess> xToolBarNode ( + aConfiguration.GetConfigurationNode(rsConfigurationPath), + UNO_QUERY); + if (!xToolBarNode.is()) + return; + + Reference<container::XNameAccess> xEntries ( + PresenterConfigurationAccess::GetConfigurationNode(xToolBarNode, "Entries"), + UNO_QUERY); + Context aContext; + aContext.mxPresenterHelper = mpPresenterController->GetPresenterHelper(); + aContext.mxCanvas = mxCanvas; + if (xEntries.is() + && aContext.mxPresenterHelper.is() + && aContext.mxCanvas.is()) + { + PresenterConfigurationAccess::ForAll( + xEntries, + [this, &aContext] (OUString const&, uno::Reference<beans::XPropertySet> const& xProps) + { + return this->ProcessEntry(xProps, aContext); + }); + } +} + +void PresenterToolBar::ProcessEntry ( + const Reference<beans::XPropertySet>& rxProperties, + Context const & rContext) +{ + if ( ! rxProperties.is()) + return; + + // Type has to be present. + OUString sType; + if ( ! (PresenterConfigurationAccess::GetProperty(rxProperties, "Type") >>= sType)) + return; + + // Read mode specific values. + SharedElementMode pNormalMode = std::make_shared<ElementMode>(); + SharedElementMode pMouseOverMode = std::make_shared<ElementMode>(); + SharedElementMode pSelectedMode = std::make_shared<ElementMode>(); + SharedElementMode pDisabledMode = std::make_shared<ElementMode>(); + SharedElementMode pMouseOverSelectedMode = std::make_shared<ElementMode>(); + pNormalMode->ReadElementMode(rxProperties, "Normal", pNormalMode, rContext); + pMouseOverMode->ReadElementMode(rxProperties, "MouseOver", pNormalMode, rContext); + pSelectedMode->ReadElementMode(rxProperties, "Selected", pNormalMode, rContext); + pDisabledMode->ReadElementMode(rxProperties, "Disabled", pNormalMode, rContext); + pMouseOverSelectedMode->ReadElementMode(rxProperties, "MouseOverSelected", pSelectedMode, rContext); + + // Create new element. + ::rtl::Reference<Element> pElement; + if ( sType == "Button" ) + pElement = Button::Create(this); + else if ( sType == "CurrentTimeLabel" ) + pElement = CurrentTimeLabel::Create(this); + else if ( sType == "PresentationTimeLabel" ) + pElement = PresentationTimeLabel::Create(this); + else if ( sType == "VerticalSeparator" ) + pElement.set(new VerticalSeparator(this)); + else if ( sType == "HorizontalSeparator" ) + pElement.set(new HorizontalSeparator(this)); + else if ( sType == "Label" ) + pElement.set(new Label(this)); + else if ( sType == "ChangeOrientation" ) + { + mpCurrentContainerPart = std::make_shared<ElementContainerPart>(); + maElementContainer.push_back(mpCurrentContainerPart); + return; + } + if (pElement.is()) + { + pElement->SetModes( pNormalMode, pMouseOverMode, pSelectedMode, pDisabledMode, pMouseOverSelectedMode); + pElement->UpdateState(); + if (mpCurrentContainerPart) + mpCurrentContainerPart->push_back(pElement); + } +} + +void PresenterToolBar::Layout ( + const Reference<rendering::XCanvas>& rxCanvas) +{ + if (maElementContainer.empty()) + return; + + mbIsLayoutPending = false; + + const awt::Rectangle aWindowBox (mxWindow->getPosSize()); + ::std::vector<geometry::RealSize2D> aPartSizes (maElementContainer.size()); + geometry::RealSize2D aTotalSize (0,0); + bool bIsHorizontal (true); + sal_Int32 nIndex (0); + double nTotalHorizontalGap (0); + sal_Int32 nGapCount (0); + for (const auto& rxPart : maElementContainer) + { + geometry::RealSize2D aSize (CalculatePartSize(rxCanvas, rxPart, bIsHorizontal)); + + // Remember the size of each part for later. + aPartSizes[nIndex] = aSize; + + // Add gaps between elements. + if (rxPart->size()>1 && bIsHorizontal) + { + nTotalHorizontalGap += (rxPart->size() - 1) * gnGapSize; + nGapCount += rxPart->size() - 1; + } + + // Orientation changes for each part. + bIsHorizontal = !bIsHorizontal; + // Width is accumulated. + aTotalSize.Width += aSize.Width; + // Height is the maximum height of all parts. + aTotalSize.Height = ::std::max(aTotalSize.Height, aSize.Height); + ++nIndex; + } + // Add gaps between parts. + if (maElementContainer.size() > 1) + { + nTotalHorizontalGap += (maElementContainer.size() - 1) * gnGapSize; + nGapCount += maElementContainer.size()-1; + } + + // Done to introduce gap between the end of the toolbar and the last button + aTotalSize.Width += gnGapSize/2; + + // Calculate the minimal size so that the window size of the tool bar + // can be adapted accordingly. + maMinimalSize = aTotalSize; + maMinimalSize.Width += nTotalHorizontalGap; + + // Calculate the gaps between elements. + double nGapWidth (0); + if (nGapCount > 0) + { + if (aTotalSize.Width + nTotalHorizontalGap > aWindowBox.Width) + nTotalHorizontalGap = aWindowBox.Width - aTotalSize.Width; + nGapWidth = nTotalHorizontalGap / nGapCount; + } + + // Determine the location of the left edge. + double nX (0); + switch (meAnchor) + { + case Left : nX = 0; break; + case Center: nX = (aWindowBox.Width - aTotalSize.Width - nTotalHorizontalGap) / 2; break; + } + + // Place the parts. + double nY ((aWindowBox.Height - aTotalSize.Height) / 2); + bIsHorizontal = true; + + /* push front or back ? ... */ + /// check whether RTL interface or not + if(!AllSettings::GetLayoutRTL()){ + nIndex = 0; + for (const auto& rxPart : maElementContainer) + { + geometry::RealRectangle2D aBoundingBox( + nX, nY, + nX+aPartSizes[nIndex].Width, nY+aTotalSize.Height); + + // Add space for gaps between elements. + if (rxPart->size() > 1 && bIsHorizontal) + aBoundingBox.X2 += (rxPart->size() - 1) * nGapWidth; + + LayoutPart(rxCanvas, rxPart, aBoundingBox, aPartSizes[nIndex], bIsHorizontal); + bIsHorizontal = !bIsHorizontal; + nX += aBoundingBox.X2 - aBoundingBox.X1 + nGapWidth; + ++nIndex; + } + } + else { + ElementContainer::iterator iPart; + ElementContainer::iterator iBegin (maElementContainer.begin()); + for (iPart=maElementContainer.end()-1, nIndex=2; iPart!=iBegin-1; --iPart, --nIndex) + { + geometry::RealRectangle2D aBoundingBox( + nX, nY, + nX+aPartSizes[nIndex].Width, nY+aTotalSize.Height); + + // Add space for gaps between elements. + if ((*iPart)->size() > 1) + if (bIsHorizontal) + aBoundingBox.X2 += ((*iPart)->size()-1) * nGapWidth; + + LayoutPart(rxCanvas, *iPart, aBoundingBox, aPartSizes[nIndex], bIsHorizontal); + bIsHorizontal = !bIsHorizontal; + nX += aBoundingBox.X2 - aBoundingBox.X1 + nGapWidth; + } + } + + // The whole window has to be repainted. + std::shared_ptr<PresenterPaintManager> xManager(mpPresenterController->GetPaintManager()); + if (!xManager) + return; + xManager->Invalidate(mxWindow); +} + +geometry::RealSize2D PresenterToolBar::CalculatePartSize ( + const Reference<rendering::XCanvas>& rxCanvas, + const SharedElementContainerPart& rpPart, + const bool bIsHorizontal) +{ + geometry::RealSize2D aTotalSize (0,0); + + if (mxWindow.is()) + { + // Calculate the summed width of all elements. + for (const auto& rxElement : *rpPart) + { + if (!rxElement) + continue; + + const awt::Size aBSize (rxElement->GetBoundingSize(rxCanvas)); + if (bIsHorizontal) + { + aTotalSize.Width += aBSize.Width; + if (aBSize.Height > aTotalSize.Height) + aTotalSize.Height = aBSize.Height; + } + else + { + aTotalSize.Height += aBSize.Height; + if (aBSize.Width > aTotalSize.Width) + aTotalSize.Width = aBSize.Width; + } + } + } + return aTotalSize; +} + +void PresenterToolBar::LayoutPart ( + const Reference<rendering::XCanvas>& rxCanvas, + const SharedElementContainerPart& rpPart, + const geometry::RealRectangle2D& rBoundingBox, + const geometry::RealSize2D& rPartSize, + const bool bIsHorizontal) +{ + double nGap (0); + if (rpPart->size() > 1) + { + if (bIsHorizontal) + nGap = (rBoundingBox.X2 - rBoundingBox.X1 - rPartSize.Width) / (rpPart->size()-1); + else + nGap = (rBoundingBox.Y2 - rBoundingBox.Y1 - rPartSize.Height) / (rpPart->size()-1); + } + + // Place the elements. + double nX (rBoundingBox.X1); + double nY (rBoundingBox.Y1); + + /// check whether RTL interface or not + if(!AllSettings::GetLayoutRTL()){ + for (auto& rxElement : *rpPart) + { + if (!rxElement) + continue; + + const awt::Size aElementSize (rxElement->GetBoundingSize(rxCanvas)); + if (bIsHorizontal) + { + if (rxElement->IsFilling()) + { + nY = rBoundingBox.Y1; + rxElement->SetSize(geometry::RealSize2D(aElementSize.Width, rBoundingBox.Y2 - rBoundingBox.Y1)); + } + else + nY = rBoundingBox.Y1 + (rBoundingBox.Y2-rBoundingBox.Y1 - aElementSize.Height) / 2; + rxElement->SetLocation(awt::Point(sal_Int32(0.5 + nX), sal_Int32(0.5 + nY))); + nX += aElementSize.Width + nGap; + } + else + { + if (rxElement->IsFilling()) + { + nX = rBoundingBox.X1; + rxElement->SetSize(geometry::RealSize2D(rBoundingBox.X2 - rBoundingBox.X1, aElementSize.Height)); + } + else + nX = rBoundingBox.X1 + (rBoundingBox.X2-rBoundingBox.X1 - aElementSize.Width) / 2; + rxElement->SetLocation(awt::Point(sal_Int32(0.5 + nX), sal_Int32(0.5 + nY))); + nY += aElementSize.Height + nGap; + } + } + } + else { + ElementContainerPart::const_iterator iElement; + ElementContainerPart::const_iterator iBegin (rpPart->begin()); + + for (iElement=rpPart->end()-1; iElement!=iBegin-1; --iElement) + { + if (iElement->get() == nullptr) + continue; + + const awt::Size aElementSize ((*iElement)->GetBoundingSize(rxCanvas)); + if (bIsHorizontal) + { + if ((*iElement)->IsFilling()) + { + nY = rBoundingBox.Y1; + (*iElement)->SetSize(geometry::RealSize2D(aElementSize.Width, rBoundingBox.Y2 - rBoundingBox.Y1)); + } + else + nY = rBoundingBox.Y1 + (rBoundingBox.Y2-rBoundingBox.Y1 - aElementSize.Height) / 2; + (*iElement)->SetLocation(awt::Point(sal_Int32(0.5 + nX), sal_Int32(0.5 + nY))); + nX += aElementSize.Width + nGap; + } + else + { + // reverse presentation time with current time + if (iElement==iBegin){ + iElement=iBegin+2; + } + else if (iElement==iBegin+2){ + iElement=iBegin; + } + const awt::Size aNewElementSize ((*iElement)->GetBoundingSize(rxCanvas)); + if ((*iElement)->IsFilling()) + { + nX = rBoundingBox.X1; + (*iElement)->SetSize(geometry::RealSize2D(rBoundingBox.X2 - rBoundingBox.X1, aNewElementSize.Height)); + } + else + nX = rBoundingBox.X1 + (rBoundingBox.X2-rBoundingBox.X1 - aNewElementSize.Width) / 2; + (*iElement)->SetLocation(awt::Point(sal_Int32(0.5 + nX), sal_Int32(0.5 + nY))); + nY += aNewElementSize.Height + nGap; + + // return the index as it was before the reversing + if (iElement==iBegin) + iElement=iBegin+2; + else if (iElement==iBegin+2) + iElement=iBegin; + } + } + } +} + +void PresenterToolBar::Paint ( + const awt::Rectangle& rUpdateBox, + const rendering::ViewState& rViewState) +{ + OSL_ASSERT(mxCanvas.is()); + + for (const auto& rxPart : maElementContainer) + { + for (auto& rxElement : *rxPart) + { + if (rxElement) + { + if ( ! rxElement->IsOutside(rUpdateBox)) + rxElement->Paint(mxCanvas, rViewState); + } + } + } +} + +void PresenterToolBar::UpdateSlideNumber() +{ + if( mxSlideShowController.is() ) + { + for (const auto& rxPart : maElementContainer) + { + for (auto& rxElement : *rxPart) + { + if (rxElement) + rxElement->CurrentSlideHasChanged(); + } + } + } +} + +void PresenterToolBar::CheckMouseOver ( + const css::awt::MouseEvent& rEvent, + const bool bOverWindow, + const bool bMouseDown) +{ + css::awt::MouseEvent rTemp =rEvent; + if(AllSettings::GetLayoutRTL()){ + awt::Rectangle aWindowBox = mxWindow->getPosSize(); + rTemp.X=aWindowBox.Width-rTemp.X; + } + for (const auto& rxPart : maElementContainer) + { + for (auto& rxElement : *rxPart) + { + if (!rxElement) + continue; + + awt::Rectangle aBox (rxElement->GetBoundingBox()); + const bool bIsOver = bOverWindow + && aBox.X <= rTemp.X + && aBox.Width+aBox.X-1 >= rTemp.X + && aBox.Y <= rTemp.Y + && aBox.Height+aBox.Y-1 >= rTemp.Y; + rxElement->SetState( + bIsOver, + bIsOver && rTemp.Buttons!=0 && bMouseDown && rTemp.ClickCount>0); + } + } +} + +void PresenterToolBar::ThrowIfDisposed() const +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ( + "PresenterToolBar has already been disposed", + const_cast<uno::XWeak*>(static_cast<const uno::XWeak*>(this))); + } +} + +//===== PresenterToolBarView ================================================== + +PresenterToolBarView::PresenterToolBarView ( + const Reference<XComponentContext>& rxContext, + const Reference<XResourceId>& rxViewId, + const Reference<frame::XController>& rxController, + const ::rtl::Reference<PresenterController>& rpPresenterController) + : PresenterToolBarViewInterfaceBase(m_aMutex), + mxViewId(rxViewId), + mpPresenterController(rpPresenterController) +{ + try + { + Reference<XControllerManager> xCM (rxController, UNO_QUERY_THROW); + Reference<XConfigurationController> xCC(xCM->getConfigurationController(),UNO_SET_THROW); + mxPane.set(xCC->getResource(rxViewId->getAnchor()), UNO_QUERY_THROW); + + mxWindow = mxPane->getWindow(); + mxCanvas = mxPane->getCanvas(); + + mpToolBar = new PresenterToolBar( + rxContext, + mxWindow, + mxCanvas, + rpPresenterController, + PresenterToolBar::Center); + mpToolBar->Initialize("PresenterScreenSettings/ToolBars/ToolBar"); + + if (mxWindow.is()) + { + mxWindow->addPaintListener(this); + + Reference<awt::XWindowPeer> xPeer (mxWindow, UNO_QUERY); + if (xPeer.is()) + xPeer->setBackground(util::Color(0xff000000)); + + mxWindow->setVisible(true); + } + } + catch (RuntimeException&) + { + mxViewId = nullptr; + throw; + } +} + +PresenterToolBarView::~PresenterToolBarView() +{ +} + +void SAL_CALL PresenterToolBarView::disposing() +{ + Reference<lang::XComponent> xComponent = mpToolBar; + mpToolBar = nullptr; + if (xComponent.is()) + xComponent->dispose(); + + if (mxWindow.is()) + { + mxWindow->removePaintListener(this); + mxWindow = nullptr; + } + mxCanvas = nullptr; + mxViewId = nullptr; + mxPane = nullptr; + mpPresenterController = nullptr; +} + +const ::rtl::Reference<PresenterToolBar>& PresenterToolBarView::GetPresenterToolBar() const +{ + return mpToolBar; +} + +//----- XPaintListener -------------------------------------------------------- + +void SAL_CALL PresenterToolBarView::windowPaint (const css::awt::PaintEvent& rEvent) +{ + awt::Rectangle aWindowBox (mxWindow->getPosSize()); + mpPresenterController->GetCanvasHelper()->Paint( + mpPresenterController->GetViewBackground(mxViewId->getResourceURL()), + mxCanvas, + rEvent.UpdateRect, + awt::Rectangle(0,0,aWindowBox.Width, aWindowBox.Height), + awt::Rectangle()); +} + +//----- lang::XEventListener ------------------------------------------------- + +void SAL_CALL PresenterToolBarView::disposing (const lang::EventObject& rEventObject) +{ + if (rEventObject.Source == mxWindow) + mxWindow = nullptr; +} + +//----- XResourceId ----------------------------------------------------------- + +Reference<XResourceId> SAL_CALL PresenterToolBarView::getResourceId() +{ + return mxViewId; +} + +sal_Bool SAL_CALL PresenterToolBarView::isAnchorOnly() +{ + return false; +} + +//----- XDrawView ------------------------------------------------------------- + +void SAL_CALL PresenterToolBarView::setCurrentPage (const Reference<drawing::XDrawPage>& rxSlide) +{ + Reference<drawing::XDrawView> xToolBar = mpToolBar; + if (xToolBar.is()) + xToolBar->setCurrentPage(rxSlide); +} + +Reference<drawing::XDrawPage> SAL_CALL PresenterToolBarView::getCurrentPage() +{ + return nullptr; +} + +//===== PresenterToolBar::Element ============================================= + +namespace { + +Element::Element ( + const ::rtl::Reference<PresenterToolBar>& rpToolBar) + : ElementInterfaceBase(m_aMutex), + mpToolBar(rpToolBar), + mbIsOver(false), + mbIsPressed(false), + mbIsSelected(false), + mbIsEnabled(true) +{ + if (mpToolBar) + { + OSL_ASSERT(mpToolBar->GetPresenterController().is()); + OSL_ASSERT(mpToolBar->GetPresenterController()->GetWindowManager().is()); + } +} + +void Element::SetModes ( + const SharedElementMode& rpNormalMode, + const SharedElementMode& rpMouseOverMode, + const SharedElementMode& rpSelectedMode, + const SharedElementMode& rpDisabledMode, + const SharedElementMode& rpMouseOverSelectedMode) +{ + mpNormal = rpNormalMode; + mpMouseOver = rpMouseOverMode; + mpSelected = rpSelectedMode; + mpDisabled = rpDisabledMode; + mpMouseOverSelected = rpMouseOverSelectedMode; + mpMode = rpNormalMode; +} + +void Element::disposing() +{ +} + +awt::Size const & Element::GetBoundingSize ( + const Reference<rendering::XCanvas>& rxCanvas) +{ + maSize = CreateBoundingSize(rxCanvas); + return maSize; +} + +awt::Rectangle Element::GetBoundingBox() const +{ + return awt::Rectangle(maLocation.X,maLocation.Y, maSize.Width, maSize.Height); +} + +void Element::CurrentSlideHasChanged() +{ + UpdateState(); +} + +void Element::SetLocation (const awt::Point& rLocation) +{ + maLocation = rLocation; +} + +void Element::SetSize (const geometry::RealSize2D& rSize) +{ + maSize = awt::Size(sal_Int32(0.5+rSize.Width), sal_Int32(0.5+rSize.Height)); +} + +bool Element::SetState ( + const bool bIsOver, + const bool bIsPressed) +{ + bool bModified (mbIsOver != bIsOver || mbIsPressed != bIsPressed); + bool bClicked (mbIsPressed && bIsOver && ! bIsPressed); + + mbIsOver = bIsOver; + mbIsPressed = bIsPressed; + + // When the element is disabled then ignore mouse over or selection. + // When the element is selected then ignore mouse over. + if ( ! mbIsEnabled) + mpMode = mpDisabled; + else if (mbIsSelected && mbIsOver) + mpMode = mpMouseOverSelected; + else if (mbIsSelected) + mpMode = mpSelected; + else if (mbIsOver) + mpMode = mpMouseOver; + else + mpMode = mpNormal; + + if (bClicked && mbIsEnabled) + { + if (mpMode) + { + do + { + if (mpMode->msAction.isEmpty()) + break; + + if (!mpToolBar) + break; + + if (!mpToolBar->GetPresenterController()) + break; + + mpToolBar->GetPresenterController()->DispatchUnoCommand(mpMode->msAction); + mpToolBar->RequestLayout(); + } + while (false); + } + + } + else if (bModified) + { + Invalidate(true); + } + + return bModified; +} + +void Element::Invalidate (const bool bSynchronous) +{ + OSL_ASSERT(mpToolBar.is()); + mpToolBar->InvalidateArea(GetBoundingBox(), bSynchronous); +} + +bool Element::IsOutside (const awt::Rectangle& rBox) +{ + if (rBox.X >= maLocation.X+maSize.Width) + return true; + else if (rBox.Y >= maLocation.Y+maSize.Height) + return true; + else if (maLocation.X >= rBox.X+rBox.Width) + return true; + else if (maLocation.Y >= rBox.Y+rBox.Height) + return true; + else + return false; +} + + +bool Element::IsFilling() const +{ + return false; +} + +void Element::UpdateState() +{ + OSL_ASSERT(mpToolBar); + OSL_ASSERT(mpToolBar->GetPresenterController()); + + if (!mpMode) + return; + + util::URL aURL (mpToolBar->GetPresenterController()->CreateURLFromString(mpMode->msAction)); + Reference<frame::XDispatch> xDispatch (mpToolBar->GetPresenterController()->GetDispatch(aURL)); + if (xDispatch.is()) + { + xDispatch->addStatusListener(this, aURL); + xDispatch->removeStatusListener(this, aURL); + } +} + +//----- lang::XEventListener -------------------------------------------------- + +void SAL_CALL Element::disposing (const css::lang::EventObject&) {} + +//----- document::XEventListener ---------------------------------------------- + +void SAL_CALL Element::notifyEvent (const css::document::EventObject&) +{ + UpdateState(); +} + +//----- frame::XStatusListener ------------------------------------------------ + +void SAL_CALL Element::statusChanged (const css::frame::FeatureStateEvent& rEvent) +{ + bool bIsSelected (mbIsSelected); + bool bIsEnabled (rEvent.IsEnabled); + rEvent.State >>= bIsSelected; + + if (bIsSelected != mbIsSelected || bIsEnabled != mbIsEnabled) + { + mbIsEnabled = bIsEnabled; + mbIsSelected = bIsSelected; + SetState(mbIsOver, mbIsPressed); + mpToolBar->RequestLayout(); + } +} + +} // end of anonymous namespace + +//===== ElementMode =========================================================== + +namespace { + +ElementMode::ElementMode() +{ +} + +void ElementMode::ReadElementMode ( + const Reference<beans::XPropertySet>& rxElementProperties, + const OUString& rsModeName, + std::shared_ptr<ElementMode> const & rpDefaultMode, + ::sdext::presenter::PresenterToolBar::Context const & rContext) +{ + try + { + Reference<container::XHierarchicalNameAccess> xNode ( + PresenterConfigurationAccess::GetProperty(rxElementProperties, rsModeName), + UNO_QUERY); + Reference<beans::XPropertySet> xProperties ( + PresenterConfigurationAccess::GetNodeProperties(xNode, OUString())); + if (!xProperties.is() && rpDefaultMode != nullptr) + { + // The mode is not specified. Use the given, possibly empty, + // default mode instead. + mpIcon = rpDefaultMode->mpIcon; + msAction = rpDefaultMode->msAction; + maText = rpDefaultMode->maText; + } + + // Read action. + if ( ! (PresenterConfigurationAccess::GetProperty(xProperties, "Action") >>= msAction)) + if (rpDefaultMode != nullptr) + msAction = rpDefaultMode->msAction; + + // Read text and font + OUString sText(rpDefaultMode != nullptr ? rpDefaultMode->maText.GetText() : OUString()); + PresenterConfigurationAccess::GetProperty(xProperties, "Text") >>= sText; + Reference<container::XHierarchicalNameAccess> xFontNode ( + PresenterConfigurationAccess::GetProperty(xProperties, "Font"), UNO_QUERY); + PresenterTheme::SharedFontDescriptor pFont(PresenterTheme::ReadFont( + xFontNode, rpDefaultMode != nullptr ? rpDefaultMode->maText.GetFont() + : PresenterTheme::SharedFontDescriptor())); + maText = Text(sText,pFont); + + // Read bitmaps to display as icons. + Reference<container::XHierarchicalNameAccess> xIconNode ( + PresenterConfigurationAccess::GetProperty(xProperties, "Icon"), UNO_QUERY); + mpIcon = PresenterBitmapContainer::LoadBitmap( + xIconNode, "", rContext.mxPresenterHelper, rContext.mxCanvas, + rpDefaultMode != nullptr ? rpDefaultMode->mpIcon : SharedBitmapDescriptor()); + } + catch(Exception&) + { + OSL_ASSERT(false); + } +} + +} // end of anonymous namespace + +//===== Button ================================================================ + +namespace { + +::rtl::Reference<Element> Button::Create ( + const ::rtl::Reference<PresenterToolBar>& rpToolBar) +{ + ::rtl::Reference<Button> pElement (new Button(rpToolBar)); + pElement->Initialize(); + return pElement; +} + +Button::Button ( + const ::rtl::Reference<PresenterToolBar>& rpToolBar) + : Element(rpToolBar), + mbIsListenerRegistered(false) +{ + OSL_ASSERT(mpToolBar); + OSL_ASSERT(mpToolBar->GetPresenterController().is()); + OSL_ASSERT(mpToolBar->GetPresenterController()->GetWindowManager().is()); +} + +void Button::Initialize() +{ + mpToolBar->GetPresenterController()->GetWindowManager()->AddLayoutListener(this); + mbIsListenerRegistered = true; +} + +void Button::disposing() +{ + OSL_ASSERT(mpToolBar); + if (mpToolBar && mbIsListenerRegistered) + { + OSL_ASSERT(mpToolBar->GetPresenterController().is()); + OSL_ASSERT(mpToolBar->GetPresenterController()->GetWindowManager().is()); + + mbIsListenerRegistered = false; + mpToolBar->GetPresenterController()->GetWindowManager()->RemoveLayoutListener(this); + } + Element::disposing(); +} + +void Button::Paint ( + const Reference<rendering::XCanvas>& rxCanvas, + const rendering::ViewState& rViewState) +{ + OSL_ASSERT(rxCanvas.is()); + + if (!mpMode) + return; + + if (!mpMode->mpIcon) + return; + + geometry::RealRectangle2D aTextBBox (mpMode->maText.GetBoundingBox(rxCanvas)); + sal_Int32 nTextHeight (sal::static_int_cast<sal_Int32>(0.5 + aTextBBox.Y2 - aTextBBox.Y1)); + + PaintIcon(rxCanvas, nTextHeight, rViewState); + mpMode->maText.Paint(rxCanvas, rViewState, GetBoundingBox()); +} + +awt::Size Button::CreateBoundingSize ( + const Reference<rendering::XCanvas>& rxCanvas) +{ + if (!mpMode) + return awt::Size(); + + geometry::RealRectangle2D aTextBBox (mpMode->maText.GetBoundingBox(rxCanvas)); + + // tdf#128964 This ensures that if the text of a button changes due to a change in + // the state of the button the other buttons of the toolbar do not move. The button is + // allotted the maximum size so that it doesn't resize during a change of state. + geometry::RealRectangle2D aTextBBoxNormal (mpNormal->maText.GetBoundingBox(rxCanvas)); + geometry::RealRectangle2D aTextBBoxMouseOver (mpMouseOver->maText.GetBoundingBox(rxCanvas)); + geometry::RealRectangle2D aTextBBoxSelected (mpSelected->maText.GetBoundingBox(rxCanvas)); + geometry::RealRectangle2D aTextBBoxDisabled (mpDisabled->maText.GetBoundingBox(rxCanvas)); + geometry::RealRectangle2D aTextBBoxMouseOverSelected (mpMouseOverSelected->maText.GetBoundingBox(rxCanvas)); + std::vector<sal_Int32> widths + { + sal::static_int_cast<sal_Int32>(0.5 + aTextBBoxNormal.X2 - aTextBBoxNormal.X1), + sal::static_int_cast<sal_Int32>(0.5 + aTextBBoxMouseOver.X2 - aTextBBoxMouseOver.X1), + sal::static_int_cast<sal_Int32>(0.5 + aTextBBoxSelected.X2 - aTextBBoxSelected.X1), + sal::static_int_cast<sal_Int32>(0.5 + aTextBBoxDisabled.X2 - aTextBBoxDisabled.X1), + sal::static_int_cast<sal_Int32>(0.5 + aTextBBoxMouseOverSelected.X2 - aTextBBoxMouseOverSelected.X1) + }; + + sal_Int32 nTextHeight (sal::static_int_cast<sal_Int32>(0.5 + aTextBBox.Y2 - aTextBBox.Y1)); + Reference<rendering::XBitmap> xBitmap; + if (mpMode->mpIcon) + xBitmap = mpMode->mpIcon->GetNormalBitmap(); + if (xBitmap.is()) + { + const sal_Int32 nGap (5); + geometry::IntegerSize2D aSize (xBitmap->getSize()); + return awt::Size( + ::std::max(aSize.Width, *std::max_element(widths.begin(), widths.end())), + aSize.Height + nGap + nTextHeight); + } + else + { + return awt::Size(*std::max_element(widths.begin(), widths.end()), nTextHeight); + } +} + +void Button::PaintIcon ( + const Reference<rendering::XCanvas>& rxCanvas, + const sal_Int32 nTextHeight, + const rendering::ViewState& rViewState) +{ + if (!mpMode) + return; + + Reference<rendering::XBitmap> xBitmap (mpMode->mpIcon->GetBitmap(GetMode())); + if (!xBitmap.is()) + return; + + /// check whether RTL interface or not + if(!AllSettings::GetLayoutRTL()){ + const sal_Int32 nX (maLocation.X + + (maSize.Width-xBitmap->getSize().Width) / 2); + const sal_Int32 nY (maLocation.Y + + (maSize.Height - nTextHeight - xBitmap->getSize().Height) / 2); + const rendering::RenderState aRenderState( + geometry::AffineMatrix2D(1,0,nX, 0,1,nY), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::OVER); + rxCanvas->drawBitmap(xBitmap, rViewState, aRenderState); + } + else { + const sal_Int32 nX (maLocation.X + + (maSize.Width+xBitmap->getSize().Width) / 2); + const sal_Int32 nY (maLocation.Y + + (maSize.Height - nTextHeight - xBitmap->getSize().Height) / 2); + const rendering::RenderState aRenderState( + geometry::AffineMatrix2D(-1,0,nX, 0,1,nY), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::OVER); + rxCanvas->drawBitmap(xBitmap, rViewState, aRenderState); + } +} + +PresenterBitmapDescriptor::Mode Button::GetMode() const +{ + if ( ! IsEnabled()) + return PresenterBitmapDescriptor::Disabled; + else if (mbIsPressed) + return PresenterBitmapDescriptor::ButtonDown; + else if (mbIsOver) + return PresenterBitmapDescriptor::MouseOver; + else + return PresenterBitmapDescriptor::Normal; +} + +//----- lang::XEventListener -------------------------------------------------- + +void SAL_CALL Button::disposing (const css::lang::EventObject& rEvent) +{ + mbIsListenerRegistered = false; + Element::disposing(rEvent); +} + +} // end of anonymous namespace + +//===== PresenterToolBar::Label =============================================== + +namespace { + +Label::Label (const ::rtl::Reference<PresenterToolBar>& rpToolBar) + : Element(rpToolBar) +{ +} + +awt::Size Label::CreateBoundingSize ( + const Reference<rendering::XCanvas>& rxCanvas) +{ + if (!mpMode) + return awt::Size(0,0); + + geometry::RealRectangle2D aTextBBox (mpMode->maText.GetBoundingBox(rxCanvas)); + return awt::Size( + sal::static_int_cast<sal_Int32>(0.5 + aTextBBox.X2 - aTextBBox.X1), + sal::static_int_cast<sal_Int32>(0.5 + aTextBBox.Y2 - aTextBBox.Y1)); +} + +void Label::SetText (const OUString& rsText) +{ + OSL_ASSERT(mpToolBar); + if (!mpMode) + return; + + const bool bRequestLayout (mpMode->maText.GetText().getLength() != rsText.getLength()); + + mpMode->maText.SetText(rsText); + // Just use the character count for determining whether a layout is + // necessary. This is an optimization to avoid layouts every time a new + // time value is set on some labels. + if (bRequestLayout) + mpToolBar->RequestLayout(); + else + Invalidate(false); +} + +void Label::Paint ( + const Reference<rendering::XCanvas>& rxCanvas, + const rendering::ViewState& rViewState) +{ + OSL_ASSERT(rxCanvas.is()); + if (!mpMode) + return; + + mpMode->maText.Paint(rxCanvas, rViewState, GetBoundingBox()); +} + +bool Label::SetState (const bool, const bool) +{ + // For labels there is no mouse over effect. + return Element::SetState(false, false); +} + +} // end of anonymous namespace + +//===== Text ================================================================== + +namespace { + +Text::Text() +{ +} + +Text::Text ( + const OUString& rsText, + const PresenterTheme::SharedFontDescriptor& rpFont) + : msText(rsText), + mpFont(rpFont) +{ +} + +void Text::SetText (const OUString& rsText) +{ + msText = rsText; +} + +const OUString& Text::GetText() const +{ + return msText; +} + +const PresenterTheme::SharedFontDescriptor& Text::GetFont() const +{ + return mpFont; +} + +void Text::Paint ( + const Reference<rendering::XCanvas>& rxCanvas, + const rendering::ViewState& rViewState, + const awt::Rectangle& rBoundingBox) +{ + OSL_ASSERT(rxCanvas.is()); + + if (msText.isEmpty()) + return; + if (!mpFont) + return; + + if ( ! mpFont->mxFont.is()) + mpFont->PrepareFont(rxCanvas); + if ( ! mpFont->mxFont.is()) + return; + + rendering::StringContext aContext (msText, 0, msText.getLength()); + + Reference<rendering::XTextLayout> xLayout ( + mpFont->mxFont->createTextLayout( + aContext, + rendering::TextDirection::WEAK_LEFT_TO_RIGHT, + 0)); + geometry::RealRectangle2D aBox (xLayout->queryTextBounds()); + const double nTextWidth = aBox.X2 - aBox.X1; + const double nY = rBoundingBox.Y + rBoundingBox.Height - aBox.Y2; + const double nX = rBoundingBox.X + (rBoundingBox.Width - nTextWidth)/2; + + rendering::RenderState aRenderState( + geometry::AffineMatrix2D(1,0,nX, 0,1,nY), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + PresenterCanvasHelper::SetDeviceColor(aRenderState, mpFont->mnColor); + rxCanvas->drawTextLayout( + xLayout, + rViewState, + aRenderState); +} + +geometry::RealRectangle2D Text::GetBoundingBox (const Reference<rendering::XCanvas>& rxCanvas) +{ + if (mpFont && !msText.isEmpty()) + { + if ( ! mpFont->mxFont.is()) + mpFont->PrepareFont(rxCanvas); + if (mpFont->mxFont.is()) + { + rendering::StringContext aContext (msText, 0, msText.getLength()); + Reference<rendering::XTextLayout> xLayout ( + mpFont->mxFont->createTextLayout( + aContext, + rendering::TextDirection::WEAK_LEFT_TO_RIGHT, + 0)); + return xLayout->queryTextBounds(); + } + } + return geometry::RealRectangle2D(0,0,0,0); +} + +//===== TimeFormatter ========================================================= + +OUString TimeFormatter::FormatTime (const oslDateTime& rTime) +{ + OUStringBuffer sText; + + const sal_Int32 nHours (sal::static_int_cast<sal_Int32>(rTime.Hours)); + const sal_Int32 nMinutes (sal::static_int_cast<sal_Int32>(rTime.Minutes)); + const sal_Int32 nSeconds(sal::static_int_cast<sal_Int32>(rTime.Seconds)); + // Hours + sText.append(nHours); + + sText.append(":"); + + // Minutes + const OUString sMinutes (OUString::number(nMinutes)); + if (sMinutes.getLength() == 1) + sText.append("0"); + sText.append(sMinutes); + + // Seconds + sText.append(":"); + const OUString sSeconds (OUString::number(nSeconds)); + if (sSeconds.getLength() == 1) + sText.append("0"); + sText.append(sSeconds); + return sText.makeStringAndClear(); +} + +//===== TimeLabel ============================================================= + +TimeLabel::TimeLabel (const ::rtl::Reference<PresenterToolBar>& rpToolBar) + : Label(rpToolBar) +{ +} + +void SAL_CALL TimeLabel::disposing() +{ + PresenterClockTimer::Instance(mpToolBar->GetComponentContext())->RemoveListener(mpListener); + mpListener.reset(); +} + +void TimeLabel::ConnectToTimer() +{ + mpListener = std::make_shared<Listener>(this); + PresenterClockTimer::Instance(mpToolBar->GetComponentContext())->AddListener(mpListener); +} + +//===== CurrentTimeLabel ====================================================== + +::rtl::Reference<Element> CurrentTimeLabel::Create ( + const ::rtl::Reference<PresenterToolBar>& rpToolBar) +{ + ::rtl::Reference<TimeLabel> pElement(new CurrentTimeLabel(rpToolBar)); + pElement->ConnectToTimer(); + return pElement; +} + +CurrentTimeLabel::~CurrentTimeLabel() +{ +} + +CurrentTimeLabel::CurrentTimeLabel ( + const ::rtl::Reference<PresenterToolBar>& rpToolBar) + : TimeLabel(rpToolBar) +{ +} + +void CurrentTimeLabel::TimeHasChanged (const oslDateTime& rCurrentTime) +{ + SetText(TimeFormatter::FormatTime(rCurrentTime)); + Invalidate(false); +} + +void CurrentTimeLabel::SetModes ( + const SharedElementMode& rpNormalMode, + const SharedElementMode& rpMouseOverMode, + const SharedElementMode& rpSelectedMode, + const SharedElementMode& rpDisabledMode, + const SharedElementMode& rpMouseOverSelectedMode) +{ + TimeLabel::SetModes(rpNormalMode, rpMouseOverMode, rpSelectedMode, rpDisabledMode, rpMouseOverSelectedMode); + SetText(TimeFormatter::FormatTime(PresenterClockTimer::GetCurrentTime())); +} + +//===== PresentationTimeLabel ================================================= + +::rtl::Reference<Element> PresentationTimeLabel::Create ( + const ::rtl::Reference<PresenterToolBar>& rpToolBar) +{ + ::rtl::Reference<TimeLabel> pElement(new PresentationTimeLabel(rpToolBar)); + pElement->ConnectToTimer(); + return pElement; +} + +PresentationTimeLabel::~PresentationTimeLabel() +{ + mpToolBar->GetPresenterController()->SetPresentationTime(nullptr); +} + +PresentationTimeLabel::PresentationTimeLabel ( + const ::rtl::Reference<PresenterToolBar>& rpToolBar) + : TimeLabel(rpToolBar), + maStartTimeValue() +{ + restart(); + setPauseStatus(false); + TimeValue pauseTime(0,0); + setPauseTimeValue(pauseTime); + mpToolBar->GetPresenterController()->SetPresentationTime(this); +} + +void PresentationTimeLabel::restart() +{ + TimeValue pauseTime(0, 0); + setPauseTimeValue(pauseTime); + maStartTimeValue.Seconds = 0; + maStartTimeValue.Nanosec = 0; +} + +bool PresentationTimeLabel::isPaused() +{ + return paused; +} + +void PresentationTimeLabel::setPauseStatus(const bool pauseStatus) +{ + paused = pauseStatus; +} + +const TimeValue& PresentationTimeLabel::getPauseTimeValue() const +{ + return pauseTimeValue; +} + +void PresentationTimeLabel::setPauseTimeValue(const TimeValue pauseTime) +{ + //store the time at which the presentation was paused + pauseTimeValue = pauseTime; +} + +void PresentationTimeLabel::TimeHasChanged (const oslDateTime& rCurrentTime) +{ + TimeValue aCurrentTimeValue; + if (!osl_getTimeValueFromDateTime(&rCurrentTime, &aCurrentTimeValue)) + return; + + if (maStartTimeValue.Seconds==0 && maStartTimeValue.Nanosec==0) + { + // This method is called for the first time. Initialize the + // start time. The start time is rounded to nearest second to + // keep the time updates synchronized with the current time label. + maStartTimeValue = aCurrentTimeValue; + if (maStartTimeValue.Nanosec >= 500000000) + maStartTimeValue.Seconds += 1; + maStartTimeValue.Nanosec = 0; + } + + //The start time value is incremented by the amount of time + //the presentation was paused for in order to continue the + //timer from the same position + if(!isPaused()) + { + TimeValue pauseTime = getPauseTimeValue(); + if(pauseTime.Seconds != 0 || pauseTime.Nanosec != 0) + { + TimeValue incrementValue(0, 0); + incrementValue.Seconds = aCurrentTimeValue.Seconds - pauseTime.Seconds; + if(pauseTime.Nanosec > aCurrentTimeValue.Nanosec) + { + incrementValue.Nanosec = 1000000000 + aCurrentTimeValue.Nanosec - pauseTime.Nanosec; + } + else + { + incrementValue.Nanosec = aCurrentTimeValue.Nanosec - pauseTime.Nanosec; + } + + maStartTimeValue.Seconds += incrementValue.Seconds; + maStartTimeValue.Nanosec += incrementValue.Nanosec; + if(maStartTimeValue.Nanosec >= 1000000000) + { + maStartTimeValue.Seconds += 1; + maStartTimeValue.Nanosec -= 1000000000; + } + + TimeValue pauseTime_(0, 0); + setPauseTimeValue(pauseTime_); + } + } + else + { + TimeValue pauseTime = getPauseTimeValue(); + if(pauseTime.Seconds == 0 && pauseTime.Nanosec == 0) + { + setPauseTimeValue(aCurrentTimeValue); + } + } + + TimeValue aElapsedTimeValue; + aElapsedTimeValue.Seconds = aCurrentTimeValue.Seconds - maStartTimeValue.Seconds; + aElapsedTimeValue.Nanosec = aCurrentTimeValue.Nanosec - maStartTimeValue.Nanosec; + + oslDateTime aElapsedDateTime; + if (osl_getDateTimeFromTimeValue(&aElapsedTimeValue, &aElapsedDateTime) && !isPaused()) + { + SetText(TimeFormatter::FormatTime(aElapsedDateTime)); + Invalidate(false); + } +} + +void PresentationTimeLabel::SetModes ( + const SharedElementMode& rpNormalMode, + const SharedElementMode& rpMouseOverMode, + const SharedElementMode& rpSelectedMode, + const SharedElementMode& rpDisabledMode, + const SharedElementMode& rpMouseOverSelectedMode) +{ + TimeLabel::SetModes(rpNormalMode, rpMouseOverMode, rpSelectedMode, rpDisabledMode, rpMouseOverSelectedMode); + + oslDateTime aStartDateTime; + if (osl_getDateTimeFromTimeValue(&maStartTimeValue, &aStartDateTime)) + { + SetText(TimeFormatter::FormatTime(aStartDateTime)); + } +} + +//===== VerticalSeparator ===================================================== + +VerticalSeparator::VerticalSeparator ( + const ::rtl::Reference<PresenterToolBar>& rpToolBar) + : Element(rpToolBar) +{ +} + +void VerticalSeparator::Paint ( + const Reference<rendering::XCanvas>& rxCanvas, + const rendering::ViewState& rViewState) +{ + OSL_ASSERT(rxCanvas.is()); + + awt::Rectangle aBBox (GetBoundingBox()); + + rendering::RenderState aRenderState( + geometry::AffineMatrix2D(1,0,aBBox.X, 0,1,aBBox.Y), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::OVER); + if (mpMode) + { + PresenterTheme::SharedFontDescriptor pFont (mpMode->maText.GetFont()); + if (pFont) + PresenterCanvasHelper::SetDeviceColor(aRenderState, pFont->mnColor); + } + + Reference<rendering::XBitmap> xBitmap(mpToolBar->GetPresenterController()->GetPresenterHelper()->loadBitmap("bitmaps/Separator.png", rxCanvas)); + if (!xBitmap.is()) + return; + + rxCanvas->drawBitmap( + xBitmap, + rViewState, + aRenderState); +} + +awt::Size VerticalSeparator::CreateBoundingSize ( + const Reference<rendering::XCanvas>&) +{ + return awt::Size(1,20); +} + +bool VerticalSeparator::IsFilling() const +{ + return true; +} + +//===== HorizontalSeparator =================================================== + +HorizontalSeparator::HorizontalSeparator ( + const ::rtl::Reference<PresenterToolBar>& rpToolBar) + : Element(rpToolBar) +{ +} + +void HorizontalSeparator::Paint ( + const Reference<rendering::XCanvas>& rxCanvas, + const rendering::ViewState& rViewState) +{ + OSL_ASSERT(rxCanvas.is()); + + awt::Rectangle aBBox (GetBoundingBox()); + + rendering::RenderState aRenderState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::OVER); + if (mpMode) + { + PresenterTheme::SharedFontDescriptor pFont (mpMode->maText.GetFont()); + if (pFont) + PresenterCanvasHelper::SetDeviceColor(aRenderState, pFont->mnColor); + } + + rxCanvas->fillPolyPolygon( + PresenterGeometryHelper::CreatePolygon(aBBox, rxCanvas->getDevice()), + rViewState, + aRenderState); +} + +awt::Size HorizontalSeparator::CreateBoundingSize ( + const Reference<rendering::XCanvas>&) +{ + return awt::Size(20,1); +} + +bool HorizontalSeparator::IsFilling() const +{ + return true; +} + +} // end of anonymous namespace + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterToolBar.hxx b/sdext/source/presenter/PresenterToolBar.hxx new file mode 100644 index 000000000..90931df31 --- /dev/null +++ b/sdext/source/presenter/PresenterToolBar.hxx @@ -0,0 +1,250 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERTOOLBAR_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERTOOLBAR_HXX + +#include "PresenterController.hxx" +#include "PresenterViewFactory.hxx" + +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <com/sun/star/awt/XMouseListener.hpp> +#include <com/sun/star/awt/XMouseMotionListener.hpp> +#include <com/sun/star/awt/XPaintListener.hpp> +#include <com/sun/star/awt/XWindowListener.hpp> +#include <com/sun/star/drawing/XDrawPage.hpp> +#include <com/sun/star/drawing/XDrawView.hpp> +#include <com/sun/star/drawing/framework/XView.hpp> +#include <com/sun/star/drawing/framework/XResourceId.hpp> +#include <com/sun/star/frame/XController.hpp> + +#include <functional> + +namespace sdext::presenter { + +typedef cppu::WeakComponentImplHelper< + css::awt::XWindowListener, + css::awt::XPaintListener, + css::awt::XMouseListener, + css::awt::XMouseMotionListener, + css::drawing::XDrawView + > PresenterToolBarInterfaceBase; + +typedef cppu::WeakComponentImplHelper< + css::awt::XPaintListener, + css::drawing::framework::XView, + css::drawing::XDrawView + > PresenterToolBarViewInterfaceBase; + +/** A simple tool bar that can display bitmapped buttons and labels. At the + moment there are buttons for moving to the next and previous slide and + to the next effect. A label displays the index of the current slide + and the total number of slides. +*/ +class PresenterToolBar + : private ::cppu::BaseMutex, + public PresenterToolBarInterfaceBase, + public CachablePresenterView +{ +public: + typedef ::std::function<void ()> Action; + + enum Anchor { Left, Center }; + + PresenterToolBar ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::awt::XWindow>& rxWindow, + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const ::rtl::Reference<PresenterController>& rpPresenterController, + const Anchor eAnchor); + virtual ~PresenterToolBar() override; + PresenterToolBar(const PresenterToolBar&) = delete; + PresenterToolBar& operator=(const PresenterToolBar&) = delete; + + void Initialize ( + const OUString& rsConfigurationPath); + + virtual void SAL_CALL disposing() override; + + void InvalidateArea ( + const css::awt::Rectangle& rRepaintBox, + const bool bSynchronous); + + void RequestLayout(); + css::geometry::RealSize2D const & GetMinimalSize(); + const ::rtl::Reference<PresenterController>& GetPresenterController() const; + const css::uno::Reference<css::uno::XComponentContext>& GetComponentContext() const; + + // lang::XEventListener + + virtual void SAL_CALL + disposing (const css::lang::EventObject& rEventObject) override; + + // XWindowListener + + virtual void SAL_CALL windowResized (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowMoved (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowShown (const css::lang::EventObject& rEvent) override; + + virtual void SAL_CALL windowHidden (const css::lang::EventObject& rEvent) override; + + // XPaintListener + + virtual void SAL_CALL windowPaint (const css::awt::PaintEvent& rEvent) override; + + // XMouseListener + + virtual void SAL_CALL mousePressed (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseReleased (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseEntered (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseExited (const css::awt::MouseEvent& rEvent) override; + + // XMouseMotionListener + + virtual void SAL_CALL mouseMoved (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseDragged (const css::awt::MouseEvent& rEvent) override; + + // XDrawView + + virtual void SAL_CALL setCurrentPage ( + const css::uno::Reference<css::drawing::XDrawPage>& rxSlide) override; + + virtual css::uno::Reference<css::drawing::XDrawPage> SAL_CALL getCurrentPage() override; + + class Context; + +private: + css::uno::Reference<css::uno::XComponentContext> mxComponentContext; + + class ElementContainerPart; + typedef std::shared_ptr<ElementContainerPart> SharedElementContainerPart; + typedef ::std::vector<SharedElementContainerPart> ElementContainer; + ElementContainer maElementContainer; + SharedElementContainerPart mpCurrentContainerPart; + css::uno::Reference<css::awt::XWindow> mxWindow; + css::uno::Reference<css::rendering::XCanvas> mxCanvas; + css::uno::Reference<css::presentation::XSlideShowController> mxSlideShowController; + css::uno::Reference<css::drawing::XDrawPage> mxCurrentSlide; + ::rtl::Reference<PresenterController> mpPresenterController; + bool mbIsLayoutPending; + const Anchor meAnchor; + /** The minimal size that is necessary to display all elements without + overlap and with minimal gaps between them. + */ + css::geometry::RealSize2D maMinimalSize; + + void CreateControls ( + const OUString& rsConfigurationPath); + void Layout (const css::uno::Reference<css::rendering::XCanvas>& rxCanvas); + css::geometry::RealSize2D CalculatePartSize ( + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const SharedElementContainerPart& rpPart, + const bool bIsHorizontal); + static void LayoutPart ( + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const SharedElementContainerPart& rpPart, + const css::geometry::RealRectangle2D& rBoundingBox, + const css::geometry::RealSize2D& rPartSize, + const bool bIsHorizontal); + void Paint ( + const css::awt::Rectangle& rUpdateBox, + const css::rendering::ViewState& rViewState); + + void UpdateSlideNumber(); + + void CheckMouseOver ( + const css::awt::MouseEvent& rEvent, + const bool bOverWindow, + const bool bMouseDown=false); + + void ProcessEntry ( + const css::uno::Reference<css::beans::XPropertySet>& rProperties, + Context const & rContext); + + /** @throws css::lang::DisposedException when the object has already been + disposed. + */ + void ThrowIfDisposed() const; +}; + +/** View for the PresenterToolBar. +*/ +class PresenterToolBarView + : private ::cppu::BaseMutex, + public PresenterToolBarViewInterfaceBase +{ +public: + explicit PresenterToolBarView ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId, + const css::uno::Reference<css::frame::XController>& rxController, + const ::rtl::Reference<PresenterController>& rpPresenterController); + virtual ~PresenterToolBarView() override; + PresenterToolBarView(const PresenterToolBarView&) = delete; + PresenterToolBarView& operator=(const PresenterToolBarView&) = delete; + + virtual void SAL_CALL disposing() override; + + const ::rtl::Reference<PresenterToolBar>& GetPresenterToolBar() const; + + // XPaintListener + + virtual void SAL_CALL windowPaint (const css::awt::PaintEvent& rEvent) override; + + // lang::XEventListener + + virtual void SAL_CALL + disposing (const css::lang::EventObject& rEventObject) override; + + // XResourceId + + virtual css::uno::Reference<css::drawing::framework::XResourceId> SAL_CALL getResourceId() override; + + virtual sal_Bool SAL_CALL isAnchorOnly() override; + + // XDrawView + + virtual void SAL_CALL setCurrentPage ( + const css::uno::Reference<css::drawing::XDrawPage>& rxSlide) override; + + virtual css::uno::Reference<css::drawing::XDrawPage> SAL_CALL getCurrentPage() override; + +private: + // css::uno::Reference<css::uno::XComponentContext> mxComponentContext; + css::uno::Reference<css::drawing::framework::XPane> mxPane; + css::uno::Reference<css::drawing::framework::XResourceId> mxViewId; + css::uno::Reference<css::awt::XWindow> mxWindow; + css::uno::Reference<css::rendering::XCanvas> mxCanvas; + ::rtl::Reference<PresenterController> mpPresenterController; + ::rtl::Reference<PresenterToolBar> mpToolBar; + +}; + +} // end of namespace ::sdext::presenter + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterUIPainter.cxx b/sdext/source/presenter/PresenterUIPainter.cxx new file mode 100644 index 000000000..7b37736ff --- /dev/null +++ b/sdext/source/presenter/PresenterUIPainter.cxx @@ -0,0 +1,241 @@ +/* -*- 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 <sal/config.h> + +#include <algorithm> + +#include "PresenterUIPainter.hxx" + +#include "PresenterGeometryHelper.hxx" +#include <com/sun/star/rendering/CompositeOperation.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + +namespace sdext::presenter { + +void PresenterUIPainter::PaintHorizontalBitmapComposite ( + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rRepaintBox, + const css::awt::Rectangle& rBoundingBox, + const css::uno::Reference<css::rendering::XBitmap>& rxLeftBitmap, + const css::uno::Reference<css::rendering::XBitmap>& rxRepeatableCenterBitmap, + const css::uno::Reference<css::rendering::XBitmap>& rxRightBitmap) +{ + if (PresenterGeometryHelper::AreRectanglesDisjoint(rRepaintBox, rBoundingBox)) + { + // The bounding box lies completely outside the repaint area. + // Nothing has to be repainted. + return; + } + + // Get bitmap sizes. + geometry::IntegerSize2D aLeftBitmapSize; + if (rxLeftBitmap.is()) + aLeftBitmapSize = rxLeftBitmap->getSize(); + geometry::IntegerSize2D aCenterBitmapSize; + if (rxRepeatableCenterBitmap.is()) + aCenterBitmapSize = rxRepeatableCenterBitmap->getSize(); + geometry::IntegerSize2D aRightBitmapSize; + if (rxRightBitmap.is()) + aRightBitmapSize = rxRightBitmap->getSize(); + + // Prepare painting. + rendering::ViewState aViewState ( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr); + + rendering::RenderState aRenderState ( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + + // Paint the left bitmap once. + if (rxLeftBitmap.is()) + { + const awt::Rectangle aLeftBoundingBox ( + rBoundingBox.X, + rBoundingBox.Y, + ::std::min(aLeftBitmapSize.Width, rBoundingBox.Width), + rBoundingBox.Height); + aViewState.Clip.set( + PresenterGeometryHelper::CreatePolygon( + PresenterGeometryHelper::Intersection(rRepaintBox, aLeftBoundingBox), + rxCanvas->getDevice())); + aRenderState.AffineTransform.m02 = aLeftBoundingBox.X; + aRenderState.AffineTransform.m12 + = aLeftBoundingBox.Y + (aLeftBoundingBox.Height - aLeftBitmapSize.Height) / 2; + rxCanvas->drawBitmap(rxLeftBitmap, aViewState, aRenderState); + } + + // Paint the right bitmap once. + if (rxRightBitmap.is()) + { + const awt::Rectangle aRightBoundingBox ( + rBoundingBox.X + rBoundingBox.Width - aRightBitmapSize.Width, + rBoundingBox.Y, + ::std::min(aRightBitmapSize.Width, rBoundingBox.Width), + rBoundingBox.Height); + aViewState.Clip.set( + PresenterGeometryHelper::CreatePolygon( + PresenterGeometryHelper::Intersection(rRepaintBox, aRightBoundingBox), + rxCanvas->getDevice())); + aRenderState.AffineTransform.m02 + = aRightBoundingBox.X + aRightBoundingBox.Width - aRightBitmapSize.Width; + aRenderState.AffineTransform.m12 + = aRightBoundingBox.Y + (aRightBoundingBox.Height - aRightBitmapSize.Height) / 2; + rxCanvas->drawBitmap(rxRightBitmap, aViewState, aRenderState); + } + + // Paint the center bitmap to fill the remaining space. + if (!rxRepeatableCenterBitmap.is()) + return; + + const awt::Rectangle aCenterBoundingBox ( + rBoundingBox.X + aLeftBitmapSize.Width, + rBoundingBox.Y, + rBoundingBox.Width - aLeftBitmapSize.Width - aRightBitmapSize.Width, + rBoundingBox.Height); + if (aCenterBoundingBox.Width <= 0) + return; + + aViewState.Clip.set( + PresenterGeometryHelper::CreatePolygon( + PresenterGeometryHelper::Intersection(rRepaintBox, aCenterBoundingBox), + rxCanvas->getDevice())); + sal_Int32 nX (aCenterBoundingBox.X); + const sal_Int32 nRight (aCenterBoundingBox.X + aCenterBoundingBox.Width - 1); + aRenderState.AffineTransform.m12 + = aCenterBoundingBox.Y + (aCenterBoundingBox.Height-aCenterBitmapSize.Height) / 2; + while(nX <= nRight) + { + aRenderState.AffineTransform.m02 = nX; + rxCanvas->drawBitmap(rxRepeatableCenterBitmap, aViewState, aRenderState); + nX += aCenterBitmapSize.Width; + } +} + +void PresenterUIPainter::PaintVerticalBitmapComposite ( + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rRepaintBox, + const css::awt::Rectangle& rBoundingBox, + const css::uno::Reference<css::rendering::XBitmap>& rxTopBitmap, + const css::uno::Reference<css::rendering::XBitmap>& rxRepeatableCenterBitmap, + const css::uno::Reference<css::rendering::XBitmap>& rxBottomBitmap) +{ + if (PresenterGeometryHelper::AreRectanglesDisjoint(rRepaintBox, rBoundingBox)) + { + // The bounding box lies completely outside the repaint area. + // Nothing has to be repainted. + return; + } + + // Get bitmap sizes. + geometry::IntegerSize2D aTopBitmapSize; + if (rxTopBitmap.is()) + aTopBitmapSize = rxTopBitmap->getSize(); + geometry::IntegerSize2D aCenterBitmapSize; + if (rxRepeatableCenterBitmap.is()) + aCenterBitmapSize = rxRepeatableCenterBitmap->getSize(); + geometry::IntegerSize2D aBottomBitmapSize; + if (rxBottomBitmap.is()) + aBottomBitmapSize = rxBottomBitmap->getSize(); + + // Prepare painting. + rendering::ViewState aViewState ( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr); + + rendering::RenderState aRenderState ( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + + // Paint the top bitmap once. + if (rxTopBitmap.is()) + { + const awt::Rectangle aTopBoundingBox ( + rBoundingBox.X, + rBoundingBox.Y, + rBoundingBox.Width, + ::std::min(aTopBitmapSize.Height, rBoundingBox.Height)); + aViewState.Clip.set( + PresenterGeometryHelper::CreatePolygon( + PresenterGeometryHelper::Intersection(rRepaintBox, aTopBoundingBox), + rxCanvas->getDevice())); + aRenderState.AffineTransform.m02 + = aTopBoundingBox.X + (aTopBoundingBox.Width - aTopBitmapSize.Width) / 2; + aRenderState.AffineTransform.m12 = aTopBoundingBox.Y; + rxCanvas->drawBitmap(rxTopBitmap, aViewState, aRenderState); + } + + // Paint the bottom bitmap once. + if (rxBottomBitmap.is()) + { + const sal_Int32 nBBoxHeight (::std::min(aBottomBitmapSize.Height, rBoundingBox.Height)); + const awt::Rectangle aBottomBoundingBox ( + rBoundingBox.X, + rBoundingBox.Y + rBoundingBox.Height - nBBoxHeight, + rBoundingBox.Width, + nBBoxHeight); + aViewState.Clip.set( + PresenterGeometryHelper::CreatePolygon( + PresenterGeometryHelper::Intersection(rRepaintBox, aBottomBoundingBox), + rxCanvas->getDevice())); + aRenderState.AffineTransform.m02 + = aBottomBoundingBox.X + (aBottomBoundingBox.Width - aBottomBitmapSize.Width) / 2; + aRenderState.AffineTransform.m12 + = aBottomBoundingBox.Y + aBottomBoundingBox.Height - aBottomBitmapSize.Height; + rxCanvas->drawBitmap(rxBottomBitmap, aViewState, aRenderState); + } + + // Paint the center bitmap to fill the remaining space. + if (!rxRepeatableCenterBitmap.is()) + return; + + const awt::Rectangle aCenterBoundingBox ( + rBoundingBox.X, + rBoundingBox.Y + aTopBitmapSize.Height, + rBoundingBox.Width, + rBoundingBox.Height - aTopBitmapSize.Height - aBottomBitmapSize.Height); + if (aCenterBoundingBox.Height <= 0) + return; + + aViewState.Clip.set( + PresenterGeometryHelper::CreatePolygon( + PresenterGeometryHelper::Intersection(rRepaintBox, aCenterBoundingBox), + rxCanvas->getDevice())); + sal_Int32 nY (aCenterBoundingBox.Y); + const sal_Int32 nBottom (aCenterBoundingBox.Y + aCenterBoundingBox.Height - 1); + aRenderState.AffineTransform.m02 + = aCenterBoundingBox.X + (aCenterBoundingBox.Width-aCenterBitmapSize.Width) / 2; + while(nY <= nBottom) + { + aRenderState.AffineTransform.m12 = nY; + rxCanvas->drawBitmap(rxRepeatableCenterBitmap, aViewState, aRenderState); + nY += aCenterBitmapSize.Height; + } +} + +} // end of namespace sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterUIPainter.hxx b/sdext/source/presenter/PresenterUIPainter.hxx new file mode 100644 index 000000000..f21ca291a --- /dev/null +++ b/sdext/source/presenter/PresenterUIPainter.hxx @@ -0,0 +1,56 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERUIPAINTER_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERUIPAINTER_HXX + +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XBitmap.hpp> + +namespace sdext::presenter +{ +/** Functions for painting UI elements. +*/ +class PresenterUIPainter +{ +public: + PresenterUIPainter() = delete; + PresenterUIPainter(const PresenterUIPainter&) = delete; + PresenterUIPainter& operator=(const PresenterUIPainter&) = delete; + + static void PaintHorizontalBitmapComposite( + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rRepaintBox, const css::awt::Rectangle& rBoundingBox, + const css::uno::Reference<css::rendering::XBitmap>& rxLeftBitmap, + const css::uno::Reference<css::rendering::XBitmap>& rxRepeatableCenterBitmap, + const css::uno::Reference<css::rendering::XBitmap>& rxRightBitmap); + + static void PaintVerticalBitmapComposite( + const css::uno::Reference<css::rendering::XCanvas>& rxCanvas, + const css::awt::Rectangle& rRepaintBox, const css::awt::Rectangle& rBoundingBox, + const css::uno::Reference<css::rendering::XBitmap>& rxTopBitmap, + const css::uno::Reference<css::rendering::XBitmap>& rxRepeatableCenterBitmap, + const css::uno::Reference<css::rendering::XBitmap>& rxBottomBitmap); +}; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterViewFactory.cxx b/sdext/source/presenter/PresenterViewFactory.cxx new file mode 100644 index 000000000..7c7f8c98b --- /dev/null +++ b/sdext/source/presenter/PresenterViewFactory.cxx @@ -0,0 +1,503 @@ +/* -*- 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 "PresenterViewFactory.hxx" +#include "PresenterPaneContainer.hxx" +#include "PresenterHelpView.hxx" +#include "PresenterNotesView.hxx" +#include "PresenterSlideShowView.hxx" +#include "PresenterSlidePreview.hxx" +#include "PresenterSlideSorter.hxx" +#include "PresenterToolBar.hxx" +#include <com/sun/star/drawing/framework/XControllerManager.hpp> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing::framework; + +namespace sdext::presenter { + +namespace { + +/** By default the PresenterSlidePreview shows the preview of the current + slide. This adapter class makes it display the preview of the next + slide. +*/ +class NextSlidePreview : public PresenterSlidePreview +{ +public: + NextSlidePreview ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId, + const css::uno::Reference<css::drawing::framework::XPane>& rxAnchorPane, + const ::rtl::Reference<PresenterController>& rpPresenterController) + : PresenterSlidePreview(rxContext, rxViewId, rxAnchorPane, rpPresenterController) + { + } + + virtual void SAL_CALL setCurrentPage ( + const css::uno::Reference<css::drawing::XDrawPage>& rxSlide) override + { + Reference<presentation::XSlideShowController> xSlideShowController ( + mpPresenterController->GetSlideShowController()); + Reference<drawing::XDrawPage> xSlide; + if (xSlideShowController.is()) + { + const sal_Int32 nCount (xSlideShowController->getSlideCount()); + sal_Int32 nNextSlideIndex (-1); + if (xSlideShowController->getCurrentSlide() == rxSlide) + { + nNextSlideIndex = xSlideShowController->getNextSlideIndex(); + } + else + { + for (sal_Int32 nIndex=0; nIndex<nCount; ++nIndex) + { + if (rxSlide == xSlideShowController->getSlideByIndex(nIndex)) + { + nNextSlideIndex = nIndex + 1; + } + } + } + if (nNextSlideIndex >= 0) + { + if (nNextSlideIndex < nCount) + { + xSlide = xSlideShowController->getSlideByIndex(nNextSlideIndex); + } + } + } + PresenterSlidePreview::setCurrentPage(xSlide); + } +}; + +} // end of anonymous namespace + +//===== PresenterViewFactory ============================================== + +PresenterViewFactory::PresenterViewFactory ( + const Reference<uno::XComponentContext>& rxContext, + const Reference<frame::XController>& rxController, + const ::rtl::Reference<PresenterController>& rpPresenterController) + : PresenterViewFactoryInterfaceBase(m_aMutex), + mxComponentContext(rxContext), + mxControllerWeak(rxController), + mpPresenterController(rpPresenterController) +{ +} + +Reference<drawing::framework::XResourceFactory> PresenterViewFactory::Create ( + const Reference<uno::XComponentContext>& rxContext, + const Reference<frame::XController>& rxController, + const ::rtl::Reference<PresenterController>& rpPresenterController) +{ + rtl::Reference<PresenterViewFactory> pFactory ( + new PresenterViewFactory(rxContext,rxController,rpPresenterController)); + pFactory->Register(rxController); + return Reference<drawing::framework::XResourceFactory>(pFactory); +} + +void PresenterViewFactory::Register (const Reference<frame::XController>& rxController) +{ + try + { + // Get the configuration controller. + Reference<XControllerManager> xCM (rxController, UNO_QUERY_THROW); + mxConfigurationController = xCM->getConfigurationController(); + if ( ! mxConfigurationController.is()) + { + throw RuntimeException(); + } + mxConfigurationController->addResourceFactory(msCurrentSlidePreviewViewURL, this); + mxConfigurationController->addResourceFactory(msNextSlidePreviewViewURL, this); + mxConfigurationController->addResourceFactory(msNotesViewURL, this); + mxConfigurationController->addResourceFactory(msToolBarViewURL, this); + mxConfigurationController->addResourceFactory(msSlideSorterURL, this); + mxConfigurationController->addResourceFactory(msHelpViewURL, this); + } + catch (RuntimeException&) + { + OSL_ASSERT(false); + if (mxConfigurationController.is()) + mxConfigurationController->removeResourceFactoryForReference(this); + mxConfigurationController = nullptr; + + throw; + } +} + +PresenterViewFactory::~PresenterViewFactory() +{ +} + +void SAL_CALL PresenterViewFactory::disposing() +{ + if (mxConfigurationController.is()) + mxConfigurationController->removeResourceFactoryForReference(this); + mxConfigurationController = nullptr; + + if (mpResourceCache == nullptr) + return; + + // Dispose all views in the cache. + for (const auto& rView : *mpResourceCache) + { + try + { + Reference<lang::XComponent> xComponent (rView.second.first, UNO_QUERY); + if (xComponent.is()) + xComponent->dispose(); + } + catch (lang::DisposedException&) + { + } + } + mpResourceCache.reset(); +} + +//----- XViewFactory ---------------------------------------------------------- + +Reference<XResource> SAL_CALL PresenterViewFactory::createResource ( + const Reference<XResourceId>& rxViewId) +{ + ThrowIfDisposed(); + + Reference<XResource> xView; + + if (rxViewId.is()) + { + Reference<XPane> xAnchorPane ( + mxConfigurationController->getResource(rxViewId->getAnchor()), + UNO_QUERY_THROW); + xView = GetViewFromCache(rxViewId, xAnchorPane); + if (xView == nullptr) + xView = CreateView(rxViewId, xAnchorPane); + + // Activate the view. + PresenterPaneContainer::SharedPaneDescriptor pDescriptor ( + mpPresenterController->GetPaneContainer()->FindPaneId(rxViewId->getAnchor())); + if (pDescriptor) + pDescriptor->SetActivationState(true); + } + + return xView; +} + +void SAL_CALL PresenterViewFactory::releaseResource (const Reference<XResource>& rxView) +{ + ThrowIfDisposed(); + + if ( ! rxView.is()) + return; + + // Deactivate the view. + PresenterPaneContainer::SharedPaneDescriptor pDescriptor ( + mpPresenterController->GetPaneContainer()->FindPaneId( + rxView->getResourceId()->getAnchor())); + if (pDescriptor) + pDescriptor->SetActivationState(false); + + // Dispose only views that we can not put into the cache. + CachablePresenterView* pView = dynamic_cast<CachablePresenterView*>(rxView.get()); + if (pView == nullptr || mpResourceCache == nullptr) + { + try + { + if (pView != nullptr) + pView->ReleaseView(); + Reference<lang::XComponent> xComponent (rxView, UNO_QUERY); + if (xComponent.is()) + xComponent->dispose(); + } + catch (lang::DisposedException&) + { + // Do not let disposed exceptions get out. It might be interpreted + // as coming from the factory, which would then be removed from the + // drawing framework. + } + } + else + { + // Put cacheable views in the cache. + Reference<XResourceId> xViewId (rxView->getResourceId()); + if (xViewId.is()) + { + Reference<XPane> xAnchorPane ( + mxConfigurationController->getResource(xViewId->getAnchor()), + UNO_QUERY_THROW); + (*mpResourceCache)[xViewId->getResourceURL()] + = ViewResourceDescriptor(Reference<XView>(rxView, UNO_QUERY), xAnchorPane); + pView->DeactivatePresenterView(); + } + } +} + + +Reference<XResource> PresenterViewFactory::GetViewFromCache( + const Reference<XResourceId>& rxViewId, + const Reference<XPane>& rxAnchorPane) const +{ + if (mpResourceCache == nullptr) + return nullptr; + + try + { + const OUString sResourceURL (rxViewId->getResourceURL()); + + // Can we use a view from the cache? + ResourceContainer::const_iterator iView (mpResourceCache->find(sResourceURL)); + if (iView != mpResourceCache->end()) + { + // The view is in the container but it can only be used if + // the anchor pane is the same now as it was at creation of + // the view. + if (iView->second.second == rxAnchorPane) + { + CachablePresenterView* pView + = dynamic_cast<CachablePresenterView*>(iView->second.first.get()); + if (pView != nullptr) + pView->ActivatePresenterView(); + return iView->second.first; + } + + // Right view, wrong pane. Create a new view. + } + } + catch (RuntimeException&) + { + } + return nullptr; +} + +Reference<XResource> PresenterViewFactory::CreateView( + const Reference<XResourceId>& rxViewId, + const Reference<XPane>& rxAnchorPane) +{ + Reference<XView> xView; + + try + { + const OUString sResourceURL (rxViewId->getResourceURL()); + + if (sResourceURL == msCurrentSlidePreviewViewURL) + { + xView = CreateSlideShowView(rxViewId); + } + else if (sResourceURL == msNotesViewURL) + { + xView = CreateNotesView(rxViewId); + } + else if (sResourceURL == msNextSlidePreviewViewURL) + { + xView = CreateSlidePreviewView(rxViewId, rxAnchorPane); + } + else if (sResourceURL == msToolBarViewURL) + { + xView = CreateToolBarView(rxViewId); + } + else if (sResourceURL == msSlideSorterURL) + { + xView = CreateSlideSorterView(rxViewId); + } + else if (sResourceURL == msHelpViewURL) + { + xView = CreateHelpView(rxViewId); + } + + // Activate it. + CachablePresenterView* pView = dynamic_cast<CachablePresenterView*>(xView.get()); + if (pView != nullptr) + pView->ActivatePresenterView(); + } + catch (RuntimeException&) + { + xView = nullptr; + } + + return xView; +} + +Reference<XView> PresenterViewFactory::CreateSlideShowView( + const Reference<XResourceId>& rxViewId) const +{ + Reference<XView> xView; + + if ( ! mxConfigurationController.is()) + return xView; + if ( ! mxComponentContext.is()) + return xView; + + try + { + rtl::Reference<PresenterSlideShowView> pShowView ( + new PresenterSlideShowView( + mxComponentContext, + rxViewId, + Reference<frame::XController>(mxControllerWeak), + mpPresenterController)); + pShowView->LateInit(); + xView = pShowView; + } + catch (RuntimeException&) + { + xView = nullptr; + } + + return xView; +} + +Reference<XView> PresenterViewFactory::CreateSlidePreviewView( + const Reference<XResourceId>& rxViewId, + const Reference<XPane>& rxAnchorPane) const +{ + Reference<XView> xView; + + if ( ! mxConfigurationController.is()) + return xView; + if ( ! mxComponentContext.is()) + return xView; + + try + { + xView.set( + static_cast<XWeak*>(new NextSlidePreview( + mxComponentContext, + rxViewId, + rxAnchorPane, + mpPresenterController)), + UNO_QUERY_THROW); + } + catch (RuntimeException&) + { + xView = nullptr; + } + + return xView; +} + +Reference<XView> PresenterViewFactory::CreateToolBarView( + const Reference<XResourceId>& rxViewId) const +{ + return new PresenterToolBarView( + mxComponentContext, + rxViewId, + Reference<frame::XController>(mxControllerWeak), + mpPresenterController); +} + +Reference<XView> PresenterViewFactory::CreateNotesView( + const Reference<XResourceId>& rxViewId) const +{ + Reference<XView> xView; + + if ( ! mxConfigurationController.is()) + return xView; + if ( ! mxComponentContext.is()) + return xView; + + try + { + xView.set(static_cast<XWeak*>( + new PresenterNotesView( + mxComponentContext, + rxViewId, + Reference<frame::XController>(mxControllerWeak), + mpPresenterController)), + UNO_QUERY_THROW); + } + catch (RuntimeException&) + { + xView = nullptr; + } + + return xView; +} + +Reference<XView> PresenterViewFactory::CreateSlideSorterView( + const Reference<XResourceId>& rxViewId) const +{ + Reference<XView> xView; + + if ( ! mxConfigurationController.is()) + return xView; + if ( ! mxComponentContext.is()) + return xView; + + try + { + rtl::Reference<PresenterSlideSorter> pView ( + new PresenterSlideSorter( + mxComponentContext, + rxViewId, + Reference<frame::XController>(mxControllerWeak), + mpPresenterController)); + xView = pView.get(); + } + catch (RuntimeException&) + { + xView = nullptr; + } + + return xView; +} + +Reference<XView> PresenterViewFactory::CreateHelpView( + const Reference<XResourceId>& rxViewId) const +{ + return Reference<XView>(new PresenterHelpView( + mxComponentContext, + rxViewId, + Reference<frame::XController>(mxControllerWeak), + mpPresenterController)); +} + +void PresenterViewFactory::ThrowIfDisposed() const +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ( + "PresenterViewFactory object has already been disposed", + const_cast<uno::XWeak*>(static_cast<const uno::XWeak*>(this))); + } +} + +//===== CachablePresenterView ================================================= + +CachablePresenterView::CachablePresenterView() + : mbIsPresenterViewActive(true) +{ +} + +void CachablePresenterView::ActivatePresenterView() +{ + mbIsPresenterViewActive = true; +} + +void CachablePresenterView::DeactivatePresenterView() +{ + mbIsPresenterViewActive = false; +} + +void CachablePresenterView::ReleaseView() +{ +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterViewFactory.hxx b/sdext/source/presenter/PresenterViewFactory.hxx new file mode 100644 index 000000000..30d488cfc --- /dev/null +++ b/sdext/source/presenter/PresenterViewFactory.hxx @@ -0,0 +1,164 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERVIEWFACTORY_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERVIEWFACTORY_HXX + +#include "PresenterController.hxx" +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> +#include <com/sun/star/drawing/framework/XConfigurationController.hpp> +#include <com/sun/star/drawing/framework/XResourceFactory.hpp> +#include <com/sun/star/drawing/framework/XView.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <rtl/ref.hxx> +#include <memory> + +namespace sdext::presenter { + +typedef ::cppu::WeakComponentImplHelper < + css::drawing::framework::XResourceFactory +> PresenterViewFactoryInterfaceBase; + +/** Base class for presenter views that allows the view factory to store + them in a cache and reuse deactivated views. +*/ +class CachablePresenterView +{ +public: + virtual void ActivatePresenterView(); + + /** Called when the view is put into a cache. The view must not paint + itself while being deactivated. + */ + virtual void DeactivatePresenterView(); + + /** Called before the view is disposed. This gives the view the + opportunity to trigger actions that may lead to (synchronous) + callbacks that do not result in DisposedExceptions. + */ + virtual void ReleaseView(); + +protected: + bool mbIsPresenterViewActive; + + CachablePresenterView(); + + ~CachablePresenterView() {} +}; + +/** Factory of the presenter screen specific views. The supported set of + views includes: + a life view of the current slide, + a static preview of the next slide, + the notes of the current slide, + a tool bar +*/ +class PresenterViewFactory + : public ::cppu::BaseMutex, + public PresenterViewFactoryInterfaceBase +{ +public: + static constexpr OUStringLiteral msCurrentSlidePreviewViewURL + = u"private:resource/view/Presenter/CurrentSlidePreview"; + static constexpr OUStringLiteral msNextSlidePreviewViewURL + = u"private:resource/view/Presenter/NextSlidePreview"; + static constexpr OUStringLiteral msNotesViewURL = u"private:resource/view/Presenter/Notes"; + static constexpr OUStringLiteral msToolBarViewURL = u"private:resource/view/Presenter/ToolBar"; + static constexpr OUStringLiteral msSlideSorterURL + = u"private:resource/view/Presenter/SlideSorter"; + static constexpr OUStringLiteral msHelpViewURL = u"private:resource/view/Presenter/Help"; + + /** Create a new instance of this class and register it as resource + factory in the drawing framework of the given controller. + This registration keeps it alive. When the drawing framework is + shut down and releases its reference to the factory then the factory + is destroyed. + */ + static css::uno::Reference<css::drawing::framework::XResourceFactory> Create ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::frame::XController>& rxController, + const ::rtl::Reference<PresenterController>& rpPresenterController); + virtual ~PresenterViewFactory() override; + + virtual void SAL_CALL disposing() override; + + // XResourceFactory + + virtual css::uno::Reference<css::drawing::framework::XResource> + SAL_CALL createResource ( + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId) override; + + virtual void SAL_CALL + releaseResource ( + const css::uno::Reference<css::drawing::framework::XResource>& rxPane) override; + +private: + css::uno::Reference<css::uno::XComponentContext> mxComponentContext; + css::uno::Reference<css::drawing::framework::XConfigurationController> + mxConfigurationController; + css::uno::WeakReference<css::frame::XController> mxControllerWeak; + ::rtl::Reference<PresenterController> mpPresenterController; + typedef ::std::pair<css::uno::Reference<css::drawing::framework::XView>, + css::uno::Reference<css::drawing::framework::XPane> > ViewResourceDescriptor; + typedef ::std::map<OUString, ViewResourceDescriptor> ResourceContainer; + std::unique_ptr<ResourceContainer> mpResourceCache; + + PresenterViewFactory ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const css::uno::Reference<css::frame::XController>& rxController, + const ::rtl::Reference<PresenterController>& rpPresenterController); + + void Register (const css::uno::Reference<css::frame::XController>& rxController); + + css::uno::Reference<css::drawing::framework::XView> CreateSlideShowView( + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId) const; + + css::uno::Reference<css::drawing::framework::XView> CreateSlidePreviewView( + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId, + const css::uno::Reference<css::drawing::framework::XPane>& rxPane) const; + + css::uno::Reference<css::drawing::framework::XView> CreateToolBarView( + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId) const; + + css::uno::Reference<css::drawing::framework::XView> CreateNotesView( + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId) const; + + css::uno::Reference<css::drawing::framework::XView> CreateSlideSorterView( + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId) const; + + css::uno::Reference<css::drawing::framework::XView> CreateHelpView( + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId) const; + + css::uno::Reference<css::drawing::framework::XResource> GetViewFromCache ( + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId, + const css::uno::Reference<css::drawing::framework::XPane>& rxAnchorPane) const; + css::uno::Reference<css::drawing::framework::XResource> CreateView( + const css::uno::Reference<css::drawing::framework::XResourceId>& rxViewId, + const css::uno::Reference<css::drawing::framework::XPane>& rxAnchorPane); + + /// @throws css::lang::DisposedException + void ThrowIfDisposed() const; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterWindowManager.cxx b/sdext/source/presenter/PresenterWindowManager.cxx new file mode 100644 index 000000000..d1ccaadc7 --- /dev/null +++ b/sdext/source/presenter/PresenterWindowManager.cxx @@ -0,0 +1,1043 @@ +/* -*- 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 <vcl/settings.hxx> +#include "PresenterWindowManager.hxx" +#include "PresenterController.hxx" +#include "PresenterGeometryHelper.hxx" +#include "PresenterPaintManager.hxx" +#include "PresenterPaneBorderPainter.hxx" +#include "PresenterPaneContainer.hxx" +#include "PresenterPaneFactory.hxx" +#include "PresenterToolBar.hxx" +#include "PresenterViewFactory.hxx" +#include "PresenterTheme.hxx" +#include <com/sun/star/awt/InvalidateStyle.hpp> +#include <com/sun/star/awt/PosSize.hpp> +#include <com/sun/star/awt/XWindow2.hpp> +#include <com/sun/star/awt/XWindowPeer.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/FillRule.hpp> +#include <com/sun/star/rendering/Texture.hpp> +#include <com/sun/star/rendering/TexturingMode.hpp> +#include <math.h> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing::framework; + +namespace sdext::presenter { + +//===== PresenterWindowManager ================================================ + +PresenterWindowManager::PresenterWindowManager ( + const Reference<XComponentContext>& rxContext, + const ::rtl::Reference<PresenterPaneContainer>& rpPaneContainer, + const ::rtl::Reference<PresenterController>& rpPresenterController) + : PresenterWindowManagerInterfaceBase(m_aMutex), + mxComponentContext(rxContext), + mpPresenterController(rpPresenterController), + mpPaneContainer(rpPaneContainer), + mbIsLayoutPending(true), + mbIsLayouting(false), + meLayoutMode(LM_Generic), + mbIsSlideSorterActive(false), + mbIsHelpViewActive(false), + mbisPaused(false), + mbIsMouseClickPending(false) +{ + +} + +PresenterWindowManager::~PresenterWindowManager() +{ +} + +void SAL_CALL PresenterWindowManager::disposing() +{ + NotifyDisposing(); + + SetParentPane(nullptr); + + Reference<lang::XComponent> xComponent (mxPaneBorderManager, UNO_QUERY); + if (xComponent.is()) + xComponent->dispose(); + mxPaneBorderManager = nullptr; + + for (const auto& rxPane : mpPaneContainer->maPanes) + { + if (rxPane->mxBorderWindow.is()) + { + rxPane->mxBorderWindow->removeWindowListener(this); + rxPane->mxBorderWindow->removeFocusListener(this); + rxPane->mxBorderWindow->removeMouseListener(this); + } + } +} + +void PresenterWindowManager::SetParentPane ( + const Reference<drawing::framework::XPane>& rxPane) +{ + if (mxParentWindow.is()) + { + mxParentWindow->removeWindowListener(this); + mxParentWindow->removePaintListener(this); + mxParentWindow->removeMouseListener(this); + mxParentWindow->removeFocusListener(this); + } + mxParentWindow = nullptr; + mxParentCanvas = nullptr; + + if (rxPane.is()) + { + mxParentWindow = rxPane->getWindow(); + mxParentCanvas = rxPane->getCanvas(); + } + else + { + mxParentWindow = nullptr; + } + + if (mxParentWindow.is()) + { + mxParentWindow->addWindowListener(this); + mxParentWindow->addPaintListener(this); + mxParentWindow->addMouseListener(this); + mxParentWindow->addFocusListener(this); + + // We paint our own background, make that of the parent window transparent. + Reference<awt::XWindowPeer> xPeer (mxParentWindow, UNO_QUERY); + if (xPeer.is()) + xPeer->setBackground(util::Color(0xff000000)); + } +} + +void PresenterWindowManager::SetTheme (const std::shared_ptr<PresenterTheme>& rpTheme) +{ + mpTheme = rpTheme; + + // Get background bitmap or background color from the theme. + + if (mpTheme != nullptr) + { + mpBackgroundBitmap = mpTheme->GetBitmap(OUString(), "Background"); + } +} + +void PresenterWindowManager::NotifyViewCreation (const Reference<XView>& rxView) +{ + PresenterPaneContainer::SharedPaneDescriptor pDescriptor ( + mpPaneContainer->FindPaneId(rxView->getResourceId()->getAnchor())); + OSL_ASSERT(pDescriptor); + if (pDescriptor) + { + Layout(); + + mpPresenterController->GetPaintManager()->Invalidate( + pDescriptor->mxContentWindow, + sal_Int16(awt::InvalidateStyle::TRANSPARENT + | awt::InvalidateStyle::CHILDREN)); + } +} + +void PresenterWindowManager::SetPanePosSizeAbsolute ( + const OUString& rsPaneURL, + const double nX, + const double nY, + const double nWidth, + const double nHeight) +{ + PresenterPaneContainer::SharedPaneDescriptor pDescriptor ( + mpPaneContainer->FindPaneURL(rsPaneURL)); + if (pDescriptor) + { + if (pDescriptor->mxBorderWindow.is()) + pDescriptor->mxBorderWindow->setPosSize( + ::sal::static_int_cast<sal_Int32>(nX), + ::sal::static_int_cast<sal_Int32>(nY), + ::sal::static_int_cast<sal_Int32>(nWidth), + ::sal::static_int_cast<sal_Int32>(nHeight), + awt::PosSize::POSSIZE); + } +} + +void PresenterWindowManager::SetPaneBorderPainter ( + const ::rtl::Reference<PresenterPaneBorderPainter>& rPainter) +{ + mpPaneBorderPainter = rPainter; +} + +//----- XWindowListener ------------------------------------------------------- + +void SAL_CALL PresenterWindowManager::windowResized (const awt::WindowEvent& rEvent) +{ + ThrowIfDisposed(); + if (rEvent.Source == mxParentWindow) + { + Layout(); + } + else + { + Reference<awt::XWindow> xWindow (rEvent.Source,UNO_QUERY); + if (xWindow.is()) + { + UpdateWindowSize(xWindow); + + // Make sure the background of a transparent window is painted. + mpPresenterController->GetPaintManager()->Invalidate(mxParentWindow); + } + } +} + +void SAL_CALL PresenterWindowManager::windowMoved (const awt::WindowEvent& rEvent) +{ + ThrowIfDisposed(); + if (rEvent.Source != mxParentWindow) + { + Reference<awt::XWindow> xWindow (rEvent.Source,UNO_QUERY); + UpdateWindowSize(xWindow); + + // Make sure the background of a transparent window is painted. + mpPresenterController->GetPaintManager()->Invalidate(xWindow); + } +} + +void SAL_CALL PresenterWindowManager::windowShown (const lang::EventObject&) {} + +void SAL_CALL PresenterWindowManager::windowHidden (const lang::EventObject&) {} + +//----- XPaintListener -------------------------------------------------------- + +void SAL_CALL PresenterWindowManager::windowPaint (const awt::PaintEvent& rEvent) +{ + ThrowIfDisposed(); + + if ( ! mxParentWindow.is()) + return; + if ( ! mxParentCanvas.is()) + return; + + if (mpTheme == nullptr) + return; + + try + { + if (mbIsLayoutPending) + Layout(); + PaintBackground(rEvent.UpdateRect); + PaintChildren(rEvent); + } + catch (RuntimeException&) + { + OSL_FAIL("paint failed!"); + } +} + +//----- XMouseListener -------------------------------------------------------- + +void SAL_CALL PresenterWindowManager::mousePressed (const css::awt::MouseEvent&) +{ + if (!mbIsSlideSorterActive) // tdf#127921 + mbIsMouseClickPending = true; +} + +void SAL_CALL PresenterWindowManager::mouseReleased (const css::awt::MouseEvent& rEvent) +{ + if (mbIsMouseClickPending) + { + mbIsMouseClickPending = false; + mpPresenterController->HandleMouseClick(rEvent); + } +} + +void SAL_CALL PresenterWindowManager::mouseEntered (const css::awt::MouseEvent&) +{ + mbIsMouseClickPending = false; +} + +void SAL_CALL PresenterWindowManager::mouseExited (const css::awt::MouseEvent&) +{ + mbIsMouseClickPending = false; +} + +//----- XFocusListener -------------------------------------------------------- + +void SAL_CALL PresenterWindowManager::focusGained (const css::awt::FocusEvent& /*rEvent*/) +{ + ThrowIfDisposed(); +} + +void SAL_CALL PresenterWindowManager::focusLost (const css::awt::FocusEvent&) +{ + ThrowIfDisposed(); +} + +//----- XEventListener -------------------------------------------------------- + +void SAL_CALL PresenterWindowManager::disposing (const lang::EventObject& rEvent) +{ + if (rEvent.Source == mxParentWindow) + mxParentWindow = nullptr; +} + + +void PresenterWindowManager::PaintChildren (const awt::PaintEvent& rEvent) const +{ + // Call windowPaint on all children that lie in or touch the + // update rectangle. + for (const auto& rxPane : mpPaneContainer->maPanes) + { + try + { + // Make sure that the pane shall and can be painted. + if ( ! rxPane->mbIsActive) + continue; + if (rxPane->mbIsSprite) + continue; + if ( ! rxPane->mxPane.is()) + continue; + if ( ! rxPane->mxBorderWindow.is()) + continue; + Reference<awt::XWindow> xBorderWindow (rxPane->mxBorderWindow); + if ( ! xBorderWindow.is()) + continue; + + // Get the area in which the border of the pane has to be painted. + const awt::Rectangle aBorderBox (xBorderWindow->getPosSize()); + const awt::Rectangle aBorderUpdateBox( + PresenterGeometryHelper::Intersection( + rEvent.UpdateRect, + aBorderBox)); + if (aBorderUpdateBox.Width<=0 || aBorderUpdateBox.Height<=0) + continue; + + const awt::Rectangle aLocalBorderUpdateBox( + PresenterGeometryHelper::TranslateRectangle( + aBorderUpdateBox, + -aBorderBox.X, + -aBorderBox.Y)); + + // Invalidate the area of the content window. + mpPresenterController->GetPaintManager()->Invalidate( + xBorderWindow, + aLocalBorderUpdateBox, + sal_Int16(awt::InvalidateStyle::CHILDREN + | awt::InvalidateStyle::NOTRANSPARENT)); + } + catch (RuntimeException&) + { + OSL_FAIL("paint children failed!"); + } + } +} + +void PresenterWindowManager::SetLayoutMode (const LayoutMode eMode) +{ + OSL_ASSERT(mpPresenterController); + + if (meLayoutMode == eMode + && !mbIsSlideSorterActive + && !mbIsHelpViewActive) + return; + + meLayoutMode = eMode; + mbIsSlideSorterActive = false; + mbIsHelpViewActive = false; + + mpPresenterController->RequestViews( + mbIsSlideSorterActive, + meLayoutMode==LM_Notes, + mbIsHelpViewActive); + Layout(); + NotifyLayoutModeChange(); +} + +void PresenterWindowManager::SetSlideSorterState (bool bIsActive) +{ + if (mbIsSlideSorterActive == bIsActive) + return; + + mbIsSlideSorterActive = bIsActive; + if (mbIsSlideSorterActive) + mbIsHelpViewActive = false; + StoreViewMode(GetViewMode()); + + mpPresenterController->RequestViews( + mbIsSlideSorterActive, + meLayoutMode==LM_Notes, + mbIsHelpViewActive); + Layout(); + NotifyLayoutModeChange(); +} + +void PresenterWindowManager::SetHelpViewState (bool bIsActive) +{ + if (mbIsHelpViewActive == bIsActive) + return; + + mbIsHelpViewActive = bIsActive; + if (mbIsHelpViewActive) + mbIsSlideSorterActive = false; + StoreViewMode(GetViewMode()); + + mpPresenterController->RequestViews( + mbIsSlideSorterActive, + meLayoutMode==LM_Notes, + mbIsHelpViewActive); + Layout(); + NotifyLayoutModeChange(); +} + +void PresenterWindowManager::SetPauseState (bool bIsPaused) +{ + if (mbisPaused == bIsPaused) + return; + + mbisPaused = bIsPaused; + + NotifyLayoutModeChange(); +} + +void PresenterWindowManager::SetViewMode (const ViewMode eMode) +{ + switch (eMode) + { + case VM_Standard: + SetSlideSorterState(false); + SetHelpViewState(false); + SetLayoutMode(LM_Standard); + break; + + case VM_Notes: + SetSlideSorterState(false); + SetHelpViewState(false); + SetLayoutMode(LM_Notes); + break; + + case VM_SlideOverview: + SetHelpViewState(false); + SetSlideSorterState(true); + break; + + case VM_Help: + SetHelpViewState(true); + SetSlideSorterState(false); + break; + } + + StoreViewMode(eMode); +} + +PresenterWindowManager::ViewMode PresenterWindowManager::GetViewMode() const +{ + if (mbIsHelpViewActive) + return VM_Help; + else if (mbIsSlideSorterActive) + return VM_SlideOverview; + else if (meLayoutMode == LM_Notes) + return VM_Notes; + else + return VM_Standard; +} + +void PresenterWindowManager::RestoreViewMode() +{ + sal_Int32 nMode (0); + PresenterConfigurationAccess aConfiguration ( + mxComponentContext, + "/org.openoffice.Office.PresenterScreen/", + PresenterConfigurationAccess::READ_ONLY); + aConfiguration.GetConfigurationNode("Presenter/InitialViewMode") >>= nMode; + switch (nMode) + { + default: + case 0: + SetViewMode(VM_Standard); + break; + + case 1: + SetViewMode(VM_Notes); + break; + + case 2: + SetViewMode(VM_SlideOverview); + break; + } +} + +void PresenterWindowManager::StoreViewMode (const ViewMode eViewMode) +{ + try + { + PresenterConfigurationAccess aConfiguration ( + mxComponentContext, + "/org.openoffice.Office.PresenterScreen/", + PresenterConfigurationAccess::READ_WRITE); + aConfiguration.GoToChild("Presenter"); + Any aValue; + switch (eViewMode) + { + default: + case VM_Standard: + aValue <<= sal_Int32(0); + break; + + case VM_Notes: + aValue <<= sal_Int32(1); + break; + + case VM_SlideOverview: + aValue <<= sal_Int32(2); + break; + } + + aConfiguration.SetProperty ("InitialViewMode", aValue); + aConfiguration.CommitChanges(); + } + catch (Exception&) + { + } +} + +void PresenterWindowManager::AddLayoutListener ( + const Reference<document::XEventListener>& rxListener) +{ + maLayoutListeners.push_back(rxListener); +} + +void PresenterWindowManager::RemoveLayoutListener ( + const Reference<document::XEventListener>& rxListener) +{ + // Assume that there are no multiple entries. + auto iListener = std::find(maLayoutListeners.begin(), maLayoutListeners.end(), rxListener); + if (iListener != maLayoutListeners.end()) + maLayoutListeners.erase(iListener); +} + +void PresenterWindowManager::Layout() +{ + if (!mxParentWindow.is() || mbIsLayouting) + return; + + mbIsLayoutPending = false; + mbIsLayouting = true; + mxScaledBackgroundBitmap = nullptr; + mxClipPolygon = nullptr; + + try + { + if (mbIsSlideSorterActive) + LayoutSlideSorterMode(); + else if (mbIsHelpViewActive) + LayoutHelpMode(); + else + switch (meLayoutMode) + { + case LM_Standard: + default: + LayoutStandardMode(); + break; + + case LM_Notes: + LayoutNotesMode(); + break; + } + } + catch (Exception&) + { + OSL_ASSERT(false); + throw; + } + + mbIsLayouting = false; +} + +void PresenterWindowManager::LayoutStandardMode() +{ + awt::Rectangle aBox = mxParentWindow->getPosSize(); + + const double nGoldenRatio ((1 + sqrt(5.0)) / 2); + const double nGap (20); + const double nHorizontalSlideDivide (aBox.Width / nGoldenRatio); + double nSlidePreviewTop (0); + + + // For the current slide view calculate the outer height from the outer + // width. This takes into account the slide aspect ratio and thus has to + // go over the inner pane size. + PresenterPaneContainer::SharedPaneDescriptor pPane ( + mpPaneContainer->FindPaneURL(PresenterPaneFactory::msCurrentSlidePreviewPaneURL)); + if (pPane) + { + const awt::Size aCurrentSlideOuterBox(CalculatePaneSize( + nHorizontalSlideDivide - 1.5*nGap, + PresenterPaneFactory::msCurrentSlidePreviewPaneURL)); + nSlidePreviewTop = (aBox.Height - aCurrentSlideOuterBox.Height) / 2; + double Temp=nGap; + /// check whether RTL interface or not + if(AllSettings::GetLayoutRTL()) + Temp=aBox.Width - aCurrentSlideOuterBox.Width - nGap; + SetPanePosSizeAbsolute ( + PresenterPaneFactory::msCurrentSlidePreviewPaneURL, + Temp, + nSlidePreviewTop, + aCurrentSlideOuterBox.Width, + aCurrentSlideOuterBox.Height); + } + + // For the next slide view calculate the outer height from the outer + // width. This takes into account the slide aspect ratio and thus has to + // go over the inner pane size. + pPane = mpPaneContainer->FindPaneURL(PresenterPaneFactory::msNextSlidePreviewPaneURL); + if (pPane) + { + const awt::Size aNextSlideOuterBox (CalculatePaneSize( + aBox.Width - nHorizontalSlideDivide - 1.5*nGap, + PresenterPaneFactory::msNextSlidePreviewPaneURL)); + double Temp=aBox.Width - aNextSlideOuterBox.Width - nGap; + /// check whether RTL interface or not + if(AllSettings::GetLayoutRTL()) + Temp=nGap; + SetPanePosSizeAbsolute ( + PresenterPaneFactory::msNextSlidePreviewPaneURL, + Temp, + nSlidePreviewTop, + aNextSlideOuterBox.Width, + aNextSlideOuterBox.Height); + } + + LayoutToolBar(); +} + +void PresenterWindowManager::LayoutNotesMode() +{ + awt::Rectangle aBox = mxParentWindow->getPosSize(); + + const geometry::RealRectangle2D aToolBarBox (LayoutToolBar()); + + const double nGoldenRatio ((1 + sqrt(5.0)) / 2); + const double nGap (20); + const double nPrimaryWidth (aBox.Width / nGoldenRatio); + const double nSecondaryWidth (aBox.Width - nPrimaryWidth); + const double nTertiaryWidth (nSecondaryWidth / nGoldenRatio); + double nSlidePreviewTop (0); + double nNotesViewBottom (aToolBarBox.Y1 - nGap); + /// check whether RTL interface or not + + + // The notes view has no fixed aspect ratio. + PresenterPaneContainer::SharedPaneDescriptor pPane ( + mpPaneContainer->FindPaneURL(PresenterPaneFactory::msNotesPaneURL)); + if (pPane) + { + const geometry::RealSize2D aNotesViewOuterSize( + nPrimaryWidth - 1.5*nGap + 0.5, + nNotesViewBottom); + nSlidePreviewTop = (aBox.Height + - aToolBarBox.Y2 + aToolBarBox.Y1 - aNotesViewOuterSize.Height) / 2; + /// check whether RTL interface or not + double Temp=aBox.Width - aNotesViewOuterSize.Width - nGap; + if(AllSettings::GetLayoutRTL()) + Temp=nGap; + SetPanePosSizeAbsolute ( + PresenterPaneFactory::msNotesPaneURL, + Temp, + nSlidePreviewTop, + aNotesViewOuterSize.Width, + aNotesViewOuterSize.Height); + nNotesViewBottom = nSlidePreviewTop + aNotesViewOuterSize.Height; + } + + // For the current slide view calculate the outer height from the outer + // width. This takes into account the slide aspect ratio and thus has to + // go over the inner pane size. + pPane = mpPaneContainer->FindPaneURL(PresenterPaneFactory::msCurrentSlidePreviewPaneURL); + if (pPane) + { + const awt::Size aCurrentSlideOuterBox(CalculatePaneSize( + nSecondaryWidth - 1.5*nGap, + PresenterPaneFactory::msCurrentSlidePreviewPaneURL)); + /// check whether RTL interface or not + double Temp=nGap; + if(AllSettings::GetLayoutRTL()) + Temp=aBox.Width - aCurrentSlideOuterBox.Width - nGap; + SetPanePosSizeAbsolute ( + PresenterPaneFactory::msCurrentSlidePreviewPaneURL, + Temp, + nSlidePreviewTop, + aCurrentSlideOuterBox.Width, + aCurrentSlideOuterBox.Height); + } + + // For the next slide view calculate the outer height from the outer + // width. This takes into account the slide aspect ratio and thus has to + // go over the inner pane size. + pPane = mpPaneContainer->FindPaneURL(PresenterPaneFactory::msNextSlidePreviewPaneURL); + if (!pPane) + return; + + const awt::Size aNextSlideOuterBox (CalculatePaneSize( + nTertiaryWidth, + PresenterPaneFactory::msNextSlidePreviewPaneURL)); + /// check whether RTL interface or not + double Temp=nGap; + if(AllSettings::GetLayoutRTL()) + Temp=aBox.Width - aNextSlideOuterBox.Width - nGap; + SetPanePosSizeAbsolute ( + PresenterPaneFactory::msNextSlidePreviewPaneURL, + Temp, + nNotesViewBottom - aNextSlideOuterBox.Height, + aNextSlideOuterBox.Width, + aNextSlideOuterBox.Height); + + +} + +void PresenterWindowManager::LayoutSlideSorterMode() +{ + const geometry::RealRectangle2D aToolBarBox (LayoutToolBar()); + + awt::Rectangle aWindowBox = mxParentWindow->getPosSize(); + const double nGap (20); + SetPanePosSizeAbsolute( + mpPaneContainer->GetPaneURLForViewURL(PresenterViewFactory::msSlideSorterURL), + nGap, + nGap, + aWindowBox.Width - 2*nGap, + aToolBarBox.Y1 - 2*nGap); +} + +void PresenterWindowManager::LayoutHelpMode() +{ + const geometry::RealRectangle2D aToolBarBox (LayoutToolBar()); + + awt::Rectangle aWindowBox = mxParentWindow->getPosSize(); + const double nGap (20); + const double nGoldenRatio ((1 + sqrt(5.0)) / 2); + const double nWidth = ::std::min(aWindowBox.Width - 2*nGap, aWindowBox.Width/nGoldenRatio); + SetPanePosSizeAbsolute( + mpPaneContainer->GetPaneURLForViewURL(PresenterViewFactory::msHelpViewURL), + (aWindowBox.Width - nWidth)/2, + nGap, + nWidth, + aToolBarBox.Y1 - 2*nGap); +} + +geometry::RealRectangle2D PresenterWindowManager::LayoutToolBar() +{ + double nToolBarWidth (400); + double nToolBarHeight (80); + + // Get access to the tool bar. + PresenterPaneContainer::SharedPaneDescriptor pDescriptor( + mpPaneContainer->FindPaneURL(PresenterPaneFactory::msToolBarPaneURL)); + if (pDescriptor) + { + PresenterToolBarView* pToolBarView + = dynamic_cast<PresenterToolBarView*>(pDescriptor->mxView.get()); + if (pToolBarView != nullptr && pToolBarView->GetPresenterToolBar().is()) + { + geometry::RealSize2D aSize (pToolBarView->GetPresenterToolBar()->GetMinimalSize()); + + if (mpPaneBorderPainter.is()) + { + const awt::Rectangle aBox (mpPaneBorderPainter->addBorder ( + PresenterPaneFactory::msToolBarPaneURL, + awt::Rectangle( + 0, + 0, + PresenterGeometryHelper::Round(aSize.Width), + PresenterGeometryHelper::Round(aSize.Height)), + css::drawing::framework::BorderType_TOTAL_BORDER)); + + nToolBarWidth = aBox.Width; + nToolBarHeight = aBox.Height; + } + else + { + nToolBarWidth = aSize.Width + 20; + nToolBarHeight = aSize.Height + 10; + } + } + } + + const awt::Rectangle aBox = mxParentWindow->getPosSize(); + const double nToolBarX ((aBox.Width - nToolBarWidth) / 2); + const double nToolBarY (aBox.Height - nToolBarHeight); + SetPanePosSizeAbsolute( + PresenterPaneFactory::msToolBarPaneURL, + nToolBarX, + nToolBarY, + nToolBarWidth, + nToolBarHeight); + + return geometry::RealRectangle2D( + nToolBarX, + nToolBarY, + nToolBarX + nToolBarWidth - 1, + nToolBarY + nToolBarHeight - 1); +} + +awt::Size PresenterWindowManager::CalculatePaneSize ( + const double nOuterWidth, + const OUString& rsPaneURL) +{ + // Calculate the inner width by removing the pane border. + awt::Rectangle aInnerBox (mpPaneBorderPainter->RemoveBorder ( + rsPaneURL, + awt::Rectangle(0,0, + sal_Int32(nOuterWidth+0.5),sal_Int32(nOuterWidth)), + drawing::framework::BorderType_TOTAL_BORDER)); + + // Calculate the inner height with the help of the slide aspect ratio. + const double nCurrentSlideInnerHeight ( + aInnerBox.Width / mpPresenterController->GetSlideAspectRatio()); + + // Add the pane border to get the outer box. + awt::Rectangle aOuterBox (mpPaneBorderPainter->AddBorder ( + rsPaneURL, + awt::Rectangle(0,0, + aInnerBox.Width,sal_Int32(nCurrentSlideInnerHeight+0.5)), + drawing::framework::BorderType_TOTAL_BORDER)); + + return awt::Size(aOuterBox.Width, aOuterBox.Height); +} + +void PresenterWindowManager::NotifyLayoutModeChange() +{ + document::EventObject aEvent; + aEvent.Source = Reference<XInterface>(static_cast<XWeak*>(this)); + + LayoutListenerContainer aContainerCopy (maLayoutListeners); + for (const auto& rxListener : aContainerCopy) + { + if (rxListener.is()) + { + try + { + rxListener->notifyEvent(aEvent); + } + catch (lang::DisposedException&) + { + RemoveLayoutListener(rxListener); + } + catch (RuntimeException&) + { + } + } + } +} + +void PresenterWindowManager::NotifyDisposing() +{ + lang::EventObject aEvent; + aEvent.Source = static_cast<XWeak*>(this); + + LayoutListenerContainer aContainer; + aContainer.swap(maLayoutListeners); + for (auto& rxListener : aContainer) + { + if (rxListener.is()) + { + try + { + rxListener->disposing(aEvent); + } + catch (lang::DisposedException&) + { + } + catch (RuntimeException&) + { + } + } + } +} + +void PresenterWindowManager::UpdateWindowSize (const Reference<awt::XWindow>& rxBorderWindow) +{ + PresenterPaneContainer::SharedPaneDescriptor pDescriptor ( + mpPaneContainer->FindBorderWindow(rxBorderWindow)); + if (pDescriptor) + { + mxClipPolygon = nullptr; + + // ToTop is called last because it may invalidate the iterator. + if ( ! mbIsLayouting) + mpPaneContainer->ToTop(pDescriptor); + } +} + +void PresenterWindowManager::PaintBackground (const awt::Rectangle& rUpdateBox) +{ + if ( ! mxParentWindow.is()) + return; + + Reference<rendering::XGraphicDevice> xDevice (mxParentCanvas->getDevice()); + if ( ! xDevice.is()) + return; + + // Create a polygon for the background and for clipping. + Reference<rendering::XPolyPolygon2D> xBackgroundPolygon ( + PresenterGeometryHelper::CreatePolygon(mxParentWindow->getPosSize(), xDevice)); + if ( ! mxClipPolygon.is()) + mxClipPolygon = CreateClipPolyPolygon(); + + // Create View- and RenderState structs. + const rendering::ViewState aViewState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + PresenterGeometryHelper::CreatePolygon(rUpdateBox, xDevice)); + rendering::RenderState aRenderState ( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + mxClipPolygon, + Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + + // Paint the background. + if (!mpBackgroundBitmap) + return; + + ProvideBackgroundBitmap(); + + if (mxScaledBackgroundBitmap.is()) + { + const geometry::IntegerSize2D aBitmapSize(mxScaledBackgroundBitmap->getSize()); + Sequence<rendering::Texture> aTextures + { + { + geometry::AffineMatrix2D( aBitmapSize.Width,0,0, 0,aBitmapSize.Height,0), + 1, + 0, + mxScaledBackgroundBitmap, + nullptr, + nullptr, + rendering::StrokeAttributes(), + rendering::TexturingMode::REPEAT, + rendering::TexturingMode::REPEAT + } + }; + + mxParentCanvas->fillTexturedPolyPolygon( + xBackgroundPolygon, + aViewState, + aRenderState, + aTextures); + } + else + { + const util::Color aBackgroundColor (mpBackgroundBitmap->maReplacementColor); + auto pDeviceColor = aRenderState.DeviceColor.getArray(); + pDeviceColor[0] = ((aBackgroundColor >> 16) & 0x0ff) / 255.0; + pDeviceColor[1] = ((aBackgroundColor >> 8) & 0x0ff) / 255.0; + pDeviceColor[2] = ((aBackgroundColor >> 0) & 0x0ff) / 255.0; + pDeviceColor[3] = ((aBackgroundColor >> 24) & 0x0ff) / 255.0; + mxParentCanvas->fillPolyPolygon( + xBackgroundPolygon, + aViewState, + aRenderState); + } +} + +void PresenterWindowManager::ProvideBackgroundBitmap() +{ + if ( mxScaledBackgroundBitmap.is()) + return; + + Reference<rendering::XBitmap> xBitmap (mpBackgroundBitmap->GetNormalBitmap()); + if (!xBitmap.is()) + return; + + const bool bStretchVertical (mpBackgroundBitmap->meVerticalTexturingMode + == PresenterBitmapDescriptor::Stretch); + const bool bStretchHorizontal (mpBackgroundBitmap->meHorizontalTexturingMode + == PresenterBitmapDescriptor::Stretch); + if (bStretchHorizontal || bStretchVertical) + { + geometry::RealSize2D aSize; + if (bStretchVertical) + aSize.Height = mxParentWindow->getPosSize().Height; + else + aSize.Height = xBitmap->getSize().Height; + if (bStretchHorizontal) + aSize.Width = mxParentWindow->getPosSize().Width; + else + aSize.Width = xBitmap->getSize().Width; + mxScaledBackgroundBitmap = xBitmap->getScaledBitmap(aSize, false); + } + else + { + mxScaledBackgroundBitmap = xBitmap; + } +} + +Reference<rendering::XPolyPolygon2D> PresenterWindowManager::CreateClipPolyPolygon() const +{ + // Create a clip polygon that includes the whole update area but has the + // content windows as holes. + const sal_Int32 nPaneCount (mpPaneContainer->maPanes.size()); + ::std::vector<awt::Rectangle> aRectangles; + aRectangles.reserve(1+nPaneCount); + aRectangles.push_back(mxParentWindow->getPosSize()); + for (const auto& pDescriptor : mpPaneContainer->maPanes) + { + if ( ! pDescriptor->mbIsActive) + continue; + if ( ! pDescriptor->mbIsOpaque) + continue; + if ( ! pDescriptor->mxBorderWindow.is() || ! pDescriptor->mxContentWindow.is()) + continue; + Reference<awt::XWindow2> xWindow (pDescriptor->mxBorderWindow, UNO_QUERY); + if (xWindow.is() && ! xWindow->isVisible()) + continue; + + const awt::Rectangle aOuterBorderBox (pDescriptor->mxBorderWindow->getPosSize()); + awt::Rectangle aInnerBorderBox (pDescriptor->mxContentWindow->getPosSize()); + aInnerBorderBox.X += aOuterBorderBox.X; + aInnerBorderBox.Y += aOuterBorderBox.Y; + aRectangles.push_back(aInnerBorderBox); + } + Reference<rendering::XPolyPolygon2D> xPolyPolygon ( + PresenterGeometryHelper::CreatePolygon( + aRectangles, + mxParentCanvas->getDevice())); + if (xPolyPolygon.is()) + xPolyPolygon->setFillRule(rendering::FillRule_EVEN_ODD); + return xPolyPolygon; +} + +void PresenterWindowManager::Update() +{ + mxClipPolygon = nullptr; + mbIsLayoutPending = true; + + mpPresenterController->GetPaintManager()->Invalidate(mxParentWindow); +} + +void PresenterWindowManager::ThrowIfDisposed() const +{ + if (rBHelper.bDisposed || rBHelper.bInDispose) + { + throw lang::DisposedException ( + "PresenterWindowManager has already been disposed", + const_cast<uno::XWeak*>(static_cast<const uno::XWeak*>(this))); + } +} + +} // end of namespace ::sdext::presenter + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/PresenterWindowManager.hxx b/sdext/source/presenter/PresenterWindowManager.hxx new file mode 100644 index 000000000..9c032e6df --- /dev/null +++ b/sdext/source/presenter/PresenterWindowManager.hxx @@ -0,0 +1,208 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERWINDOWMANAGER_HXX +#define INCLUDED_SDEXT_SOURCE_PRESENTER_PRESENTERWINDOWMANAGER_HXX + +#include "PresenterPaneContainer.hxx" +#include "PresenterTheme.hxx" +#include <com/sun/star/awt/Size.hpp> +#include <com/sun/star/awt/XFocusListener.hpp> +#include <com/sun/star/awt/XPaintListener.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/awt/XWindowListener.hpp> +#include <com/sun/star/document/XEventListener.hpp> +#include <com/sun/star/drawing/framework/XPane.hpp> +#include <com/sun/star/rendering/XBitmap.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/compbase.hxx> +#include <rtl/ref.hxx> +#include <memory> + +namespace sdext::presenter { + +class PresenterController; +class PresenterPaneBorderPainter; +class PresenterTheme; + +typedef ::cppu::WeakComponentImplHelper< + css::awt::XWindowListener, + css::awt::XPaintListener, + css::awt::XMouseListener, + css::awt::XFocusListener +> PresenterWindowManagerInterfaceBase; + +/** A simple manager of the positions of the panes of the presenter screen. + Uses relative coordinates of the four sides of each pane. Allows panes + to be moved or resized with the mouse. +*/ +class PresenterWindowManager + : protected ::cppu::BaseMutex, + public PresenterWindowManagerInterfaceBase +{ +public: + PresenterWindowManager ( + const css::uno::Reference<css::uno::XComponentContext>& rxContext, + const ::rtl::Reference<PresenterPaneContainer>& rpPaneContainer, + const ::rtl::Reference<PresenterController>& rpPresenterController); + virtual ~PresenterWindowManager() override; + PresenterWindowManager(const PresenterWindowManager&) = delete; + PresenterWindowManager& operator=(const PresenterWindowManager&) = delete; + + void SAL_CALL disposing() override; + + void SetParentPane (const css::uno::Reference<css::drawing::framework::XPane>& rxPane); + void SetTheme (const std::shared_ptr<PresenterTheme>& rpTheme); + void NotifyViewCreation (const css::uno::Reference<css::drawing::framework::XView>& rxView); + void SetPanePosSizeAbsolute ( + const OUString& rsPaneURL, + const double nX, + const double nY, + const double nWidth, + const double nHeight); + void SetPaneBorderPainter (const ::rtl::Reference<PresenterPaneBorderPainter>& rPainter); + void Update(); + void Layout(); + + void SetSlideSorterState (bool bIsActive); + void SetHelpViewState (bool bIsActive); + void SetPauseState (bool bIsPaused); + + enum LayoutMode { LM_Standard, LM_Notes, LM_Generic }; +private: + void SetLayoutMode (const LayoutMode eMode); + +public: + enum ViewMode { VM_Standard, VM_Notes, VM_SlideOverview, VM_Help }; + /** The high-level method to switch the view mode. Use this instead of + SetLayoutMode and Set(Help|SlideSorter)State when possible. + */ + void SetViewMode (const ViewMode eMode); + + ViewMode GetViewMode() const; + + /** Restore the layout mode (or slide sorter state) from the + configuration. + */ + void RestoreViewMode(); + + void AddLayoutListener ( + const css::uno::Reference<css::document::XEventListener>& rxListener); + void RemoveLayoutListener ( + const css::uno::Reference<css::document::XEventListener>& rxListener); + + // XWindowListener + + virtual void SAL_CALL windowResized (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowMoved (const css::awt::WindowEvent& rEvent) override; + + virtual void SAL_CALL windowShown (const css::lang::EventObject& rEvent) override; + + virtual void SAL_CALL windowHidden (const css::lang::EventObject& rEvent) override; + + // XPaintListener + + virtual void SAL_CALL windowPaint (const css::awt::PaintEvent& rEvent) override; + + // XMouseListener + + virtual void SAL_CALL mousePressed (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseReleased (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseEntered (const css::awt::MouseEvent& rEvent) override; + + virtual void SAL_CALL mouseExited (const css::awt::MouseEvent& rEvent) override; + + // XFocusListener + + virtual void SAL_CALL focusGained (const css::awt::FocusEvent& rEvent) override; + + virtual void SAL_CALL focusLost (const css::awt::FocusEvent& rEvent) override; + + // XEventListener + + virtual void SAL_CALL disposing ( + const css::lang::EventObject& rEvent) override; + +private: + css::uno::Reference<css::uno::XComponentContext> mxComponentContext; + ::rtl::Reference<PresenterController> mpPresenterController; + css::uno::Reference<css::awt::XWindow> mxParentWindow; + css::uno::Reference<css::rendering::XCanvas> mxParentCanvas; + css::uno::Reference<css::uno::XInterface> mxPaneBorderManager; + ::rtl::Reference<PresenterPaneBorderPainter> mpPaneBorderPainter; + ::rtl::Reference<PresenterPaneContainer> mpPaneContainer; + bool mbIsLayoutPending; + /** This flag is set to <TRUE/> while the Layout() method is being + executed. Prevents windowMoved() and windowResized() from changing + the window sizes. + */ + bool mbIsLayouting; + std::shared_ptr<PresenterTheme> mpTheme; + SharedBitmapDescriptor mpBackgroundBitmap; + css::uno::Reference<css::rendering::XBitmap> mxScaledBackgroundBitmap; + css::uno::Reference<css::rendering::XPolyPolygon2D> mxClipPolygon; + LayoutMode meLayoutMode; + bool mbIsSlideSorterActive; + bool mbIsHelpViewActive; + bool mbisPaused; + typedef ::std::vector<css::uno::Reference<css::document::XEventListener> > + LayoutListenerContainer; + LayoutListenerContainer maLayoutListeners; + bool mbIsMouseClickPending; + + void PaintChildren (const css::awt::PaintEvent& rEvent) const; + void UpdateWindowSize (const css::uno::Reference<css::awt::XWindow>& rxBorderWindow); + void PaintBackground (const css::awt::Rectangle& rUpdateBox); + void ProvideBackgroundBitmap(); + css::uno::Reference<css::rendering::XPolyPolygon2D> CreateClipPolyPolygon() const; + + void StoreViewMode (const ViewMode eViewMode); + + void LayoutStandardMode(); + void LayoutNotesMode(); + void LayoutSlideSorterMode(); + void LayoutHelpMode(); + + /** Layout the tool bar and return its outer bounding box. + */ + css::geometry::RealRectangle2D LayoutToolBar(); + + css::awt::Size CalculatePaneSize ( + const double nOuterWidth, + const OUString& rsPaneURL); + + /** Notify changes of the layout mode and of the slide sorter state. + */ + void NotifyLayoutModeChange(); + + void NotifyDisposing(); + + /// @throws css::lang::DisposedException + void ThrowIfDisposed() const; +}; + +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sdext/source/presenter/presenter.component b/sdext/source/presenter/presenter.component new file mode 100644 index 000000000..01e18b38e --- /dev/null +++ b/sdext/source/presenter/presenter.component @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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/. + * +--> +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="org.libreoffice.comp.PresenterScreenJob" + constructor="sdext_PresenterScreenJob_get_implementation" /> + <implementation name="org.libreoffice.comp.PresenterScreenProtocolHandler" + constructor="sdext_PresenterProtocolHandler_get_implementation"> + <service name="com.sun.star.frame.ProtocolHandler"/> + </implementation> +</component> |