diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sd/source/ui/slidesorter/view | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sd/source/ui/slidesorter/view')
-rw-r--r-- | sd/source/ui/slidesorter/view/SlideSorterView.cxx | 856 | ||||
-rw-r--r-- | sd/source/ui/slidesorter/view/SlsFramePainter.cxx | 225 | ||||
-rw-r--r-- | sd/source/ui/slidesorter/view/SlsFramePainter.hxx | 109 | ||||
-rw-r--r-- | sd/source/ui/slidesorter/view/SlsInsertAnimator.cxx | 428 | ||||
-rw-r--r-- | sd/source/ui/slidesorter/view/SlsInsertionIndicatorOverlay.cxx | 360 | ||||
-rw-r--r-- | sd/source/ui/slidesorter/view/SlsLayeredDevice.cxx | 491 | ||||
-rw-r--r-- | sd/source/ui/slidesorter/view/SlsLayeredDevice.hxx | 84 | ||||
-rw-r--r-- | sd/source/ui/slidesorter/view/SlsLayouter.cxx | 1225 | ||||
-rw-r--r-- | sd/source/ui/slidesorter/view/SlsPageObjectLayouter.cxx | 259 | ||||
-rw-r--r-- | sd/source/ui/slidesorter/view/SlsPageObjectPainter.cxx | 442 | ||||
-rw-r--r-- | sd/source/ui/slidesorter/view/SlsTheme.cxx | 239 | ||||
-rw-r--r-- | sd/source/ui/slidesorter/view/SlsToolTip.cxx | 160 | ||||
-rw-r--r-- | sd/source/ui/slidesorter/view/SlsViewCacheContext.cxx | 117 | ||||
-rw-r--r-- | sd/source/ui/slidesorter/view/SlsViewCacheContext.hxx | 61 |
14 files changed, 5056 insertions, 0 deletions
diff --git a/sd/source/ui/slidesorter/view/SlideSorterView.cxx b/sd/source/ui/slidesorter/view/SlideSorterView.cxx new file mode 100644 index 000000000..390541e37 --- /dev/null +++ b/sd/source/ui/slidesorter/view/SlideSorterView.cxx @@ -0,0 +1,856 @@ +/* -*- 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 <view/SlideSorterView.hxx> + +#include <SlideSorter.hxx> +#include <ViewShell.hxx> +#include "SlsViewCacheContext.hxx" +#include "SlsLayeredDevice.hxx" +#include <view/SlsLayouter.hxx> +#include <view/SlsPageObjectLayouter.hxx> +#include <view/SlsPageObjectPainter.hxx> +#include <view/SlsILayerPainter.hxx> +#include <view/SlsToolTip.hxx> +#include <controller/SlideSorterController.hxx> +#include <controller/SlsClipboard.hxx> +#include <model/SlideSorterModel.hxx> +#include <model/SlsPageEnumerationProvider.hxx> +#include <model/SlsPageDescriptor.hxx> +#include <cache/SlsPageCache.hxx> +#include <cache/SlsPageCacheManager.hxx> +#include <PaneDockingWindow.hxx> + +#include <sdpage.hxx> +#include <Window.hxx> + +#include <comphelper/lok.hxx> +#include <osl/diagnose.h> +#include <vcl/svapp.hxx> +#include <vcl/scrbar.hxx> +#include <vcl/settings.hxx> +#include <vcl/graphicfilter.hxx> + +#include <algorithm> + +//#define DEBUG_TIMING +#ifdef DEBUG_TIMING +#include <memory> +#include <vector> +#endif + +using namespace ::sd::slidesorter::model; +using namespace ::drawinglayer::primitive2d; + +namespace sd::slidesorter::view { + +namespace { + /** Wrapper around the SlideSorterView that supports the IPainter + interface and that allows the LayeredDevice to hold the + SlideSorterView (held as unique_ptr by the SlideSorter) as + shared_ptr. + */ + class Painter : public ILayerPainter + { + public: + explicit Painter (SlideSorterView& rView) : mrView(rView) {} + + virtual void Paint (OutputDevice& rDevice, const ::tools::Rectangle& rRepaintArea) override + { + mrView.Paint(rDevice,rRepaintArea); + } + + virtual void SetLayerInvalidator (const SharedILayerInvalidator&) override {} + + private: + SlideSorterView& mrView; + }; +} + +namespace { + +class BackgroundPainter + : public ILayerPainter +{ +public: + explicit BackgroundPainter (const Color& rBackgroundColor) : maBackgroundColor(rBackgroundColor) {} + BackgroundPainter(const BackgroundPainter&) = delete; + BackgroundPainter& operator=(const BackgroundPainter&) = delete; + + virtual void Paint (OutputDevice& rDevice, const ::tools::Rectangle& rRepaintArea) override + { + rDevice.SetFillColor(maBackgroundColor); + rDevice.SetLineColor(); + rDevice.DrawRect(rRepaintArea); + } + + virtual void SetLayerInvalidator (const SharedILayerInvalidator&) override {} + + void SetColor (const Color& rColor) { maBackgroundColor = rColor; } + +private: + Color maBackgroundColor; +}; + +} + +SlideSorterView::SlideSorterView (SlideSorter& rSlideSorter) + : ::sd::View ( + *rSlideSorter.GetModel().GetDocument(), + rSlideSorter.GetContentWindow()->GetOutDev(), + rSlideSorter.GetViewShell()), + mrSlideSorter(rSlideSorter), + mrModel(rSlideSorter.GetModel()), + mbIsDisposed(false), + mpLayouter (new Layouter(rSlideSorter.GetContentWindow(), rSlideSorter.GetTheme())), + mbPageObjectVisibilitiesValid (false), + mpLayeredDevice(std::make_shared<LayeredDevice>(rSlideSorter.GetContentWindow())), + maVisiblePageRange(-1,-1), + maPreviewSize(0,0), + mbPreciousFlagUpdatePending(true), + meOrientation(Layouter::GRID), + mpBackgroundPainter( + std::make_shared<BackgroundPainter>(mrSlideSorter.GetTheme()->GetColor(Theme::Color_Background))), + mpToolTip(new ToolTip(mrSlideSorter)), + mbIsRearrangePending(true) +{ + // Hide the page that contains the page objects. + SetPageVisible (false); + + // Register the background painter on level 1 to avoid the creation of a + // background buffer. + mpLayeredDevice->RegisterPainter(mpBackgroundPainter, 1); + + // Wrap a shared_ptr-held-wrapper around this view and register it as + // painter at the layered device. There is no explicit destruction: in + // the SlideSorterView destructor the layered device is destroyed and + // with it the only reference to the wrapper which therefore is also + // destroyed. + SharedILayerPainter pPainter = std::make_shared<Painter>(*this); + + // The painter is placed on level 1 to avoid buffering. This should be + // a little faster during animations because the previews are painted + // directly into the window, not via the buffer. + mpLayeredDevice->RegisterPainter(pPainter, 1); +} + +SlideSorterView::~SlideSorterView() +{ + if ( ! mbIsDisposed) + { + OSL_ASSERT(mbIsDisposed); + Dispose(); + } +} + +void SlideSorterView::Init() +{ + HandleModelChange(); +} + +void SlideSorterView::Dispose() +{ + mpLayeredDevice->Dispose(); + mpPreviewCache.reset(); + + SetPageUnderMouse(SharedPageDescriptor()); + + // Hide the page to avoid problems in the view when deleting + // visualized objects + HideSdrPage(); + + // Deletion of the objects and the page will be done in SdrModel + // destructor (as long as objects and pages are added) + + OSL_ASSERT(mpLayeredDevice.use_count() == 1); + mpLayeredDevice.reset(); + + mbIsDisposed = true; +} + +sal_Int32 SlideSorterView::GetPageIndexAtPoint (const Point& rWindowPosition) const +{ + sal_Int32 nIndex (-1); + + sd::Window *pWindow (mrSlideSorter.GetContentWindow().get()); + if (pWindow) + { + nIndex = mpLayouter->GetIndexAtPoint(pWindow->PixelToLogic(rWindowPosition), false, false); + + // Clip the page index against the page count. + if (nIndex >= mrModel.GetPageCount()) + nIndex = -1; + } + + return nIndex; +} + +Layouter& SlideSorterView::GetLayouter() { return *mpLayouter; } + +void SlideSorterView::ModelHasChanged() +{ + // Ignore this call. Rely on hints sent by the model to get informed of + // model changes. +} + +void SlideSorterView::PreModelChange() +{ + // Reset the slide under the mouse. It will be re-set in PostModelChange(). + SetPageUnderMouse(SharedPageDescriptor()); +} + +void SlideSorterView::PostModelChange() +{ + // In PreModelChange() the page objects have been released. Here we + // create new ones. + ::osl::MutexGuard aGuard (mrModel.GetMutex()); + + model::PageEnumerationProvider::CreateAllPagesEnumeration(mrModel); + + // The new page objects have to be scaled and positioned. + RequestRearrange(); + RequestRepaint(); +} + +/** At the moment for every model change all page objects are destroyed and + re-created again. This can be optimized by accepting hints that + describe the type of change so that existing page objects can be + reused. +*/ +void SlideSorterView::HandleModelChange() +{ + PreModelChange (); + PostModelChange(); +} + +void SlideSorterView::HandleDrawModeChange() +{ + // Replace the preview cache with a new and empty one. The + // PreviewRenderer that is used by the cache is replaced by this as + // well. + mpPreviewCache.reset(); + GetPreviewCache()->InvalidateCache(); + + RequestRepaint(); +} + +void SlideSorterView::HandleDataChangeEvent() +{ + GetPageObjectPainter()->SetTheme(mrSlideSorter.GetTheme()); + + // Update the color used by the background painter. + std::shared_ptr<BackgroundPainter> pPainter ( + std::dynamic_pointer_cast<BackgroundPainter>(mpBackgroundPainter)); + if (pPainter) + pPainter->SetColor(mrSlideSorter.GetTheme()->GetColor(Theme::Color_Background)); + + RequestRepaint(); +} + +void SlideSorterView::Resize() +{ + UpdateOrientation(); + + mpLayeredDevice->Resize(); + RequestRearrange(); +} + +void SlideSorterView::RequestRearrange() +{ + mbIsRearrangePending = true; + Rearrange(); +} + +void SlideSorterView::Rearrange() +{ + if ( ! mbIsRearrangePending) + return; + if (mrModel.GetPageCount() <= 0) + return; + + sd::Window *pWindow (mrSlideSorter.GetContentWindow().get()); + if ( ! pWindow) + return; + const Size aWindowSize (pWindow->GetSizePixel()); + if (aWindowSize.IsEmpty()) + return; + + const bool bRearrangeSuccess ( + mpLayouter->Rearrange ( + meOrientation, + aWindowSize, + mrModel.GetPageDescriptor(0)->GetPage()->GetSize(), + mrModel.GetPageCount())); + if (bRearrangeSuccess) + { + mbIsRearrangePending = false; + Layout(); + UpdatePageUnderMouse(); + // RequestRepaint(); + } +} + +void SlideSorterView::UpdateOrientation() +{ + // The layout of slides depends on whether the slide sorter is + // displayed in the center or the side pane. + if (mrSlideSorter.GetViewShell()->IsMainViewShell()) + SetOrientation(Layouter::GRID); + else + { + // Get access to the docking window. + vcl::Window* pWindow = mrSlideSorter.GetContentWindow(); + PaneDockingWindow* pDockingWindow = nullptr; + while (pWindow!=nullptr && pDockingWindow==nullptr) + { + pDockingWindow = dynamic_cast<PaneDockingWindow*>(pWindow); + pWindow = pWindow->GetParent(); + } + + if (pDockingWindow != nullptr) + { + const ::tools::Long nScrollBarSize ( + Application::GetSettings().GetStyleSettings().GetScrollBarSize()); + switch (pDockingWindow->GetOrientation()) + { + case PaneDockingWindow::HorizontalOrientation: + if (SetOrientation(Layouter::HORIZONTAL)) + { + const Range aRange (mpLayouter->GetValidVerticalSizeRange()); + pDockingWindow->SetValidSizeRange(Range( + aRange.Min() + nScrollBarSize, + aRange.Max() + nScrollBarSize)); + } + break; + + case PaneDockingWindow::VerticalOrientation: + if (SetOrientation(Layouter::VERTICAL)) + { + const Range aRange (mpLayouter->GetValidHorizontalSizeRange()); + pDockingWindow->SetValidSizeRange(Range( + aRange.Min() + nScrollBarSize, + aRange.Max() + nScrollBarSize)); + } + break; + + case PaneDockingWindow::UnknownOrientation: + if (SetOrientation(Layouter::GRID)) + { + const sal_Int32 nAdditionalSize (10); + pDockingWindow->SetMinOutputSizePixel(Size( + mpLayouter->GetValidHorizontalSizeRange().Min() + + nScrollBarSize + + nAdditionalSize, + mpLayouter->GetValidVerticalSizeRange().Min() + + nScrollBarSize + + nAdditionalSize)); + } + return; + } + } + else + { + // We are not placed in a docking window. One possible reason + // is that the slide sorter is temporarily into a cache and was + // reparented to a non-docking window. + SetOrientation(Layouter::GRID); + } + } +} + +void SlideSorterView::Layout () +{ + sd::Window *pWindow (mrSlideSorter.GetContentWindow().get()); + if (pWindow) + { + // Set the model area, i.e. the smallest rectangle that includes all + // page objects. + const ::tools::Rectangle aViewBox (mpLayouter->GetTotalBoundingBox()); + pWindow->SetViewOrigin (aViewBox.TopLeft()); + pWindow->SetViewSize (aViewBox.GetSize()); + + std::shared_ptr<PageObjectLayouter> pPageObjectLayouter( + mpLayouter->GetPageObjectLayouter()); + if (pPageObjectLayouter) + { + const Size aNewPreviewSize (mpLayouter->GetPageObjectLayouter()->GetPreviewSize()); + if (maPreviewSize != aNewPreviewSize && GetPreviewCache()) + { + mpPreviewCache->ChangeSize(aNewPreviewSize, Bitmap::HasFastScale()); + maPreviewSize = aNewPreviewSize; + } + } + + // Iterate over all page objects and place them relative to the + // containing page. + model::PageEnumeration aPageEnumeration ( + model::PageEnumerationProvider::CreateAllPagesEnumeration(mrModel)); + while (aPageEnumeration.HasMoreElements()) + { + model::SharedPageDescriptor pDescriptor (aPageEnumeration.GetNextElement()); + pDescriptor->SetBoundingBox(mpLayouter->GetPageObjectBox(pDescriptor->GetPageIndex(), false)); + } + } + + InvalidatePageObjectVisibilities (); +} + +void SlideSorterView::InvalidatePageObjectVisibilities() +{ + mbPageObjectVisibilitiesValid = false; +} + +void SlideSorterView::DeterminePageObjectVisibilities() +{ + sd::Window *pWindow (mrSlideSorter.GetContentWindow().get()); + if (!pWindow) + return; + + // Set this flag to true here so that an invalidate during the + // visibility calculation can correctly invalidate it again. + mbPageObjectVisibilitiesValid = true; + + ::tools::Rectangle aViewArea (pWindow->PixelToLogic(::tools::Rectangle(Point(0,0),pWindow->GetSizePixel()))); + const Range aRange (mpLayouter->GetRangeOfVisiblePageObjects(aViewArea)); + const Range aUnion( + ::std::min(maVisiblePageRange.Min(), aRange.Min()), + ::std::max(maVisiblePageRange.Max(), aRange.Max())); + + // For page objects that just dropped off the visible area we + // decrease the priority of pending requests for preview bitmaps. + if (maVisiblePageRange != aRange) + mbPreciousFlagUpdatePending |= true; + + model::SharedPageDescriptor pDescriptor; + for (::tools::Long nIndex=aUnion.Min(); nIndex<=aUnion.Max(); nIndex++) + { + pDescriptor = mrModel.GetPageDescriptor(nIndex); + if (pDescriptor) + SetState( + pDescriptor, + PageDescriptor::ST_Visible, + aRange.Contains(nIndex)); + } + + // Broadcast a change of the set of visible page objects. + if (maVisiblePageRange != aRange) + { + maVisiblePageRange = aRange; + + // Tell the listeners that the visibility of some objects has + // changed. + ::std::vector<Link<LinkParamNone*,void>>& aChangeListeners (maVisibilityChangeListeners); + for (const auto& rLink : aChangeListeners) + { + rLink.Call(nullptr); + } + } + + // Restore the mouse over state. + UpdatePageUnderMouse(); +} + +void SlideSorterView::UpdatePreciousFlags() +{ + if (!mbPreciousFlagUpdatePending) + return; + + mbPreciousFlagUpdatePending = false; + + model::SharedPageDescriptor pDescriptor; + std::shared_ptr<cache::PageCache> pCache = GetPreviewCache(); + sal_Int32 nPageCount (mrModel.GetPageCount()); + + for (int nIndex=0; nIndex<=nPageCount; ++nIndex) + { + pDescriptor = mrModel.GetPageDescriptor(nIndex); + if (pDescriptor) + { + pCache->SetPreciousFlag( + pDescriptor->GetPage(), + maVisiblePageRange.Contains(nIndex)); + } + else + { + // At least one cache entry can not be updated. Remember to + // repeat the whole updating later and leave the loop now. + mbPreciousFlagUpdatePending = true; + break; + } + } +} + +bool SlideSorterView::SetOrientation (const Layouter::Orientation eOrientation) +{ + if (meOrientation != eOrientation) + { + meOrientation = eOrientation; + return true; + } + else + return false; +} + +void SlideSorterView::RequestRepaint() +{ + sd::Window *pWindow (mrSlideSorter.GetContentWindow().get()); + if (pWindow) + { + mpLayeredDevice->InvalidateAllLayers( + ::tools::Rectangle( + pWindow->PixelToLogic(Point(0,0)), + pWindow->PixelToLogic(pWindow->GetSizePixel()))); + pWindow->Invalidate(); + } +} + +void SlideSorterView::RequestRepaint (const model::SharedPageDescriptor& rpDescriptor) +{ + if (rpDescriptor) + RequestRepaint(rpDescriptor->GetBoundingBox()); +} + +void SlideSorterView::RequestRepaint (const ::tools::Rectangle& rRepaintBox) +{ + sd::Window *pWindow (mrSlideSorter.GetContentWindow().get()); + if (pWindow) + { + mpLayeredDevice->InvalidateAllLayers(rRepaintBox); + pWindow->Invalidate(rRepaintBox); + } +} + +void SlideSorterView::RequestRepaint (const vcl::Region& rRepaintRegion) +{ + sd::Window *pWindow (mrSlideSorter.GetContentWindow().get()); + if (pWindow) + { + mpLayeredDevice->InvalidateAllLayers(rRepaintRegion); + pWindow->Invalidate(rRepaintRegion); + } +} + +::tools::Rectangle SlideSorterView::GetModelArea() const +{ + return mpLayouter->GetTotalBoundingBox(); +} + +#ifdef DEBUG_TIMING +static ::canvas::tools::ElapsedTime gaTimer; +static const size_t gFrameTimeCount (10); +static size_t gFrameTimeIndex (0); +static ::std::vector<double> gFrameTimes (gFrameTimeCount, 0); +static double gFrameTimeSum (0); +static const ::tools::Rectangle gFrameTimeBox (10,10,150,20); +static double gnLastFrameStart = 0; +#endif + +void SlideSorterView::CompleteRedraw ( + OutputDevice* pDevice, + const vcl::Region& rPaintArea, + sdr::contact::ViewObjectContactRedirector* pRedirector) +{ + (void)pRedirector; + + if (comphelper::LibreOfficeKit::isActive()) + return; + + if (pDevice == nullptr || pDevice!=mrSlideSorter.GetContentWindow()->GetOutDev()) + return; + +#ifdef DEBUG_TIMING + const double nStartTime (gaTimer.getElapsedTime()); + SAL_INFO("sd.timing", "SlideSorterView::CompleteRedraw start" << (mnLockRedrawSmph ? " locked" : "")); +#endif + + // The parent implementation of CompleteRedraw is called only when + // painting is locked. We do all the painting ourself. When painting + // is locked the parent implementation keeps track of the repaint + // requests and later, when painting is unlocked, calls CompleteRedraw + // for all missed repaints. + + if (mnLockRedrawSmph == 0) + { + if (mpLayeredDevice->HandleMapModeChange()) + DeterminePageObjectVisibilities(); + mpLayeredDevice->Repaint(rPaintArea); + } + else + { + maRedrawRegion.Union(rPaintArea); + } + +#ifdef DEBUG_TIMING + const double nEndTime (gaTimer.getElapsedTime()); + SAL_INFO("sd.timing", "SlideSorterView::CompleteRedraw end after " << (nEndTime-nStartTime)*1000 << " ms"); + gFrameTimeSum -= gFrameTimes[gFrameTimeIndex]; + gFrameTimes[gFrameTimeIndex] = nStartTime - gnLastFrameStart; + gnLastFrameStart = nStartTime; + gFrameTimeSum += gFrameTimes[gFrameTimeIndex]; + gFrameTimeIndex = (gFrameTimeIndex+1) % gFrameTimeCount; + + mrSlideSorter.GetContentWindow()->SetFillColor(COL_BLUE); + mrSlideSorter.GetContentWindow()->DrawRect(gFrameTimeBox); + mrSlideSorter.GetContentWindow()->SetTextColor(COL_WHITE); + mrSlideSorter.GetContentWindow()->DrawText( + gFrameTimeBox, + OUString::number(1 / (gFrameTimeSum / gFrameTimeCount)), + DrawTextFlags::Right | DrawTextFlags::VCenter); + // mrSlideSorter.GetContentWindow()->Invalidate(gFrameTimeBox); +#endif +} + +void SlideSorterView::Paint ( + OutputDevice& rDevice, + const ::tools::Rectangle& rRepaintArea) +{ + if (rRepaintArea.IsEmpty()) + return; + + if ( ! mpPageObjectPainter) + if ( ! GetPageObjectPainter()) + return; + + // Update the page visibilities when they have been invalidated. + if ( ! mbPageObjectVisibilitiesValid) + DeterminePageObjectVisibilities(); + + if (mbPreciousFlagUpdatePending) + UpdatePreciousFlags(); + + if (mbIsRearrangePending) + Rearrange(); + + // Paint all page objects that are fully or partially inside the + // repaint region. + const Range aRange (mpLayouter->GetRangeOfVisiblePageObjects(rRepaintArea)); + // Try to prefetch all graphics from the pages to paint. This will be done + // in threads to be more efficient than loading them on-demand one by one. + std::vector<Graphic*> graphics; + for (::tools::Long nIndex=aRange.Min(); nIndex<=aRange.Max(); ++nIndex) + { + model::SharedPageDescriptor pDescriptor (mrModel.GetPageDescriptor(nIndex)); + if (!pDescriptor || ! pDescriptor->HasState(PageDescriptor::ST_Visible)) + continue; + pDescriptor->GetPage()->getGraphicsForPrefetch(graphics); + } + // Handle also one page before and after to have those in advance on scrolling. + for (::tools::Long nIndex : { aRange.Min() - 1, aRange.Max() + 1 }) + { + model::SharedPageDescriptor pDescriptor (mrModel.GetPageDescriptor(nIndex)); + if (!pDescriptor) + continue; + pDescriptor->GetPage()->getGraphicsForPrefetch(graphics); + } + if(graphics.size() > 1) // threading does not help with loading just one + GraphicFilter::GetGraphicFilter().MakeGraphicsAvailableThreaded(graphics); + + for (::tools::Long nIndex=aRange.Min(); nIndex<=aRange.Max(); ++nIndex) + { + model::SharedPageDescriptor pDescriptor (mrModel.GetPageDescriptor(nIndex)); + if (!pDescriptor || ! pDescriptor->HasState(PageDescriptor::ST_Visible)) + continue; + + mpPageObjectPainter->PaintPageObject(rDevice, pDescriptor); + } +} + +void SlideSorterView::ConfigurationChanged ( + utl::ConfigurationBroadcaster* pBroadcaster, + ConfigurationHints nHint) +{ + // Some changes of the configuration (some of the colors for example) + // may affect the previews. Throw away the old ones and create new ones. + cache::PageCacheManager::Instance()->InvalidateAllCaches(); + + ::sd::View::ConfigurationChanged(pBroadcaster, nHint); + RequestRepaint(); + +} + +std::shared_ptr<cache::PageCache> const & SlideSorterView::GetPreviewCache() +{ + sd::Window *pWindow (mrSlideSorter.GetContentWindow().get()); + if (pWindow && mpPreviewCache == nullptr) + { + mpPreviewCache = + std::make_shared<cache::PageCache>( + mpLayouter->GetPageObjectSize(), + Bitmap::HasFastScale(), + std::make_shared<ViewCacheContext>(mrSlideSorter)); + } + + return mpPreviewCache; +} + +Range const & SlideSorterView::GetVisiblePageRange() +{ + if ( ! mbPageObjectVisibilitiesValid) + DeterminePageObjectVisibilities(); + return maVisiblePageRange; +} + +void SlideSorterView::AddVisibilityChangeListener (const Link<LinkParamNone*,void>& rListener) +{ + if (::std::find ( + maVisibilityChangeListeners.begin(), + maVisibilityChangeListeners.end(), + rListener) == maVisibilityChangeListeners.end()) + { + maVisibilityChangeListeners.push_back(rListener); + } +} + +void SlideSorterView::RemoveVisibilityChangeListener(const Link<LinkParamNone*,void>&rListener) +{ + maVisibilityChangeListeners.erase ( + ::std::find ( + maVisibilityChangeListeners.begin(), + maVisibilityChangeListeners.end(), + rListener)); +} + +ToolTip& SlideSorterView::GetToolTip() const +{ + OSL_ASSERT(mpToolTip); + return *mpToolTip; +} + +void SlideSorterView::DragFinished (sal_Int8 nDropAction) +{ + mrSlideSorter.GetController().GetClipboard().DragFinished(nDropAction); + + View::DragFinished(nDropAction); +} + +void SlideSorterView::UpdatePageUnderMouse () +{ + VclPtr<ScrollBar> pVScrollBar (mrSlideSorter.GetVerticalScrollBar()); + VclPtr<ScrollBar> pHScrollBar (mrSlideSorter.GetHorizontalScrollBar()); + if ((pVScrollBar && pVScrollBar->IsVisible() && pVScrollBar->IsTracking()) + || (pHScrollBar && pHScrollBar->IsVisible() && pHScrollBar->IsTracking())) + { + // One of the scroll bars is tracking mouse movement. Do not + // highlight the slide under the mouse in this case. + SetPageUnderMouse(SharedPageDescriptor()); + return; + } + + sd::Window *pWindow (mrSlideSorter.GetContentWindow().get()); + if (pWindow && pWindow->IsVisible() && ! pWindow->IsMouseCaptured()) + { + const Window::PointerState aPointerState (pWindow->GetPointerState()); + const ::tools::Rectangle aWindowBox (pWindow->GetPosPixel(), pWindow->GetSizePixel()); + if (aWindowBox.Contains(aPointerState.maPos)) + { + UpdatePageUnderMouse(aPointerState.maPos); + return; + } + } + + SetPageUnderMouse(SharedPageDescriptor()); +} + +void SlideSorterView::UpdatePageUnderMouse ( + const Point& rMousePosition) +{ + SetPageUnderMouse(mrSlideSorter.GetController().GetPageAt(rMousePosition)); +} + +void SlideSorterView::SetPageUnderMouse ( + const model::SharedPageDescriptor& rpDescriptor) +{ + if (mpPageUnderMouse == rpDescriptor) + return; + + if (mpPageUnderMouse) + SetState(mpPageUnderMouse, PageDescriptor::ST_MouseOver, false); + + mpPageUnderMouse = rpDescriptor; + + if (mpPageUnderMouse) + SetState(mpPageUnderMouse, PageDescriptor::ST_MouseOver, true); + + // Change the quick help text to display the name of the page under + // the mouse. + mpToolTip->SetPage(rpDescriptor); +} + +bool SlideSorterView::SetState ( + const model::SharedPageDescriptor& rpDescriptor, + const PageDescriptor::State eState, + const bool bStateValue) +{ + if ( ! rpDescriptor) + return false; + + const bool bModified (rpDescriptor->SetState(eState, bStateValue)); + if ( ! bModified) + return false; + + // When the page object is not visible (i.e. not on the screen then + // nothing has to be painted. + if (rpDescriptor->HasState(PageDescriptor::ST_Visible)) + { + // For most states a change of that state leads to visible + // difference and we have to request a repaint. + if (eState != PageDescriptor::ST_WasSelected) + RequestRepaint(rpDescriptor); + } + + return bModified; +} + +std::shared_ptr<PageObjectPainter> const & SlideSorterView::GetPageObjectPainter() +{ + if ( ! mpPageObjectPainter) + mpPageObjectPainter = std::make_shared<PageObjectPainter>(mrSlideSorter); + return mpPageObjectPainter; +} + +//===== SlideSorterView::DrawLock ============================================= + +SlideSorterView::DrawLock::DrawLock (SlideSorter const & rSlideSorter) + : mrView(rSlideSorter.GetView()), + mpWindow(rSlideSorter.GetContentWindow()) +{ + if (mrView.mnLockRedrawSmph == 0) + mrView.maRedrawRegion.SetEmpty(); + ++mrView.mnLockRedrawSmph; +} + +SlideSorterView::DrawLock::~DrawLock() +{ + OSL_ASSERT(mrView.mnLockRedrawSmph>0); + --mrView.mnLockRedrawSmph; + if (mrView.mnLockRedrawSmph == 0) + if (mpWindow) + { + mpWindow->Invalidate(mrView.maRedrawRegion); + } +} + +void SlideSorterView::DrawLock::Dispose() +{ + mpWindow.reset(); +} + +} // end of namespace ::sd::slidesorter::view + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/view/SlsFramePainter.cxx b/sd/source/ui/slidesorter/view/SlsFramePainter.cxx new file mode 100644 index 000000000..31c301868 --- /dev/null +++ b/sd/source/ui/slidesorter/view/SlsFramePainter.cxx @@ -0,0 +1,225 @@ +/* -*- 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 "SlsFramePainter.hxx" +#include <vcl/outdev.hxx> +#include <osl/diagnose.h> + +namespace sd::slidesorter::view { + +FramePainter::FramePainter (const BitmapEx& rShadowBitmap) + : maTopLeft(rShadowBitmap,-1,-1), + maTop(rShadowBitmap,0,-1), + maTopRight(rShadowBitmap,+1,-1), + maLeft(rShadowBitmap,-1,0), + maRight(rShadowBitmap,+1,0), + maBottomLeft(rShadowBitmap,-1,+1), + maBottom(rShadowBitmap,0,+1), + maBottomRight(rShadowBitmap,+1,+1), + maCenter(rShadowBitmap,0,0), + mbIsValid(false) +{ + if (rShadowBitmap.GetSizePixel().Width() == rShadowBitmap.GetSizePixel().Height() + && (rShadowBitmap.GetSizePixel().Width()-1)%2 == 0 + && ((rShadowBitmap.GetSizePixel().Width()-1)/2)%2 == 1) + { + mbIsValid = true; + } + else + { + OSL_ASSERT(rShadowBitmap.GetSizePixel().Width() == rShadowBitmap.GetSizePixel().Height()); + OSL_ASSERT((rShadowBitmap.GetSizePixel().Width()-1)%2 == 0); + OSL_ASSERT(((rShadowBitmap.GetSizePixel().Width()-1)/2)%2 == 1); + } +} + +FramePainter::~FramePainter() +{ +} + +void FramePainter::PaintFrame ( + OutputDevice& rDevice, + const ::tools::Rectangle& rBox) const +{ + if ( ! mbIsValid) + return; + + // Paint the shadow. + maTopLeft.PaintCorner(rDevice, rBox.TopLeft()); + maTopRight.PaintCorner(rDevice, rBox.TopRight()); + maBottomLeft.PaintCorner(rDevice, rBox.BottomLeft()); + maBottomRight.PaintCorner(rDevice, rBox.BottomRight()); + maLeft.PaintSide(rDevice, rBox.TopLeft(), rBox.BottomLeft(), maTopLeft, maBottomLeft); + maRight.PaintSide(rDevice, rBox.TopRight(), rBox.BottomRight(), maTopRight, maBottomRight); + maTop.PaintSide(rDevice, rBox.TopLeft(), rBox.TopRight(), maTopLeft, maTopRight); + maBottom.PaintSide(rDevice, rBox.BottomLeft(), rBox.BottomRight(), maBottomLeft, maBottomRight); + maCenter.PaintCenter(rDevice,rBox); +} + +void FramePainter::AdaptColor ( + const Color aNewColor) +{ + // Get the source color. + if (maCenter.maBitmap.IsEmpty()) + return; + const Color aSourceColor = maCenter.maBitmap.GetPixelColor(0,0); + + // Erase the center bitmap. + maCenter.maBitmap.SetEmpty(); + + // Replace the color in all bitmaps. + maTopLeft.maBitmap.Replace(aSourceColor, aNewColor); + maTop.maBitmap.Replace(aSourceColor, aNewColor); + maTopRight.maBitmap.Replace(aSourceColor, aNewColor); + maLeft.maBitmap.Replace(aSourceColor, aNewColor); + maCenter.maBitmap.Replace(aSourceColor, aNewColor); + maRight.maBitmap.Replace(aSourceColor, aNewColor); + maBottomLeft.maBitmap.Replace(aSourceColor, aNewColor); + maBottom.maBitmap.Replace(aSourceColor, aNewColor); + maBottomRight.maBitmap.Replace(aSourceColor, aNewColor); +} + +//===== FramePainter::OffsetBitmap ============================================ + +FramePainter::OffsetBitmap::OffsetBitmap ( + const BitmapEx& rBitmap, + const sal_Int32 nHorizontalPosition, + const sal_Int32 nVerticalPosition) +{ + OSL_ASSERT(nHorizontalPosition>=-1 && nHorizontalPosition<=+1); + OSL_ASSERT(nVerticalPosition>=-1 && nVerticalPosition<=+1); + + const sal_Int32 nS (1); + const sal_Int32 nC (::std::max<sal_Int32>(0,(rBitmap.GetSizePixel().Width()-nS)/2)); + const sal_Int32 nO (nC/2); + + const Point aOrigin( + nHorizontalPosition<0 ? 0 : (nHorizontalPosition == 0 ? nC : nC+nS), + nVerticalPosition<0 ? 0 : (nVerticalPosition == 0 ? nC : nC+nS)); + const Size aSize( + nHorizontalPosition==0 ? nS : nC, + nVerticalPosition==0 ? nS : nC); + maBitmap = BitmapEx(rBitmap, aOrigin, aSize); + if (maBitmap.IsEmpty()) + return; + maOffset = Point( + nHorizontalPosition<0 ? -nO : nHorizontalPosition>0 ? -nO : 0, + nVerticalPosition<0 ? -nO : nVerticalPosition>0 ? -nO : 0); + + // Enlarge the side bitmaps so that painting the frame requires less + // paint calls. + const sal_Int32 nSideBitmapSize (64); + if (nHorizontalPosition == 0 && nVerticalPosition == 0) + { + maBitmap.Scale(Size(nSideBitmapSize,nSideBitmapSize)); + } + else if (nHorizontalPosition == 0) + { + maBitmap.Scale(Size(nSideBitmapSize,aSize.Height())); + } + else if (nVerticalPosition == 0) + { + maBitmap.Scale(Size(maBitmap.GetSizePixel().Width(), nSideBitmapSize)); + } +} + +void FramePainter::OffsetBitmap::PaintCorner ( + OutputDevice& rDevice, + const Point& rAnchor) const +{ + if ( ! maBitmap.IsEmpty()) + rDevice.DrawBitmapEx(rAnchor+maOffset, maBitmap); +} + +void FramePainter::OffsetBitmap::PaintSide ( + OutputDevice& rDevice, + const Point& rAnchor1, + const Point& rAnchor2, + const OffsetBitmap& rCornerBitmap1, + const OffsetBitmap& rCornerBitmap2) const +{ + if (maBitmap.IsEmpty()) + return; + + const Size aBitmapSize (maBitmap.GetSizePixel()); + if (rAnchor1.Y() == rAnchor2.Y()) + { + // Side is horizontal. + const sal_Int32 nY (rAnchor1.Y() + maOffset.Y()); + const sal_Int32 nLeft ( + rAnchor1.X() + + rCornerBitmap1.maBitmap.GetSizePixel().Width() + + rCornerBitmap1.maOffset.X()); + const sal_Int32 nRight ( + rAnchor2.X() + + rCornerBitmap2.maOffset.X() + - 1); + for (sal_Int32 nX=nLeft; nX<=nRight; nX+=aBitmapSize.Width()) + { + rDevice.DrawBitmapEx( + Point(nX,nY), + Size(std::min(aBitmapSize.Width(),static_cast<::tools::Long>(nRight-nX+1)),aBitmapSize.Height()), + maBitmap); + } + } + else if (rAnchor1.X() == rAnchor2.X()) + { + // Side is vertical. + const sal_Int32 nX (rAnchor1.X() + maOffset.X()); + const sal_Int32 nTop ( + rAnchor1.Y() + + rCornerBitmap1.maBitmap.GetSizePixel().Height() + + rCornerBitmap1.maOffset.Y()); + const sal_Int32 nBottom ( + rAnchor2.Y() + + rCornerBitmap2.maOffset.Y() + - 1); + for (sal_Int32 nY=nTop; nY<=nBottom; nY+=aBitmapSize.Height()) + { + rDevice.DrawBitmapEx( + Point(nX,nY), + Size(aBitmapSize.Width(), std::min(aBitmapSize.Height(), static_cast<::tools::Long>(nBottom-nY+1))), + maBitmap); + } + } + else + { + // Diagonal sides indicates an error. + OSL_ASSERT(false); + } +} + +void FramePainter::OffsetBitmap::PaintCenter ( + OutputDevice& rDevice, + const ::tools::Rectangle& rBox) const +{ + const Size aBitmapSize (maBitmap.GetSizePixel()); + for (::tools::Long nY=rBox.Top(); nY<=rBox.Bottom(); nY+=aBitmapSize.Height()) + for (::tools::Long nX=rBox.Left(); nX<=rBox.Right(); nX+=aBitmapSize.Width()) + rDevice.DrawBitmapEx( + Point(nX,nY), + Size( + ::std::min(aBitmapSize.Width(), rBox.Right()-nX+1), + std::min(aBitmapSize.Height(), rBox.Bottom()-nY+1)), + maBitmap); +} + +} // end of namespace sd::slidesorter::view + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/view/SlsFramePainter.hxx b/sd/source/ui/slidesorter/view/SlsFramePainter.hxx new file mode 100644 index 000000000..9398cb94e --- /dev/null +++ b/sd/source/ui/slidesorter/view/SlsFramePainter.hxx @@ -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 . + */ + +#pragma once + +#include <vcl/bitmapex.hxx> + +namespace sd::slidesorter::view { + +class FramePainter +{ +public: + explicit FramePainter (const BitmapEx& rBitmap); + ~FramePainter(); + + /** Paint a border around the given box by using a set of bitmaps for + the corners and sides. + */ + void PaintFrame (OutputDevice&rDevice, const ::tools::Rectangle& rBox) const; + + /** Special functionality that takes the color from the center + bitmap and replaces that color in all bitmaps by the given new + color. Alpha values are not modified. + The center bitmap is erased. + */ + void AdaptColor (const Color aNewColor); + +private: + /** Bitmap with offset that is used when the bitmap is painted. The bitmap + */ + class OffsetBitmap { + public: + BitmapEx maBitmap; + Point maOffset; + + /** Create one of the eight shadow bitmaps from one that combines + them all. This larger bitmap is expected to have dimension NxN + with N=1+2*M. Of this larger bitmap there are created four + corner bitmaps of size 2*M x 2*M and four side bitmaps of sizes + 1xM (top and bottom) and Mx1 (left and right). The corner + bitmaps have each one quadrant of size MxM that is painted under + the interior of the frame. + @param rBitmap + The larger bitmap of which the eight shadow bitmaps are cut + out from. + @param nHorizontalPosition + Valid values are -1 (left), 0 (center), and +1 (right). + @param nVerticalPosition + Valid values are -1 (top), 0 (center), and +1 (bottom). + */ + OffsetBitmap ( + const BitmapEx& rBitmap, + const sal_Int32 nHorizontalPosition, + const sal_Int32 nVerticalPosition); + + /** Use the given device to paint the bitmap at the location that is + the sum of the given anchor and the internal offset. + */ + void PaintCorner (OutputDevice& rDevice, const Point& rAnchor) const; + + /** Use the given device to paint the bitmap stretched between the + two given locations. Offsets of the adjacent corner bitmaps and + the offset of the side bitmap are used to determine the area + that is to be filled with the side bitmap. + */ + void PaintSide ( + OutputDevice& rDevice, + const Point& rAnchor1, + const Point& rAnchor2, + const OffsetBitmap& rCornerBitmap1, + const OffsetBitmap& rCornerBitmap2) const; + + /** Fill the given rectangle with the bitmap. + */ + void PaintCenter ( + OutputDevice& rDevice, + const ::tools::Rectangle& rBox) const; + }; + OffsetBitmap maTopLeft; + OffsetBitmap maTop; + OffsetBitmap maTopRight; + OffsetBitmap maLeft; + OffsetBitmap maRight; + OffsetBitmap maBottomLeft; + OffsetBitmap maBottom; + OffsetBitmap maBottomRight; + OffsetBitmap maCenter; + bool mbIsValid; +}; + +} // end of namespace sd::slidesorter::view + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/view/SlsInsertAnimator.cxx b/sd/source/ui/slidesorter/view/SlsInsertAnimator.cxx new file mode 100644 index 000000000..361c55f05 --- /dev/null +++ b/sd/source/ui/slidesorter/view/SlsInsertAnimator.cxx @@ -0,0 +1,428 @@ +/* -*- 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 <view/SlsInsertAnimator.hxx> +#include <controller/SlideSorterController.hxx> +#include <controller/SlsAnimationFunction.hxx> +#include <view/SlideSorterView.hxx> +#include <view/SlsLayouter.hxx> +#include <model/SlideSorterModel.hxx> +#include <SlideSorter.hxx> +#include <Window.hxx> +#include <osl/diagnose.h> + +#include <memory> +#include <set> + +namespace sd::slidesorter::view { + +namespace { + +class PageObjectRun; + +class AnimatorAccess +{ +public: + virtual void AddRun (const std::shared_ptr<PageObjectRun>& rRun) = 0; + virtual void RemoveRun (const std::shared_ptr<PageObjectRun>& rRun) = 0; + virtual model::SlideSorterModel& GetModel () const = 0; + virtual view::SlideSorterView& GetView () const = 0; + virtual std::shared_ptr<controller::Animator> GetAnimator () = 0; + virtual VclPtr<sd::Window> GetContentWindow () = 0; + +protected: + ~AnimatorAccess() COVERITY_NOEXCEPT_FALSE {} +}; + +/** Controller of the position offsets of all page objects in one row or one + column. +*/ +class PageObjectRun : public std::enable_shared_from_this<PageObjectRun> +{ +public: + PageObjectRun ( + AnimatorAccess& rAnimatorAccess, + const sal_Int32 nRunIndex, + const sal_Int32 nStartIndex, + const sal_Int32 nEndIndex); + + void operator () (const double nTime); + + void UpdateOffsets( + const InsertPosition& rInsertPosition, + const view::Layouter& GetLayouter); + void ResetOffsets (const controller::Animator::AnimationMode eMode); + + /// Index of the row or column that this run represents. + sal_Int32 mnRunIndex; + /// The index at which to make place for the insertion indicator (-1 for + /// no indicator). + sal_Int32 mnLocalInsertIndex; + /// Index of the first page in the run. + sal_Int32 mnStartIndex; + /// Index of the last page in the run. + sal_Int32 mnEndIndex; + /// Offset of each item in the run at the start of the current animation. + ::std::vector<Point> maStartOffset; + /// Target offset of each item in the run at the end of the current animation. + ::std::vector<Point> maEndOffset; + /// Time at which the current animation started. + double mnStartTime; + + class Comparator + { + public: bool operator() ( + const std::shared_ptr<PageObjectRun>& rpRunA, + const std::shared_ptr<PageObjectRun>& rpRunB) const + { + return rpRunA->mnRunIndex < rpRunB->mnRunIndex; + } + }; +private: + controller::Animator::AnimationId mnAnimationId; + AnimatorAccess& mrAnimatorAccess; + ::std::function<double (double)> maAccelerationFunction; + + void RestartAnimation(); +}; +typedef std::shared_ptr<PageObjectRun> SharedPageObjectRun; + +Point Blend (const Point& rPointA, const Point& rPointB, const double nT) +{ + return Point( + sal_Int32(rPointA.X() * (1-nT) + rPointB.X() * nT), + sal_Int32(rPointA.Y() * (1-nT) + rPointB.Y() * nT)); +} + +} // end of anonymous namespace + +class InsertAnimator::Implementation : public AnimatorAccess +{ +public: + explicit Implementation (SlideSorter& rSlideSorter); + virtual ~Implementation(); + + void SetInsertPosition ( + const InsertPosition& rInsertPosition, + const controller::Animator::AnimationMode eAnimationMode); + + virtual void AddRun (const std::shared_ptr<PageObjectRun>& rRun) override; + virtual void RemoveRun (const std::shared_ptr<PageObjectRun>& rRun) override; + + virtual model::SlideSorterModel& GetModel() const override { return mrModel; } + virtual view::SlideSorterView& GetView() const override { return mrView; } + virtual std::shared_ptr<controller::Animator> GetAnimator() override { return mpAnimator; } + virtual VclPtr<sd::Window> GetContentWindow() override { return mrSlideSorter.GetContentWindow(); } + +private: + model::SlideSorterModel& mrModel; + view::SlideSorterView& mrView; + SlideSorter& mrSlideSorter; + std::shared_ptr<controller::Animator> mpAnimator; + typedef ::std::set<SharedPageObjectRun, PageObjectRun::Comparator> RunContainer; + RunContainer maRuns; + InsertPosition maInsertPosition; + + SharedPageObjectRun GetRun ( + view::Layouter const & rLayouter, + const InsertPosition& rInsertPosition); + RunContainer::const_iterator FindRun (const sal_Int32 nRunIndex) const; +}; + +//===== InsertAnimator ======================================================== + +InsertAnimator::InsertAnimator (SlideSorter& rSlideSorter) + : mpImplementation(std::make_shared<Implementation>(rSlideSorter)) +{ +} + +void InsertAnimator::SetInsertPosition (const InsertPosition& rInsertPosition) +{ + mpImplementation->SetInsertPosition(rInsertPosition, controller::Animator::AM_Animated); +} + +void InsertAnimator::Reset (const controller::Animator::AnimationMode eMode) +{ + mpImplementation->SetInsertPosition(InsertPosition(), eMode); +} + +//===== InsertAnimator::Implementation ======================================== + +InsertAnimator::Implementation::Implementation (SlideSorter& rSlideSorter) + : mrModel(rSlideSorter.GetModel()), + mrView(rSlideSorter.GetView()), + mrSlideSorter(rSlideSorter), + mpAnimator(rSlideSorter.GetController().GetAnimator()) +{ +} + +InsertAnimator::Implementation::~Implementation() +{ + SetInsertPosition(InsertPosition(), controller::Animator::AM_Immediate); +} + +void InsertAnimator::Implementation::SetInsertPosition ( + const InsertPosition& rInsertPosition, + const controller::Animator::AnimationMode eMode) +{ + if (maInsertPosition == rInsertPosition) + return; + + SharedPageObjectRun pOldRun (GetRun(mrView.GetLayouter(), maInsertPosition)); + SharedPageObjectRun pCurrentRun (GetRun(mrView.GetLayouter(), rInsertPosition)); + maInsertPosition = rInsertPosition; + + // When the new insert position is in a different run then move the page + // objects in the old run to their default positions. + if (pOldRun != pCurrentRun && pOldRun) + pOldRun->ResetOffsets(eMode); + + if (pCurrentRun) + { + pCurrentRun->UpdateOffsets(rInsertPosition, mrView.GetLayouter()); + } +} + +SharedPageObjectRun InsertAnimator::Implementation::GetRun ( + view::Layouter const & rLayouter, + const InsertPosition& rInsertPosition) +{ + const sal_Int32 nRow (rInsertPosition.GetRow()); + if (nRow < 0) + return SharedPageObjectRun(); + + RunContainer::const_iterator iRun (maRuns.end()); + if (rLayouter.GetColumnCount() == 1) + { + // There is only one run that contains all slides. + if (maRuns.empty()) + maRuns.insert(std::make_shared<PageObjectRun>( + *this, + 0, + 0, + mrModel.GetPageCount()-1)); + iRun = maRuns.begin(); + } + else + { + iRun = FindRun(nRow); + if (iRun == maRuns.end()) + { + // Create a new run. + const sal_Int32 nStartIndex (rLayouter.GetIndex(nRow, 0)); + const sal_Int32 nEndIndex (rLayouter.GetIndex(nRow, rLayouter.GetColumnCount()-1)); + if (nStartIndex <= nEndIndex) + { + iRun = maRuns.insert(std::make_shared<PageObjectRun>( + *this, + nRow, + nStartIndex, + nEndIndex)).first; + OSL_ASSERT(iRun != maRuns.end()); + } + } + } + + if (iRun != maRuns.end()) + return *iRun; + else + return SharedPageObjectRun(); +} + +InsertAnimator::Implementation::RunContainer::const_iterator + InsertAnimator::Implementation::FindRun (const sal_Int32 nRunIndex) const +{ + return std::find_if( + maRuns.begin(), + maRuns.end(), + [nRunIndex] (std::shared_ptr<PageObjectRun> const& rRun) + { return rRun->mnRunIndex == nRunIndex; }); +} + +void InsertAnimator::Implementation::AddRun (const std::shared_ptr<PageObjectRun>& rRun) +{ + if (rRun) + { + maRuns.insert(rRun); + } + else + { + OSL_ASSERT(rRun); + } +} + +void InsertAnimator::Implementation::RemoveRun (const std::shared_ptr<PageObjectRun>& rRun) +{ + if (rRun) + { + // Do not remove runs that show the space for the insertion indicator. + if (rRun->mnLocalInsertIndex == -1) + { + InsertAnimator::Implementation::RunContainer::const_iterator iRun (FindRun(rRun->mnRunIndex)); + if (iRun != maRuns.end()) + { + OSL_ASSERT(*iRun == rRun); + maRuns.erase(iRun); + } + } + } + else + { + OSL_ASSERT(rRun); + } +} + +//===== PageObjectRun ========================================================= + +PageObjectRun::PageObjectRun ( + AnimatorAccess& rAnimatorAccess, + const sal_Int32 nRunIndex, + const sal_Int32 nStartIndex, + const sal_Int32 nEndIndex) + : mnRunIndex(nRunIndex), + mnLocalInsertIndex(-1), + mnStartIndex(nStartIndex), + mnEndIndex(nEndIndex), + mnStartTime(-1), + mnAnimationId(controller::Animator::NotAnAnimationId), + mrAnimatorAccess(rAnimatorAccess), + maAccelerationFunction( + controller::AnimationParametricFunction( + controller::AnimationBezierFunction (0.1,0.7))) +{ + maStartOffset.resize(nEndIndex - nStartIndex + 1); + maEndOffset.resize(nEndIndex - nStartIndex + 1); +} + +void PageObjectRun::UpdateOffsets( + const InsertPosition& rInsertPosition, + const view::Layouter& rLayouter) +{ + const bool bIsVertical (rLayouter.GetColumnCount()==1); + const sal_Int32 nLocalInsertIndex(bIsVertical + ? rInsertPosition.GetRow() + : rInsertPosition.GetColumn()); + if (nLocalInsertIndex == mnLocalInsertIndex) + return; + + mnLocalInsertIndex = nLocalInsertIndex; + + model::SlideSorterModel& rModel (mrAnimatorAccess.GetModel()); + const sal_Int32 nRunLength (mnEndIndex - mnStartIndex + 1); + for (sal_Int32 nIndex=0; nIndex<nRunLength; ++nIndex) + { + model::SharedPageDescriptor pDescriptor(rModel.GetPageDescriptor(nIndex+mnStartIndex)); + if (pDescriptor) + maStartOffset[nIndex] = pDescriptor->GetVisualState().GetLocationOffset(); + maEndOffset[nIndex] = nIndex < mnLocalInsertIndex + ? rInsertPosition.GetLeadingOffset() + : rInsertPosition.GetTrailingOffset(); + if (bIsVertical) + maEndOffset[nIndex].setX( 0 ); + else + maEndOffset[nIndex].setY( 0 ); + } + RestartAnimation(); +} + +void PageObjectRun::ResetOffsets (const controller::Animator::AnimationMode eMode) +{ + mnLocalInsertIndex = -1; + const sal_Int32 nRunLength (mnEndIndex - mnStartIndex + 1); + model::SlideSorterModel& rModel (mrAnimatorAccess.GetModel()); + view::SlideSorterView& rView (mrAnimatorAccess.GetView()); + for (sal_Int32 nIndex=0; nIndex<nRunLength; ++nIndex) + { + model::SharedPageDescriptor pDescriptor(rModel.GetPageDescriptor(nIndex+mnStartIndex)); + if (pDescriptor) + { + if (eMode == controller::Animator::AM_Animated) + maStartOffset[nIndex] = pDescriptor->GetVisualState().GetLocationOffset(); + else + { + const ::tools::Rectangle aOldBoundingBox (pDescriptor->GetBoundingBox()); + pDescriptor->GetVisualState().SetLocationOffset(Point(0,0)); + rView.RequestRepaint(aOldBoundingBox); + rView.RequestRepaint(pDescriptor); + } + } + maEndOffset[nIndex] = Point(0,0); + } + if (eMode == controller::Animator::AM_Animated) + RestartAnimation(); + else + mrAnimatorAccess.RemoveRun(shared_from_this()); +} + +void PageObjectRun::RestartAnimation() +{ + // Stop the current animation. + if (mnAnimationId != controller::Animator::NotAnAnimationId) + { + mrAnimatorAccess.GetAnimator()->RemoveAnimation(mnAnimationId); + } + + // Restart the animation. + mrAnimatorAccess.AddRun(shared_from_this()); + auto sharedThis(shared_from_this()); + mnAnimationId = mrAnimatorAccess.GetAnimator()->AddAnimation( + [this] (double const val) { (*this)(val); }, + [sharedThis] () { sharedThis->mrAnimatorAccess.RemoveRun(sharedThis); } + ); +} + +void PageObjectRun::operator () (const double nGlobalTime) +{ + if (mnStartTime < 0) + mnStartTime = nGlobalTime; + + double nLocalTime (nGlobalTime - mnStartTime); + if (nLocalTime > 1.0) + nLocalTime = 1.0; + nLocalTime = maAccelerationFunction(nLocalTime); + + model::SlideSorterModel& rModel (mrAnimatorAccess.GetModel()); + view::SlideSorterView& rView (mrAnimatorAccess.GetView()); + for (sal_Int32 nIndex=mnStartIndex; nIndex<=mnEndIndex; ++nIndex) + { + model::SharedPageDescriptor pDescriptor (rModel.GetPageDescriptor(nIndex)); + if ( ! pDescriptor) + continue; + const ::tools::Rectangle aOldBoundingBox (pDescriptor->GetBoundingBox()); + pDescriptor->GetVisualState().SetLocationOffset( + Blend( + maStartOffset[nIndex-mnStartIndex], + maEndOffset[nIndex-mnStartIndex], + nLocalTime)); + + // Request a repaint of the old and new bounding box (which largely overlap.) + rView.RequestRepaint(aOldBoundingBox); + rView.RequestRepaint(pDescriptor); + } + + // Call Flush to make + // a) animations a bit more smooth and + // b) on Mac without the Flush a Reset of the page locations is not properly + // visualized when the mouse leaves the window during drag-and-drop. + mrAnimatorAccess.GetContentWindow()->GetOutDev()->Flush(); +} + +} // end of namespace ::sd::slidesorter::view + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/view/SlsInsertionIndicatorOverlay.cxx b/sd/source/ui/slidesorter/view/SlsInsertionIndicatorOverlay.cxx new file mode 100644 index 000000000..c1eb0ea90 --- /dev/null +++ b/sd/source/ui/slidesorter/view/SlsInsertionIndicatorOverlay.cxx @@ -0,0 +1,360 @@ +/* -*- 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 <view/SlsInsertionIndicatorOverlay.hxx> + +#include <SlideSorter.hxx> +#include <view/SlideSorterView.hxx> +#include <view/SlsLayouter.hxx> +#include <view/SlsPageObjectLayouter.hxx> +#include <view/SlsTheme.hxx> +#include "SlsFramePainter.hxx" +#include "SlsLayeredDevice.hxx" +#include <DrawDocShell.hxx> +#include <drawdoc.hxx> +#include <Window.hxx> + +#include <o3tl/safeint.hxx> +#include <rtl/math.hxx> +#include <vcl/virdev.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> + +namespace { + +const double gnPreviewOffsetScale = 1.0 / 8.0; + +::tools::Rectangle GrowRectangle (const ::tools::Rectangle& rBox, const sal_Int32 nOffset) +{ + return ::tools::Rectangle ( + rBox.Left() - nOffset, + rBox.Top() - nOffset, + rBox.Right() + nOffset, + rBox.Bottom() + nOffset); +} + +sal_Int32 RoundToInt (const double nValue) { return sal_Int32(::rtl::math::round(nValue)); } + +} // end of anonymous namespace + +namespace sd::slidesorter::view { + +//===== InsertionIndicatorOverlay =========================================== + +const sal_Int32 gnShadowBorder = 3; +const sal_Int32 gnLayerIndex = 2; + +InsertionIndicatorOverlay::InsertionIndicatorOverlay (SlideSorter& rSlideSorter) + : mrSlideSorter(rSlideSorter), + mbIsVisible(false), + mpShadowPainter( + new FramePainter(mrSlideSorter.GetTheme()->GetIcon(Theme::Icon_RawInsertShadow))) +{ +} + +InsertionIndicatorOverlay::~InsertionIndicatorOverlay() +{ + // cid#1491947 silence Uncaught exception + suppress_fun_call_w_exception(Hide()); +} + +void InsertionIndicatorOverlay::Create (const SdTransferable* pTransferable) +{ + if (pTransferable == nullptr) + return; + + std::shared_ptr<controller::TransferableData> pData ( + controller::TransferableData::GetFromTransferable(pTransferable)); + if ( ! pData) + return; + sal_Int32 nSelectionCount (0); + if (pTransferable->HasPageBookmarks()) + nSelectionCount = pTransferable->GetPageBookmarks().size(); + else + { + DrawDocShell* pDataDocShell = dynamic_cast<DrawDocShell*>(pTransferable->GetDocShell().get()); + if (pDataDocShell != nullptr) + { + SdDrawDocument* pDataDocument = pDataDocShell->GetDoc(); + if (pDataDocument != nullptr) + nSelectionCount = pDataDocument->GetSdPageCount(PageKind::Standard); + } + } + Create(pData->GetRepresentatives(), nSelectionCount); +} + +void InsertionIndicatorOverlay::Create ( + const ::std::vector<controller::TransferableData::Representative>& rRepresentatives, + const sal_Int32 nSelectionCount) +{ + view::Layouter& rLayouter (mrSlideSorter.GetView().GetLayouter()); + const std::shared_ptr<view::PageObjectLayouter>& pPageObjectLayouter ( + rLayouter.GetPageObjectLayouter()); + std::shared_ptr<view::Theme> pTheme (mrSlideSorter.GetTheme()); + const Size aOriginalPreviewSize (pPageObjectLayouter->GetPreviewSize()); + + const double nPreviewScale (0.5); + const Size aPreviewSize ( + RoundToInt(aOriginalPreviewSize.Width()*nPreviewScale), + RoundToInt(aOriginalPreviewSize.Height()*nPreviewScale)); + const sal_Int32 nOffset ( + RoundToInt(std::min(aPreviewSize.Width(),aPreviewSize.Height()) * gnPreviewOffsetScale)); + + // Determine size and offset depending on the number of previews. + sal_Int32 nCount (rRepresentatives.size()); + if (nCount > 0) + --nCount; + Size aIconSize( + aPreviewSize.Width() + 2 * gnShadowBorder + nCount*nOffset, + aPreviewSize.Height() + 2 * gnShadowBorder + nCount*nOffset); + + // Create virtual devices for bitmap and mask whose bitmaps later be + // combined to form the BitmapEx of the icon. + ScopedVclPtrInstance<VirtualDevice> pContent( + *mrSlideSorter.GetContentWindow()->GetOutDev(), DeviceFormat::DEFAULT, DeviceFormat::DEFAULT); + pContent->SetOutputSizePixel(aIconSize); + + pContent->SetFillColor(); + pContent->SetLineColor(pTheme->GetColor(Theme::Color_PreviewBorder)); + const Point aOffset = PaintRepresentatives(*pContent, aPreviewSize, nOffset, rRepresentatives); + + PaintPageCount(*pContent, nSelectionCount, aPreviewSize, aOffset); + + maIcon = pContent->GetBitmapEx(Point(0,0), aIconSize); + maIcon.Scale(aIconSize); +} + +Point InsertionIndicatorOverlay::PaintRepresentatives ( + OutputDevice& rContent, + const Size& rPreviewSize, + const sal_Int32 nOffset, + const ::std::vector<controller::TransferableData::Representative>& rRepresentatives) const +{ + const Point aOffset (0,rRepresentatives.size()==1 ? -nOffset : 0); + + // Paint the pages. + Point aPageOffset (0,0); + double nTransparency (0); + const BitmapEx aExclusionOverlay (mrSlideSorter.GetTheme()->GetIcon(Theme::Icon_HideSlideOverlay)); + for (sal_Int32 nIndex=2; nIndex>=0; --nIndex) + { + if (rRepresentatives.size() <= o3tl::make_unsigned(nIndex)) + continue; + switch(nIndex) + { + case 0 : + aPageOffset = Point(0, nOffset); + nTransparency = 0.85; + break; + case 1: + aPageOffset = Point(nOffset, 0); + nTransparency = 0.75; + break; + case 2: + aPageOffset = Point(2*nOffset, 2*nOffset); + nTransparency = 0.65; + break; + } + aPageOffset += aOffset; + aPageOffset.AdjustX(gnShadowBorder ); + aPageOffset.AdjustY(gnShadowBorder ); + + // Paint the preview. + BitmapEx aPreview (rRepresentatives[nIndex].maBitmap); + aPreview.Scale(rPreviewSize, BmpScaleFlag::BestQuality); + rContent.DrawBitmapEx(aPageOffset, aPreview); + + // When the page is marked as excluded from the slide show then + // paint an overlay that visualizes this. + if (rRepresentatives[nIndex].mbIsExcluded) + { + const vcl::Region aSavedClipRegion (rContent.GetClipRegion()); + rContent.IntersectClipRegion(::tools::Rectangle(aPageOffset, rPreviewSize)); + // Paint bitmap tiled over the preview to mark it as excluded. + const sal_Int32 nIconWidth (aExclusionOverlay.GetSizePixel().Width()); + const sal_Int32 nIconHeight (aExclusionOverlay.GetSizePixel().Height()); + if (nIconWidth>0 && nIconHeight>0) + { + for (::tools::Long nX=0; nX<rPreviewSize.Width(); nX+=nIconWidth) + for (::tools::Long nY=0; nY<rPreviewSize.Height(); nY+=nIconHeight) + rContent.DrawBitmapEx(Point(nX,nY)+aPageOffset, aExclusionOverlay); + } + rContent.SetClipRegion(aSavedClipRegion); + } + + // Tone down the bitmap. The further back the darker it becomes. + ::tools::Rectangle aBox ( + aPageOffset.X(), + aPageOffset.Y(), + aPageOffset.X()+rPreviewSize.Width()-1, + aPageOffset.Y()+rPreviewSize.Height()-1); + rContent.SetFillColor(COL_BLACK); + rContent.SetLineColor(); + rContent.DrawTransparent( + basegfx::B2DHomMatrix(), + ::basegfx::B2DPolyPolygon(::basegfx::utils::createPolygonFromRect( + ::basegfx::B2DRectangle(aBox.Left(), aBox.Top(), aBox.Right()+1, aBox.Bottom()+1), + 0, + 0)), + nTransparency); + + // Draw border around preview. + ::tools::Rectangle aBorderBox (GrowRectangle(aBox, 1)); + rContent.SetLineColor(COL_GRAY); + rContent.SetFillColor(); + rContent.DrawRect(aBorderBox); + + // Draw shadow around preview. + mpShadowPainter->PaintFrame(rContent, aBorderBox); + } + + return aPageOffset; +} + +void InsertionIndicatorOverlay::PaintPageCount ( + OutputDevice& rDevice, + const sal_Int32 nSelectionCount, + const Size& rPreviewSize, + const Point& rFirstPageOffset) const +{ + // Paint the number of slides. + std::shared_ptr<view::Theme> pTheme (mrSlideSorter.GetTheme()); + std::shared_ptr<vcl::Font> pFont(Theme::GetFont(Theme::Font_PageCount, rDevice)); + if (!pFont) + return; + + OUString sNumber (OUString::number(nSelectionCount)); + + // Determine the size of the (painted) text and create a bounding + // box that centers the text on the first preview. + rDevice.SetFont(*pFont); + ::tools::Rectangle aTextBox; + rDevice.GetTextBoundRect(aTextBox, sNumber); + Point aTextOffset (aTextBox.TopLeft()); + Size aTextSize (aTextBox.GetSize()); + // Place text inside the first page preview. + Point aTextLocation(rFirstPageOffset); + // Center the text. + aTextLocation += Point( + (rPreviewSize.Width()-aTextBox.GetWidth())/2, + (rPreviewSize.Height()-aTextBox.GetHeight())/2); + aTextBox = ::tools::Rectangle(aTextLocation, aTextSize); + + // Paint background, border and text. + static const sal_Int32 nBorder = 5; + rDevice.SetFillColor(pTheme->GetColor(Theme::Color_Selection)); + rDevice.SetLineColor(pTheme->GetColor(Theme::Color_Selection)); + rDevice.DrawRect(GrowRectangle(aTextBox, nBorder)); + + rDevice.SetFillColor(); + rDevice.SetLineColor(pTheme->GetColor(Theme::Color_PageCountFontColor)); + rDevice.DrawRect(GrowRectangle(aTextBox, nBorder-1)); + + rDevice.SetTextColor(pTheme->GetColor(Theme::Color_PageCountFontColor)); + rDevice.DrawText(aTextBox.TopLeft()-aTextOffset, sNumber); +} + +void InsertionIndicatorOverlay::SetLocation (const Point& rLocation) +{ + const Point aTopLeft ( + rLocation - Point( + maIcon.GetSizePixel().Width()/2, + maIcon.GetSizePixel().Height()/2)); + if (maLocation != aTopLeft) + { + const ::tools::Rectangle aOldBoundingBox (GetBoundingBox()); + + maLocation = aTopLeft; + + if (mpLayerInvalidator && IsVisible()) + { + mpLayerInvalidator->Invalidate(aOldBoundingBox); + mpLayerInvalidator->Invalidate(GetBoundingBox()); + } + } +} + +void InsertionIndicatorOverlay::Paint ( + OutputDevice& rDevice, + const ::tools::Rectangle&) +{ + if ( ! IsVisible()) + return; + + rDevice.DrawImage(maLocation, Image(maIcon)); +} + +void InsertionIndicatorOverlay::SetLayerInvalidator (const SharedILayerInvalidator& rpInvalidator) +{ + mpLayerInvalidator = rpInvalidator; + + if (mbIsVisible && mpLayerInvalidator) + mpLayerInvalidator->Invalidate(GetBoundingBox()); +} + +void InsertionIndicatorOverlay::Show() +{ + if ( mbIsVisible) + return; + + mbIsVisible = true; + + std::shared_ptr<LayeredDevice> pLayeredDevice ( + mrSlideSorter.GetView().GetLayeredDevice()); + if (pLayeredDevice) + { + pLayeredDevice->RegisterPainter(shared_from_this(), gnLayerIndex); + if (mpLayerInvalidator) + mpLayerInvalidator->Invalidate(GetBoundingBox()); + } +} + +void InsertionIndicatorOverlay::Hide() +{ + if (!mbIsVisible) + return; + + mbIsVisible = false; + + std::shared_ptr<LayeredDevice> pLayeredDevice ( + mrSlideSorter.GetView().GetLayeredDevice()); + if (pLayeredDevice) + { + if (mpLayerInvalidator) + mpLayerInvalidator->Invalidate(GetBoundingBox()); + pLayeredDevice->RemovePainter(shared_from_this(), gnLayerIndex); + } +} + +::tools::Rectangle InsertionIndicatorOverlay::GetBoundingBox() const +{ + return ::tools::Rectangle(maLocation, maIcon.GetSizePixel()); +} + +Size InsertionIndicatorOverlay::GetSize() const +{ + return Size( + maIcon.GetSizePixel().Width() + 10, + maIcon.GetSizePixel().Height() + 10); +} + +} // end of namespace ::sd::slidesorter::view + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/view/SlsLayeredDevice.cxx b/sd/source/ui/slidesorter/view/SlsLayeredDevice.cxx new file mode 100644 index 000000000..b41bbe307 --- /dev/null +++ b/sd/source/ui/slidesorter/view/SlsLayeredDevice.cxx @@ -0,0 +1,491 @@ +/* -*- 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 "SlsLayeredDevice.hxx" +#include <Window.hxx> + +#include <vcl/virdev.hxx> +#include <sal/log.hxx> +#include <o3tl/safeint.hxx> +#include <osl/diagnose.h> + +#include <tools/gen.hxx> +#include <tools/fract.hxx> + +#include <functional> + +namespace sd::slidesorter::view { + +namespace { +const sal_Int32 gnMaximumLayerCount = 8; + +class LayerInvalidator : public ILayerInvalidator +{ +public: + LayerInvalidator ( + const std::shared_ptr<LayeredDevice>& rpLayeredDevice, + sd::Window *pTargetWindow, + const int nLayer) + : mpLayeredDevice(rpLayeredDevice), + mpTargetWindow(pTargetWindow), + mnLayer(nLayer) + { + } + + virtual void Invalidate (const ::tools::Rectangle& rInvalidationBox) override + { + mpLayeredDevice->Invalidate(rInvalidationBox, mnLayer); + mpTargetWindow->Invalidate(rInvalidationBox); + } + +private: + const std::shared_ptr<LayeredDevice> mpLayeredDevice; + VclPtr<sd::Window> mpTargetWindow; + const int mnLayer; +}; + +void DeviceCopy ( + vcl::RenderContext& rTargetDevice, + vcl::RenderContext const & rSourceDevice, + const ::tools::Rectangle& rBox) +{ + rTargetDevice.DrawOutDev( + rBox.TopLeft(), + rBox.GetSize(), + rBox.TopLeft(), + rBox.GetSize(), + rSourceDevice); +} + +void ForAllRectangles (const vcl::Region& rRegion, const std::function<void (const ::tools::Rectangle&)>& aFunction) +{ + OSL_ASSERT(aFunction); + RectangleVector aRectangles; + rRegion.GetRegionRectangles(aRectangles); + + if(aRectangles.empty()) + { + aFunction(::tools::Rectangle()); + } + else + { + for(const auto& rRect : aRectangles) + { + aFunction(rRect); + } + + //Region aMutableRegionCopy (rRegion); + //RegionHandle aHandle(aMutableRegionCopy.BeginEnumRects()); + //Rectangle aBox; + //while (aMutableRegionCopy.GetEnumRects(aHandle, aBox)) + // aFunction(aBox); + //aMutableRegionCopy.EndEnumRects(aHandle); + } +} + +class Layer +{ +public: + Layer(); + Layer(const Layer&) = delete; + Layer& operator=(const Layer&) = delete; + + void Initialize (sd::Window *pTargetWindow); + void InvalidateRectangle (const ::tools::Rectangle& rInvalidationBox); + void InvalidateRegion (const vcl::Region& rInvalidationRegion); + void Validate (const MapMode& rMapMode); + void Repaint ( + OutputDevice& rTargetDevice, + const ::tools::Rectangle& rRepaintRectangle); + void Resize (const Size& rSize); + void AddPainter (const SharedILayerPainter& rpPainter); + void RemovePainter (const SharedILayerPainter& rpPainter); + bool HasPainter() const; + void Dispose(); + +private: + ScopedVclPtr<VirtualDevice> mpLayerDevice; + ::std::vector<SharedILayerPainter> maPainters; + vcl::Region maInvalidationRegion; + + void ValidateRectangle (const ::tools::Rectangle& rBox); +}; +typedef std::shared_ptr<Layer> SharedLayer; + +} // end of anonymous namespace + +class LayeredDevice::LayerContainer +{ +public: + LayerContainer() {} + + bool empty() const { return mvLayers.empty(); } + + size_t size() const { return mvLayers.size(); } + + const SharedLayer& back() const { return mvLayers.back(); } + + ::std::vector<SharedLayer>::const_iterator begin() const { return mvLayers.begin(); } + ::std::vector<SharedLayer>::const_iterator end() const { return mvLayers.end(); } + + void clear() { mvLayers.clear(); } + + void pop_back() { mvLayers.pop_back(); } + + void resize(size_t n) { mvLayers.resize(n); } + + SharedLayer& operator[](size_t i) { return mvLayers[i]; } + +private: + ::std::vector<SharedLayer> mvLayers; +}; + +//===== LayeredDevice ========================================================= + +LayeredDevice::LayeredDevice (const VclPtr<sd::Window>& pTargetWindow) + : mpTargetWindow(pTargetWindow), + mpLayers(new LayerContainer()), + mpBackBuffer(VclPtr<VirtualDevice>::Create(*mpTargetWindow->GetOutDev())), + maSavedMapMode(pTargetWindow->GetMapMode()) +{ + mpBackBuffer->SetOutputSizePixel(mpTargetWindow->GetSizePixel()); +} + +LayeredDevice::~LayeredDevice() +{ +} + +void LayeredDevice::Invalidate ( + const ::tools::Rectangle& rInvalidationArea, + const sal_Int32 nLayer) +{ + if (nLayer<0 || o3tl::make_unsigned(nLayer)>=mpLayers->size()) + { + OSL_ASSERT(nLayer>=0 && o3tl::make_unsigned(nLayer)<mpLayers->size()); + return; + } + + (*mpLayers)[nLayer]->InvalidateRectangle(rInvalidationArea); +} + +void LayeredDevice::InvalidateAllLayers (const ::tools::Rectangle& rInvalidationArea) +{ + for (size_t nLayer=0; nLayer<mpLayers->size(); ++nLayer) + (*mpLayers)[nLayer]->InvalidateRectangle(rInvalidationArea); +} + +void LayeredDevice::InvalidateAllLayers (const vcl::Region& rInvalidationRegion) +{ + for (size_t nLayer=0; nLayer<mpLayers->size(); ++nLayer) + (*mpLayers)[nLayer]->InvalidateRegion(rInvalidationRegion); +} + +void LayeredDevice::RegisterPainter ( + const SharedILayerPainter& rpPainter, + const sal_Int32 nLayer) +{ + OSL_ASSERT(mpLayers); + if ( ! rpPainter) + { + OSL_ASSERT(rpPainter); + return; + } + if (nLayer<0 || nLayer>=gnMaximumLayerCount) + { + OSL_ASSERT(nLayer>=0 && nLayer<gnMaximumLayerCount); + return; + } + + // Provide the layers. + if (o3tl::make_unsigned(nLayer) >= mpLayers->size()) + { + const sal_Int32 nOldLayerCount (mpLayers->size()); + mpLayers->resize(nLayer+1); + + for (size_t nIndex=nOldLayerCount; nIndex<mpLayers->size(); ++nIndex) + (*mpLayers)[nIndex] = std::make_shared<Layer>(); + } + + (*mpLayers)[nLayer]->AddPainter(rpPainter); + if (nLayer == 0) + (*mpLayers)[nLayer]->Initialize(mpTargetWindow); + + rpPainter->SetLayerInvalidator( + std::make_shared<LayerInvalidator>(shared_from_this(),mpTargetWindow,nLayer)); +} + +void LayeredDevice::RemovePainter ( + const SharedILayerPainter& rpPainter, + const sal_Int32 nLayer) +{ + if ( ! rpPainter) + { + OSL_ASSERT(rpPainter); + return; + } + if (nLayer<0 || o3tl::make_unsigned(nLayer)>=mpLayers->size()) + { + OSL_ASSERT(nLayer>=0 && o3tl::make_unsigned(nLayer)<mpLayers->size()); + return; + } + + rpPainter->SetLayerInvalidator(SharedILayerInvalidator()); + + (*mpLayers)[nLayer]->RemovePainter(rpPainter); + + // Remove top most layers that do not contain any painters. + while ( ! mpLayers->empty() && ! mpLayers->back()->HasPainter()) + mpLayers->pop_back(); +} + +void LayeredDevice::Repaint (const vcl::Region& rRepaintRegion) +{ + // Validate the contents of all layers (that have their own devices.) + for (auto const& it : *mpLayers) + { + it->Validate(mpTargetWindow->GetMapMode()); + } + + ForAllRectangles(rRepaintRegion, + [this] (::tools::Rectangle const& r) { this->RepaintRectangle(r); }); +} + +void LayeredDevice::RepaintRectangle (const ::tools::Rectangle& rRepaintRectangle) +{ + if (mpLayers->empty()) + return; + else if (mpLayers->size() == 1) + { + // Just copy the main layer into the target device. + (*mpLayers)[0]->Repaint(*mpTargetWindow->GetOutDev(), rRepaintRectangle); + } + else + { + // Paint all layers first into the back buffer (to avoid flickering + // due to synchronous paints) and then copy that into the target + // device. + mpBackBuffer->SetMapMode(mpTargetWindow->GetMapMode()); + for (auto const& it : *mpLayers) + { + it->Repaint(*mpBackBuffer, rRepaintRectangle); + } + DeviceCopy(*mpTargetWindow->GetOutDev(), *mpBackBuffer, rRepaintRectangle); + } +} + +void LayeredDevice::Resize() +{ + const Size aSize (mpTargetWindow->GetSizePixel()); + mpBackBuffer->SetOutputSizePixel(aSize); + for (auto const& it : *mpLayers) + { + it->Resize(aSize); + } +} + +void LayeredDevice::Dispose() +{ + for (auto const& it : *mpLayers) + { + it->Dispose(); + } + mpLayers->clear(); +} + +bool LayeredDevice::HandleMapModeChange() +{ + const MapMode& rMapMode (mpTargetWindow->GetMapMode()); + if (maSavedMapMode == rMapMode) + return false; + + const ::tools::Rectangle aLogicWindowBox ( + mpTargetWindow->PixelToLogic(::tools::Rectangle(Point(0,0), mpTargetWindow->GetSizePixel()))); + if (maSavedMapMode.GetScaleX() != rMapMode.GetScaleX() + || maSavedMapMode.GetScaleY() != rMapMode.GetScaleY() + || maSavedMapMode.GetMapUnit() != rMapMode.GetMapUnit()) + { + // When the scale has changed then we have to paint everything. + InvalidateAllLayers(aLogicWindowBox); + } + else if (maSavedMapMode.GetOrigin() != rMapMode.GetOrigin()) + { + // Window has been scrolled. Adapt contents of backbuffers and + // layer devices. + const Point aDelta (rMapMode.GetOrigin() - maSavedMapMode.GetOrigin()); + mpBackBuffer->CopyArea( + aLogicWindowBox.TopLeft(), + mpTargetWindow->PixelToLogic(Point(0,0), maSavedMapMode), + aLogicWindowBox.GetSize()); + + // Invalidate the area(s) that have been exposed. + const ::tools::Rectangle aWindowBox (Point(0,0), mpTargetWindow->GetSizePixel()); + if (aDelta.Y() < 0) + InvalidateAllLayers(mpTargetWindow->PixelToLogic(::tools::Rectangle( + aWindowBox.Left(), + aWindowBox.Bottom()+aDelta.Y(), + aWindowBox.Right(), + aWindowBox.Bottom()))); + else if (aDelta.Y() > 0) + InvalidateAllLayers(mpTargetWindow->PixelToLogic(::tools::Rectangle( + aWindowBox.Left(), + aWindowBox.Top(), + aWindowBox.Right(), + aWindowBox.Top()+aDelta.Y()))); + if (aDelta.X() < 0) + InvalidateAllLayers(mpTargetWindow->PixelToLogic(::tools::Rectangle( + aWindowBox.Right()+aDelta.X(), + aWindowBox.Top(), + aWindowBox.Right(), + aWindowBox.Bottom()))); + else if (aDelta.X() > 0) + InvalidateAllLayers(mpTargetWindow->PixelToLogic(::tools::Rectangle( + aWindowBox.Left(), + aWindowBox.Top(), + aWindowBox.Left()+aDelta.X(), + aWindowBox.Bottom()))); + } + else + { + // Can this happen? Lets trigger a warning when it does. + OSL_ASSERT(false); + } + + maSavedMapMode = rMapMode; + + return true; +} + +//===== Layer ================================================================= + +Layer::Layer() +{ +} + +void Layer::Initialize (sd::Window *pTargetWindow) +{ +#if 0 + (void)pTargetWindow; +#else + if ( ! mpLayerDevice) + { + mpLayerDevice.disposeAndReset(VclPtr<VirtualDevice>::Create(*pTargetWindow->GetOutDev())); + mpLayerDevice->SetOutputSizePixel(pTargetWindow->GetSizePixel()); + } +#endif +} + +void Layer::InvalidateRectangle (const ::tools::Rectangle& rInvalidationBox) +{ + maInvalidationRegion.Union(rInvalidationBox); +} + +void Layer::InvalidateRegion (const vcl::Region& rInvalidationRegion) +{ + maInvalidationRegion.Union(rInvalidationRegion); +} + +void Layer::Validate (const MapMode& rMapMode) +{ + if (mpLayerDevice && ! maInvalidationRegion.IsEmpty()) + { + vcl::Region aRegion (maInvalidationRegion); + maInvalidationRegion.SetEmpty(); + + mpLayerDevice->SetMapMode(rMapMode); + ForAllRectangles( + aRegion, + [this] (::tools::Rectangle const& r) { return this->ValidateRectangle(r); }); + } +} + +void Layer::ValidateRectangle (const ::tools::Rectangle& rBox) +{ + if ( ! mpLayerDevice) + return; + const vcl::Region aSavedClipRegion (mpLayerDevice->GetClipRegion()); + mpLayerDevice->IntersectClipRegion(rBox); + + for (const auto& rxPainter : maPainters) + { + rxPainter->Paint(*mpLayerDevice, rBox); + } + + mpLayerDevice->SetClipRegion(aSavedClipRegion); +} + +void Layer::Repaint ( + OutputDevice& rTargetDevice, + const ::tools::Rectangle& rRepaintRectangle) +{ + if (mpLayerDevice) + { + DeviceCopy(rTargetDevice, *mpLayerDevice, rRepaintRectangle); + } + else + { + for (auto const& it : maPainters) + { + it->Paint(rTargetDevice, rRepaintRectangle); + } + } +} + +void Layer::Resize (const Size& rSize) +{ + if (mpLayerDevice) + { + mpLayerDevice->SetOutputSizePixel(rSize); + maInvalidationRegion = ::tools::Rectangle(Point(0,0), rSize); + } +} + +void Layer::AddPainter (const SharedILayerPainter& rpPainter) +{ + OSL_ASSERT(::std::find(maPainters.begin(), maPainters.end(), rpPainter) == maPainters.end()); + + maPainters.push_back(rpPainter); +} + +void Layer::RemovePainter (const SharedILayerPainter& rpPainter) +{ + const ::std::vector<SharedILayerPainter>::iterator iPainter ( + ::std::find(maPainters.begin(), maPainters.end(), rpPainter)); + if (iPainter != maPainters.end()) + { + maPainters.erase(iPainter); + } + else + { + SAL_WARN("sd", "LayeredDevice::RemovePainter called for painter that is not registered"); + } +} + +bool Layer::HasPainter() const +{ + return !maPainters.empty(); +} + +void Layer::Dispose() +{ + maPainters.clear(); +} + +} // end of namespace ::sd::slidesorter::view + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/view/SlsLayeredDevice.hxx b/sd/source/ui/slidesorter/view/SlsLayeredDevice.hxx new file mode 100644 index 000000000..5ec0d0e9f --- /dev/null +++ b/sd/source/ui/slidesorter/view/SlsLayeredDevice.hxx @@ -0,0 +1,84 @@ +/* -*- 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 <view/SlsILayerPainter.hxx> + +#include <vcl/vclptr.hxx> +#include <vcl/mapmod.hxx> + +#include <memory> + +namespace sd { class Window; } +namespace tools { class Rectangle; } +namespace vcl { class Region; } + +class VirtualDevice; + +namespace sd::slidesorter::view { + +/** A simple wrapper around an OutputDevice that provides support for + independent layers and buffering. + Each layer may contain any number of painters. +*/ +class LayeredDevice + : public std::enable_shared_from_this<LayeredDevice> + +{ +public: + explicit LayeredDevice (const VclPtr<sd::Window>& pTargetWindow); + ~LayeredDevice (); + + void Invalidate ( + const ::tools::Rectangle& rInvalidationBox, + const sal_Int32 nLayer); + void InvalidateAllLayers ( + const ::tools::Rectangle& rInvalidationBox); + void InvalidateAllLayers ( + const vcl::Region& rInvalidationRegion); + + void RegisterPainter ( + const SharedILayerPainter& rPainter, + const sal_Int32 nLayer); + + void RemovePainter ( + const SharedILayerPainter& rPainter, + const sal_Int32 nLayer); + + bool HandleMapModeChange(); + void Repaint (const vcl::Region& rRepaintRegion); + + void Resize(); + + void Dispose(); + +private: + VclPtr<sd::Window> mpTargetWindow; + class LayerContainer; + std::unique_ptr<LayerContainer> mpLayers; + ScopedVclPtr<VirtualDevice> mpBackBuffer; + MapMode maSavedMapMode; + + void RepaintRectangle (const ::tools::Rectangle& rRepaintRectangle); +}; + +} // end of namespace ::sd::slidesorter::view + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/view/SlsLayouter.cxx b/sd/source/ui/slidesorter/view/SlsLayouter.cxx new file mode 100644 index 000000000..21f0be13c --- /dev/null +++ b/sd/source/ui/slidesorter/view/SlsLayouter.cxx @@ -0,0 +1,1225 @@ +/* -*- 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 <view/SlsPageObjectLayouter.hxx> +#include <view/SlsTheme.hxx> +#include <view/SlsLayouter.hxx> +#include <model/SlideSorterModel.hxx> +#include <model/SlsPageDescriptor.hxx> +#include <Window.hxx> +#include <osl/diagnose.h> + +namespace sd::slidesorter::view { + +class Layouter::Implementation +{ +public: + VclPtr<sd::Window> mpWindow; + static const sal_Int32 mnRequestedLeftBorder = 5; + static const sal_Int32 mnRequestedRightBorder = 5; + static const sal_Int32 mnRequestedTopBorder = 5; + static const sal_Int32 mnRequestedBottomBorder = 5; + sal_Int32 mnLeftBorder; + sal_Int32 mnRightBorder; + sal_Int32 mnTopBorder; + sal_Int32 mnBottomBorder; + static const sal_Int32 gnVerticalGap = 10 - 2*Theme_FocusIndicatorWidth; + static const sal_Int32 gnHorizontalGap = 10 - 2*Theme_FocusIndicatorWidth; + Size maMinimalSize; + Size maPreferredSize; + Size maMaximalSize; + sal_Int32 mnMinimalColumnCount; + sal_Int32 mnMaximalColumnCount; + sal_Int32 mnPageCount; + sal_Int32 mnColumnCount; + sal_Int32 mnRowCount; + /// The maximum number of columns. Can only be larger than the current + /// number of columns when there are not enough pages to fill all + /// available columns. + sal_Int32 mnMaxColumnCount; + /// The maximum number of rows. Can only be larger than the current + /// number of rows when there are not enough pages to fill all available + /// rows. + sal_Int32 mnMaxRowCount; + Size maPageObjectSize; + std::shared_ptr<PageObjectLayouter> mpPageObjectLayouter; + std::shared_ptr<view::Theme> mpTheme; + + /** Specify how the gap between two page objects is associated with the + page objects. + */ + enum GapMembership { + GM_NONE, // Gap is not associated with any page object. + GM_PREVIOUS, // The whole gap is associated with the previous page + // object (left or above the gap.) + GM_BOTH, // Half of the gap is associated with previous, half + // with the next page object. + GM_NEXT, // The whole gap is associated with the next page + // object (right or below the gap.) + GM_PAGE_BORDER + }; + + static Implementation* Create ( + const Implementation& rImplementation, + const Layouter::Orientation eOrientation); + + virtual Layouter::Orientation GetOrientation() const = 0; + + bool Rearrange ( + const Size& rWindowSize, + const Size& rPreviewModelSize, + const sal_uInt32 nPageCount); + + /** Calculate the row that the point with the given vertical coordinate + is over. The horizontal component is ignored. + @param nYPosition + Vertical position in model coordinates. + @param bIncludeBordersAndGaps + When this flag is <TRUE/> then the area of borders and gaps are + interpreted as belonging to one of the rows. + @param eGapMembership + Specifies to what row the gap areas belong. Here GM_NONE + corresponds to bIncludeBordersAndGaps being <FALSE/>. When + GM_BOTH is given then the upper half is associated to the row + above and the lower half to the row below. Values of + GM_PREVIOUS and GM_NEXT associate the whole gap area with the + row above or below respectively. + */ + sal_Int32 GetRowAtPosition ( + sal_Int32 nYPosition, + bool bIncludeBordersAndGaps, + GapMembership eGapMembership) const; + + /** Calculate the column that the point with the given horizontal + coordinate is over. The vertical component is ignored. + @param nXPosition + Horizontal position in model coordinates. + @param bIncludeBordersAndGaps + When this flag is <TRUE/> then the area of borders and gaps are + interpreted as belonging to one of the columns. + @param eGapMembership + Specifies to what column the gap areas belong. + */ + sal_Int32 GetColumnAtPosition ( + sal_Int32 nXPosition, + bool bIncludeBordersAndGaps, + GapMembership eGapMembership) const; + + /** This method is typically called from GetRowAtPosition() and + GetColumnAtPosition() to handle a position that lies inside the gap + between two adjacent rows or columns. + @param nDistanceIntoGap + Vertical distance from the bottom of the upper row down into the + gap or horizontal distance from the right edge right into the + gap. + @param eGapMemberhship + This value decides what areas in the gap belong to which (or no) + row or column. + @param nIndex + The row index of the upper row or the column index of the left + column. + @param nGap + Width or height of the gap in model coordinates between the + page borders. + @return + Returns either the index of the upper row (as given as nRow), the + index of the lower row (nRow+1) or -1 to indicate that the + position belongs to no row. + */ + static sal_Int32 ResolvePositionInGap ( + sal_Int32 nDistanceIntoGap, + GapMembership eGapMembership, + sal_Int32 nIndex, + sal_Int32 nGap); + + /** Calculate the logical part of the insert position, i.e. the page + after which to insert. + */ + virtual void CalculateLogicalInsertPosition ( + const Point& rModelPosition, + InsertPosition& rPosition) const = 0; + + /** Calculate the geometrical part of the insert position, i.e. the + location of where to display the insertion indicator and the + distances about which the leading and trailing pages have to be + moved to make room for the indicator. + */ + void CalculateGeometricPosition ( + InsertPosition& rPosition, + const Size& rIndicatorSize, + const bool bIsVertical, + model::SlideSorterModel const & rModel) const; + + /** Return the bounding box of the preview or, when selected, of the page + object. Thus, it returns something like a visual bounding box. + */ + ::tools::Rectangle GetInnerBoundingBox ( + model::SlideSorterModel const & rModel, + const sal_Int32 nIndex) const; + + Range GetValidHorizontalSizeRange() const; + Range GetValidVerticalSizeRange() const; + + Range GetRangeOfVisiblePageObjects (const ::tools::Rectangle& aVisibleArea) const; + sal_Int32 GetIndex ( + const sal_Int32 nRow, + const sal_Int32 nColumn, + const bool bClampToValidRange) const; + + ::tools::Rectangle GetPageObjectBox ( + const sal_Int32 nIndex, + const bool bIncludeBorderAndGap = false) const; + + ::tools::Rectangle GetPageObjectBox ( + const sal_Int32 nRow, + const sal_Int32 nColumn) const; + + ::tools::Rectangle AddBorderAndGap ( + const ::tools::Rectangle& rBoundingBox, + const sal_Int32 nRow, + const sal_Int32 nColumn) const; + + ::tools::Rectangle GetTotalBoundingBox() const; + + virtual ~Implementation(); + +protected: + Implementation ( + sd::Window *pWindow, + const std::shared_ptr<view::Theme>& rpTheme); + explicit Implementation (const Implementation& rImplementation); + + virtual void CalculateRowAndColumnCount (const Size& rWindowSize) = 0; + virtual void CalculateMaxRowAndColumnCount (const Size& rWindowSize) = 0; + virtual Size CalculateTargetSize ( + const Size& rWindowSize) const = 0; + Size GetTargetSize ( + const Size& rWindowSize, + const bool bCalculateWidth, + const bool bCalculateHeight) const; + void CalculateVerticalLogicalInsertPosition ( + const Point& rModelPosition, + InsertPosition& rPosition) const; +}; + +namespace { + +/** The vertical layouter has one column and as many rows as there are + pages. +*/ +class VerticalImplementation : public Layouter::Implementation +{ +public: + explicit VerticalImplementation (const Implementation& rImplementation); + + virtual Layouter::Orientation GetOrientation() const override; + + void CalculateLogicalInsertPosition ( + const Point& rModelPosition, + InsertPosition& rPosition) const override; + +protected: + virtual void CalculateRowAndColumnCount (const Size& rWindowSize) override; + virtual void CalculateMaxRowAndColumnCount (const Size& rWindowSize) override; + virtual Size CalculateTargetSize ( + const Size& rWindowSize) const override; +}; + +/** The horizontal layouter has one row and as many columns as there are + pages. +*/ +class HorizontalImplementation : public Layouter::Implementation +{ +public: + explicit HorizontalImplementation(const Implementation& rImplementation); + + virtual Layouter::Orientation GetOrientation() const override; + + void CalculateLogicalInsertPosition ( + const Point& rModelPosition, + InsertPosition& rPosition) const override; + +protected: + virtual void CalculateRowAndColumnCount (const Size& rWindowSize) override; + virtual void CalculateMaxRowAndColumnCount (const Size& rWindowSize) override; + virtual Size CalculateTargetSize ( + const Size& rWindowSize) const override; +}; + +/** The number of columns of the grid layouter is defined via a control in + the slide sorter tool bar. The number of rows is calculated from the + number of columns and the number of pages. +*/ +class GridImplementation : public Layouter::Implementation +{ +public: + GridImplementation ( + sd::Window *pWindow, + const std::shared_ptr<view::Theme>& rpTheme); + explicit GridImplementation(const Implementation& rImplementation); + + virtual Layouter::Orientation GetOrientation() const override; + + void CalculateLogicalInsertPosition ( + const Point& rModelPosition, + InsertPosition& rPosition) const override; + +protected: + virtual void CalculateRowAndColumnCount (const Size& rWindowSize) override; + virtual void CalculateMaxRowAndColumnCount (const Size& rWindowSize) override; + virtual Size CalculateTargetSize ( + const Size& rWindowSize) const override; +}; + +} + +//===== Layouter ============================================================== + +Layouter::Layouter ( + sd::Window *pWindow, + const std::shared_ptr<Theme>& rpTheme) + : mpImplementation(new GridImplementation(pWindow, rpTheme)), + mpWindow(pWindow) +{ +} + +Layouter::~Layouter() +{ +} + +std::shared_ptr<PageObjectLayouter> const & Layouter::GetPageObjectLayouter() const +{ + return mpImplementation->mpPageObjectLayouter; +} + +void Layouter::SetColumnCount ( + sal_Int32 nMinimalColumnCount, + sal_Int32 nMaximalColumnCount) +{ + if (nMinimalColumnCount <= nMaximalColumnCount) + { + mpImplementation->mnMinimalColumnCount = nMinimalColumnCount; + mpImplementation->mnMaximalColumnCount = nMaximalColumnCount; + } +} + +bool Layouter::Rearrange ( + const Orientation eOrientation, + const Size& rWindowSize, + const Size& rPageSize, + const sal_uInt32 nPageCount) +{ + OSL_ASSERT(mpWindow); + + if (eOrientation != mpImplementation->GetOrientation()) + mpImplementation.reset(Implementation::Create(*mpImplementation, eOrientation)); + + return mpImplementation->Rearrange(rWindowSize, rPageSize, nPageCount); +} + +sal_Int32 Layouter::GetColumnCount() const +{ + return mpImplementation->mnColumnCount; +} + +sal_Int32 Layouter::GetIndex (const sal_Int32 nRow, const sal_Int32 nColumn) const +{ + return mpImplementation->GetIndex(nRow,nColumn,true); +} + +Size const & Layouter::GetPageObjectSize() const +{ + return mpImplementation->maPageObjectSize; +} + +::tools::Rectangle Layouter::GetPageObjectBox ( + const sal_Int32 nIndex, + const bool bIncludeBorderAndGap) const +{ + return mpImplementation->GetPageObjectBox(nIndex, bIncludeBorderAndGap); +} + +::tools::Rectangle Layouter::GetTotalBoundingBox() const +{ + return mpImplementation->GetTotalBoundingBox(); +} + +InsertPosition Layouter::GetInsertPosition ( + const Point& rModelPosition, + const Size& rIndicatorSize, + model::SlideSorterModel const & rModel) const +{ + InsertPosition aPosition; + mpImplementation->CalculateLogicalInsertPosition( + rModelPosition, + aPosition); + mpImplementation->CalculateGeometricPosition( + aPosition, + rIndicatorSize, + GetColumnCount()==1, + rModel); + return aPosition; +} + +Range Layouter::GetValidHorizontalSizeRange() const +{ + return mpImplementation->GetValidHorizontalSizeRange(); +} + +Range Layouter::GetValidVerticalSizeRange() const +{ + return mpImplementation->GetValidVerticalSizeRange(); +} + +Range Layouter::GetRangeOfVisiblePageObjects (const ::tools::Rectangle& aVisibleArea) const +{ + return mpImplementation->GetRangeOfVisiblePageObjects(aVisibleArea); +} + +sal_Int32 Layouter::GetIndexAtPoint ( + const Point& rPosition, + const bool bIncludePageBorders, + const bool bClampToValidRange) const +{ + const sal_Int32 nRow ( + mpImplementation->GetRowAtPosition ( + rPosition.Y(), + bIncludePageBorders, + bIncludePageBorders ? Implementation::GM_PAGE_BORDER : Implementation::GM_NONE)); + const sal_Int32 nColumn ( + mpImplementation->GetColumnAtPosition ( + rPosition.X(), + bIncludePageBorders, + bIncludePageBorders ? Implementation::GM_PAGE_BORDER : Implementation::GM_NONE)); + + return mpImplementation->GetIndex(nRow,nColumn,bClampToValidRange); +} + +//===== Layouter::Implementation ============================================== + +Layouter::Implementation* Layouter::Implementation::Create ( + const Implementation& rImplementation, + const Layouter::Orientation eOrientation) +{ + switch (eOrientation) + { + case HORIZONTAL: return new HorizontalImplementation(rImplementation); + case VERTICAL: return new VerticalImplementation(rImplementation); + case GRID: + default: return new GridImplementation(rImplementation); + } +} + +Layouter::Implementation::Implementation ( + sd::Window *pWindow, + const std::shared_ptr<view::Theme>& rpTheme) + : mpWindow(pWindow), + mnLeftBorder(5), + mnRightBorder(5), + mnTopBorder(5), + mnBottomBorder(5), + maMinimalSize(132,98), + maPreferredSize(200,150), + maMaximalSize(600,400), + mnMinimalColumnCount(1), + mnMaximalColumnCount(15), + mnPageCount(0), + mnColumnCount(1), + mnRowCount(0), + mnMaxColumnCount(0), + mnMaxRowCount(0), + maPageObjectSize(1,1), + mpTheme(rpTheme) +{ +} + +Layouter::Implementation::Implementation (const Implementation& rImplementation) + : mpWindow(rImplementation.mpWindow), + mnLeftBorder(rImplementation.mnLeftBorder), + mnRightBorder(rImplementation.mnRightBorder), + mnTopBorder(rImplementation.mnTopBorder), + mnBottomBorder(rImplementation.mnBottomBorder), + maMinimalSize(rImplementation.maMinimalSize), + maPreferredSize(rImplementation.maPreferredSize), + maMaximalSize(rImplementation.maMaximalSize), + mnMinimalColumnCount(rImplementation.mnMinimalColumnCount), + mnMaximalColumnCount(rImplementation.mnMaximalColumnCount), + mnPageCount(rImplementation.mnPageCount), + mnColumnCount(rImplementation.mnColumnCount), + mnRowCount(rImplementation.mnRowCount), + mnMaxColumnCount(rImplementation.mnMaxColumnCount), + mnMaxRowCount(rImplementation.mnMaxRowCount), + maPageObjectSize(rImplementation.maPageObjectSize), + mpTheme(rImplementation.mpTheme) +{ +} + +Layouter::Implementation::~Implementation() +{ +} + +bool Layouter::Implementation::Rearrange ( + const Size& rWindowSize, + const Size& rPreviewModelSize, + const sal_uInt32 nPageCount) +{ + mnPageCount = nPageCount; + + // Return early when the window or the model have not yet been initialized. + if (rWindowSize.IsEmpty()) + return false; + if (rPreviewModelSize.IsEmpty()) + return false; + + CalculateRowAndColumnCount(rWindowSize); + + // Update the border values. + mnLeftBorder = mnRequestedLeftBorder; + mnTopBorder = mnRequestedTopBorder; + mnRightBorder = mnRequestedRightBorder; + mnBottomBorder = mnRequestedBottomBorder; + if (mnColumnCount > 1) + { + int nMinimumBorderWidth = gnHorizontalGap/2; + if (mnLeftBorder < nMinimumBorderWidth) + mnLeftBorder = nMinimumBorderWidth; + if (mnRightBorder < nMinimumBorderWidth) + mnRightBorder = nMinimumBorderWidth; + } + else + { + int nMinimumBorderHeight = gnVerticalGap/2; + if (mnTopBorder < nMinimumBorderHeight) + mnTopBorder = nMinimumBorderHeight; + if (mnBottomBorder < nMinimumBorderHeight) + mnBottomBorder = nMinimumBorderHeight; + } + + mpPageObjectLayouter = + std::make_shared<PageObjectLayouter>( + CalculateTargetSize(rWindowSize), + rPreviewModelSize, + mpWindow, + mnPageCount); + + maPageObjectSize = mpPageObjectLayouter->GetGridMaxSize(); + + CalculateMaxRowAndColumnCount(rWindowSize); + + return true; +} + +sal_Int32 Layouter::Implementation::GetRowAtPosition ( + sal_Int32 nYPosition, + bool bIncludeBordersAndGaps, + GapMembership eGapMembership) const +{ + sal_Int32 nRow = -1; + + const sal_Int32 nY = nYPosition - mnTopBorder; + if (nY >= 0) + { + // Vertical distance from one row to the next. + const sal_Int32 nRowOffset (maPageObjectSize.Height() + gnVerticalGap); + + // Calculate row consisting of page objects and gap below. + nRow = nY / nRowOffset; + + const sal_Int32 nDistanceIntoGap ((nY - nRow*nRowOffset) - maPageObjectSize.Height()); + // When inside the gap below then nYPosition is not over a page + // object. + if (nDistanceIntoGap > 0) + { + sal_Int32 nResolvedRow = ResolvePositionInGap( + nDistanceIntoGap, + eGapMembership, + nRow, + gnVerticalGap); + if (!bIncludeBordersAndGaps || nResolvedRow != -1) + nRow = nResolvedRow; + } + } + else if (bIncludeBordersAndGaps) + { + // We are in the top border area. Set nRow to the first row when + // the top border shall be considered to belong to the first row. + nRow = 0; + } + + return nRow; +} + +sal_Int32 Layouter::Implementation::GetColumnAtPosition ( + sal_Int32 nXPosition, + bool bIncludeBordersAndGaps, + GapMembership eGapMembership) const +{ + sal_Int32 nColumn = -1; + + sal_Int32 nX = nXPosition - mnLeftBorder; + if (nX >= 0) + { + // Horizontal distance from one column to the next. + const sal_Int32 nColumnOffset (maPageObjectSize.Width() + gnHorizontalGap); + + // Calculate row consisting of page objects and gap below. + nColumn = nX / nColumnOffset; + if (nColumn < 0) + nColumn = 0; + else if (nColumn >= mnColumnCount) + nColumn = mnColumnCount-1; + + const sal_Int32 nDistanceIntoGap ((nX - nColumn*nColumnOffset) - maPageObjectSize.Width()); + // When inside the gap at the right then nXPosition is not over a + // page object. + if (nDistanceIntoGap > 0) + { + sal_Int32 nResolvedColumn = ResolvePositionInGap( + nDistanceIntoGap, + eGapMembership, + nColumn, + gnHorizontalGap); + if (!bIncludeBordersAndGaps || nResolvedColumn != -1) + nColumn = nResolvedColumn; + } + } + else if (bIncludeBordersAndGaps) + { + // We are in the left border area. Set nColumn to the first column + // when the left border shall be considered to belong to the first + // column. + nColumn = 0; + } + return nColumn; +} + +sal_Int32 Layouter::Implementation::ResolvePositionInGap ( + sal_Int32 nDistanceIntoGap, + GapMembership eGapMembership, + sal_Int32 nIndex, + sal_Int32 nGap) +{ + switch (eGapMembership) + { + case GM_NONE: + // The gap is no man's land. + nIndex = -1; + break; + + case GM_BOTH: + { + // The lower half of the gap belongs to the next row or column. + sal_Int32 nFirstHalfGapWidth = nGap / 2; + if (nDistanceIntoGap > nFirstHalfGapWidth) + nIndex ++; + break; + } + + case GM_PREVIOUS: + // Row or column already at correct value. + break; + + case GM_NEXT: + // The complete gap belongs to the next row or column. + nIndex ++; + break; + + case GM_PAGE_BORDER: + if (nDistanceIntoGap > 0) + { + if (nDistanceIntoGap > nGap) + { + // Inside the border of the next row or column. + nIndex ++; + } + else + { + // Inside the gap between the page borders. + nIndex = -1; + } + } + break; + + default: + nIndex = -1; + } + + return nIndex; +} + +void Layouter::Implementation::CalculateGeometricPosition ( + InsertPosition& rPosition, + const Size& rIndicatorSize, + const bool bIsVertical, + model::SlideSorterModel const & rModel) const +{ + // 1. Determine right/bottom of the leading page and the left/top of the + // trailing page object and how to distribute the missing space. + sal_Int32 nLeadingLocation (0); + sal_Int32 nTrailingLocation (0); + bool bIsLeadingFixed (false); + bool bIsTrailingFixed (false); + sal_Int32 nSecondaryLocation (0); + const sal_Int32 nIndex (rPosition.GetIndex()); + + if (rPosition.IsAtRunStart()) + { + // Place indicator at the top of the column. + const ::tools::Rectangle aOuterBox (GetPageObjectBox(nIndex)); + const ::tools::Rectangle aInnerBox (GetInnerBoundingBox(rModel, nIndex)); + if (bIsVertical) + { + nLeadingLocation = aOuterBox.Top(); + nTrailingLocation = aInnerBox.Top(); + nSecondaryLocation = aInnerBox.Center().X(); + } + else + { + nLeadingLocation = aOuterBox.Left(); + nTrailingLocation = aInnerBox.Left(); + nSecondaryLocation = aInnerBox.Center().Y(); + } + bIsLeadingFixed = true; + } + else if (rPosition.IsAtRunEnd()) + { + // Place indicator at the bottom/right of the column/row. + + const ::tools::Rectangle aOuterBox (GetPageObjectBox(nIndex-1)); + const ::tools::Rectangle aInnerBox (GetInnerBoundingBox(rModel, nIndex-1)); + if (bIsVertical) + { + nLeadingLocation = aInnerBox.Bottom(); + nTrailingLocation = aOuterBox.Bottom(); + nSecondaryLocation = aInnerBox.Center().X(); + } + else + { + nLeadingLocation = aInnerBox.Right(); + nTrailingLocation = aOuterBox.Right(); + nSecondaryLocation = aInnerBox.Center().Y(); + } + bIsTrailingFixed = true; + if ( ! rPosition.IsExtraSpaceNeeded()) + bIsLeadingFixed = true; + } + else + { + // Place indicator between two rows/columns. + const ::tools::Rectangle aBox1 (GetInnerBoundingBox(rModel, nIndex-1)); + const ::tools::Rectangle aBox2 (GetInnerBoundingBox(rModel, nIndex)); + if (bIsVertical) + { + nLeadingLocation = aBox1.Bottom(); + nTrailingLocation = aBox2.Top(); + nSecondaryLocation = (aBox1.Center().X() + aBox2.Center().X()) / 2; + } + else + { + nLeadingLocation = aBox1.Right(); + nTrailingLocation = aBox2.Left(); + nSecondaryLocation = (aBox1.Center().Y() + aBox2.Center().Y()) / 2; + } + } + + // 2. Calculate the location of the insert indicator and the offsets of + // leading and trailing pages. + const sal_Int32 nAvailableSpace (nTrailingLocation - nLeadingLocation); + const sal_Int32 nRequiredSpace (bIsVertical ? rIndicatorSize.Height():rIndicatorSize.Width()); + const sal_Int32 nMissingSpace (::std::max(sal_Int32(0), nRequiredSpace - nAvailableSpace)); + sal_Int32 nPrimaryLocation (0); + sal_Int32 nLeadingOffset (0); + sal_Int32 nTrailingOffset (0); + if (bIsLeadingFixed) + { + nPrimaryLocation = nLeadingLocation + nRequiredSpace/2; + if ( ! bIsTrailingFixed) + nTrailingOffset = nMissingSpace; + } + else if (bIsTrailingFixed) + { + nPrimaryLocation = nTrailingLocation - nRequiredSpace/2; + nLeadingOffset = -nMissingSpace; + } + else + { + nPrimaryLocation = (nLeadingLocation + nTrailingLocation) /2; + nLeadingOffset = -nMissingSpace/2; + nTrailingOffset = nMissingSpace + nLeadingOffset; + } + + if (bIsVertical) + { + rPosition.SetGeometricalPosition( + Point(nSecondaryLocation, nPrimaryLocation), + Point(0, nLeadingOffset), + Point(0, nTrailingOffset)); + } + else + { + rPosition.SetGeometricalPosition( + Point(nPrimaryLocation, nSecondaryLocation), + Point(nLeadingOffset, 0), + Point(nTrailingOffset, 0)); + } +} + +::tools::Rectangle Layouter::Implementation::GetInnerBoundingBox ( + model::SlideSorterModel const & rModel, + const sal_Int32 nIndex) const +{ + model::SharedPageDescriptor pDescriptor (rModel.GetPageDescriptor(nIndex)); + if ( ! pDescriptor) + return ::tools::Rectangle(); + + PageObjectLayouter::Part ePart = PageObjectLayouter::Part::Preview; + + if (pDescriptor->HasState(model::PageDescriptor::ST_Selected)) + ePart = PageObjectLayouter::Part::PageObject; + + return mpPageObjectLayouter->GetBoundingBox( + pDescriptor, ePart, + PageObjectLayouter::ModelCoordinateSystem, true); +} + +Range Layouter::Implementation::GetValidHorizontalSizeRange() const +{ + return Range( + mnLeftBorder + maMinimalSize.Width() + mnRightBorder, + mnLeftBorder + maMaximalSize.Width() + mnRightBorder); +} + +Range Layouter::Implementation::GetValidVerticalSizeRange() const +{ + return Range( + mnTopBorder + maMinimalSize.Height() + mnBottomBorder, + mnTopBorder + maMaximalSize.Height() + mnBottomBorder); +} + +Range Layouter::Implementation::GetRangeOfVisiblePageObjects (const ::tools::Rectangle& aVisibleArea) const +{ + // technically that's not empty, but it's the default, so... + if (aVisibleArea.IsEmpty()) + return Range(-1, -1); + + const sal_Int32 nRow0 (GetRowAtPosition(aVisibleArea.Top(), true, GM_NEXT)); + const sal_Int32 nCol0 (GetColumnAtPosition(aVisibleArea.Left(),true, GM_NEXT)); + const sal_Int32 nRow1 (GetRowAtPosition(aVisibleArea.Bottom(), true, GM_PREVIOUS)); + const sal_Int32 nCol1 (GetColumnAtPosition(aVisibleArea.Right(), true, GM_PREVIOUS)); + + // When start and end lie in different rows then the range may include + // slides outside (left or right of) the given area. + return Range(GetIndex(nRow0,nCol0,true), GetIndex(nRow1,nCol1,true)); +} + +Size Layouter::Implementation::GetTargetSize ( + const Size& rWindowSize, + const bool bCalculateWidth, + const bool bCalculateHeight) const +{ + if (mnColumnCount<=0 || mnRowCount<=0) + return maPreferredSize; + if ( ! (bCalculateWidth || bCalculateHeight)) + { + OSL_ASSERT(bCalculateWidth || bCalculateHeight); + return maPreferredSize; + } + + // Calculate the width of each page object. + Size aTargetSize (0,0); + if (bCalculateWidth) + aTargetSize.setWidth( + (rWindowSize.Width() - mnLeftBorder - mnRightBorder + - (mnColumnCount-1) * gnHorizontalGap) + / mnColumnCount); + else if (bCalculateHeight) + aTargetSize.setHeight( + (rWindowSize.Height() - mnTopBorder - mnBottomBorder + - (mnRowCount-1) * gnVerticalGap) + / mnRowCount); + + if (bCalculateWidth) + { + if (aTargetSize.Width() < maMinimalSize.Width()) + aTargetSize.setWidth(maMinimalSize.Width()); + else if (aTargetSize.Width() > maMaximalSize.Width()) + aTargetSize.setWidth(maMaximalSize.Width()); + } + else if (bCalculateHeight) + { + if (aTargetSize.Height() < maMinimalSize.Height()) + aTargetSize.setHeight(maMinimalSize.Height()); + else if (aTargetSize.Height() > maMaximalSize.Height()) + aTargetSize.setHeight(maMaximalSize.Height()); + } + + return aTargetSize; +} + +sal_Int32 Layouter::Implementation::GetIndex ( + const sal_Int32 nRow, + const sal_Int32 nColumn, + const bool bClampToValidRange) const +{ + if (nRow >= 0 && nColumn >= 0) + { + const sal_Int32 nIndex (nRow * mnColumnCount + nColumn); + if (nIndex >= mnPageCount) + if (bClampToValidRange) + return mnPageCount-1; + else + return -1; + else + return nIndex; + } + else if (bClampToValidRange) + return 0; + else + return -1; +} + +::tools::Rectangle Layouter::Implementation::GetPageObjectBox ( + const sal_Int32 nIndex, + const bool bIncludeBorderAndGap) const +{ + const sal_Int32 nRow (nIndex / mnColumnCount); + const sal_Int32 nColumn (nIndex % mnColumnCount); + + const ::tools::Rectangle aBoundingBox (GetPageObjectBox(nRow,nColumn)); + if (bIncludeBorderAndGap) + return AddBorderAndGap(aBoundingBox, nRow, nColumn); + else + return aBoundingBox; +} + +::tools::Rectangle Layouter::Implementation::GetPageObjectBox ( + const sal_Int32 nRow, + const sal_Int32 nColumn) const +{ + return ::tools::Rectangle( + Point (mnLeftBorder + + nColumn * maPageObjectSize.Width() + + std::max<sal_Int32>(nColumn,0) * gnHorizontalGap, + mnTopBorder + + nRow * maPageObjectSize.Height() + + std::max<sal_Int32>(nRow,0) * gnVerticalGap), + maPageObjectSize); +} + +::tools::Rectangle Layouter::Implementation::AddBorderAndGap ( + const ::tools::Rectangle& rBoundingBox, + const sal_Int32 nRow, + const sal_Int32 nColumn) const +{ + ::tools::Rectangle aBoundingBox (rBoundingBox); + + if (nColumn == 0) + aBoundingBox.SetLeft( 0 ); + else + aBoundingBox.AdjustLeft( -(gnHorizontalGap/2) ); + if (nColumn == mnColumnCount-1) + aBoundingBox.AdjustRight(mnRightBorder ); + else + aBoundingBox.AdjustRight(gnHorizontalGap/2 ); + if (nRow == 0) + aBoundingBox.SetTop( 0 ); + else + aBoundingBox.AdjustTop( -(gnVerticalGap/2) ); + if (nRow == mnRowCount-1) + aBoundingBox.AdjustBottom(mnBottomBorder ); + else + aBoundingBox.AdjustBottom(gnVerticalGap/2 ); + return aBoundingBox; +} + +::tools::Rectangle Layouter::Implementation::GetTotalBoundingBox() const +{ + sal_Int32 nHorizontalSize = 0; + sal_Int32 nVerticalSize = 0; + if (mnColumnCount > 0) + { + sal_Int32 nRowCount = (mnPageCount+mnColumnCount-1) / mnColumnCount; + nHorizontalSize = + mnLeftBorder + + mnRightBorder + + mnColumnCount * maPageObjectSize.Width(); + if (mnColumnCount > 1) + nHorizontalSize += (mnColumnCount-1) * gnHorizontalGap; + nVerticalSize = + mnTopBorder + + mnBottomBorder + + nRowCount * maPageObjectSize.Height(); + if (nRowCount > 1) + nVerticalSize += (nRowCount-1) * gnVerticalGap; + } + + return ::tools::Rectangle ( + Point(0,0), + Size (nHorizontalSize, nVerticalSize) + ); +} + +void Layouter::Implementation::CalculateVerticalLogicalInsertPosition ( + const Point& rModelPosition, + InsertPosition& rPosition) const +{ + const sal_Int32 nY = rModelPosition.Y() - mnTopBorder + maPageObjectSize.Height()/2; + const sal_Int32 nRowHeight (maPageObjectSize.Height() + gnVerticalGap); + const sal_Int32 nRow (::std::min(mnPageCount, nY / nRowHeight)); + rPosition.SetLogicalPosition ( + nRow, + 0, + nRow, + (nRow == 0), + (nRow == mnRowCount), + (nRow >= mnMaxRowCount)); +} + +//===== HorizontalImplementation ================================================ + +HorizontalImplementation::HorizontalImplementation (const Implementation& rImplementation) + : Implementation(rImplementation) +{ +} + +Layouter::Orientation HorizontalImplementation::GetOrientation() const +{ + return Layouter::HORIZONTAL; +} + +void HorizontalImplementation::CalculateRowAndColumnCount (const Size&) +{ + // Row and column count are fixed (for a given page count.) + mnColumnCount = mnPageCount; + mnRowCount = 1; +} + +void HorizontalImplementation::CalculateMaxRowAndColumnCount (const Size& rWindowSize) +{ + mnMaxColumnCount = (rWindowSize.Width() - mnLeftBorder - mnRightBorder) + / (maPageObjectSize.Width() + gnHorizontalGap); + mnMaxRowCount = 1; +} + +Size HorizontalImplementation::CalculateTargetSize ( + const Size& rWindowSize) const +{ + return Implementation::GetTargetSize(rWindowSize, false, true); +} + +void HorizontalImplementation::CalculateLogicalInsertPosition ( + const Point& rModelPosition, + InsertPosition& rPosition) const +{ + const sal_Int32 nX = rModelPosition.X() - mnLeftBorder + maPageObjectSize.Width()/2; + const sal_Int32 nColumnWidth (maPageObjectSize.Width() + gnHorizontalGap); + const sal_Int32 nColumn (::std::min(mnPageCount, nX / nColumnWidth)); + rPosition.SetLogicalPosition ( + 0, + nColumn, + nColumn, + (nColumn == 0), + (nColumn == mnColumnCount), + (nColumn >= mnMaxColumnCount)); +} + +//===== VerticalImplementation ================================================ + +VerticalImplementation::VerticalImplementation (const Implementation& rImplementation) + : Implementation(rImplementation) +{ +} + +Layouter::Orientation VerticalImplementation::GetOrientation() const +{ + return Layouter::VERTICAL; +} + +void VerticalImplementation::CalculateRowAndColumnCount (const Size&) +{ + // Row and column count are fixed (for a given page count.) + mnRowCount = mnPageCount; + mnColumnCount = 1; + +} + +void VerticalImplementation::CalculateMaxRowAndColumnCount (const Size& rWindowSize) +{ + mnMaxRowCount = (rWindowSize.Height() - mnTopBorder - mnBottomBorder) + / (maPageObjectSize.Height() + gnVerticalGap); + mnMaxColumnCount = 1; +} + +Size VerticalImplementation::CalculateTargetSize ( + const Size& rWindowSize) const +{ + return Implementation::GetTargetSize(rWindowSize, true, false); +} + +void VerticalImplementation::CalculateLogicalInsertPosition ( + const Point& rModelPosition, + InsertPosition& rPosition) const +{ + return CalculateVerticalLogicalInsertPosition(rModelPosition, rPosition); +} + +//===== GridImplementation ================================================ + +GridImplementation::GridImplementation ( + sd::Window *pWindow, + const std::shared_ptr<view::Theme>& rpTheme) + : Implementation(pWindow, rpTheme) +{ +} + +GridImplementation::GridImplementation (const Implementation& rImplementation) + : Implementation(rImplementation) +{ +} + +Layouter::Orientation GridImplementation::GetOrientation() const +{ + return Layouter::GRID; +} + +void GridImplementation::CalculateRowAndColumnCount (const Size& rWindowSize) +{ + // Calculate the column count. + mnColumnCount + = (rWindowSize.Width() - mnRequestedLeftBorder - mnRequestedRightBorder) + / (maPreferredSize.Width() + gnHorizontalGap); + if (mnColumnCount < mnMinimalColumnCount) + mnColumnCount = mnMinimalColumnCount; + if (mnColumnCount > mnMaximalColumnCount) + mnColumnCount = mnMaximalColumnCount; + mnRowCount = (mnPageCount + mnColumnCount-1)/mnColumnCount; +} + +void GridImplementation::CalculateMaxRowAndColumnCount (const Size& rWindowSize) +{ + mnMaxColumnCount = (rWindowSize.Width() - mnLeftBorder - mnRightBorder) + / (maPageObjectSize.Width() + gnHorizontalGap); + mnMaxRowCount = (rWindowSize.Height() - mnTopBorder - mnBottomBorder) + / (maPageObjectSize.Height() + gnVerticalGap); +} + +Size GridImplementation::CalculateTargetSize ( + const Size& rWindowSize) const +{ + return Implementation::GetTargetSize(rWindowSize, true, true); +} + +void GridImplementation::CalculateLogicalInsertPosition ( + const Point& rModelPosition, + InsertPosition& rPosition) const +{ + if (mnColumnCount == 1) + { + CalculateVerticalLogicalInsertPosition(rModelPosition, rPosition); + } + else + { + // Handle the general case of more than one column. + sal_Int32 nRow (::std::min( + mnRowCount-1, + GetRowAtPosition (rModelPosition.Y(), true, GM_BOTH))); + const sal_Int32 nX = rModelPosition.X() - mnLeftBorder + maPageObjectSize.Width()/2; + const sal_Int32 nColumnWidth (maPageObjectSize.Width() + gnHorizontalGap); + sal_Int32 nColumn (::std::min(mnColumnCount, nX / nColumnWidth)); + sal_Int32 nIndex (nRow * mnColumnCount + nColumn); + bool bIsAtRunEnd (nColumn == mnColumnCount); + + if (nIndex >= mnPageCount) + { + nIndex = mnPageCount; + nRow = mnRowCount-1; + nColumn = ::std::min(::std::min(mnPageCount, mnColumnCount), nColumn); + bIsAtRunEnd = true; + } + + rPosition.SetLogicalPosition ( + nRow, + nColumn, + nIndex, + (nColumn == 0), + bIsAtRunEnd, + (nColumn >= mnMaxColumnCount)); + } +} + +//===== InsertPosition ======================================================== + +InsertPosition::InsertPosition() + : mnRow(-1), + mnColumn(-1), + mnIndex(-1), + mbIsAtRunStart(false), + mbIsAtRunEnd(false), + mbIsExtraSpaceNeeded(false), + maLocation(0,0), + maLeadingOffset(0,0), + maTrailingOffset(0,0) +{ +} + +bool InsertPosition::operator== (const InsertPosition& rInsertPosition) const +{ + // Do not compare the geometrical information (maLocation). + return mnRow==rInsertPosition.mnRow + && mnColumn==rInsertPosition.mnColumn + && mnIndex==rInsertPosition.mnIndex + && mbIsAtRunStart==rInsertPosition.mbIsAtRunStart + && mbIsAtRunEnd==rInsertPosition.mbIsAtRunEnd + && mbIsExtraSpaceNeeded==rInsertPosition.mbIsExtraSpaceNeeded; +} + +bool InsertPosition::operator!= (const InsertPosition& rInsertPosition) const +{ + return !operator==(rInsertPosition); +} + +void InsertPosition::SetLogicalPosition ( + const sal_Int32 nRow, + const sal_Int32 nColumn, + const sal_Int32 nIndex, + const bool bIsAtRunStart, + const bool bIsAtRunEnd, + const bool bIsExtraSpaceNeeded) +{ + mnRow = nRow; + mnColumn = nColumn; + mnIndex = nIndex; + mbIsAtRunStart = bIsAtRunStart; + mbIsAtRunEnd = bIsAtRunEnd; + mbIsExtraSpaceNeeded = bIsExtraSpaceNeeded; +} + +void InsertPosition::SetGeometricalPosition( + const Point& rLocation, + const Point& rLeadingOffset, + const Point& rTrailingOffset) +{ + maLocation = rLocation; + maLeadingOffset = rLeadingOffset; + maTrailingOffset = rTrailingOffset; +} + +} // end of namespace ::sd::slidesorter::namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/view/SlsPageObjectLayouter.cxx b/sd/source/ui/slidesorter/view/SlsPageObjectLayouter.cxx new file mode 100644 index 000000000..b26eb0746 --- /dev/null +++ b/sd/source/ui/slidesorter/view/SlsPageObjectLayouter.cxx @@ -0,0 +1,259 @@ +/* -*- 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 <view/SlsPageObjectLayouter.hxx> + +#include <model/SlsPageDescriptor.hxx> +#include <view/SlsTheme.hxx> +#include <tools/IconCache.hxx> +#include <Window.hxx> + +#include <bitmaps.hlst> +#include <osl/diagnose.h> + +namespace sd::slidesorter::view { + +namespace { +const sal_Int32 gnLeftPageNumberOffset = 2; +const sal_Int32 gnRightPageNumberOffset = 5; +const sal_Int32 gnOuterBorderWidth = 5; +const sal_Int32 gnInfoAreaMinWidth = 26; +} + +PageObjectLayouter::PageObjectLayouter ( + const Size& rPageObjectWindowSize, + const Size& rPageSize, + sd::Window *pWindow, + const sal_Int32 nPageCount) + : mpWindow(pWindow), + maTransitionEffectIcon(IconCache::Instance().GetIcon(BMP_FADE_EFFECT_INDICATOR)), + maCustomAnimationEffectIcon(IconCache::Instance().GetIcon(BMP_CUSTOM_ANIMATION_INDICATOR)), + mpPageNumberFont(Theme::GetFont(Theme::Font_PageNumber, *pWindow->GetOutDev())) +{ + const Size aPageNumberAreaSize (GetPageNumberAreaSize(nPageCount)); + + const int nMaximumBorderWidth (gnOuterBorderWidth); + const int nFocusIndicatorWidth (Theme_FocusIndicatorWidth); + + Size aPageObjectSize(rPageObjectWindowSize.Width(), rPageObjectWindowSize.Height()); + maPreviewBoundingBox = CalculatePreviewBoundingBox( + aPageObjectSize, + Size(rPageSize.Width(), rPageSize.Height()), + aPageNumberAreaSize.Width(), + nFocusIndicatorWidth); + maFocusIndicatorBoundingBox = ::tools::Rectangle(Point(0,0), aPageObjectSize); + maPageObjectBoundingBox = ::tools::Rectangle( + Point( + nFocusIndicatorWidth, + nFocusIndicatorWidth), + Size( + aPageObjectSize.Width()-2*nFocusIndicatorWidth, + aPageObjectSize.Height()-2*nFocusIndicatorWidth)); + + maPageNumberAreaBoundingBox = ::tools::Rectangle( + Point( + std::max(gnLeftPageNumberOffset, + sal_Int32(maPreviewBoundingBox.Left() + - gnRightPageNumberOffset + - aPageNumberAreaSize.Width())), + nMaximumBorderWidth), + aPageNumberAreaSize); + + const Size aIconSize (maTransitionEffectIcon.GetSizePixel()); + maTransitionEffectBoundingBox = ::tools::Rectangle( + Point( + (maPreviewBoundingBox.Left() - 2*aIconSize.Width()) / 2, + maPreviewBoundingBox.Bottom() - aIconSize.Height()), + aIconSize); + maCustomAnimationEffectBoundingBox = ::tools::Rectangle( + Point( + (maPreviewBoundingBox.Left() - 2*aIconSize.Width()) / 2, + maPreviewBoundingBox.Bottom() - 2*aIconSize.Height()), + aIconSize); +} + +PageObjectLayouter::~PageObjectLayouter() +{ +} + +::tools::Rectangle PageObjectLayouter::CalculatePreviewBoundingBox ( + Size& rPageObjectSize, + const Size& rPageSize, + const sal_Int32 nPageNumberAreaWidth, + const sal_Int32 nFocusIndicatorWidth) +{ + const sal_Int32 nIconWidth (maTransitionEffectIcon.GetSizePixel().Width()); + const sal_Int32 nLeftAreaWidth ( + ::std::max( + gnInfoAreaMinWidth, + gnRightPageNumberOffset + + ::std::max( + nPageNumberAreaWidth, + nIconWidth))); + sal_Int32 nPreviewWidth; + sal_Int32 nPreviewHeight; + const double nPageAspectRatio (double(rPageSize.Width()) / double(rPageSize.Height())); + if (rPageObjectSize.Height() == 0) + { + // Calculate height so that the preview fills the available + // horizontal space completely while observing the aspect ratio of + // the preview. + nPreviewWidth = rPageObjectSize.Width() + - nLeftAreaWidth - gnOuterBorderWidth - 2*nFocusIndicatorWidth - 1; + nPreviewHeight = ::basegfx::fround(nPreviewWidth / nPageAspectRatio); + rPageObjectSize.setHeight(nPreviewHeight + 2*gnOuterBorderWidth + 2*nFocusIndicatorWidth + 1); + } + else if (rPageObjectSize.Width() == 0) + { + // Calculate the width of the page object so that the preview fills + // the available vertical space completely while observing the + // aspect ratio of the preview. + nPreviewHeight = rPageObjectSize.Height() - 2*gnOuterBorderWidth - 2*nFocusIndicatorWidth - 1; + nPreviewWidth = ::basegfx::fround(nPreviewHeight * nPageAspectRatio); + rPageObjectSize.setWidth(nPreviewWidth + + nLeftAreaWidth + gnOuterBorderWidth + 2*nFocusIndicatorWidth + 1); + + } + else + { + // The size of the page object is given. Calculate the size of the + // preview. + nPreviewWidth = rPageObjectSize.Width() + - nLeftAreaWidth - gnOuterBorderWidth - 2*nFocusIndicatorWidth - 1; + nPreviewHeight = rPageObjectSize.Height() + - gnOuterBorderWidth - 2*nFocusIndicatorWidth - 1; + if (double(nPreviewWidth)/double(nPreviewHeight) > nPageAspectRatio) + nPreviewWidth = ::basegfx::fround(nPreviewHeight * nPageAspectRatio); + else + nPreviewHeight = ::basegfx::fround(nPreviewWidth / nPageAspectRatio); + } + // When the preview does not fill the available space completely then + // place it flush right and vertically centered. + const int nLeft (rPageObjectSize.Width() + - gnOuterBorderWidth - nPreviewWidth - nFocusIndicatorWidth - 1); + const int nTop ((rPageObjectSize.Height() - nPreviewHeight)/2); + return ::tools::Rectangle( + nLeft, + nTop, + nLeft + nPreviewWidth, + nTop + nPreviewHeight); +} + +::tools::Rectangle PageObjectLayouter::GetBoundingBox ( + const model::SharedPageDescriptor& rpPageDescriptor, + const Part ePart, + const CoordinateSystem eCoordinateSystem, + bool bIgnoreLocation) +{ + OSL_ASSERT(rpPageDescriptor); + Point aLocation(0,0); + if (rpPageDescriptor) + aLocation = rpPageDescriptor->GetLocation( bIgnoreLocation ); + return GetBoundingBox(aLocation, ePart, eCoordinateSystem); +} + +::tools::Rectangle PageObjectLayouter::GetBoundingBox ( + const Point& rPageObjectLocation, + const Part ePart, + const CoordinateSystem eCoordinateSystem) +{ + ::tools::Rectangle aBoundingBox; + switch (ePart) + { + case Part::FocusIndicator: + aBoundingBox = maFocusIndicatorBoundingBox; + break; + + case Part::PageObject: + aBoundingBox = maPageObjectBoundingBox; + break; + + case Part::Preview: + aBoundingBox = maPreviewBoundingBox; + break; + + case Part::PageNumber: + aBoundingBox = maPageNumberAreaBoundingBox; + break; + + case Part::TransitionEffectIndicator: + aBoundingBox = maTransitionEffectBoundingBox; + break; + case Part::CustomAnimationEffectIndicator: + aBoundingBox = maCustomAnimationEffectBoundingBox; + break; + } + + // Adapt coordinates to the requested coordinate system. + Point aLocation (rPageObjectLocation); + if (eCoordinateSystem == WindowCoordinateSystem) + aLocation += mpWindow->GetMapMode().GetOrigin(); + + return ::tools::Rectangle( + aBoundingBox.TopLeft() + aLocation, + aBoundingBox.BottomRight() + aLocation); +} + +Size PageObjectLayouter::GetPreviewSize () +{ + return GetBoundingBox(Point(0,0), PageObjectLayouter::Part::Preview, + WindowCoordinateSystem).GetSize(); +} + +Size PageObjectLayouter::GetGridMaxSize() +{ + return GetBoundingBox(Point(0,0), PageObjectLayouter::Part::FocusIndicator, + WindowCoordinateSystem).GetSize(); +} + +Size PageObjectLayouter::GetPageNumberAreaSize (const int nPageCount) +{ + OSL_ASSERT(mpWindow); + + // Set the correct font. + vcl::Font aOriginalFont (mpWindow->GetFont()); + if (mpPageNumberFont) + mpWindow->SetFont(*mpPageNumberFont); + + OUString sPageNumberTemplate; + if (nPageCount < 10) + sPageNumberTemplate = "9"; + else if (nPageCount < 100) + sPageNumberTemplate = "99"; + else if (nPageCount < 200) + // Just for the case that 1 is narrower than 9. + sPageNumberTemplate = "199"; + else if (nPageCount < 1000) + sPageNumberTemplate = "999"; + else + sPageNumberTemplate = "9999"; + // More than 9999 pages are not handled. + + const Size aSize ( + mpWindow->GetTextWidth(sPageNumberTemplate), + mpWindow->GetTextHeight()); + + mpWindow->SetFont(aOriginalFont); + + return aSize; +} + +} // end of namespace ::sd::slidesorter::view + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/view/SlsPageObjectPainter.cxx b/sd/source/ui/slidesorter/view/SlsPageObjectPainter.cxx new file mode 100644 index 000000000..feaf5a5fa --- /dev/null +++ b/sd/source/ui/slidesorter/view/SlsPageObjectPainter.cxx @@ -0,0 +1,442 @@ +/* -*- 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 <view/SlsPageObjectPainter.hxx> + +#include <model/SlsPageDescriptor.hxx> +#include <view/SlideSorterView.hxx> +#include <view/SlsPageObjectLayouter.hxx> +#include <view/SlsLayouter.hxx> +#include <view/SlsTheme.hxx> +#include <SlideSorter.hxx> +#include "SlsFramePainter.hxx" +#include <cache/SlsPageCache.hxx> +#include <Window.hxx> +#include <sdpage.hxx> +#include <vcl/virdev.hxx> +#include <CustomAnimationEffect.hxx> +#include <osl/diagnose.h> +#include <memory> + +using namespace ::drawinglayer::primitive2d; + +namespace sd::slidesorter::view { + +//===== PageObjectPainter ===================================================== + +PageObjectPainter::PageObjectPainter ( + const SlideSorter& rSlideSorter) + : mrLayouter(rSlideSorter.GetView().GetLayouter()), + mpCache(rSlideSorter.GetView().GetPreviewCache()), + mpTheme(rSlideSorter.GetTheme()), + mpPageNumberFont(Theme::GetFont(Theme::Font_PageNumber, *rSlideSorter.GetContentWindow()->GetOutDev())), + mpShadowPainter(new FramePainter(mpTheme->GetIcon(Theme::Icon_RawShadow))), + mpFocusBorderPainter(new FramePainter(mpTheme->GetIcon(Theme::Icon_FocusBorder))) +{ + // Replace the color (not the alpha values) in the focus border with a + // color derived from the current selection color. + Color aColor (mpTheme->GetColor(Theme::Color_Selection)); + sal_uInt16 nHue, nSat, nBri; + aColor.RGBtoHSB(nHue, nSat, nBri); + aColor = Color::HSBtoRGB(nHue, 28, 65); + mpFocusBorderPainter->AdaptColor(aColor); +} + +PageObjectPainter::~PageObjectPainter() +{ +} + +void PageObjectPainter::PaintPageObject ( + OutputDevice& rDevice, + const model::SharedPageDescriptor& rpDescriptor) +{ + if (!UpdatePageObjectLayouter()) + return; + + PageObjectLayouter *pPageObjectLayouter = mrLayouter.GetPageObjectLayouter().get(); + // Turn off antialiasing to avoid the bitmaps from being + // shifted by fractions of a pixel and thus show blurry edges. + const AntialiasingFlags nSavedAntialiasingMode (rDevice.GetAntialiasing()); + rDevice.SetAntialiasing(nSavedAntialiasingMode & ~AntialiasingFlags::Enable); + + PaintBackground(pPageObjectLayouter, rDevice, rpDescriptor); + PaintPreview(pPageObjectLayouter, rDevice, rpDescriptor); + PaintPageNumber(pPageObjectLayouter, rDevice, rpDescriptor); + PaintTransitionEffect(pPageObjectLayouter, rDevice, rpDescriptor); + if (rpDescriptor->GetPage()->hasAnimationNode()) + PaintCustomAnimationEffect(pPageObjectLayouter, rDevice, rpDescriptor); + rDevice.SetAntialiasing(nSavedAntialiasingMode); +} + +bool PageObjectPainter::UpdatePageObjectLayouter() +{ + // The page object layouter is quite volatile. It may have been replaced + // since the last call. Update it now. + PageObjectLayouter *pPageObjectLayouter = mrLayouter.GetPageObjectLayouter().get(); + if ( ! pPageObjectLayouter) + { + OSL_FAIL("no page object layouter"); + return false; + } + + return true; +} + +void PageObjectPainter::SetTheme (const std::shared_ptr<view::Theme>& rpTheme) +{ + mpTheme = rpTheme; +} + +void PageObjectPainter::PaintBackground ( + PageObjectLayouter *pPageObjectLayouter, + OutputDevice& rDevice, + const model::SharedPageDescriptor& rpDescriptor) const +{ + PaintBackgroundDetail(pPageObjectLayouter, rDevice, rpDescriptor); + + // Fill the interior of the preview area with the default background + // color of the page. + SdPage* pPage = rpDescriptor->GetPage(); + if (pPage != nullptr) + { + rDevice.SetFillColor(pPage->GetPageBackgroundColor(nullptr)); + rDevice.SetLineColor(pPage->GetPageBackgroundColor(nullptr)); + const ::tools::Rectangle aPreviewBox (pPageObjectLayouter->GetBoundingBox( + rpDescriptor, + PageObjectLayouter::Part::Preview, + PageObjectLayouter::ModelCoordinateSystem)); + rDevice.DrawRect(aPreviewBox); + } +} + +void PageObjectPainter::PaintPreview ( + PageObjectLayouter *pPageObjectLayouter, + OutputDevice& rDevice, + const model::SharedPageDescriptor& rpDescriptor) const +{ + const ::tools::Rectangle aBox (pPageObjectLayouter->GetBoundingBox( + rpDescriptor, + PageObjectLayouter::Part::Preview, + PageObjectLayouter::ModelCoordinateSystem)); + + if (mpCache == nullptr) + return; + + const SdrPage* pPage = rpDescriptor->GetPage(); + mpCache->SetPreciousFlag(pPage, true); + + const BitmapEx aPreview (GetPreviewBitmap(rpDescriptor, &rDevice)); + if ( ! aPreview.IsEmpty()) + { + if (aPreview.GetSizePixel() != aBox.GetSize()) + rDevice.DrawBitmapEx(aBox.TopLeft(), aBox.GetSize(), aPreview); + else + rDevice.DrawBitmapEx(aBox.TopLeft(), aPreview); + } +} + +BitmapEx PageObjectPainter::CreateMarkedPreview ( + const Size& rSize, + const BitmapEx& rPreview, + const BitmapEx& rOverlay, + const OutputDevice* pReferenceDevice) +{ + ScopedVclPtr<VirtualDevice> pDevice; + if (pReferenceDevice != nullptr) + pDevice.disposeAndReset(VclPtr<VirtualDevice>::Create(*pReferenceDevice)); + else + pDevice.disposeAndReset(VclPtr<VirtualDevice>::Create()); + pDevice->SetOutputSizePixel(rSize); + + pDevice->DrawBitmapEx(Point(0,0), rSize, rPreview); + + // Paint bitmap tiled over the preview to mark it as excluded. + const sal_Int32 nIconWidth (rOverlay.GetSizePixel().Width()); + const sal_Int32 nIconHeight (rOverlay.GetSizePixel().Height()); + if (nIconWidth>0 && nIconHeight>0) + { + for (::tools::Long nX=0; nX<rSize.Width(); nX+=nIconWidth) + for (::tools::Long nY=0; nY<rSize.Height(); nY+=nIconHeight) + pDevice->DrawBitmapEx(Point(nX,nY), rOverlay); + } + return pDevice->GetBitmapEx(Point(0,0), rSize); +} + +BitmapEx PageObjectPainter::GetPreviewBitmap ( + const model::SharedPageDescriptor& rpDescriptor, + const OutputDevice* pReferenceDevice) const +{ + const SdrPage* pPage = rpDescriptor->GetPage(); + const bool bIsExcluded (rpDescriptor->HasState(model::PageDescriptor::ST_Excluded)); + + if (bIsExcluded) + { + PageObjectLayouter *pPageObjectLayouter = mrLayouter.GetPageObjectLayouter().get(); + + BitmapEx aMarkedPreview (mpCache->GetMarkedPreviewBitmap(pPage)); + const ::tools::Rectangle aPreviewBox (pPageObjectLayouter->GetBoundingBox( + rpDescriptor, + PageObjectLayouter::Part::Preview, + PageObjectLayouter::ModelCoordinateSystem)); + if (aMarkedPreview.IsEmpty() || aMarkedPreview.GetSizePixel()!=aPreviewBox.GetSize()) + { + aMarkedPreview = CreateMarkedPreview( + aPreviewBox.GetSize(), + mpCache->GetPreviewBitmap(pPage,true), + mpTheme->GetIcon(Theme::Icon_HideSlideOverlay), + pReferenceDevice); + mpCache->SetMarkedPreviewBitmap(pPage, aMarkedPreview); + } + return aMarkedPreview; + } + else + { + return mpCache->GetPreviewBitmap(pPage,false); + } +} + +void PageObjectPainter::PaintPageNumber ( + PageObjectLayouter *pPageObjectLayouter, + OutputDevice& rDevice, + const model::SharedPageDescriptor& rpDescriptor) const +{ + const ::tools::Rectangle aBox (pPageObjectLayouter->GetBoundingBox( + rpDescriptor, + PageObjectLayouter::Part::PageNumber, + PageObjectLayouter::ModelCoordinateSystem)); + + // Determine the color of the page number. + Color aPageNumberColor (mpTheme->GetColor(Theme::Color_PageNumberDefault)); + if (rpDescriptor->HasState(model::PageDescriptor::ST_MouseOver) || + rpDescriptor->HasState(model::PageDescriptor::ST_Selected)) + { + // Page number is painted on background for hover or selection or + // both. Each of these background colors has a predefined luminance + // which is compatible with the PageNumberHover color. + aPageNumberColor = mpTheme->GetColor(Theme::Color_PageNumberHover); + } + else + { + const Color aBackgroundColor (mpTheme->GetColor(Theme::Color_Background)); + const sal_Int32 nBackgroundLuminance (aBackgroundColor.GetLuminance()); + // When the background color is black then this is interpreted as + // high contrast mode and the font color is set to white. + if (nBackgroundLuminance == 0) + aPageNumberColor = mpTheme->GetColor(Theme::Color_PageNumberHighContrast); + else + { + // Compare luminance of default page number color and background + // color. When the two are similar then use a darker + // (preferred) or brighter font color. + const sal_Int32 nFontLuminance (aPageNumberColor.GetLuminance()); + if (abs(nBackgroundLuminance - nFontLuminance) < 60) + { + if (nBackgroundLuminance > nFontLuminance-30) + aPageNumberColor = mpTheme->GetColor(Theme::Color_PageNumberBrightBackground); + else + aPageNumberColor = mpTheme->GetColor(Theme::Color_PageNumberDarkBackground); + } + } + } + + // Paint the page number. + OSL_ASSERT(rpDescriptor->GetPage()!=nullptr); + const sal_Int32 nPageNumber ((rpDescriptor->GetPage()->GetPageNum() - 1) / 2 + 1); + const OUString sPageNumber(OUString::number(nPageNumber)); + rDevice.SetFont(*mpPageNumberFont); + rDevice.SetTextColor(aPageNumberColor); + rDevice.DrawText(aBox, sPageNumber, DrawTextFlags::Right | DrawTextFlags::VCenter); +} + +void PageObjectPainter::PaintTransitionEffect ( + PageObjectLayouter *pPageObjectLayouter, + OutputDevice& rDevice, + const model::SharedPageDescriptor& rpDescriptor) +{ + const SdPage* pPage = rpDescriptor->GetPage(); + if (pPage!=nullptr && pPage->getTransitionType() > 0) + { + const ::tools::Rectangle aBox (pPageObjectLayouter->GetBoundingBox( + rpDescriptor, + PageObjectLayouter::Part::TransitionEffectIndicator, + PageObjectLayouter::ModelCoordinateSystem)); + + rDevice.DrawBitmapEx( + aBox.TopCenter(), + pPageObjectLayouter->GetTransitionEffectIcon().GetBitmapEx()); + } +} + +void PageObjectPainter::PaintCustomAnimationEffect ( + PageObjectLayouter *pPageObjectLayouter, + OutputDevice& rDevice, + const model::SharedPageDescriptor& rpDescriptor) +{ + SdPage* pPage = rpDescriptor->GetPage(); + std::shared_ptr< MainSequence > aMainSequence = pPage->getMainSequence(); + EffectSequence::iterator aIter = aMainSequence->getBegin(); + EffectSequence::iterator aEnd = aMainSequence->getEnd(); + if ( aIter != aEnd ) + { + const ::tools::Rectangle aBox (pPageObjectLayouter->GetBoundingBox( + rpDescriptor, + PageObjectLayouter::Part::CustomAnimationEffectIndicator, + PageObjectLayouter::ModelCoordinateSystem)); + rDevice.DrawBitmapEx( + aBox.TopCenter(), + pPageObjectLayouter->GetCustomAnimationEffectIcon().GetBitmapEx()); + } +} + +void PageObjectPainter::PaintBackgroundDetail ( + PageObjectLayouter *pPageObjectLayouter, + OutputDevice& rDevice, + const model::SharedPageDescriptor& rpDescriptor) const +{ + enum State { None = 0x00, Selected = 0x01, MouseOver = 0x02, Focused = 0x04 }; + const int eState = + (rpDescriptor->HasState(model::PageDescriptor::ST_Selected) ? Selected : None) + | (rpDescriptor->HasState(model::PageDescriptor::ST_MouseOver) ? MouseOver : None) + | (rpDescriptor->HasState(model::PageDescriptor::ST_Focused) ? Focused : None); + + bool bHasFocusBorder; + Theme::GradientColorType eColorType; + + switch (eState) + { + case MouseOver | Selected | Focused: + eColorType = Theme::Gradient_MouseOverSelectedAndFocusedPage; + bHasFocusBorder = true; + break; + + case MouseOver | Selected: + eColorType = Theme::Gradient_MouseOverSelected; + bHasFocusBorder = false; + break; + + case MouseOver: + eColorType = Theme::Gradient_MouseOverPage; + bHasFocusBorder = false; + break; + + case MouseOver | Focused: + eColorType = Theme::Gradient_MouseOverPage; + bHasFocusBorder = true; + break; + + case Selected | Focused: + eColorType = Theme::Gradient_SelectedAndFocusedPage; + bHasFocusBorder = true; + break; + + case Selected: + eColorType = Theme::Gradient_SelectedPage; + bHasFocusBorder = false; + break; + + case Focused: + eColorType = Theme::Gradient_FocusedPage; + bHasFocusBorder = true; + break; + + case None: + default: + eColorType = Theme::Gradient_NormalPage; + bHasFocusBorder = false; + break; + } + + const ::tools::Rectangle aFocusSize (pPageObjectLayouter->GetBoundingBox( + rpDescriptor, + PageObjectLayouter::Part::FocusIndicator, + PageObjectLayouter::ModelCoordinateSystem)); + + const ::tools::Rectangle aPageObjectBox (pPageObjectLayouter->GetBoundingBox( + rpDescriptor, + PageObjectLayouter::Part::PageObject, + PageObjectLayouter::ModelCoordinateSystem)); + + // Fill the background with the background color of the slide sorter. + const Color aBackgroundColor (mpTheme->GetColor(Theme::Color_Background)); + rDevice.SetFillColor(aBackgroundColor); + rDevice.SetLineColor(aBackgroundColor); + rDevice.DrawRect(aFocusSize); + + // Paint the slide area with a linear gradient that starts some pixels + // below the top and ends some pixels above the bottom. + const Color aTopColor(mpTheme->GetGradientColor(eColorType, Theme::GradientColorClass::Fill1)); + const Color aBottomColor(mpTheme->GetGradientColor(eColorType, Theme::GradientColorClass::Fill2)); + if (aTopColor != aBottomColor) + { + Gradient gradient(GradientStyle::Linear, aTopColor, aBottomColor); + rDevice.DrawGradient(aPageObjectBox, gradient); + } + else + { + rDevice.SetFillColor(aTopColor); + rDevice.DrawRect(aPageObjectBox); + } + + // Paint the simple border and, for some backgrounds, the focus border. + if (bHasFocusBorder) + mpFocusBorderPainter->PaintFrame(rDevice, aPageObjectBox); + else + PaintBorder(rDevice, eColorType, aPageObjectBox); + + // Get bounding box of the preview around which a shadow is painted. + // Compensate for the border around the preview. + const ::tools::Rectangle aBox (pPageObjectLayouter->GetBoundingBox( + rpDescriptor, + PageObjectLayouter::Part::Preview, + PageObjectLayouter::ModelCoordinateSystem)); + ::tools::Rectangle aFrameBox (aBox.Left()-1,aBox.Top()-1,aBox.Right()+1,aBox.Bottom()+1); + mpShadowPainter->PaintFrame(rDevice, aFrameBox); +} + +void PageObjectPainter::PaintBorder ( + OutputDevice& rDevice, + const Theme::GradientColorType eColorType, + const ::tools::Rectangle& rBox) const +{ + rDevice.SetFillColor(); + const sal_Int32 nBorderWidth (1); + for (int nIndex=0; nIndex<nBorderWidth; ++nIndex) + { + const int nDelta (nIndex); + rDevice.SetLineColor(mpTheme->GetGradientColor(eColorType, Theme::GradientColorClass::Border2)); + rDevice.DrawLine( + Point(rBox.Left()-nDelta, rBox.Top()-nDelta), + Point(rBox.Left()-nDelta, rBox.Bottom()+nDelta)); + rDevice.DrawLine( + Point(rBox.Left()-nDelta, rBox.Bottom()+nDelta), + Point(rBox.Right()+nDelta, rBox.Bottom()+nDelta)); + rDevice.DrawLine( + Point(rBox.Right()+nDelta, rBox.Bottom()+nDelta), + Point(rBox.Right()+nDelta, rBox.Top()-nDelta)); + + rDevice.SetLineColor(mpTheme->GetGradientColor(eColorType, Theme::GradientColorClass::Border1)); + rDevice.DrawLine( + Point(rBox.Left()-nDelta, rBox.Top()-nDelta), + Point(rBox.Right()+nDelta, rBox.Top()-nDelta)); + } +} + +} // end of namespace sd::slidesorter::view + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/view/SlsTheme.cxx b/sd/source/ui/slidesorter/view/SlsTheme.cxx new file mode 100644 index 000000000..5172e6241 --- /dev/null +++ b/sd/source/ui/slidesorter/view/SlsTheme.cxx @@ -0,0 +1,239 @@ +/* -*- 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 <bitmaps.hlst> +#include <view/SlsTheme.hxx> +#include <controller/SlsProperties.hxx> +#include <tools/color.hxx> +#include <vcl/outdev.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> + +#include <osl/diagnose.h> + +namespace sd::slidesorter::view { + +const Color Black(0x000000); +const Color White(0xffffff); + +static Color ChangeLuminance (Color aColor, const int nValue) +{ + if (nValue > 0) + aColor.IncreaseLuminance(nValue); + else + aColor.DecreaseLuminance(-nValue); + return aColor; +} + +static Color HGBAdapt ( + const Color aColor, + const sal_Int32 nNewSaturation, + const sal_Int32 nNewBrightness) +{ + sal_uInt16 nHue (0); + sal_uInt16 nSaturation (0); + sal_uInt16 nBrightness (0); + aColor.RGBtoHSB(nHue, nSaturation, nBrightness); + return Color::HSBtoRGB( + nHue, + nNewSaturation>=0 ? nNewSaturation : nSaturation, + nNewBrightness>=0 ? nNewBrightness : nBrightness); +} + +Theme::Theme (const std::shared_ptr<controller::Properties>& rpProperties) + : maBackgroundColor(rpProperties->GetBackgroundColor()) +{ + maColor.resize(ColorType_Size_); + maColor[Color_Background] = maBackgroundColor; + maColor[Color_PageNumberDefault] = Color(0x0808080); + maColor[Color_PageNumberHover] = Color(0x4c4c4c); + maColor[Color_PageNumberHighContrast] = White; + maColor[Color_PageNumberBrightBackground] = Color(0x333333); + maColor[Color_PageNumberDarkBackground] = Color(0xcccccc); + maColor[Color_PreviewBorder] = Color(0x949599); + + Update(rpProperties); +} + +void Theme::Update (const std::shared_ptr<controller::Properties>& rpProperties) +{ + // Set up colors. + maBackgroundColor = rpProperties->GetBackgroundColor(); + + maColor[Color_Background] = maBackgroundColor; + + maGradients.resize(GradientColorType_Size_); + + maColor[Color_Background] = maBackgroundColor; + const Color aSelectionColor (rpProperties->GetSelectionColor()); + maColor[Color_Selection] = aSelectionColor; + if (aSelectionColor.IsBright()) + maColor[Color_PageCountFontColor] = Black; + else + maColor[Color_PageCountFontColor] = White; + + // Set up gradients. + SetGradient(Gradient_MouseOverPage, aSelectionColor, 0, 60, +80,+100, +50,+25); + SetGradient(Gradient_SelectedPage, aSelectionColor, 50, 50, +80,+100, +50,+25); + SetGradient(Gradient_FocusedPage, aSelectionColor, -1,-1, 0,0, -50,-75); + SetGradient(Gradient_MouseOverSelected, aSelectionColor, 55, 60, +80,+100, +50,+25); + SetGradient(Gradient_SelectedAndFocusedPage, aSelectionColor, 50, 50, +80,+100, -50,-75); + SetGradient(Gradient_MouseOverSelectedAndFocusedPage, aSelectionColor, 55, 60, +80,+100, -50,-75); + + SetGradient(Gradient_NormalPage, maBackgroundColor, -1,-1, 0,0, 0,0); + + // The focused gradient needs special handling because its fill color is + // like that of the NormalPage gradient. + GetGradient(Gradient_FocusedPage).maFillColor1 = GetGradient(Gradient_NormalPage).maFillColor1; + GetGradient(Gradient_FocusedPage).maFillColor2 = GetGradient(Gradient_NormalPage).maFillColor2; + + // Set up icons. + if (maIcons.empty()) + { + maIcons.resize(IconType_Size_); + + InitializeIcon(Icon_RawShadow, IMAGE_SHADOW); + InitializeIcon(Icon_RawInsertShadow, IMAGE_INSERT_SHADOW); + InitializeIcon(Icon_HideSlideOverlay, IMAGE_HIDE_SLIDE_OVERLAY); + InitializeIcon(Icon_FocusBorder, IMAGE_FOCUS_BORDER); + } +} + +std::shared_ptr<vcl::Font> Theme::GetFont ( + const FontType eType, + const OutputDevice& rDevice) +{ + std::shared_ptr<vcl::Font> pFont; + + switch (eType) + { + case Font_PageNumber: + pFont = std::make_shared<vcl::Font>(Application::GetSettings().GetStyleSettings().GetAppFont()); + pFont->SetTransparent(true); + pFont->SetWeight(WEIGHT_BOLD); + break; + + case Font_PageCount: + pFont = std::make_shared<vcl::Font>(Application::GetSettings().GetStyleSettings().GetAppFont()); + pFont->SetTransparent(true); + pFont->SetWeight(WEIGHT_NORMAL); + { + const Size aSize (pFont->GetFontSize()); + pFont->SetFontSize(Size(aSize.Width()*5/3, aSize.Height()*5/3)); + } + break; + } + + if (pFont) + { + // Transform the point size to pixel size. + const MapMode aFontMapMode (MapUnit::MapPoint); + const Size aFontSize (rDevice.LogicToPixel(pFont->GetFontSize(), aFontMapMode)); + + // Transform the font size to the logical coordinates of the device. + pFont->SetFontSize(rDevice.PixelToLogic(aFontSize)); + } + + return pFont; +} + +Color Theme::GetColor (const ColorType eType) +{ + if (sal_uInt32(eType)<maColor.size()) + return maColor[eType]; + else + return Color(0); +} + +Color Theme::GetGradientColor ( + const GradientColorType eType, + const GradientColorClass eClass) +{ + GradientDescriptor& rDescriptor (GetGradient(eType)); + + switch (eClass) + { + case GradientColorClass::Border1: return rDescriptor.maBorderColor1; + case GradientColorClass::Border2: return rDescriptor.maBorderColor2; + case GradientColorClass::Fill1: return rDescriptor.maFillColor1; + case GradientColorClass::Fill2: return rDescriptor.maFillColor2; + } + return Color(0); +} + +void Theme::SetGradient ( + const GradientColorType eType, + const Color aBaseColor, + const sal_Int32 nSaturationOverride, + const sal_Int32 nBrightnessOverride, + const sal_Int32 nFillStartOffset, + const sal_Int32 nFillEndOffset, + const sal_Int32 nBorderStartOffset, + const sal_Int32 nBorderEndOffset) +{ + GradientDescriptor& rGradient (GetGradient(eType)); + + const Color aColor (nSaturationOverride>=0 || nBrightnessOverride>=0 + ? HGBAdapt(aBaseColor, nSaturationOverride, nBrightnessOverride) + : aBaseColor); + + rGradient.maFillColor1 = ChangeLuminance(aColor, nFillStartOffset); + rGradient.maFillColor2 = ChangeLuminance(aColor, nFillEndOffset); + rGradient.maBorderColor1 = ChangeLuminance(aColor, nBorderStartOffset); + rGradient.maBorderColor2 = ChangeLuminance(aColor, nBorderEndOffset); +} + +const BitmapEx& Theme::GetIcon (const IconType eType) +{ + if (size_t(eType)<maIcons.size()) + return maIcons[eType]; + else + { + OSL_ASSERT(eType>=0 && size_t(eType)<maIcons.size()); + return maIcons[0]; + } +} + +Theme::GradientDescriptor& Theme::GetGradient (const GradientColorType eType) +{ + if (size_t(eType)<maGradients.size()) + return maGradients[eType]; + else + { + OSL_ASSERT(eType>=0 && size_t(eType)<maGradients.size()); + return maGradients[0]; + } +} + +void Theme::InitializeIcon(const IconType eType, const OUString& rResourceId) +{ + if (size_t(eType)<maIcons.size()) + { + const BitmapEx aIcon(rResourceId); + maIcons[eType] = aIcon; + } + else + { + OSL_ASSERT(eType>=0 && size_t(eType)<maIcons.size()); + } +} + +} // end of namespace ::sd::slidesorter::view + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/view/SlsToolTip.cxx b/sd/source/ui/slidesorter/view/SlsToolTip.cxx new file mode 100644 index 000000000..c266bbe3d --- /dev/null +++ b/sd/source/ui/slidesorter/view/SlsToolTip.cxx @@ -0,0 +1,160 @@ +/* -*- 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 <view/SlsPageObjectLayouter.hxx> +#include <view/SlsToolTip.hxx> +#include <view/SlideSorterView.hxx> +#include <view/SlsLayouter.hxx> +#include <SlideSorter.hxx> +#include <Window.hxx> +#include <sdpage.hxx> +#include <sdresid.hxx> +#include <strings.hrc> + +#include <osl/diagnose.h> +#include <vcl/settings.hxx> +#include <vcl/help.hxx> + +namespace sd::slidesorter::view { + +ToolTip::ToolTip (SlideSorter& rSlideSorter) + : mrSlideSorter(rSlideSorter), + mnHelpWindowHandle(nullptr), + maShowTimer("sd::slidesorter::view::ToolTip maShowTimer"), + maHiddenTimer("sd::slidesorter::view::ToolTip maHiddenTimer") +{ + maShowTimer.SetTimeout(HelpSettings::GetTipDelay()); + maShowTimer.SetInvokeHandler(LINK(this, ToolTip, DelayTrigger)); + maHiddenTimer.SetTimeout(HelpSettings::GetTipDelay()); +} + +ToolTip::~ToolTip() +{ + maShowTimer.Stop(); + maHiddenTimer.Stop(); + Hide(); +} + +void ToolTip::SetPage (const model::SharedPageDescriptor& rpDescriptor) +{ + if (mpDescriptor == rpDescriptor) + return; + + maShowTimer.Stop(); + bool bWasVisible = Hide(); + + if (bWasVisible) + { + maHiddenTimer.Start(); + } + + mpDescriptor = rpDescriptor; + + if (mpDescriptor) + { + SdPage* pPage = mpDescriptor->GetPage(); + OUString sHelpText; + if (pPage != nullptr) + sHelpText = pPage->GetName(); + else + { + OSL_ASSERT(mpDescriptor->GetPage() != nullptr); + } + if (sHelpText.isEmpty()) + { + sHelpText = SdResId(STR_PAGE) + + OUString::number(mpDescriptor->GetPageIndex()+1); + } + + msCurrentHelpText = sHelpText; + // show new tooltip immediately, if last one was recently hidden + if(maHiddenTimer.IsActive()) + DoShow(); + else + maShowTimer.Start(); + } + else + { + msCurrentHelpText.clear(); + } +} + +void ToolTip::DoShow() +{ + if (maShowTimer.IsActive()) + { + // The delay timer is active. Wait for it to trigger the showing of + // the tool tip. + return; + } + + sd::Window *pWindow (mrSlideSorter.GetContentWindow().get()); + if (msCurrentHelpText.isEmpty() || !pWindow) + return; + + ::tools::Rectangle aBox ( + mrSlideSorter.GetView().GetLayouter().GetPageObjectLayouter()->GetBoundingBox( + mpDescriptor, + PageObjectLayouter::Part::Preview, + PageObjectLayouter::WindowCoordinateSystem)); + + // Do not show the help text when the (lower edge of the ) preview + // is not visible. The tool tip itself may still be outside the + // window. + if (aBox.Bottom() >= pWindow->GetSizePixel().Height()) + return; + + vcl::Window* pParent (pWindow); + while (pParent!=nullptr && pParent->GetParent()!=nullptr) + pParent = pParent->GetParent(); + const Point aOffset (pWindow->GetWindowExtentsRelative(pParent).TopLeft()); + + // We do not know how high the tool tip will be but want its top + // edge not its bottom to be at a specific position (a little below + // the preview). Therefore we use a little trick and place the tool + // tip at the top of a rectangle that is placed below the preview. + aBox.Move(aOffset.X(), aOffset.Y() + aBox.GetHeight() + 3); + mnHelpWindowHandle = Help::ShowPopover( + pWindow, + aBox, + msCurrentHelpText, + QuickHelpFlags::Center | QuickHelpFlags::Top); +} + +bool ToolTip::Hide() +{ + if (mnHelpWindowHandle) + { + sd::Window *pWindow (mrSlideSorter.GetContentWindow().get()); + Help::HidePopover(pWindow, mnHelpWindowHandle); + mnHelpWindowHandle = nullptr; + return true; + } + else + return false; +} + +IMPL_LINK_NOARG(ToolTip, DelayTrigger, Timer *, void) +{ + DoShow(); +} + +} // end of namespace ::sd::slidesorter::view + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/view/SlsViewCacheContext.cxx b/sd/source/ui/slidesorter/view/SlsViewCacheContext.cxx new file mode 100644 index 000000000..ce27cec28 --- /dev/null +++ b/sd/source/ui/slidesorter/view/SlsViewCacheContext.cxx @@ -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 . + */ + +#include "SlsViewCacheContext.hxx" + +#include <SlideSorter.hxx> +#include <model/SlideSorterModel.hxx> +#include <model/SlsPageDescriptor.hxx> +#include <model/SlsPageEnumerationProvider.hxx> +#include <view/SlideSorterView.hxx> +#include <sdpage.hxx> +#include <Window.hxx> +#include <drawdoc.hxx> +#include <tools/IdleDetection.hxx> +#include <svx/svdpage.hxx> + +namespace sd::slidesorter::view { + +ViewCacheContext::ViewCacheContext (SlideSorter& rSlideSorter) + : mrModel(rSlideSorter.GetModel()), + mrSlideSorter(rSlideSorter) +{ +} + +ViewCacheContext::~ViewCacheContext() +{ +} + +void ViewCacheContext::NotifyPreviewCreation(cache::CacheKey aKey) +{ + const model::SharedPageDescriptor pDescriptor (GetDescriptor(aKey)); + if (pDescriptor) + { + // Force a repaint that will trigger their re-creation. + mrSlideSorter.GetView().RequestRepaint(pDescriptor); + } + else + { + // It is OK when a preview was created for a page that is not + // currently displayed because both normal and master pages are + // kept in the same cache. + } +} + +bool ViewCacheContext::IsIdle() +{ + tools::IdleState nIdleState (tools::IdleDetection::GetIdleState(mrSlideSorter.GetContentWindow())); + return nIdleState == tools::IdleState::Idle; +} + +bool ViewCacheContext::IsVisible (cache::CacheKey aKey) +{ + const model::SharedPageDescriptor pDescriptor (GetDescriptor(aKey)); + return pDescriptor && pDescriptor->HasState(model::PageDescriptor::ST_Visible); +} + +const SdrPage* ViewCacheContext::GetPage (cache::CacheKey aKey) +{ + return aKey; +} + +std::shared_ptr<std::vector<cache::CacheKey> > ViewCacheContext::GetEntryList (bool bVisible) +{ + auto pKeys = std::make_shared<std::vector<cache::CacheKey>>(); + + model::PageEnumeration aPageEnumeration ( + bVisible + ? model::PageEnumerationProvider::CreateVisiblePagesEnumeration(mrModel) + : model::PageEnumerationProvider::CreateAllPagesEnumeration(mrModel)); + + while (aPageEnumeration.HasMoreElements()) + { + model::SharedPageDescriptor pDescriptor (aPageEnumeration.GetNextElement()); + pKeys->push_back(pDescriptor->GetPage()); + } + + return pKeys; +} + +sal_Int32 ViewCacheContext::GetPriority (cache::CacheKey aKey) +{ + return - (aKey->GetPageNum()-1) / 2; +} + +model::SharedPageDescriptor ViewCacheContext::GetDescriptor (cache::CacheKey aKey) +{ + sal_uInt16 nPageIndex ((aKey->GetPageNum() - 1) / 2); + return mrModel.GetPageDescriptor(nPageIndex); +} + +css::uno::Reference<css::uno::XInterface> ViewCacheContext::GetModel() +{ + if (mrModel.GetDocument() == nullptr) + return nullptr; + else + return mrModel.GetDocument()->getUnoModel(); +} + +} // end of namespace ::sd::slidesorter::view + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/slidesorter/view/SlsViewCacheContext.hxx b/sd/source/ui/slidesorter/view/SlsViewCacheContext.hxx new file mode 100644 index 000000000..501517cb8 --- /dev/null +++ b/sd/source/ui/slidesorter/view/SlsViewCacheContext.hxx @@ -0,0 +1,61 @@ +/* -*- 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 <cache/SlsCacheContext.hxx> +#include <model/SlsSharedPageDescriptor.hxx> + +namespace sd::slidesorter::model +{ +class SlideSorterModel; +} +namespace sd::slidesorter +{ +class SlideSorter; +} + +namespace sd::slidesorter::view +{ +/** The cache context for the SlideSorter as used by Draw and Impress. See + the base class for documentation of the individual methods. +*/ +class ViewCacheContext : public cache::CacheContext +{ +public: + explicit ViewCacheContext(SlideSorter& rSlideSorter); + virtual ~ViewCacheContext() override; + virtual void NotifyPreviewCreation(cache::CacheKey aKey) override; + virtual bool IsIdle() override; + virtual bool IsVisible(cache::CacheKey aKey) override; + virtual const SdrPage* GetPage(cache::CacheKey aKey) override; + virtual std::shared_ptr<std::vector<cache::CacheKey>> GetEntryList(bool bVisible) override; + virtual sal_Int32 GetPriority(cache::CacheKey aKey) override; + virtual css::uno::Reference<css::uno::XInterface> GetModel() override; + +private: + model::SlideSorterModel& mrModel; + SlideSorter& mrSlideSorter; + + model::SharedPageDescriptor GetDescriptor(cache::CacheKey aKey); +}; + +} // end of namespace ::sd::slidesorter::view + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |