/* -*- 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 #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 #include #include #include #include #include #include using namespace ::com::sun::star; using namespace ::com::sun::star::uno; using namespace ::com::sun::star::drawing::framework; namespace { const static sal_Int32 gnVerticalGap (10); const static sal_Int32 gnVerticalBorder (10); const static sal_Int32 gnHorizontalGap (10); const static sal_Int32 gnHorizontalBorder (10); const static double gnMinimalPreviewWidth (200); const static double gnPreferredPreviewWidth (300); const static double gnMaximalPreviewWidth (400); const static sal_Int32 gnPreferredColumnCount (6); const static double gnMinimalHorizontalPreviewGap(15); const static double gnPreferredHorizontalPreviewGap(25); const static double gnMaximalHorizontalPreviewGap(50); const static double gnPreferredVerticalPreviewGap(25); const static sal_Int32 gnHorizontalLabelBorder (3); const static sal_Int32 gnHorizontalLabelPadding (5); const static sal_Int32 gnVerticalButtonPadding (gnVerticalGap); } namespace sdext::presenter { namespace { sal_Int32 round (const double nValue) { return sal::static_int_cast(0.5 + nValue); } sal_Int32 floor (const double nValue) { return sal::static_int_cast(nValue); } } //===== PresenterSlideSorter::Layout ========================================== class PresenterSlideSorter::Layout { public: explicit Layout (const ::rtl::Reference& 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& 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 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& rxSlides, const std::shared_ptr& rpTheme, const Reference& rxInvalidateTarget, const std::shared_ptr& rpPaintManager); MouseOverManager(const MouseOverManager&) = delete; MouseOverManager& operator=(const MouseOverManager&) = delete; void Paint ( const sal_Int32 nSlideIndex, const Reference& rxCanvas, const Reference& rxClip); void SetSlide ( const sal_Int32 nSlideIndex, const awt::Rectangle& rBox); private: Reference mxCanvas; const Reference mxSlides; SharedBitmapDescriptor mpLeftLabelBitmap; SharedBitmapDescriptor mpCenterLabelBitmap; SharedBitmapDescriptor mpRightLabelBitmap; PresenterTheme::SharedFontDescriptor mpFont; sal_Int32 mnSlideIndex; awt::Rectangle maSlideBoundingBox; OUString msText; Reference mxBitmap; Reference mxInvalidateTarget; std::shared_ptr mpPaintManager; void SetCanvas ( const Reference& rxCanvas); /** Create a bitmap that shows the given text and is not wider than the given maximal width. */ Reference 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& rxCanvas, const geometry::IntegerSize2D& rSize) const; }; //==== PresenterSlideSorter::CurrentSlideFrameRenderer ======================== class PresenterSlideSorter::CurrentSlideFrameRenderer { public: CurrentSlideFrameRenderer ( const css::uno::Reference& rxContext, const css::uno::Reference& rxCanvas); void PaintCurrentSlideFrame ( const awt::Rectangle& rSlideBoundingBox, const Reference& 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& rxBitmap, const css::uno::Reference& rxCanvas, const Reference& rxClip, const double nX, const double nY); static void PaintBitmapTiled( const css::uno::Reference& rxBitmap, const css::uno::Reference& rxCanvas, const geometry::RealRectangle2D& rClipBox, const double nX, const double nY, const double nWidth, const double nHeight); }; //===== PresenterSlideSorter ================================================== PresenterSlideSorter::PresenterSlideSorter ( const Reference& rxContext, const Reference& rxViewId, const Reference& rxController, const ::rtl::Reference& rpPresenterController) : PresenterSlideSorterInterfaceBase(m_aMutex), mxComponentContext(rxContext), mxViewId(rxViewId), mxPane(), mxCanvas(), mxWindow(), mpPresenterController(rpPresenterController), mxSlideShowController(mpPresenterController->GetSlideShowController()), mxPreviewCache(), mbIsLayoutPending(true), mpLayout(), mpVerticalScrollBar(), mpCloseButton(), mpMouseOverManager(), mnSlideIndexMousePressed(-1), mnCurrentSlideIndex(-1), mnSeparatorY(0), maSeparatorColor(0x00ffffff), maCurrentSlideFrameBoundingBox(), mpCurrentSlideFrameRenderer(), mxPreviewFrame() { if ( ! rxContext.is() || ! rxViewId.is() || ! rxController.is() || rpPresenterController.get()==nullptr) { throw lang::IllegalArgumentException(); } if ( ! mxSlideShowController.is()) throw RuntimeException(); try { // Get pane and window. Reference xCM (rxController, UNO_QUERY_THROW); Reference xCC ( xCM->getConfigurationController(), UNO_SET_THROW); Reference 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.get() != nullptr) maSeparatorColor = pFont->mnColor; } // Create the layout. mpLayout = std::make_shared(mpVerticalScrollBar); // Create the preview cache. mxPreviewCache.set( xFactory->createInstanceWithContext( "com.sun.star.drawing.PresenterPreviewCache", mxComponentContext), UNO_QUERY_THROW); Reference 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(mxSlideShowController, UNO_QUERY), mpPresenterController->GetTheme(), mxWindow, mpPresenterController->GetPaintManager())); // Listen for changes of the current slide. Reference 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 xComponent ( static_cast(mpVerticalScrollBar.get()), UNO_QUERY); mpVerticalScrollBar = nullptr; if (xComponent.is()) xComponent->dispose(); } if (mpCloseButton.is()) { Reference xComponent ( static_cast(mpCloseButton.get()), UNO_QUERY); mpCloseButton = nullptr; if (xComponent.is()) xComponent->dispose(); } if (mxCanvas.is()) { Reference xComponent (mxCanvas, UNO_QUERY); if (xComponent.is()) xComponent->removeEventListener(static_cast(this)); mxCanvas = nullptr; } mpPresenterController = nullptr; mxSlideShowController = nullptr; mpLayout.reset(); mpMouseOverManager.reset(); if (mxPreviewCache.is()) { mxPreviewCache->removePreviewCreationNotifyListener(this); Reference 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 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.get()!=nullptr); OSL_ASSERT(mpPresenterController->GetWindowManager().get()!=nullptr); 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; if (nSlideIndex < 0) { 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 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&) { 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 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.get() == nullptr) break; if ( ! pPane->mxPane.is()) break; Reference 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 xSlides (mxSlideShowController, UNO_QUERY_THROW); bIsScrollBarNeeded = mpLayout->IsScrollBarNeeded(xSlides->getCount()); if (mpVerticalScrollBar.get() != nullptr) { 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.get() != nullptr && 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& 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 xSlides(mxSlideShowController, UNO_QUERY_THROW); if (mxSlideShowController.is() && xSlides->getCount()>0) { Reference 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 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& 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 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(), mxComponentContext, rxCanvas); Reference xIndexAccess(mxSlideShowController, UNO_QUERY); Reference 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 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(4), rendering::CompositeOperation::SOURCE); // Emphasize the current slide. if (nSlideIndex == mnCurrentSlideIndex) { if (mpCurrentSlideFrameRenderer != nullptr) { const awt::Rectangle aSlideBoundingBox( sal::static_int_cast(0.5 + aTopLeft.X), sal::static_int_cast(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 xAnimationIcon (aAnimationDescriptor->GetNormalBitmap()); rendering::RenderState aAnimationRenderState ( geometry::AffineMatrix2D( 1, 0, aAnimationPreviewBox.X, 0, 1, aAnimationPreviewBox.Y), nullptr, Sequence(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 xTransitionIcon (aTransitionDescriptor->GetNormalBitmap()); rendering::RenderState aTransitionRenderState ( geometry::AffineMatrix2D( 1, 0, aTransitionPreviewBox.X, 0, 1, aTransitionPreviewBox.Y), nullptr, Sequence(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(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 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 xComponent (mxCanvas, UNO_QUERY); if (xComponent.is()) xComponent->addEventListener(static_cast(this)); mpCurrentSlideFrameRenderer = std::make_shared(mxComponentContext, mxCanvas); } return mxCanvas.is(); } void PresenterSlideSorter::ThrowIfDisposed() { if (rBHelper.bDisposed || rBHelper.bInDispose) { throw lang::DisposedException ( "PresenterSlideSorter has been already disposed", static_cast(this)); } } //===== PresenterSlideSorter::Layout ========================================== PresenterSlideSorter::Layout::Layout ( const ::rtl::Reference& rpVerticalScrollBar) : maBoundingBox(), maPreviewSize(), 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& 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.get() != nullptr) { 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& rxSlides, const std::shared_ptr& rpTheme, const Reference& rxInvalidateTarget, const std::shared_ptr& rpPaintManager) : mxCanvas(), mxSlides(rxSlides), mpLeftLabelBitmap(), mpCenterLabelBitmap(), mpRightLabelBitmap(), mpFont(), mnSlideIndex(-1), maSlideBoundingBox(), mxInvalidateTarget(rxInvalidateTarget), mpPaintManager(rpPaintManager) { if (rpTheme != nullptr) { std::shared_ptr 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& rxCanvas, const Reference& 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(4), rendering::CompositeOperation::SOURCE)); } void PresenterSlideSorter::MouseOverManager::SetCanvas ( const Reference& rxCanvas) { mxCanvas = rxCanvas; if (mpFont.get() != nullptr) 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.get() != nullptr) { msText.clear(); Reference 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 PresenterSlideSorter::MouseOverManager::CreateBitmap ( const OUString& rsText, const sal_Int32 nMaximalWidth) const { if ( ! mxCanvas.is()) return nullptr; if (mpFont.get()==nullptr || !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 xBitmap ( mxCanvas->getDevice()->createCompatibleAlphaBitmap(aLabelSize)); if ( ! xBitmap.is()) return nullptr; Reference 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 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(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)); const OUString sEllipses ("..."); while (true) { const OUString sCandidate (rsText.copy(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.get() != nullptr) { Reference 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& rxCanvas, const geometry::IntegerSize2D& rSize) const { // Get the bitmaps for painting the label background. Reference xLeftLabelBitmap; if (mpLeftLabelBitmap.get() != nullptr) xLeftLabelBitmap = mpLeftLabelBitmap->GetNormalBitmap(); Reference xCenterLabelBitmap; if (mpCenterLabelBitmap.get() != nullptr) xCenterLabelBitmap = mpCenterLabelBitmap->GetNormalBitmap(); Reference xRightLabelBitmap; if (mpRightLabelBitmap.get() != nullptr) 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& rxContext, const css::uno::Reference& rxCanvas) : mpTopLeft(), mpTop(), mpTopRight(), mpLeft(), mpRight(), mpBottomLeft(), mpBottom(), mpBottomRight(), mnTopFrameSize(0), mnLeftFrameSize(0), mnRightFrameSize(0), mnBottomFrameSize(0) { PresenterConfigurationAccess aConfiguration ( rxContext, "/org.openoffice.Office.PresenterScreen/", PresenterConfigurationAccess::READ_ONLY); Reference xBitmaps ( aConfiguration.GetConfigurationNode( "PresenterScreenSettings/SlideSorter/CurrentSlideBorderBitmaps"), UNO_QUERY); if ( ! xBitmaps.is()) return; PresenterBitmapContainer aContainer ( "PresenterScreenSettings/SlideSorter/CurrentSlideBorderBitmaps", std::shared_ptr(), 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.get() != nullptr) mnTopFrameSize = mpTop->mnHeight; if (mpLeft.get() != nullptr) mnLeftFrameSize = mpLeft->mnWidth; if (mpRight.get() != nullptr) mnRightFrameSize = mpRight->mnWidth; if (mpBottom.get() != nullptr) mnBottomFrameSize = mpBottom->mnHeight; if (mpTopLeft.get() != nullptr) { mnTopFrameSize = ::std::max(mnTopFrameSize, mpTopLeft->mnHeight); mnLeftFrameSize = ::std::max(mnLeftFrameSize, mpTopLeft->mnWidth); } if (mpTopRight.get() != nullptr) { mnTopFrameSize = ::std::max(mnTopFrameSize, mpTopRight->mnHeight); mnRightFrameSize = ::std::max(mnRightFrameSize, mpTopRight->mnWidth); } if (mpBottomLeft.get() != nullptr) { mnLeftFrameSize = ::std::max(mnLeftFrameSize, mpBottomLeft->mnWidth); mnBottomFrameSize = ::std::max(mnBottomFrameSize, mpBottomLeft->mnHeight); } if (mpBottomRight.get() != nullptr) { mnRightFrameSize = ::std::max(mnRightFrameSize, mpBottomRight->mnWidth); mnBottomFrameSize = ::std::max(mnBottomFrameSize, mpBottomRight->mnHeight); } } void PresenterSlideSorter::CurrentSlideFrameRenderer::PaintCurrentSlideFrame ( const awt::Rectangle& rSlideBoundingBox, const Reference& rxCanvas, const geometry::RealRectangle2D& rClipBox) { if ( ! rxCanvas.is()) return; const Reference xClip ( PresenterGeometryHelper::CreatePolygon(rClipBox, rxCanvas->getDevice())); if (mpTop.get() != nullptr) { PaintBitmapTiled( mpTop->GetNormalBitmap(), rxCanvas, rClipBox, rSlideBoundingBox.X, rSlideBoundingBox.Y - mpTop->mnHeight, rSlideBoundingBox.Width, mpTop->mnHeight); } if (mpLeft.get() != nullptr) { PaintBitmapTiled( mpLeft->GetNormalBitmap(), rxCanvas, rClipBox, rSlideBoundingBox.X - mpLeft->mnWidth, rSlideBoundingBox.Y, mpLeft->mnWidth, rSlideBoundingBox.Height); } if (mpRight.get() != nullptr) { PaintBitmapTiled( mpRight->GetNormalBitmap(), rxCanvas, rClipBox, rSlideBoundingBox.X + rSlideBoundingBox.Width, rSlideBoundingBox.Y, mpRight->mnWidth, rSlideBoundingBox.Height); } if (mpBottom.get() != nullptr) { PaintBitmapTiled( mpBottom->GetNormalBitmap(), rxCanvas, rClipBox, rSlideBoundingBox.X, rSlideBoundingBox.Y + rSlideBoundingBox.Height, rSlideBoundingBox.Width, mpBottom->mnHeight); } if (mpTopLeft.get() != nullptr) { PaintBitmapOnce( mpTopLeft->GetNormalBitmap(), rxCanvas, xClip, rSlideBoundingBox.X - mpTopLeft->mnWidth, rSlideBoundingBox.Y - mpTopLeft->mnHeight); } if (mpTopRight.get() != nullptr) { PaintBitmapOnce( mpTopRight->GetNormalBitmap(), rxCanvas, xClip, rSlideBoundingBox.X + rSlideBoundingBox.Width, rSlideBoundingBox.Y - mpTopLeft->mnHeight); } if (mpBottomLeft.get() != nullptr) { PaintBitmapOnce( mpBottomLeft->GetNormalBitmap(), rxCanvas, xClip, rSlideBoundingBox.X - mpBottomLeft->mnWidth, rSlideBoundingBox.Y + rSlideBoundingBox.Height); } if (mpBottomRight.get() != nullptr) { 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& rxBitmap, const css::uno::Reference& rxCanvas, const Reference& 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(4), rendering::CompositeOperation::SOURCE); rxCanvas->drawBitmap( rxBitmap, aViewState, aRenderState); } void PresenterSlideSorter::CurrentSlideFrameRenderer::PaintBitmapTiled( const css::uno::Reference& rxBitmap, const css::uno::Reference& 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(4), rendering::CompositeOperation::SOURCE); const double nX1 = nX0 + nWidth; const double nY1 = nY0 + nHeight; for (double nY=nY0; nYdrawBitmap( rxBitmap, aViewState, aRenderState); } } } // end of namespace ::sdext::presenter /* vim:set shiftwidth=4 softtabstop=4 expandtab: */