summaryrefslogtreecommitdiffstats
path: root/sd/source/ui/slidesorter/view
diff options
context:
space:
mode:
Diffstat (limited to 'sd/source/ui/slidesorter/view')
-rw-r--r--sd/source/ui/slidesorter/view/SlideSorterView.cxx856
-rw-r--r--sd/source/ui/slidesorter/view/SlsFramePainter.cxx225
-rw-r--r--sd/source/ui/slidesorter/view/SlsFramePainter.hxx109
-rw-r--r--sd/source/ui/slidesorter/view/SlsInsertAnimator.cxx428
-rw-r--r--sd/source/ui/slidesorter/view/SlsInsertionIndicatorOverlay.cxx360
-rw-r--r--sd/source/ui/slidesorter/view/SlsLayeredDevice.cxx491
-rw-r--r--sd/source/ui/slidesorter/view/SlsLayeredDevice.hxx84
-rw-r--r--sd/source/ui/slidesorter/view/SlsLayouter.cxx1225
-rw-r--r--sd/source/ui/slidesorter/view/SlsPageObjectLayouter.cxx259
-rw-r--r--sd/source/ui/slidesorter/view/SlsPageObjectPainter.cxx442
-rw-r--r--sd/source/ui/slidesorter/view/SlsTheme.cxx239
-rw-r--r--sd/source/ui/slidesorter/view/SlsToolTip.cxx160
-rw-r--r--sd/source/ui/slidesorter/view/SlsViewCacheContext.cxx117
-rw-r--r--sd/source/ui/slidesorter/view/SlsViewCacheContext.hxx61
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: */