summaryrefslogtreecommitdiffstats
path: root/sd/source/ui/slidesorter
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sd/source/ui/slidesorter
parentInitial commit. (diff)
downloadlibreoffice-upstream/4%7.4.7.tar.xz
libreoffice-upstream/4%7.4.7.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sd/source/ui/slidesorter')
-rw-r--r--sd/source/ui/slidesorter/cache/SlsBitmapCache.cxx550
-rw-r--r--sd/source/ui/slidesorter/cache/SlsBitmapCache.hxx208
-rw-r--r--sd/source/ui/slidesorter/cache/SlsBitmapCompressor.cxx197
-rw-r--r--sd/source/ui/slidesorter/cache/SlsBitmapCompressor.hxx138
-rw-r--r--sd/source/ui/slidesorter/cache/SlsBitmapFactory.cxx71
-rw-r--r--sd/source/ui/slidesorter/cache/SlsBitmapFactory.hxx46
-rw-r--r--sd/source/ui/slidesorter/cache/SlsCacheCompactor.cxx189
-rw-r--r--sd/source/ui/slidesorter/cache/SlsCacheCompactor.hxx87
-rw-r--r--sd/source/ui/slidesorter/cache/SlsCacheConfiguration.cxx144
-rw-r--r--sd/source/ui/slidesorter/cache/SlsCacheConfiguration.hxx68
-rw-r--r--sd/source/ui/slidesorter/cache/SlsGenericPageCache.cxx278
-rw-r--r--sd/source/ui/slidesorter/cache/SlsGenericPageCache.hxx152
-rw-r--r--sd/source/ui/slidesorter/cache/SlsPageCache.cxx109
-rw-r--r--sd/source/ui/slidesorter/cache/SlsPageCacheManager.cxx420
-rw-r--r--sd/source/ui/slidesorter/cache/SlsQueueProcessor.cxx176
-rw-r--r--sd/source/ui/slidesorter/cache/SlsQueueProcessor.hxx98
-rw-r--r--sd/source/ui/slidesorter/cache/SlsRequestFactory.cxx50
-rw-r--r--sd/source/ui/slidesorter/cache/SlsRequestFactory.hxx36
-rw-r--r--sd/source/ui/slidesorter/cache/SlsRequestPriorityClass.hxx44
-rw-r--r--sd/source/ui/slidesorter/cache/SlsRequestQueue.cxx275
-rw-r--r--sd/source/ui/slidesorter/cache/SlsRequestQueue.hxx122
-rw-r--r--sd/source/ui/slidesorter/controller/SlideSorterController.cxx910
-rw-r--r--sd/source/ui/slidesorter/controller/SlsAnimationFunction.cxx129
-rw-r--r--sd/source/ui/slidesorter/controller/SlsAnimator.cxx280
-rw-r--r--sd/source/ui/slidesorter/controller/SlsClipboard.cxx918
-rw-r--r--sd/source/ui/slidesorter/controller/SlsCurrentSlideManager.cxx256
-rw-r--r--sd/source/ui/slidesorter/controller/SlsDragAndDropContext.cxx120
-rw-r--r--sd/source/ui/slidesorter/controller/SlsDragAndDropContext.hxx68
-rw-r--r--sd/source/ui/slidesorter/controller/SlsFocusManager.cxx245
-rw-r--r--sd/source/ui/slidesorter/controller/SlsInsertionIndicatorHandler.cxx243
-rw-r--r--sd/source/ui/slidesorter/controller/SlsListener.cxx597
-rw-r--r--sd/source/ui/slidesorter/controller/SlsListener.hxx164
-rw-r--r--sd/source/ui/slidesorter/controller/SlsPageSelector.cxx386
-rw-r--r--sd/source/ui/slidesorter/controller/SlsProperties.cxx106
-rw-r--r--sd/source/ui/slidesorter/controller/SlsScrollBarManager.cxx608
-rw-r--r--sd/source/ui/slidesorter/controller/SlsSelectionFunction.cxx1485
-rw-r--r--sd/source/ui/slidesorter/controller/SlsSelectionManager.cxx309
-rw-r--r--sd/source/ui/slidesorter/controller/SlsSelectionObserver.cxx139
-rw-r--r--sd/source/ui/slidesorter/controller/SlsSlotManager.cxx1284
-rw-r--r--sd/source/ui/slidesorter/controller/SlsTransferableData.cxx86
-rw-r--r--sd/source/ui/slidesorter/controller/SlsVisibleAreaManager.cxx234
-rw-r--r--sd/source/ui/slidesorter/inc/cache/SlsCacheContext.hxx98
-rw-r--r--sd/source/ui/slidesorter/inc/cache/SlsPageCache.hxx141
-rw-r--r--sd/source/ui/slidesorter/inc/cache/SlsPageCacheManager.hxx155
-rw-r--r--sd/source/ui/slidesorter/inc/controller/SlideSorterController.hxx327
-rw-r--r--sd/source/ui/slidesorter/inc/controller/SlsAnimationFunction.hxx77
-rw-r--r--sd/source/ui/slidesorter/inc/controller/SlsAnimator.hxx122
-rw-r--r--sd/source/ui/slidesorter/inc/controller/SlsClipboard.hxx208
-rw-r--r--sd/source/ui/slidesorter/inc/controller/SlsCurrentSlideManager.hxx112
-rw-r--r--sd/source/ui/slidesorter/inc/controller/SlsFocusManager.hxx211
-rw-r--r--sd/source/ui/slidesorter/inc/controller/SlsInsertionIndicatorHandler.hxx138
-rw-r--r--sd/source/ui/slidesorter/inc/controller/SlsPageSelector.hxx219
-rw-r--r--sd/source/ui/slidesorter/inc/controller/SlsProperties.hxx125
-rw-r--r--sd/source/ui/slidesorter/inc/controller/SlsScrollBarManager.hxx248
-rw-r--r--sd/source/ui/slidesorter/inc/controller/SlsSelectionFunction.hxx145
-rw-r--r--sd/source/ui/slidesorter/inc/controller/SlsSelectionManager.hxx139
-rw-r--r--sd/source/ui/slidesorter/inc/controller/SlsSelectionObserver.hxx77
-rw-r--r--sd/source/ui/slidesorter/inc/controller/SlsSlotManager.hxx98
-rw-r--r--sd/source/ui/slidesorter/inc/controller/SlsTransferableData.hxx78
-rw-r--r--sd/source/ui/slidesorter/inc/controller/SlsVisibleAreaManager.hxx90
-rw-r--r--sd/source/ui/slidesorter/inc/model/SlideSorterModel.hxx227
-rw-r--r--sd/source/ui/slidesorter/inc/model/SlsEnumeration.hxx44
-rw-r--r--sd/source/ui/slidesorter/inc/model/SlsPageDescriptor.hxx144
-rw-r--r--sd/source/ui/slidesorter/inc/model/SlsPageEnumeration.hxx95
-rw-r--r--sd/source/ui/slidesorter/inc/model/SlsPageEnumerationProvider.hxx51
-rw-r--r--sd/source/ui/slidesorter/inc/model/SlsSharedPageDescriptor.hxx32
-rw-r--r--sd/source/ui/slidesorter/inc/model/SlsVisualState.hxx47
-rw-r--r--sd/source/ui/slidesorter/inc/view/SlideSorterView.hxx225
-rw-r--r--sd/source/ui/slidesorter/inc/view/SlsILayerPainter.hxx53
-rw-r--r--sd/source/ui/slidesorter/inc/view/SlsInsertAnimator.hxx59
-rw-r--r--sd/source/ui/slidesorter/inc/view/SlsInsertionIndicatorOverlay.hxx101
-rw-r--r--sd/source/ui/slidesorter/inc/view/SlsLayouter.hxx237
-rw-r--r--sd/source/ui/slidesorter/inc/view/SlsPageObjectLayouter.hxx144
-rw-r--r--sd/source/ui/slidesorter/inc/view/SlsPageObjectPainter.hxx119
-rw-r--r--sd/source/ui/slidesorter/inc/view/SlsTheme.hxx135
-rw-r--r--sd/source/ui/slidesorter/inc/view/SlsToolTip.hxx75
-rw-r--r--sd/source/ui/slidesorter/model/SlideSorterModel.cxx676
-rw-r--r--sd/source/ui/slidesorter/model/SlsPageDescriptor.cxx226
-rw-r--r--sd/source/ui/slidesorter/model/SlsPageEnumeration.cxx202
-rw-r--r--sd/source/ui/slidesorter/model/SlsPageEnumerationProvider.cxx81
-rw-r--r--sd/source/ui/slidesorter/model/SlsVisualState.cxx40
-rw-r--r--sd/source/ui/slidesorter/shell/SlideSorter.cxx456
-rw-r--r--sd/source/ui/slidesorter/shell/SlideSorterService.cxx412
-rw-r--r--sd/source/ui/slidesorter/shell/SlideSorterService.hxx153
-rw-r--r--sd/source/ui/slidesorter/shell/SlideSorterViewShell.cxx924
-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
99 files changed, 24847 insertions, 0 deletions
diff --git a/sd/source/ui/slidesorter/cache/SlsBitmapCache.cxx b/sd/source/ui/slidesorter/cache/SlsBitmapCache.cxx
new file mode 100644
index 000000000..87c727408
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsBitmapCache.cxx
@@ -0,0 +1,550 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <unordered_map>
+#include "SlsBitmapCache.hxx"
+#include "SlsCacheCompactor.hxx"
+#include "SlsBitmapCompressor.hxx"
+#include "SlsCacheConfiguration.hxx"
+
+#include <sal/log.hxx>
+
+// Define the default value for the maximal cache size that is used for
+// previews that are currently not visible. The visible previews are all
+// held in memory at all times. This default is used only when the
+// configuration does not have a value.
+const sal_Int32 MAXIMAL_CACHE_SIZE = 4*1024L*1024L;
+
+using namespace ::com::sun::star::uno;
+
+namespace sd::slidesorter::cache {
+
+class BitmapCache::CacheEntry
+{
+public:
+ CacheEntry(const BitmapEx& rBitmap, sal_Int32 nLastAccessTime, bool bIsPrecious);
+ CacheEntry(sal_Int32 nLastAccessTime, bool bIsPrecious);
+ inline void Recycle (const CacheEntry& rEntry);
+ inline sal_Int32 GetMemorySize() const;
+ void Compress (const std::shared_ptr<BitmapCompressor>& rpCompressor);
+ inline void Decompress();
+
+ bool IsUpToDate() const { return mbIsUpToDate; }
+ void SetUpToDate (bool bIsUpToDate) { mbIsUpToDate = bIsUpToDate; }
+ sal_Int32 GetAccessTime() const { return mnLastAccessTime; }
+ void SetAccessTime (sal_Int32 nAccessTime) { mnLastAccessTime = nAccessTime; }
+
+ const BitmapEx& GetPreview() const { return maPreview; }
+ inline void SetPreview (const BitmapEx& rPreview);
+ bool HasPreview() const;
+
+ const BitmapEx& GetMarkedPreview() const { return maMarkedPreview; }
+ inline void SetMarkedPreview (const BitmapEx& rMarkePreview);
+
+ bool HasReplacement() const { return (mpReplacement != nullptr); }
+ inline bool HasLosslessReplacement() const;
+ void Invalidate() { mpReplacement.reset(); mpCompressor.reset(); mbIsUpToDate = false; }
+ bool IsPrecious() const { return mbIsPrecious; }
+ void SetPrecious (bool bIsPrecious) { mbIsPrecious = bIsPrecious; }
+
+private:
+ BitmapEx maPreview;
+ BitmapEx maMarkedPreview;
+ std::shared_ptr<BitmapReplacement> mpReplacement;
+ std::shared_ptr<BitmapCompressor> mpCompressor;
+ bool mbIsUpToDate;
+ sal_Int32 mnLastAccessTime;
+ // When this flag is set then the bitmap is not modified by a cache
+ // compactor.
+ bool mbIsPrecious;
+};
+
+namespace {
+
+class CacheHash {
+public:
+ size_t operator()(const BitmapCache::CacheKey& p) const
+ { return reinterpret_cast<size_t>(p); }
+};
+
+}
+
+class BitmapCache::CacheBitmapContainer
+ : public std::unordered_map<CacheKey, CacheEntry, CacheHash>
+{
+public:
+ CacheBitmapContainer() {}
+};
+
+namespace {
+
+typedef ::std::vector<
+ ::std::pair< ::sd::slidesorter::cache::BitmapCache::CacheKey,
+ ::sd::slidesorter::cache::BitmapCache::CacheEntry>
+ > SortableBitmapContainer;
+
+ /** Compare elements of the bitmap cache according to their last access
+ time.
+ */
+ class AccessTimeComparator
+ {
+ public:
+ bool operator () (
+ const SortableBitmapContainer::value_type& e1,
+ const SortableBitmapContainer::value_type& e2)
+ {
+ return e1.second.GetAccessTime() < e2.second.GetAccessTime();
+ }
+ };
+
+} // end of anonymous namespace
+
+//===== BitmapCache =========================================================
+
+BitmapCache::BitmapCache ()
+ : mpBitmapContainer(new CacheBitmapContainer()),
+ mnNormalCacheSize(0),
+ mnPreciousCacheSize(0),
+ mnCurrentAccessTime(0),
+ mnMaximalNormalCacheSize(MAXIMAL_CACHE_SIZE),
+ mbIsFull(false)
+{
+ Any aCacheSize (CacheConfiguration::Instance()->GetValue("CacheSize"));
+ if (aCacheSize.has<sal_Int32>())
+ aCacheSize >>= mnMaximalNormalCacheSize;
+
+ mpCacheCompactor = CacheCompactor::Create(*this,mnMaximalNormalCacheSize);
+}
+
+BitmapCache::~BitmapCache()
+{
+ Clear();
+}
+
+void BitmapCache::Clear()
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ mpBitmapContainer->clear();
+ mnNormalCacheSize = 0;
+ mnPreciousCacheSize = 0;
+ mnCurrentAccessTime = 0;
+}
+
+bool BitmapCache::HasBitmap (const CacheKey& rKey)
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey));
+ return (iEntry != mpBitmapContainer->end()
+ && (iEntry->second.HasPreview() || iEntry->second.HasReplacement()));
+}
+
+bool BitmapCache::BitmapIsUpToDate (const CacheKey& rKey)
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ bool bIsUpToDate = false;
+ CacheBitmapContainer::iterator aIterator (mpBitmapContainer->find(rKey));
+ if (aIterator != mpBitmapContainer->end())
+ bIsUpToDate = aIterator->second.IsUpToDate();
+
+ return bIsUpToDate;
+}
+
+BitmapEx BitmapCache::GetBitmap (const CacheKey& rKey)
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey));
+ if (iEntry == mpBitmapContainer->end())
+ {
+ // Create an empty bitmap for the given key that acts as placeholder
+ // until we are given the real one. Mark it as not being up to date.
+ SetBitmap(rKey, BitmapEx(), false);
+ iEntry = mpBitmapContainer->find(rKey);
+ iEntry->second.SetUpToDate(false);
+ }
+ else
+ {
+ iEntry->second.SetAccessTime(mnCurrentAccessTime++);
+
+ // Maybe we have to decompress the preview.
+ if ( ! iEntry->second.HasPreview() && iEntry->second.HasReplacement())
+ {
+ UpdateCacheSize(iEntry->second, REMOVE);
+ iEntry->second.Decompress();
+ UpdateCacheSize(iEntry->second, ADD);
+ }
+ }
+ return iEntry->second.GetPreview();
+}
+
+BitmapEx BitmapCache::GetMarkedBitmap (const CacheKey& rKey)
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey));
+ if (iEntry != mpBitmapContainer->end())
+ {
+ iEntry->second.SetAccessTime(mnCurrentAccessTime++);
+ return iEntry->second.GetMarkedPreview();
+ }
+ else
+ return BitmapEx();
+}
+
+void BitmapCache::ReleaseBitmap (const CacheKey& rKey)
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ CacheBitmapContainer::iterator aIterator (mpBitmapContainer->find(rKey));
+ if (aIterator != mpBitmapContainer->end())
+ {
+ UpdateCacheSize(aIterator->second, REMOVE);
+ mpBitmapContainer->erase(aIterator);
+ }
+}
+
+bool BitmapCache::InvalidateBitmap (const CacheKey& rKey)
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey));
+ if (iEntry != mpBitmapContainer->end())
+ {
+ iEntry->second.SetUpToDate(false);
+
+ // When there is a preview then we release the replacement. The
+ // preview itself is kept until a new one is created.
+ if (iEntry->second.HasPreview())
+ {
+ UpdateCacheSize(iEntry->second, REMOVE);
+ iEntry->second.Invalidate();
+ UpdateCacheSize(iEntry->second, ADD);
+ }
+ return true;
+ }
+ else
+ return false;
+}
+
+void BitmapCache::InvalidateCache()
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ for (auto& rEntry : *mpBitmapContainer)
+ {
+ rEntry.second.Invalidate();
+ }
+ ReCalculateTotalCacheSize();
+}
+
+void BitmapCache::SetBitmap (
+ const CacheKey& rKey,
+ const BitmapEx& rPreview,
+ bool bIsPrecious)
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey));
+ if (iEntry != mpBitmapContainer->end())
+ {
+ UpdateCacheSize(iEntry->second, REMOVE);
+ iEntry->second.SetPreview(rPreview);
+ iEntry->second.SetUpToDate(true);
+ iEntry->second.SetAccessTime(mnCurrentAccessTime++);
+ }
+ else
+ {
+ iEntry = mpBitmapContainer->emplace(
+ rKey,
+ CacheEntry(rPreview, mnCurrentAccessTime++, bIsPrecious)
+ ).first;
+ }
+
+ if (iEntry != mpBitmapContainer->end())
+ UpdateCacheSize(iEntry->second, ADD);
+}
+
+void BitmapCache::SetMarkedBitmap (
+ const CacheKey& rKey,
+ const BitmapEx& rPreview)
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey));
+ if (iEntry != mpBitmapContainer->end())
+ {
+ UpdateCacheSize(iEntry->second, REMOVE);
+ iEntry->second.SetMarkedPreview(rPreview);
+ iEntry->second.SetAccessTime(mnCurrentAccessTime++);
+ UpdateCacheSize(iEntry->second, ADD);
+ }
+}
+
+void BitmapCache::SetPrecious (const CacheKey& rKey, bool bIsPrecious)
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey));
+ if (iEntry != mpBitmapContainer->end())
+ {
+ if (iEntry->second.IsPrecious() != bIsPrecious)
+ {
+ UpdateCacheSize(iEntry->second, REMOVE);
+ iEntry->second.SetPrecious(bIsPrecious);
+ UpdateCacheSize(iEntry->second, ADD);
+ }
+ }
+ else if (bIsPrecious)
+ {
+ iEntry = mpBitmapContainer->emplace(
+ rKey,
+ CacheEntry(BitmapEx(), mnCurrentAccessTime++, bIsPrecious)
+ ).first;
+ UpdateCacheSize(iEntry->second, ADD);
+ }
+}
+
+void BitmapCache::ReCalculateTotalCacheSize()
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ mnNormalCacheSize = 0;
+ mnPreciousCacheSize = 0;
+ for (const auto& rEntry : *mpBitmapContainer)
+ {
+ if (rEntry.second.IsPrecious())
+ mnPreciousCacheSize += rEntry.second.GetMemorySize();
+ else
+ mnNormalCacheSize += rEntry.second.GetMemorySize();
+ }
+ mbIsFull = (mnNormalCacheSize >= mnMaximalNormalCacheSize);
+
+ SAL_INFO("sd.sls", __func__ << ": cache size is " << mnNormalCacheSize << "/" << mnPreciousCacheSize);
+}
+
+void BitmapCache::Recycle (const BitmapCache& rCache)
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ for (const auto& rOtherEntry : *rCache.mpBitmapContainer)
+ {
+ CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rOtherEntry.first));
+ if (iEntry == mpBitmapContainer->end())
+ {
+ iEntry = mpBitmapContainer->emplace(
+ rOtherEntry.first,
+ CacheEntry(mnCurrentAccessTime++, true)
+ ).first;
+ UpdateCacheSize(iEntry->second, ADD);
+ }
+ if (iEntry != mpBitmapContainer->end())
+ {
+ UpdateCacheSize(iEntry->second, REMOVE);
+ iEntry->second.Recycle(rOtherEntry.second);
+ UpdateCacheSize(iEntry->second, ADD);
+ }
+ }
+}
+
+BitmapCache::CacheIndex BitmapCache::GetCacheIndex() const
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ // Create a copy of the bitmap container.
+ SortableBitmapContainer aSortedContainer;
+ aSortedContainer.reserve(mpBitmapContainer->size());
+
+ // Copy the relevant entries.
+ for (const auto& rEntry : *mpBitmapContainer)
+ {
+ if ( rEntry.second.IsPrecious())
+ continue;
+
+ if ( ! rEntry.second.HasPreview())
+ continue;
+
+ aSortedContainer.emplace_back(rEntry.first, rEntry.second);
+ }
+
+ // Sort the remaining entries.
+ ::std::sort(aSortedContainer.begin(), aSortedContainer.end(), AccessTimeComparator());
+
+ // Return a list with the keys of the sorted entries.
+ CacheIndex aIndex;
+ aIndex.reserve(aSortedContainer.size());
+ for (const auto& rIndexEntry : aSortedContainer)
+ aIndex.push_back(rIndexEntry.first);
+ return aIndex;
+}
+
+void BitmapCache::Compress (
+ const CacheKey& rKey,
+ const std::shared_ptr<BitmapCompressor>& rpCompressor)
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ CacheBitmapContainer::iterator iEntry (mpBitmapContainer->find(rKey));
+ if (iEntry != mpBitmapContainer->end() && iEntry->second.HasPreview())
+ {
+ UpdateCacheSize(iEntry->second, REMOVE);
+ iEntry->second.Compress(rpCompressor);
+ UpdateCacheSize(iEntry->second, ADD);
+ }
+}
+
+void BitmapCache::UpdateCacheSize (const CacheEntry& rEntry, CacheOperation eOperation)
+{
+ sal_Int32 nEntrySize (rEntry.GetMemorySize());
+ sal_Int32& rCacheSize (rEntry.IsPrecious() ? mnPreciousCacheSize : mnNormalCacheSize);
+ switch (eOperation)
+ {
+ case ADD:
+ rCacheSize += nEntrySize;
+ if ( ! rEntry.IsPrecious() && mnNormalCacheSize>mnMaximalNormalCacheSize)
+ {
+ mbIsFull = true;
+ SAL_INFO("sd.sls", __func__ << ": cache size is " << mnNormalCacheSize << " > " << mnMaximalNormalCacheSize);
+ mpCacheCompactor->RequestCompaction();
+ }
+ break;
+
+ case REMOVE:
+ rCacheSize -= nEntrySize;
+ if (mnNormalCacheSize < mnMaximalNormalCacheSize)
+ mbIsFull = false;
+ break;
+
+ default:
+ assert(false);
+ break;
+ }
+}
+
+//===== CacheEntry ============================================================
+
+BitmapCache::CacheEntry::CacheEntry(
+ sal_Int32 nLastAccessTime,
+ bool bIsPrecious)
+ : mbIsUpToDate(true),
+ mnLastAccessTime(nLastAccessTime),
+ mbIsPrecious(bIsPrecious)
+{
+}
+
+BitmapCache::CacheEntry::CacheEntry(
+ const BitmapEx& rPreview,
+ sal_Int32 nLastAccessTime,
+ bool bIsPrecious)
+ : maPreview(rPreview),
+ mbIsUpToDate(true),
+ mnLastAccessTime(nLastAccessTime),
+ mbIsPrecious(bIsPrecious)
+{
+}
+
+inline void BitmapCache::CacheEntry::Recycle (const CacheEntry& rEntry)
+{
+ if ((rEntry.HasPreview() || rEntry.HasLosslessReplacement())
+ && ! (HasPreview() || HasLosslessReplacement()))
+ {
+ maPreview = rEntry.maPreview;
+ maMarkedPreview = rEntry.maMarkedPreview;
+ mpReplacement = rEntry.mpReplacement;
+ mpCompressor = rEntry.mpCompressor;
+ mnLastAccessTime = rEntry.mnLastAccessTime;
+ mbIsUpToDate = rEntry.mbIsUpToDate;
+ }
+}
+
+inline sal_Int32 BitmapCache::CacheEntry::GetMemorySize() const
+{
+ sal_Int32 nSize (0);
+ nSize += maPreview.GetSizeBytes();
+ nSize += maMarkedPreview.GetSizeBytes();
+ if (mpReplacement != nullptr)
+ nSize += mpReplacement->GetMemorySize();
+ return nSize;
+}
+
+void BitmapCache::CacheEntry::Compress (const std::shared_ptr<BitmapCompressor>& rpCompressor)
+{
+ if ( maPreview.IsEmpty())
+ return;
+
+ if (mpReplacement == nullptr)
+ {
+ mpReplacement = rpCompressor->Compress(maPreview);
+
+#ifdef DEBUG_SD_SLSBITMAPCACHE
+ sal_uInt32 nOldSize (maPreview.GetSizeBytes());
+ sal_uInt32 nNewSize (mpReplacement.get()!=NULL ? mpReplacement->GetMemorySize() : 0);
+ if (nOldSize == 0)
+ nOldSize = 1;
+ sal_Int32 nRatio (100L * nNewSize / nOldSize);
+ SAL_INFO("sd.sls", __func__ << ": compressing bitmap for " << %x << " from " << nOldSize << " to " << nNewSize << " bytes (" << nRatio << "%)");
+#endif
+
+ mpCompressor = rpCompressor;
+ }
+
+ maPreview.SetEmpty();
+ maMarkedPreview.SetEmpty();
+}
+
+inline void BitmapCache::CacheEntry::Decompress()
+{
+ if (mpReplacement != nullptr && mpCompressor != nullptr && maPreview.IsEmpty())
+ {
+ maPreview = mpCompressor->Decompress(*mpReplacement);
+ maMarkedPreview.SetEmpty();
+ if ( ! mpCompressor->IsLossless())
+ mbIsUpToDate = false;
+ }
+}
+
+inline void BitmapCache::CacheEntry::SetPreview (const BitmapEx& rPreview)
+{
+ maPreview = rPreview;
+ maMarkedPreview.SetEmpty();
+ mpReplacement.reset();
+ mpCompressor.reset();
+}
+
+bool BitmapCache::CacheEntry::HasPreview() const
+{
+ return ! maPreview.IsEmpty();
+}
+
+inline void BitmapCache::CacheEntry::SetMarkedPreview (const BitmapEx& rMarkedPreview)
+{
+ maMarkedPreview = rMarkedPreview;
+}
+
+inline bool BitmapCache::CacheEntry::HasLosslessReplacement() const
+{
+ return mpReplacement != nullptr && mpCompressor != nullptr && mpCompressor->IsLossless();
+}
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsBitmapCache.hxx b/sd/source/ui/slidesorter/cache/SlsBitmapCache.hxx
new file mode 100644
index 000000000..98b0bcb53
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsBitmapCache.hxx
@@ -0,0 +1,208 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <vcl/bitmapex.hxx>
+#include <osl/mutex.hxx>
+#include <memory>
+
+class SdrPage;
+
+namespace sd::slidesorter::cache
+{
+class CacheCompactor;
+class BitmapCompressor;
+
+/** This low level cache is the actual bitmap container. It supports a
+ precious flag for every preview bitmap and keeps track of total sizes
+ for all previews with/without this flag. The precious flag is used by
+ compaction algorithms to determine which previews may be compressed or
+ even discarded and which have to remain in their original form. The
+ precious flag is usually set for the visible previews.
+
+ Additionally to the actual preview there is an optional marked preview.
+ This is used for slides excluded from the slide show which have a preview
+ that shows a mark (some sort of bitmap overlay) to that effect.
+*/
+class BitmapCache
+{
+public:
+ /** The key for looking up preview bitmaps is a pointer to an SdrPage
+ object. The prior use of PageObjectViewObjectContact objects (which
+ ultimately use them) turned out to be less suitable because their
+ life time is shorter then that of the page objects. Frequent
+ destruction and re-creation of the preview bitmaps was the result.
+ */
+ typedef const SdrPage* CacheKey;
+ class CacheEntry;
+ class CacheBitmapContainer;
+ typedef ::std::vector<CacheKey> CacheIndex;
+
+ /** Create a new cache for bitmap objects.
+ The default value from the configuration is used.
+ When that does not exist then an internal default value is
+ used.
+ */
+ explicit BitmapCache();
+
+ /** The destructor clears the cache and releases all bitmaps still in it.
+ */
+ ~BitmapCache();
+
+ /** Remove all preview bitmaps from the cache. After this call the
+ cache is empty.
+ */
+ void Clear();
+
+ /** Return <TRUE/> when the cache is full, i.e. the cache compactor had
+ to be run.
+ */
+ bool IsFull() const { return mbIsFull; }
+
+ /** Return the memory size that is occupied by all non-precious bitmaps
+ in the cache.
+ */
+ sal_Int32 GetSize() const { return mnNormalCacheSize; }
+
+ /** Return <TRUE/> when a preview bitmap exists for the given key.
+ */
+ bool HasBitmap(const CacheKey& rKey);
+
+ /** Return <TRUE/> when a preview bitmap exists for the given key and
+ when it is up-to-date.
+ */
+ bool BitmapIsUpToDate(const CacheKey& rKey);
+
+ /** Return the preview bitmap for the given contact object.
+ */
+ BitmapEx GetBitmap(const CacheKey& rKey);
+
+ /** Return the marked preview bitmap for the given contact object.
+ */
+ BitmapEx GetMarkedBitmap(const CacheKey& rKey);
+
+ /** Release the reference to the preview bitmap that is associated with
+ the given key.
+ */
+ void ReleaseBitmap(const CacheKey& rKey);
+
+ /** Mark the specified preview bitmap as not being up-to-date
+ anymore.
+ @return
+ When the key references a page in the cache then
+ return <TRUE/>. When the key is not known then <FALSE/>
+ is returned.
+ */
+ bool InvalidateBitmap(const CacheKey& rKey);
+
+ /** Mark all preview bitmaps as not being up-to-date anymore.
+ */
+ void InvalidateCache();
+
+ /** Add or replace a bitmap for the given key.
+ */
+ void SetBitmap(const CacheKey& rKey, const BitmapEx& rPreview, bool bIsPrecious);
+
+ /** Add or replace a marked bitmap for the given key.
+ */
+ void SetMarkedBitmap(const CacheKey& rKey, const BitmapEx& rPreview);
+
+ /** Mark the specified preview bitmap as precious, i.e. that it must not
+ be compressed or otherwise removed from the cache.
+ */
+ void SetPrecious(const CacheKey& rKey, bool bIsPrecious);
+
+ /** Calculate the cache size. This should rarely be necessary because
+ the cache size is tracked with each modification of preview
+ bitmaps.
+ */
+ void ReCalculateTotalCacheSize();
+
+ /** Use the previews in the given cache to initialize missing previews.
+ */
+ void Recycle(const BitmapCache& rCache);
+
+ /** Return a list of sorted cache keys that represent an index into (a
+ part of) the cache. The entries of the index are sorted according
+ to last access times with the least recently access time first.
+ Entries with the precious flag set are omitted.
+ Entries with that have no preview bitmaps are omitted.
+ */
+ CacheIndex GetCacheIndex() const;
+
+ /** Compress the specified preview bitmap with the given bitmap
+ compressor. A reference to the compressor is stored for later
+ decompression.
+ */
+ void Compress(const CacheKey& rKey, const std::shared_ptr<BitmapCompressor>& rpCompressor);
+
+private:
+ mutable ::osl::Mutex maMutex;
+
+ std::unique_ptr<CacheBitmapContainer> mpBitmapContainer;
+
+ /** Total size of bytes that are occupied by bitmaps in the cache for
+ whom the slides are currently not inside the visible area.
+ */
+ sal_Int32 mnNormalCacheSize;
+
+ /** Total size of bytes that are occupied by bitmaps in the cache for
+ whom the slides are currently visible.
+ */
+ sal_Int32 mnPreciousCacheSize;
+
+ /** At the moment the access time is not an actual time or date value
+ but a counter that is increased with every access. It thus defines
+ the same ordering as a true time.
+ */
+ sal_Int32 mnCurrentAccessTime;
+
+ /** The maximal cache size for the off-screen preview bitmaps. When
+ mnNormalCacheSize grows larger than this value then the
+ mpCacheCompactor member is used to reduce the cache size.
+ */
+ sal_Int32 mnMaximalNormalCacheSize;
+
+ /** The cache compactor is used to reduce the number of bytes used by
+ off-screen preview bitmaps.
+ */
+ ::std::unique_ptr<CacheCompactor> mpCacheCompactor;
+
+ /** This flag stores if the cache is or recently was full, i.e. the
+ cache compactor has or had to be run in order to reduce the cache
+ size to the allowed value.
+ */
+ bool mbIsFull;
+
+ /** Update mnNormalCacheSize or mnPreciousCacheSize according to the
+ precious flag of the specified preview bitmap and the specified
+ operation.
+ */
+ enum CacheOperation
+ {
+ ADD,
+ REMOVE
+ };
+ void UpdateCacheSize(const CacheEntry& rKey, CacheOperation eOperation);
+};
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsBitmapCompressor.cxx b/sd/source/ui/slidesorter/cache/SlsBitmapCompressor.cxx
new file mode 100644
index 000000000..d4da935dd
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsBitmapCompressor.cxx
@@ -0,0 +1,197 @@
+/* -*- 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 "SlsBitmapCompressor.hxx"
+
+#include <tools/stream.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+#include <vcl/pngwrite.hxx>
+
+namespace sd::slidesorter::cache {
+
+//===== NoBitmapCompression ===================================================
+
+/** This dummy replacement simply stores a shared pointer to the original
+ preview bitmap.
+*/
+class NoBitmapCompression::DummyReplacement
+ : public BitmapReplacement
+{
+public:
+ BitmapEx maPreview;
+
+ explicit DummyReplacement (const BitmapEx& rPreview) : maPreview(rPreview) { }
+ virtual ~DummyReplacement() {}
+ virtual sal_Int32 GetMemorySize() const override { return maPreview.GetSizeBytes(); }
+};
+
+std::shared_ptr<BitmapReplacement> NoBitmapCompression::Compress (const BitmapEx& rBitmap) const
+{
+ return std::make_shared<DummyReplacement>(rBitmap);
+}
+
+BitmapEx NoBitmapCompression::Decompress (const BitmapReplacement& rBitmapData) const
+{
+ return dynamic_cast<const DummyReplacement&>(rBitmapData).maPreview;
+}
+
+bool NoBitmapCompression::IsLossless() const
+{
+ return true;
+}
+
+//===== CompressionByDeletion =================================================
+
+std::shared_ptr<BitmapReplacement> CompressionByDeletion::Compress (const BitmapEx& ) const
+{
+ return std::shared_ptr<BitmapReplacement>();
+}
+
+BitmapEx CompressionByDeletion::Decompress (const BitmapReplacement& ) const
+{
+ // Return a NULL pointer. This will eventually lead to a request for
+ // the creation of a new one.
+ return BitmapEx();
+}
+
+bool CompressionByDeletion::IsLossless() const
+{
+ return false;
+}
+
+//===== ResolutionReduction ===================================================
+
+/** Store a scaled down bitmap together with the original size.
+*/
+class ResolutionReduction::ResolutionReducedReplacement : public BitmapReplacement
+{
+public:
+ BitmapEx maPreview;
+ Size maOriginalSize;
+
+ virtual ~ResolutionReducedReplacement();
+ virtual sal_Int32 GetMemorySize() const override;
+};
+
+ResolutionReduction::ResolutionReducedReplacement::~ResolutionReducedReplacement()
+{
+}
+
+sal_Int32 ResolutionReduction::ResolutionReducedReplacement::GetMemorySize() const
+{
+ return maPreview.GetSizeBytes();
+}
+
+std::shared_ptr<BitmapReplacement> ResolutionReduction::Compress (
+ const BitmapEx& rBitmap) const
+{
+ auto pResult = std::make_shared<ResolutionReducedReplacement>();
+ pResult->maPreview = rBitmap;
+ Size aSize (rBitmap.GetSizePixel());
+ pResult->maOriginalSize = aSize;
+ if (aSize.Width()>0 && aSize.Width()<mnWidth)
+ {
+ int nHeight = aSize.Height() * mnWidth / aSize.Width() ;
+ pResult->maPreview.Scale(Size(mnWidth,nHeight));
+ }
+
+ return pResult;
+}
+
+BitmapEx ResolutionReduction::Decompress (const BitmapReplacement& rBitmapData) const
+{
+ BitmapEx aResult;
+
+ const ResolutionReducedReplacement* pData (
+ dynamic_cast<const ResolutionReducedReplacement*>(&rBitmapData));
+
+ if ( pData && ! pData->maPreview.IsEmpty())
+ {
+ aResult = pData->maPreview;
+ if (pData->maOriginalSize.Width() > mnWidth)
+ aResult.Scale(pData->maOriginalSize);
+ }
+
+ return aResult;
+}
+
+bool ResolutionReduction::IsLossless() const
+{
+ return false;
+}
+
+//===== PNGCompression ========================================================
+
+class PngCompression::PngReplacement : public BitmapReplacement
+{
+public:
+ void* mpData;
+ sal_Int32 mnDataSize;
+ PngReplacement()
+ : mpData(nullptr),
+ mnDataSize(0)
+ {}
+ virtual ~PngReplacement()
+ {
+ delete [] static_cast<char*>(mpData);
+ }
+ virtual sal_Int32 GetMemorySize() const override
+ {
+ return mnDataSize;
+ }
+};
+
+std::shared_ptr<BitmapReplacement> PngCompression::Compress (const BitmapEx& rBitmap) const
+{
+ vcl::PNGWriter aWriter(rBitmap);
+ SvMemoryStream aStream (32768, 32768);
+ aWriter.Write(aStream);
+
+ auto pResult = std::make_shared<PngReplacement>();
+ pResult->mnDataSize = aStream.Tell();
+ pResult->mpData = new char[pResult->mnDataSize];
+ memcpy(pResult->mpData, aStream.GetData(), pResult->mnDataSize);
+
+ return pResult;
+}
+
+BitmapEx PngCompression::Decompress (
+ const BitmapReplacement& rBitmapData) const
+{
+ BitmapEx aResult;
+ const PngReplacement* pData (dynamic_cast<const PngReplacement*>(&rBitmapData));
+ if (pData != nullptr)
+ {
+ SvMemoryStream aStream (pData->mpData, pData->mnDataSize, StreamMode::READ);
+ vcl::PngImageReader aReader (aStream);
+ aResult = aReader.read().GetBitmap();
+ }
+
+ return aResult;
+}
+
+bool PngCompression::IsLossless() const
+{
+ return true;
+}
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsBitmapCompressor.hxx b/sd/source/ui/slidesorter/cache/SlsBitmapCompressor.hxx
new file mode 100644
index 000000000..4754bead9
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsBitmapCompressor.hxx
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <sal/types.h>
+#include <memory>
+
+class BitmapEx;
+
+namespace sd::slidesorter::cache
+{
+class BitmapReplacement;
+
+/** This interface class provides the minimal method set for classes that
+ implement the compression and decompression of preview bitmaps.
+*/
+class BitmapCompressor
+{
+public:
+ /** Compress the given bitmap into a replacement format that is specific
+ to the compressor class.
+ */
+ virtual std::shared_ptr<BitmapReplacement> Compress(const BitmapEx& rBitmap) const = 0;
+
+ /** Decompress the given replacement data into a preview bitmap.
+ Depending on the compression technique the returned bitmap may
+ differ from the original bitmap given to the Compress() method. It
+ may even of the wrong size or empty or the NULL pointer. It is the
+ task of the caller to create a new preview bitmap if the returned
+ one is not as desired.
+ */
+ virtual BitmapEx Decompress(const BitmapReplacement& rBitmapData) const = 0;
+
+ /** Return whether the compression and decompression is lossless. This
+ value is used by the caller of Decompress() to decide whether to use
+ the returned bitmap as is or if a new preview has to be created.
+ */
+ virtual bool IsLossless() const = 0;
+
+protected:
+ ~BitmapCompressor() {}
+};
+
+/** Interface for preview bitmap replacements. Each bitmap
+ compressor/decompressor has to provide an implementation that is
+ suitable to store the compressed bitmaps.
+*/
+class BitmapReplacement
+{
+public:
+ virtual sal_Int32 GetMemorySize() const { return 0; }
+
+protected:
+ ~BitmapReplacement() {}
+};
+
+/** This is one trivial bitmap compressor. It stores bitmaps unmodified
+ instead of compressing them.
+ This compressor is lossless.
+*/
+class NoBitmapCompression : public BitmapCompressor
+{
+ class DummyReplacement;
+
+public:
+ virtual ~NoBitmapCompression() {}
+ virtual std::shared_ptr<BitmapReplacement> Compress(const BitmapEx& rpBitmap) const override;
+ virtual BitmapEx Decompress(const BitmapReplacement& rBitmapData) const override;
+ virtual bool IsLossless() const override;
+};
+
+/** This is another trivial bitmap compressor. Instead of compressing a
+ bitmap, it throws the bitmap away. Its Decompress() method returns a
+ NULL pointer. The caller has to create a new preview bitmap instead.
+ This compressor clearly is not lossless.
+*/
+class CompressionByDeletion : public BitmapCompressor
+{
+public:
+ virtual ~CompressionByDeletion() {}
+ virtual std::shared_ptr<BitmapReplacement> Compress(const BitmapEx& rBitmap) const override;
+ virtual BitmapEx Decompress(const BitmapReplacement& rBitmapData) const override;
+ virtual bool IsLossless() const override;
+};
+
+/** Compress a preview bitmap by reducing its resolution. While the aspect
+ ratio is maintained the horizontal resolution is scaled down to 100
+ pixels.
+ This compressor is not lossless.
+*/
+class ResolutionReduction : public BitmapCompressor
+{
+ class ResolutionReducedReplacement;
+ static const sal_Int32 mnWidth = 100;
+
+public:
+ virtual ~ResolutionReduction() {}
+ virtual std::shared_ptr<BitmapReplacement> Compress(const BitmapEx& rpBitmap) const override;
+ /** Scale the replacement bitmap up to the original size.
+ */
+ virtual BitmapEx Decompress(const BitmapReplacement& rBitmapData) const override;
+ virtual bool IsLossless() const override;
+};
+
+/** Compress preview bitmaps using the PNG format.
+ This compressor is lossless.
+*/
+class PngCompression : public BitmapCompressor
+{
+ class PngReplacement;
+
+public:
+ virtual ~PngCompression() {}
+ virtual std::shared_ptr<BitmapReplacement> Compress(const BitmapEx& rBitmap) const override;
+ virtual BitmapEx Decompress(const BitmapReplacement& rBitmapData) const override;
+ virtual bool IsLossless() const override;
+};
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsBitmapFactory.cxx b/sd/source/ui/slidesorter/cache/SlsBitmapFactory.cxx
new file mode 100644
index 000000000..a9182c2a2
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsBitmapFactory.cxx
@@ -0,0 +1,71 @@
+/* -*- 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 "SlsBitmapFactory.hxx"
+
+#include <PreviewRenderer.hxx>
+#include <sdpage.hxx>
+#include <vcl/bitmapex.hxx>
+
+namespace sd::slidesorter::view {
+class SlideSorterView;
+class PageObjectViewObjectContact;
+}
+
+namespace sd::slidesorter::cache {
+
+BitmapFactory::BitmapFactory()
+ : maRenderer(false)
+{
+}
+
+BitmapFactory::~BitmapFactory()
+{
+}
+
+BitmapEx BitmapFactory::CreateBitmap (
+ const SdPage& rPage,
+ const Size& rPixelSize,
+ const bool bDoSuperSampling)
+{
+ Size aSize (rPixelSize);
+ if (bDoSuperSampling)
+ {
+ // Supersampling factor
+ int aSuperSamplingFactor = 2;
+ aSize.setWidth( aSize.Width() * aSuperSamplingFactor );
+ aSize.setHeight( aSize.Height() * aSuperSamplingFactor );
+ }
+
+ BitmapEx aPreview (maRenderer.RenderPage (
+ &rPage,
+ aSize,
+ true,
+ false).GetBitmapEx());
+ if (bDoSuperSampling)
+ {
+ aPreview.Scale(rPixelSize, BmpScaleFlag::BestQuality);
+ }
+
+ return aPreview;
+}
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsBitmapFactory.hxx b/sd/source/ui/slidesorter/cache/SlsBitmapFactory.hxx
new file mode 100644
index 000000000..c1733b825
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsBitmapFactory.hxx
@@ -0,0 +1,46 @@
+/* -*- 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 <PreviewRenderer.hxx>
+
+class SdPage;
+class Size;
+
+namespace sd::slidesorter::cache
+{
+/** This factory class creates preview bitmaps for page objects. It is
+ merely an adapter for the PreviewRenderer.
+*/
+class BitmapFactory
+{
+public:
+ BitmapFactory();
+ ~BitmapFactory();
+
+ BitmapEx CreateBitmap(const SdPage& rPage, const Size& rPixelSize, const bool bDoSuperSampling);
+
+private:
+ PreviewRenderer maRenderer;
+};
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsCacheCompactor.cxx b/sd/source/ui/slidesorter/cache/SlsCacheCompactor.cxx
new file mode 100644
index 000000000..79ab9fab2
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsCacheCompactor.cxx
@@ -0,0 +1,189 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include "SlsCacheCompactor.hxx"
+
+#include "SlsBitmapCompressor.hxx"
+#include "SlsBitmapCache.hxx"
+#include "SlsCacheConfiguration.hxx"
+
+#include <rtl/ustring.hxx>
+#include <sal/log.hxx>
+#include <com/sun/star/uno/Any.hxx>
+
+using namespace ::com::sun::star::uno;
+
+namespace {
+
+/** This is a trivial implementation of the CacheCompactor interface class.
+ It ignores calls to RequestCompaction() and thus will never decrease the
+ total size of off-screen preview bitmaps.
+*/
+class NoCacheCompaction
+ : public ::sd::slidesorter::cache::CacheCompactor
+{
+public:
+ NoCacheCompaction (
+ ::sd::slidesorter::cache::BitmapCache& rCache,
+ sal_Int32 nMaximalCacheSize)
+ : CacheCompactor(rCache, nMaximalCacheSize)
+ {}
+
+ virtual void RequestCompaction() override { /* Ignored */ };
+
+protected:
+ virtual void Run() override { /* Do nothing */ };
+};
+
+/** This implementation of the CacheCompactor interface class uses one of
+ several bitmap compression algorithms to reduce the number of the bytes
+ of the off-screen previews in the bitmap cache. See the documentation
+ of CacheCompactor::Create() for more details on configuration properties
+ that control the choice of compression algorithm.
+*/
+class CacheCompactionByCompression
+ : public ::sd::slidesorter::cache::CacheCompactor
+{
+public:
+ CacheCompactionByCompression (
+ ::sd::slidesorter::cache::BitmapCache& rCache,
+ sal_Int32 nMaximalCacheSize,
+ const std::shared_ptr< ::sd::slidesorter::cache::BitmapCompressor>& rpCompressor);
+
+protected:
+ virtual void Run() override;
+
+private:
+ std::shared_ptr< ::sd::slidesorter::cache::BitmapCompressor> mpCompressor;
+};
+
+} // end of anonymous namespace
+
+namespace sd::slidesorter::cache {
+
+::std::unique_ptr<CacheCompactor> CacheCompactor::Create (
+ BitmapCache& rCache,
+ sal_Int32 nMaximalCacheSize)
+{
+ static const char sNone[] = "None";
+
+ std::shared_ptr<BitmapCompressor> pCompressor;
+ OUString sCompressionPolicy("PNGCompression");
+ Any aCompressionPolicy (CacheConfiguration::Instance()->GetValue("CompressionPolicy"));
+ if (aCompressionPolicy.has<OUString>())
+ aCompressionPolicy >>= sCompressionPolicy;
+ if (sCompressionPolicy == sNone)
+ pCompressor = std::make_shared<NoBitmapCompression>();
+ else if (sCompressionPolicy == "Erase")
+ pCompressor = std::make_shared<CompressionByDeletion>();
+ else if (sCompressionPolicy == "ResolutionReduction")
+ pCompressor = std::make_shared<ResolutionReduction>();
+ else
+ pCompressor = std::make_shared<PngCompression>();
+
+ ::std::unique_ptr<CacheCompactor> pCompactor;
+ OUString sCompactionPolicy("Compress");
+ Any aCompactionPolicy (CacheConfiguration::Instance()->GetValue("CompactionPolicy"));
+ if (aCompactionPolicy.has<OUString>())
+ aCompactionPolicy >>= sCompactionPolicy;
+ if (sCompactionPolicy == sNone)
+ pCompactor.reset(new NoCacheCompaction(rCache,nMaximalCacheSize));
+ else
+ pCompactor.reset(new CacheCompactionByCompression(rCache,nMaximalCacheSize,pCompressor));
+
+ return pCompactor;
+}
+
+void CacheCompactor::RequestCompaction()
+{
+ if ( ! mbIsCompactionRunning && ! maCompactionTimer.IsActive())
+ maCompactionTimer.Start();
+}
+
+CacheCompactor::CacheCompactor(
+ BitmapCache& rCache,
+ sal_Int32 nMaximalCacheSize)
+ : mrCache(rCache),
+ mnMaximalCacheSize(nMaximalCacheSize),
+ maCompactionTimer("sd CacheCompactor maCompactionTimer"),
+ mbIsCompactionRunning(false)
+{
+ maCompactionTimer.SetTimeout(100);
+ maCompactionTimer.SetInvokeHandler(LINK(this,CacheCompactor,CompactionCallback));
+}
+
+IMPL_LINK_NOARG(CacheCompactor, CompactionCallback, Timer *, void)
+{
+ mbIsCompactionRunning = true;
+
+ try
+ {
+ Run();
+ }
+ catch (const css::uno::RuntimeException&)
+ {
+ }
+ catch (const css::uno::Exception&)
+ {
+ }
+
+ mbIsCompactionRunning = false;
+}
+
+} // end of namespace ::sd::slidesorter::cache
+
+namespace {
+
+//===== CacheCompactionByCompression ==========================================
+
+CacheCompactionByCompression::CacheCompactionByCompression (
+ ::sd::slidesorter::cache::BitmapCache& rCache,
+ sal_Int32 nMaximalCacheSize,
+ const std::shared_ptr< ::sd::slidesorter::cache::BitmapCompressor>& rpCompressor)
+ : CacheCompactor(rCache,nMaximalCacheSize),
+ mpCompressor(rpCompressor)
+{
+}
+
+void CacheCompactionByCompression::Run()
+{
+ if (mrCache.GetSize() <= mnMaximalCacheSize)
+ return;
+
+ SAL_INFO("sd.sls", __func__ << ": bitmap cache uses too much space: " << mrCache.GetSize() << " > " << mnMaximalCacheSize);
+
+ ::sd::slidesorter::cache::BitmapCache::CacheIndex aIndex (
+ mrCache.GetCacheIndex());
+ for (const auto& rpIndex : aIndex)
+ {
+ if (rpIndex == nullptr)
+ continue;
+
+ mrCache.Compress(rpIndex, mpCompressor);
+ if (mrCache.GetSize() < mnMaximalCacheSize)
+ break;
+ }
+ mrCache.ReCalculateTotalCacheSize();
+ SAL_INFO("sd.sls", __func__ << ": there are now " << mrCache.GetSize() << " bytes occupied");
+}
+
+} // end of anonymous namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsCacheCompactor.hxx b/sd/source/ui/slidesorter/cache/SlsCacheCompactor.hxx
new file mode 100644
index 000000000..d694ae1a1
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsCacheCompactor.hxx
@@ -0,0 +1,87 @@
+/* -*- 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 <sal/types.h>
+#include <vcl/timer.hxx>
+#include <memory>
+
+namespace sd::slidesorter::cache {
+
+class BitmapCache;
+
+/** This is an interface class whose implementations are created via the
+ Create() factory method.
+*/
+class CacheCompactor
+{
+public:
+ virtual ~CacheCompactor() {};
+
+ /** Create a new instance of the CacheCompactor interface class. The
+ type of compaction algorithm used depends on values from the
+ configuration: the SlideSorter/PreviewCache/CompactionPolicy
+ property of the Impress.xcs file currently supports the values
+ "None" and "Compress". With the later the CompressionPolicy
+ property is evaluated which implementation of the BitmapCompress
+ interface class to use as bitmap compressor.
+ @param rCache
+ The bitmap cache on which to operate.
+ @param nMaximalCacheSize
+ The total number of bytes the off-screen bitmaps in the cache
+ may have. When the Run() method is (indirectly) called the
+ compactor tries to reduce that summed size of off-screen bitmaps
+ under this number. However, it is not guaranteed that this
+ works in all cases.
+ */
+ static ::std::unique_ptr<CacheCompactor> Create (
+ BitmapCache& rCache,
+ sal_Int32 nMaximalCacheSize);
+
+ /** Request a compaction of the off-screen previews in the bitmap
+ cache. This calls via a timer the Run() method.
+ */
+ virtual void RequestCompaction();
+
+protected:
+ BitmapCache& mrCache;
+ sal_Int32 mnMaximalCacheSize;
+
+ CacheCompactor(
+ BitmapCache& rCache,
+ sal_Int32 nMaximalCacheSize);
+
+ /** This method actually tries to reduce the total number of bytes used
+ by the off-screen preview bitmaps.
+ */
+ virtual void Run() = 0;
+
+private:
+ /** This timer is used to collect calls to RequestCompaction() and
+ eventually call Run().
+ */
+ Timer maCompactionTimer;
+ bool mbIsCompactionRunning;
+ DECL_LINK(CompactionCallback, Timer *, void);
+};
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsCacheConfiguration.cxx b/sd/source/ui/slidesorter/cache/SlsCacheConfiguration.cxx
new file mode 100644
index 000000000..fd08c7627
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsCacheConfiguration.cxx
@@ -0,0 +1,144 @@
+/* -*- 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 "SlsCacheConfiguration.hxx"
+#include <vcl/svapp.hxx>
+
+#include <comphelper/processfactory.hxx>
+#include <comphelper/propertysequence.hxx>
+#include <com/sun/star/lang/XMultiServiceFactory.hpp>
+#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/configuration/theDefaultProvider.hpp>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+namespace sd::slidesorter::cache {
+
+namespace
+{
+ typedef std::shared_ptr<CacheConfiguration> CacheConfigSharedPtr;
+ CacheConfigSharedPtr& theInstance()
+ {
+ static CacheConfigSharedPtr SINGLETON;
+ return SINGLETON;
+ }
+}
+
+std::weak_ptr<CacheConfiguration> CacheConfiguration::mpWeakInstance;
+
+std::shared_ptr<CacheConfiguration> CacheConfiguration::Instance()
+{
+ SolarMutexGuard aSolarGuard;
+ CacheConfigSharedPtr &rInstancePtr = theInstance();
+ if (!rInstancePtr)
+ {
+ // Maybe somebody else kept a previously created instance alive.
+ if ( ! mpWeakInstance.expired())
+ rInstancePtr = std::shared_ptr<CacheConfiguration>(mpWeakInstance);
+ if (!rInstancePtr)
+ {
+ // We have to create a new instance.
+ rInstancePtr.reset(new CacheConfiguration());
+ mpWeakInstance = rInstancePtr;
+ // Prepare to release this instance in the near future.
+ rInstancePtr->m_ReleaseTimer.SetInvokeHandler(
+ LINK(rInstancePtr.get(),CacheConfiguration,TimerCallback));
+ rInstancePtr->m_ReleaseTimer.SetTimeout(5000 /* 5s */);
+ rInstancePtr->m_ReleaseTimer.Start();
+ }
+ }
+ return rInstancePtr;
+}
+
+CacheConfiguration::CacheConfiguration()
+ : m_ReleaseTimer("sd::CacheConfiguration maReleaseTimer")
+{
+ // Get the cache size from configuration.
+ try
+ {
+ // Obtain access to the configuration.
+ Reference<lang::XMultiServiceFactory> xProvider =
+ configuration::theDefaultProvider::get( ::comphelper::getProcessComponentContext() );
+
+ // Obtain access to Impress configuration.
+ Sequence<Any> aCreationArguments(comphelper::InitAnyPropertySequence(
+ {
+ {"nodepath", Any(OUString("/org.openoffice.Office.Impress/"))},
+ {"depth", Any(sal_Int32(-1))}
+ }));
+
+ Reference<XInterface> xRoot (xProvider->createInstanceWithArguments(
+ "com.sun.star.configuration.ConfigurationAccess",
+ aCreationArguments));
+ if ( ! xRoot.is())
+ return;
+ Reference<container::XHierarchicalNameAccess> xHierarchy (xRoot, UNO_QUERY);
+ if ( ! xHierarchy.is())
+ return;
+
+ // Get the node for the slide sorter preview cache.
+ mxCacheNode.set( xHierarchy->getByHierarchicalName("MultiPaneGUI/SlideSorter/PreviewCache"), UNO_QUERY);
+ }
+ catch (RuntimeException &)
+ {
+ }
+ catch (Exception &)
+ {
+ }
+}
+
+Any CacheConfiguration::GetValue (const OUString& rName)
+{
+ Any aResult;
+
+ if (mxCacheNode != nullptr)
+ {
+ try
+ {
+ aResult = mxCacheNode->getByName(rName);
+ }
+ catch (Exception &)
+ {
+ }
+ }
+
+ return aResult;
+}
+
+IMPL_STATIC_LINK_NOARG(CacheConfiguration, TimerCallback, Timer *, void)
+{
+ CacheConfigSharedPtr &rInstancePtr = theInstance();
+ // Release our reference to the instance.
+ rInstancePtr.reset();
+ // note: if there are no other references to the instance, m_ReleaseTimer
+ // will be deleted now
+}
+
+void CacheConfiguration::Shutdown()
+{
+ CacheConfigSharedPtr &rInstancePtr = theInstance();
+ rInstancePtr.reset();
+ assert(mpWeakInstance.expired()); // ensure m_ReleaseTimer is destroyed
+}
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsCacheConfiguration.hxx b/sd/source/ui/slidesorter/cache/SlsCacheConfiguration.hxx
new file mode 100644
index 000000000..d53bcd713
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsCacheConfiguration.hxx
@@ -0,0 +1,68 @@
+/* -*- 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 <com/sun/star/uno/Any.hxx>
+#include <vcl/timer.hxx>
+#include <memory>
+
+namespace com::sun::star::container
+{
+class XNameAccess;
+}
+
+namespace sd::slidesorter::cache
+{
+/** A very simple and easy-to-use access to configuration entries regarding
+ the slide sorter cache.
+*/
+class CacheConfiguration
+{
+public:
+ /** Return an instance to this class. The reference is released after 5
+ seconds. Subsequent calls to this function will create a new
+ instance.
+ */
+ static std::shared_ptr<CacheConfiguration> Instance();
+
+ static void Shutdown();
+
+ /** Look up the specified value in
+ MultiPaneGUI/SlideSorter/PreviewCache. When the specified value
+ does not exist then an empty Any is returned.
+ */
+ css::uno::Any GetValue(const OUString& rName);
+
+private:
+ /** When a caller holds a reference after we have released ours we use
+ this weak pointer to avoid creating a new instance.
+ */
+ static std::weak_ptr<CacheConfiguration> mpWeakInstance;
+ Timer m_ReleaseTimer;
+ css::uno::Reference<css::container::XNameAccess> mxCacheNode;
+
+ CacheConfiguration();
+
+ DECL_STATIC_LINK(CacheConfiguration, TimerCallback, Timer*, void);
+};
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsGenericPageCache.cxx b/sd/source/ui/slidesorter/cache/SlsGenericPageCache.cxx
new file mode 100644
index 000000000..6275754fa
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsGenericPageCache.cxx
@@ -0,0 +1,278 @@
+/* -*- 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 "SlsGenericPageCache.hxx"
+
+#include "SlsQueueProcessor.hxx"
+#include "SlsRequestPriorityClass.hxx"
+#include "SlsRequestFactory.hxx"
+#include "SlsBitmapCache.hxx"
+#include <cache/SlsPageCacheManager.hxx>
+#include <tools/debug.hxx>
+
+namespace sd::slidesorter::cache {
+
+GenericPageCache::GenericPageCache (
+ const Size& rPreviewSize,
+ const bool bDoSuperSampling,
+ const SharedCacheContext& rpCacheContext)
+ : maRequestQueue(rpCacheContext),
+ mpCacheContext(rpCacheContext),
+ maPreviewSize(rPreviewSize),
+ mbDoSuperSampling(bDoSuperSampling)
+{
+ // A large size may indicate an error of the caller. After all we
+ // are creating previews.
+ DBG_ASSERT (maPreviewSize.Width()<1000 && maPreviewSize.Height()<1000,
+ "GenericPageCache<>::GetPreviewBitmap(): bitmap requested with large width. "
+ "This may indicate an error.");
+}
+
+GenericPageCache::~GenericPageCache()
+{
+ if (mpQueueProcessor != nullptr)
+ mpQueueProcessor->Stop();
+ maRequestQueue.Clear();
+ mpQueueProcessor.reset();
+
+ if (mpBitmapCache != nullptr)
+ PageCacheManager::Instance()->ReleaseCache(mpBitmapCache);
+ mpBitmapCache.reset();
+}
+
+void GenericPageCache::ProvideCacheAndProcessor()
+{
+ if (mpBitmapCache == nullptr)
+ mpBitmapCache = PageCacheManager::Instance()->GetCache(
+ mpCacheContext->GetModel(),
+ maPreviewSize);
+
+ if (mpQueueProcessor == nullptr)
+ mpQueueProcessor.reset(new QueueProcessor(
+ maRequestQueue,
+ mpBitmapCache,
+ maPreviewSize,
+ mbDoSuperSampling,
+ mpCacheContext));
+}
+
+void GenericPageCache::ChangePreviewSize (
+ const Size& rPreviewSize,
+ const bool bDoSuperSampling)
+{
+ if (rPreviewSize==maPreviewSize && bDoSuperSampling==mbDoSuperSampling)
+ return;
+
+ // A large size may indicate an error of the caller. After all we
+ // are creating previews.
+ DBG_ASSERT (maPreviewSize.Width()<1000 && maPreviewSize.Height()<1000,
+ "GenericPageCache<>::GetPreviewBitmap(): bitmap requested with large width. "
+ "This may indicate an error.");
+
+ if (mpBitmapCache != nullptr)
+ {
+ mpBitmapCache = PageCacheManager::Instance()->ChangeSize(
+ mpBitmapCache, maPreviewSize, rPreviewSize);
+ if (mpQueueProcessor != nullptr)
+ {
+ mpQueueProcessor->SetPreviewSize(rPreviewSize, bDoSuperSampling);
+ mpQueueProcessor->SetBitmapCache(mpBitmapCache);
+ }
+ }
+ maPreviewSize = rPreviewSize;
+ mbDoSuperSampling = bDoSuperSampling;
+}
+
+BitmapEx GenericPageCache::GetPreviewBitmap (
+ const CacheKey aKey,
+ const bool bResize)
+{
+ assert(aKey != nullptr);
+
+ BitmapEx aPreview;
+ bool bMayBeUpToDate = true;
+ ProvideCacheAndProcessor();
+ const SdrPage* pPage = mpCacheContext->GetPage(aKey);
+ if (mpBitmapCache->HasBitmap(pPage))
+ {
+ aPreview = mpBitmapCache->GetBitmap(pPage);
+ const Size aBitmapSize (aPreview.GetSizePixel());
+ if (aBitmapSize != maPreviewSize)
+ {
+ // Scale the bitmap to the desired size when that is possible,
+ // i.e. the bitmap is not empty.
+ if (bResize && !aBitmapSize.IsEmpty())
+ {
+ aPreview.Scale(maPreviewSize);
+ }
+ bMayBeUpToDate = false;
+ }
+ else
+ bMayBeUpToDate = true;
+ }
+ else
+ bMayBeUpToDate = false;
+
+ // Request the creation of a correctly sized preview bitmap. We do this
+ // even when the size of the bitmap in the cache is correct because its
+ // content may be not up-to-date anymore.
+ RequestPreviewBitmap(aKey, bMayBeUpToDate);
+
+ return aPreview;
+}
+
+BitmapEx GenericPageCache::GetMarkedPreviewBitmap (
+ const CacheKey aKey)
+{
+ assert(aKey != nullptr);
+
+ ProvideCacheAndProcessor();
+ const SdrPage* pPage = mpCacheContext->GetPage(aKey);
+ BitmapEx aMarkedPreview (mpBitmapCache->GetMarkedBitmap(pPage));
+
+ return aMarkedPreview;
+}
+
+void GenericPageCache::SetMarkedPreviewBitmap (
+ const CacheKey aKey,
+ const BitmapEx& rMarkedBitmap)
+{
+ assert(aKey != nullptr);
+
+ ProvideCacheAndProcessor();
+ const SdrPage* pPage = mpCacheContext->GetPage(aKey);
+ mpBitmapCache->SetMarkedBitmap(pPage, rMarkedBitmap);
+}
+
+void GenericPageCache::RequestPreviewBitmap (
+ const CacheKey aKey,
+ const bool bMayBeUpToDate)
+{
+ assert(aKey != nullptr);
+
+ const SdrPage* pPage = mpCacheContext->GetPage(aKey);
+
+ ProvideCacheAndProcessor();
+
+ // Determine if the available bitmap is up to date.
+ bool bIsUpToDate = false;
+ if (bMayBeUpToDate)
+ bIsUpToDate = mpBitmapCache->BitmapIsUpToDate (pPage);
+ if (bIsUpToDate)
+ {
+ const BitmapEx aPreview (mpBitmapCache->GetBitmap(pPage));
+ if (aPreview.IsEmpty() || aPreview.GetSizePixel()!=maPreviewSize)
+ bIsUpToDate = false;
+ }
+
+ if ( bIsUpToDate)
+ return;
+
+ // No, the bitmap is not up-to-date. Request a new one.
+ RequestPriorityClass ePriorityClass (NOT_VISIBLE);
+ if (mpCacheContext->IsVisible(aKey))
+ {
+ if (mpBitmapCache->HasBitmap(pPage))
+ ePriorityClass = VISIBLE_OUTDATED_PREVIEW;
+ else
+ ePriorityClass = VISIBLE_NO_PREVIEW;
+ }
+ maRequestQueue.AddRequest(aKey, ePriorityClass);
+ mpQueueProcessor->Start(ePriorityClass);
+}
+
+bool GenericPageCache::InvalidatePreviewBitmap (const CacheKey aKey)
+{
+ // Invalidate the page in all caches that reference it, not just this one.
+ std::shared_ptr<cache::PageCacheManager> pCacheManager (
+ cache::PageCacheManager::Instance());
+ if (pCacheManager)
+ return pCacheManager->InvalidatePreviewBitmap(
+ mpCacheContext->GetModel(),
+ aKey);
+ else if (mpBitmapCache != nullptr)
+ return mpBitmapCache->InvalidateBitmap(mpCacheContext->GetPage(aKey));
+ else
+ return false;
+}
+
+void GenericPageCache::InvalidateCache ()
+{
+ if (!mpBitmapCache)
+ return;
+
+ // When the cache is being invalidated then it makes no sense to
+ // continue creating preview bitmaps. However, this may be
+ // re-started below.
+ mpQueueProcessor->Stop();
+ maRequestQueue.Clear();
+
+ // Mark the previews in the cache as not being up-to-date anymore.
+ // Depending on the given bUpdateCache flag we start to create new
+ // preview bitmaps.
+ mpBitmapCache->InvalidateCache();
+ RequestFactory()(maRequestQueue, mpCacheContext);
+}
+
+void GenericPageCache::SetPreciousFlag (
+ const CacheKey aKey,
+ const bool bIsPrecious)
+{
+ ProvideCacheAndProcessor();
+
+ // Change the request priority class according to the new precious flag.
+ if (bIsPrecious)
+ {
+ if (mpBitmapCache->HasBitmap(mpCacheContext->GetPage(aKey)))
+ maRequestQueue.ChangeClass(aKey,VISIBLE_OUTDATED_PREVIEW);
+ else
+ maRequestQueue.ChangeClass(aKey,VISIBLE_NO_PREVIEW);
+ }
+ else
+ {
+ if (mpBitmapCache->IsFull())
+ {
+ // When the bitmap cache is full then requests for slides that
+ // are not visible are removed.
+ maRequestQueue.RemoveRequest(aKey);
+ }
+ else
+ maRequestQueue.ChangeClass(aKey,NOT_VISIBLE);
+ }
+
+ mpBitmapCache->SetPrecious(mpCacheContext->GetPage(aKey), bIsPrecious);
+}
+
+void GenericPageCache::Pause()
+{
+ ProvideCacheAndProcessor();
+ if (mpQueueProcessor != nullptr)
+ mpQueueProcessor->Pause();
+}
+
+void GenericPageCache::Resume()
+{
+ ProvideCacheAndProcessor();
+ if (mpQueueProcessor != nullptr)
+ mpQueueProcessor->Resume();
+}
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsGenericPageCache.hxx b/sd/source/ui/slidesorter/cache/SlsGenericPageCache.hxx
new file mode 100644
index 000000000..900d40268
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsGenericPageCache.hxx
@@ -0,0 +1,152 @@
+/* -*- 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 "SlsRequestQueue.hxx"
+#include <memory>
+
+#include <vcl/bitmapex.hxx>
+
+namespace sd::slidesorter::cache {
+
+class BitmapCache;
+class QueueProcessor;
+
+/** This basically is the implementation class for the PageCache class.
+*/
+class GenericPageCache
+{
+public:
+ /** The page cache is created with a reference to the SlideSorter and
+ thus has access to both view and model. This allows the cache to
+ fill itself with requests for all pages or just the visible ones.
+ @param rPreviewSize
+ The size of the previews is expected in pixel values.
+ @param bDoSuperSampling
+ When <TRUE/> the previews are rendered larger and then scaled
+ down to the requested size to improve image quality.
+ */
+ GenericPageCache (
+ const Size& rPreviewSize,
+ const bool bDoSuperSampling,
+ const SharedCacheContext& rpCacheContext);
+
+ ~GenericPageCache();
+
+ /** Change the size of the preview bitmaps. This may be caused by a
+ resize of the slide sorter window or a change of the number of
+ columns.
+ */
+ void ChangePreviewSize (
+ const Size& rPreviewSize,
+ const bool bDoSuperSampling);
+
+ /** Request a preview bitmap for the specified page object in the
+ specified size. The returned bitmap may be a preview of the preview,
+ i.e. either a scaled (up or down) version of a previous preview (of
+ the wrong size) or an empty bitmap. In this case a request for the
+ generation of a new preview is created and inserted into the request
+ queue. When the preview is available the page shape will be told to
+ paint itself again. When it then calls this method again if
+ receives the correctly sized preview bitmap.
+ @param rRequestData
+ This data is used to determine the preview.
+ @param bResize
+ When <TRUE/> then when the available bitmap has not the
+ requested size, it is scaled before it is returned. When
+ <FALSE/> then the bitmap is returned in the wrong size and it is
+ the task of the caller to scale it.
+ @return
+ Returns a bitmap that is either empty, contains a scaled (up or
+ down) version or is the requested bitmap.
+ */
+ BitmapEx GetPreviewBitmap (
+ const CacheKey aKey,
+ const bool bResize);
+ BitmapEx GetMarkedPreviewBitmap (
+ const CacheKey aKey);
+ void SetMarkedPreviewBitmap (
+ const CacheKey aKey,
+ const BitmapEx& rMarkedBitmap);
+
+ /** When the requested preview bitmap does not yet exist or is not
+ up-to-date then the rendering of one is scheduled. Otherwise this
+ method does nothing.
+ @param rRequestData
+ This data is used to determine the preview.
+ @param bMayBeUpToDate
+ This flag helps the method to determine whether an existing
+ preview that matches the request is up to date. If the caller
+ knows that it is not then by passing <FALSE/> he tells us that we
+ do not have to check the up-to-date flag a second time. If
+ unsure use <TRUE/>.
+ */
+ void RequestPreviewBitmap (
+ const CacheKey aKey,
+ const bool bMayBeUpToDate);
+
+ /** Tell the cache to replace the bitmap associated with the given
+ request data with a new one that reflects recent changes in the
+ content of the page object.
+ @return
+ When the key is known then return <TRUE/>.
+ */
+ bool InvalidatePreviewBitmap (const CacheKey aKey);
+
+ /** Call this method when all preview bitmaps have to be generated anew.
+ This is the case when the size of the page objects on the screen has
+ changed or when the model has changed.
+ */
+ void InvalidateCache ();
+
+ /** With the precious flag you can control whether a bitmap can be
+ removed from the cache or reduced in size to make room for other
+ bitmaps or is so precious that it will not be touched. A typical
+ use is to set the precious flag for the visible pages.
+ */
+ void SetPreciousFlag (const CacheKey aKey, const bool bIsPrecious);
+
+ void Pause();
+ void Resume();
+
+private:
+ std::shared_ptr<BitmapCache> mpBitmapCache;
+
+ RequestQueue maRequestQueue;
+
+ std::unique_ptr<QueueProcessor> mpQueueProcessor;
+
+ SharedCacheContext mpCacheContext;
+
+ /** The current size of preview bitmaps.
+ */
+ Size maPreviewSize;
+
+ bool mbDoSuperSampling;
+
+ /** Both bitmap cache and queue processor are created on demand by this
+ method.
+ */
+ void ProvideCacheAndProcessor();
+};
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsPageCache.cxx b/sd/source/ui/slidesorter/cache/SlsPageCache.cxx
new file mode 100644
index 000000000..82b1b8ae4
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsPageCache.cxx
@@ -0,0 +1,109 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <tools/gen.hxx>
+#include "SlsGenericPageCache.hxx"
+#include <cache/SlsPageCache.hxx>
+
+using namespace ::com::sun::star;
+
+namespace sd::slidesorter::cache {
+
+//===== PageCache =============================================================
+
+PageCache::PageCache (
+ const Size& rPreviewSize,
+ const bool bDoSuperSampling,
+ const SharedCacheContext& rpCacheContext)
+ : mpImplementation(
+ new GenericPageCache(
+ rPreviewSize,
+ bDoSuperSampling,
+ rpCacheContext))
+{
+}
+
+PageCache::~PageCache()
+{
+}
+
+void PageCache::ChangeSize (
+ const Size& rPreviewSize,
+ const bool bDoSuperSampling)
+{
+ mpImplementation->ChangePreviewSize(rPreviewSize, bDoSuperSampling);
+}
+
+BitmapEx PageCache::GetPreviewBitmap (
+ const CacheKey aKey,
+ const bool bResize)
+{
+ return mpImplementation->GetPreviewBitmap(aKey, bResize);
+}
+
+BitmapEx PageCache::GetMarkedPreviewBitmap (
+ const CacheKey aKey)
+{
+ return mpImplementation->GetMarkedPreviewBitmap(aKey);
+}
+
+void PageCache::SetMarkedPreviewBitmap (
+ const CacheKey aKey,
+ const BitmapEx& rMarkedBitmap)
+{
+ mpImplementation->SetMarkedPreviewBitmap(aKey, rMarkedBitmap);
+}
+
+void PageCache::RequestPreviewBitmap (const CacheKey aKey)
+{
+ return mpImplementation->RequestPreviewBitmap(aKey, true);
+}
+
+void PageCache::InvalidatePreviewBitmap (
+ const CacheKey aKey)
+{
+ if (mpImplementation->InvalidatePreviewBitmap(aKey))
+ RequestPreviewBitmap(aKey);
+}
+
+void PageCache::InvalidateCache()
+{
+ mpImplementation->InvalidateCache();
+}
+
+void PageCache::SetPreciousFlag (
+ const CacheKey aKey,
+ const bool bIsPrecious)
+{
+ mpImplementation->SetPreciousFlag(aKey, bIsPrecious);
+}
+
+void PageCache::Pause()
+{
+ mpImplementation->Pause();
+}
+
+void PageCache::Resume()
+{
+ mpImplementation->Resume();
+}
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsPageCacheManager.cxx b/sd/source/ui/slidesorter/cache/SlsPageCacheManager.cxx
new file mode 100644
index 000000000..45afd93c9
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsPageCacheManager.cxx
@@ -0,0 +1,420 @@
+/* -*- 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 <cache/SlsPageCacheManager.hxx>
+
+#include "SlsBitmapCache.hxx"
+
+#include <deque>
+#include <map>
+#include <memory>
+#include <unordered_map>
+
+namespace {
+
+/** Collection of data that is stored for all active preview caches.
+*/
+class CacheDescriptor
+{
+public:
+ ::sd::slidesorter::cache::PageCacheManager::DocumentKey mpDocument;
+ Size maPreviewSize;
+
+ CacheDescriptor(
+ ::sd::slidesorter::cache::PageCacheManager::DocumentKey const & pDocument,
+ const Size& rPreviewSize)
+ :mpDocument(pDocument),maPreviewSize(rPreviewSize)
+ {}
+ /// Test for equality with respect to all members.
+ class Equal {public: bool operator() (
+ const CacheDescriptor& rDescriptor1, const CacheDescriptor& rDescriptor2) const {
+ return rDescriptor1.mpDocument==rDescriptor2.mpDocument
+ && rDescriptor1.maPreviewSize==rDescriptor2.maPreviewSize;
+ } };
+ /// Hash function that takes all members into account.
+ class Hash {public: size_t operator() (const CacheDescriptor& rDescriptor) const {
+ return reinterpret_cast<size_t>(rDescriptor.mpDocument.get()) + rDescriptor.maPreviewSize.Width();
+ } };
+};
+
+/** Collection of data that is stored for the inactive, recently used
+ caches.
+*/
+class RecentlyUsedCacheDescriptor
+{
+public:
+ Size maPreviewSize;
+ std::shared_ptr< ::sd::slidesorter::cache::BitmapCache> mpCache;
+
+ RecentlyUsedCacheDescriptor(
+ const Size& rPreviewSize,
+ const std::shared_ptr< ::sd::slidesorter::cache::BitmapCache>& rpCache)
+ :maPreviewSize(rPreviewSize),mpCache(rpCache)
+ {}
+};
+
+/** The list of recently used caches is organized as queue. When elements
+ are added the list is shortened to the maximally allowed number of
+ elements by removing the least recently used elements.
+*/
+typedef ::std::deque<RecentlyUsedCacheDescriptor> RecentlyUsedQueue;
+
+/** Compare the caches by preview size. Those that match the given size
+ come first, then, regardless of the given size, the largest ones before
+ the smaller ones.
+*/
+class BestFittingCacheComparer
+{
+public:
+ explicit BestFittingCacheComparer (const Size& rPreferredSize)
+ : maPreferredSize(rPreferredSize)
+ {}
+ bool operator()(const ::sd::slidesorter::cache::PageCacheManager::BestFittingPageCaches::value_type& rElement1,
+ const ::sd::slidesorter::cache::PageCacheManager::BestFittingPageCaches::value_type& rElement2)
+ {
+ if (rElement2.first == maPreferredSize)
+ return false;
+ else if (rElement1.first == maPreferredSize)
+ return true;
+ else
+ return (rElement1.first.Width()*rElement1.first.Height()
+ > rElement2.first.Width()*rElement2.first.Height());
+ }
+
+private:
+ Size maPreferredSize;
+};
+
+} // end of anonymous namespace
+
+namespace sd::slidesorter::cache {
+
+/** Container for the active caches.
+*/
+class PageCacheManager::PageCacheContainer
+ : public std::unordered_map<CacheDescriptor,
+ std::shared_ptr<BitmapCache>,
+ CacheDescriptor::Hash,
+ CacheDescriptor::Equal>
+{
+public:
+ PageCacheContainer() {}
+
+ /** Compare entries in the cache container with respect to the cache
+ address only.
+ */
+ class CompareWithCache { public:
+ explicit CompareWithCache(const std::shared_ptr<BitmapCache>& rpCache)
+ : mpCache(rpCache) {}
+ bool operator () (const PageCacheContainer::value_type& rValue) const
+ { return rValue.second == mpCache; }
+ private:
+ std::shared_ptr<BitmapCache> mpCache;
+ };
+};
+
+/** The recently used caches are stored in one queue for each document.
+*/
+class PageCacheManager::RecentlyUsedPageCaches
+{
+public:
+ typedef DocumentKey key_type;
+ typedef RecentlyUsedQueue mapped_type;
+ typedef std::map<key_type,mapped_type>::iterator iterator;
+private:
+ std::map<key_type,mapped_type> maMap;
+public:
+ RecentlyUsedPageCaches () {};
+
+ iterator end() { return maMap.end(); }
+ void clear() { maMap.clear(); }
+ iterator find(const key_type& key) { return maMap.find(key); }
+ template<class... Args>
+ std::pair<iterator,bool> emplace(Args&&... args) { return maMap.emplace(std::forward<Args>(args)...); }
+};
+
+class PageCacheManager::Deleter
+{
+public:
+ void operator() (PageCacheManager* pObject) { delete pObject; }
+};
+
+//===== PageCacheManager ====================================================
+
+std::weak_ptr<PageCacheManager> PageCacheManager::mpInstance;
+
+std::shared_ptr<PageCacheManager> PageCacheManager::Instance()
+{
+ std::shared_ptr<PageCacheManager> pInstance;
+
+ ::osl::MutexGuard aGuard (::osl::Mutex::getGlobalMutex());
+
+ pInstance = mpInstance.lock();
+ if (pInstance == nullptr)
+ {
+ pInstance = std::shared_ptr<PageCacheManager>(
+ new PageCacheManager(),
+ PageCacheManager::Deleter());
+ mpInstance = pInstance;
+ }
+
+ return pInstance;
+}
+
+PageCacheManager::PageCacheManager()
+ : mpPageCaches(new PageCacheContainer()),
+ mpRecentlyUsedPageCaches(new RecentlyUsedPageCaches())
+{
+}
+
+PageCacheManager::~PageCacheManager()
+{
+}
+
+std::shared_ptr<BitmapCache> PageCacheManager::GetCache (
+ const DocumentKey& pDocument,
+ const Size& rPreviewSize)
+{
+ std::shared_ptr<BitmapCache> pResult;
+
+ // Look for the cache in the list of active caches.
+ CacheDescriptor aKey (pDocument, rPreviewSize);
+ PageCacheContainer::iterator iCache (mpPageCaches->find(aKey));
+ if (iCache != mpPageCaches->end())
+ pResult = iCache->second;
+
+ // Look for the cache in the list of recently used caches.
+ if (pResult == nullptr)
+ pResult = GetRecentlyUsedCache(pDocument, rPreviewSize);
+
+ // Create the cache when no suitable one does exist.
+ if (pResult == nullptr)
+ pResult = std::make_shared<BitmapCache>();
+
+ // The cache may be newly created and thus empty or is old and may
+ // contain previews that are not up-to-date. Recycle previews from
+ // other caches to fill in the holes.
+ Recycle(pResult, pDocument,rPreviewSize);
+
+ // Put the new (or old) cache into the container.
+ mpPageCaches->emplace(aKey, pResult);
+
+ return pResult;
+}
+
+void PageCacheManager::Recycle (
+ const std::shared_ptr<BitmapCache>& rpCache,
+ const DocumentKey& pDocument,
+ const Size& rPreviewSize)
+{
+ BestFittingPageCaches aCaches;
+
+ // Add bitmap caches from active caches.
+ for (auto& rActiveCache : *mpPageCaches)
+ {
+ if (rActiveCache.first.mpDocument == pDocument)
+ aCaches.emplace_back(
+ rActiveCache.first.maPreviewSize, rActiveCache.second);
+ }
+
+ // Add bitmap caches from recently used caches.
+ RecentlyUsedPageCaches::iterator iQueue (mpRecentlyUsedPageCaches->find(pDocument));
+ if (iQueue != mpRecentlyUsedPageCaches->end())
+ {
+ for (const auto& rRecentCache : iQueue->second)
+ aCaches.emplace_back(
+ rRecentCache.maPreviewSize, rRecentCache.mpCache);
+ }
+
+ ::std::sort(aCaches.begin(), aCaches.end(), BestFittingCacheComparer(rPreviewSize));
+
+ for (const auto& rBestCache : aCaches)
+ {
+ rpCache->Recycle(*rBestCache.second);
+ }
+}
+
+void PageCacheManager::ReleaseCache (const std::shared_ptr<BitmapCache>& rpCache)
+{
+ PageCacheContainer::iterator iCache (::std::find_if(
+ mpPageCaches->begin(),
+ mpPageCaches->end(),
+ PageCacheContainer::CompareWithCache(rpCache)));
+
+ if (iCache != mpPageCaches->end())
+ {
+ assert(iCache->second == rpCache);
+
+ PutRecentlyUsedCache(iCache->first.mpDocument,iCache->first.maPreviewSize,rpCache);
+
+ mpPageCaches->erase(iCache);
+ }
+}
+
+std::shared_ptr<BitmapCache> PageCacheManager::ChangeSize (
+ const std::shared_ptr<BitmapCache>& rpCache,
+ const Size&,
+ const Size& rNewPreviewSize)
+{
+ std::shared_ptr<BitmapCache> pResult;
+
+ if (rpCache != nullptr)
+ {
+ // Look up the given cache in the list of active caches.
+ PageCacheContainer::iterator iCacheToChange (::std::find_if(
+ mpPageCaches->begin(),
+ mpPageCaches->end(),
+ PageCacheContainer::CompareWithCache(rpCache)));
+ if (iCacheToChange != mpPageCaches->end())
+ {
+ assert(iCacheToChange->second == rpCache);
+
+ // Now, we can change the preview size of the existing one by
+ // removing the cache from the list and re-insert it with the
+ // updated size.
+ const ::sd::slidesorter::cache::PageCacheManager::DocumentKey aKey (
+ iCacheToChange->first.mpDocument);
+ mpPageCaches->erase(iCacheToChange);
+ mpPageCaches->emplace(
+ CacheDescriptor(aKey,rNewPreviewSize),
+ rpCache);
+
+ pResult = rpCache;
+ }
+ else
+ {
+ assert(iCacheToChange != mpPageCaches->end());
+ }
+ }
+
+ return pResult;
+}
+
+bool PageCacheManager::InvalidatePreviewBitmap (
+ const DocumentKey& pDocument,
+ const SdrPage* pKey)
+{
+ bool bHasChanged (false);
+
+ if (pDocument!=nullptr)
+ {
+ // Iterate over all caches that are currently in use and invalidate
+ // the previews in those that belong to the document.
+ for (auto& rCache : *mpPageCaches)
+ if (rCache.first.mpDocument == pDocument)
+ bHasChanged |= rCache.second->InvalidateBitmap(pKey);
+
+ // Invalidate the previews in the recently used caches belonging to
+ // the given document.
+ RecentlyUsedPageCaches::iterator iQueue (mpRecentlyUsedPageCaches->find(pDocument));
+ if (iQueue != mpRecentlyUsedPageCaches->end())
+ {
+ for (const auto& rCache2 : iQueue->second)
+ bHasChanged |= rCache2.mpCache->InvalidateBitmap(pKey);
+ }
+ }
+
+ return bHasChanged;
+}
+
+void PageCacheManager::InvalidateAllPreviewBitmaps (const DocumentKey& pDocument)
+{
+ if (pDocument == nullptr)
+ return;
+
+ // Iterate over all caches that are currently in use and invalidate the
+ // previews in those that belong to the document.
+ for (auto& rCache : *mpPageCaches)
+ if (rCache.first.mpDocument == pDocument)
+ rCache.second->InvalidateCache();
+
+ // Invalidate the previews in the recently used caches belonging to the
+ // given document.
+ RecentlyUsedPageCaches::iterator iQueue (mpRecentlyUsedPageCaches->find(pDocument));
+ if (iQueue != mpRecentlyUsedPageCaches->end())
+ {
+ for (const auto& rCache2 : iQueue->second)
+ rCache2.mpCache->InvalidateCache();
+ }
+}
+
+void PageCacheManager::InvalidateAllCaches()
+{
+ // Iterate over all caches that are currently in use and invalidate
+ // them.
+ for (auto& rCache : *mpPageCaches)
+ rCache.second->InvalidateCache();
+
+ // Remove all recently used caches, there is not much sense in storing
+ // invalidated and unused caches.
+ mpRecentlyUsedPageCaches->clear();
+}
+
+void PageCacheManager::ReleasePreviewBitmap (const SdrPage* pPage)
+{
+ for (auto& rCache : *mpPageCaches)
+ rCache.second->ReleaseBitmap(pPage);
+}
+
+std::shared_ptr<BitmapCache> PageCacheManager::GetRecentlyUsedCache (
+ const DocumentKey& pDocument,
+ const Size& rPreviewSize)
+{
+ std::shared_ptr<BitmapCache> pCache;
+
+ // Look for the cache in the list of recently used caches.
+ RecentlyUsedPageCaches::iterator iQueue (mpRecentlyUsedPageCaches->find(pDocument));
+ if (iQueue != mpRecentlyUsedPageCaches->end())
+ {
+ RecentlyUsedQueue::iterator iCache = std::find_if(iQueue->second.begin(), iQueue->second.end(),
+ [&rPreviewSize](const RecentlyUsedCacheDescriptor& rCache) { return rCache.maPreviewSize == rPreviewSize; });
+ if (iCache != iQueue->second.end())
+ {
+ pCache = iCache->mpCache;
+ iQueue->second.erase(iCache);
+ }
+ }
+
+ return pCache;
+}
+
+void PageCacheManager::PutRecentlyUsedCache(
+ DocumentKey const & pDocument,
+ const Size& rPreviewSize,
+ const std::shared_ptr<BitmapCache>& rpCache)
+{
+ // Look up the list of recently used caches for the given document.
+ RecentlyUsedPageCaches::iterator iQueue (mpRecentlyUsedPageCaches->find(pDocument));
+ if (iQueue == mpRecentlyUsedPageCaches->end())
+ iQueue = mpRecentlyUsedPageCaches->emplace(
+ pDocument, RecentlyUsedQueue()
+ ).first;
+
+ if (iQueue != mpRecentlyUsedPageCaches->end())
+ {
+ iQueue->second.push_front(RecentlyUsedCacheDescriptor(rPreviewSize,rpCache));
+ // Shorten the list of recently used caches to the allowed maximal length.
+ while (iQueue->second.size() > mnMaximalRecentlyCacheCount)
+ iQueue->second.pop_back();
+ }
+}
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsQueueProcessor.cxx b/sd/source/ui/slidesorter/cache/SlsQueueProcessor.cxx
new file mode 100644
index 000000000..077c48709
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsQueueProcessor.cxx
@@ -0,0 +1,176 @@
+/* -*- 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 "SlsQueueProcessor.hxx"
+#include "SlsRequestQueue.hxx"
+#include "SlsBitmapCache.hxx"
+
+#include <sdpage.hxx>
+#include <comphelper/profilezone.hxx>
+#include <tools/diagnose_ex.h>
+
+namespace sd::slidesorter::cache {
+
+//===== QueueProcessor ======================================================
+
+QueueProcessor::QueueProcessor (
+ RequestQueue& rQueue,
+ const std::shared_ptr<BitmapCache>& rpCache,
+ const Size& rPreviewSize,
+ const bool bDoSuperSampling,
+ const SharedCacheContext& rpCacheContext)
+ : maTimer("sd::QueueProcessor maTimer"),
+ maPreviewSize(rPreviewSize),
+ mbDoSuperSampling(bDoSuperSampling),
+ mpCacheContext(rpCacheContext),
+ mrQueue(rQueue),
+ mpCache(rpCache),
+ mbIsPaused(false)
+{
+ maTimer.SetInvokeHandler (LINK(this,QueueProcessor,ProcessRequestHdl));
+ maTimer.SetTimeout (10);
+}
+
+QueueProcessor::~QueueProcessor()
+{
+}
+
+void QueueProcessor::Start (int nPriorityClass)
+{
+ if (mbIsPaused)
+ return;
+ if ( ! maTimer.IsActive())
+ {
+ if (nPriorityClass == 0)
+ maTimer.SetTimeout (10);
+ else
+ maTimer.SetTimeout (100);
+ maTimer.Start();
+ }
+}
+
+void QueueProcessor::Stop()
+{
+ if (maTimer.IsActive())
+ maTimer.Stop();
+}
+
+void QueueProcessor::Pause()
+{
+ mbIsPaused = true;
+}
+
+void QueueProcessor::Resume()
+{
+ mbIsPaused = false;
+ if ( ! mrQueue.IsEmpty())
+ Start(mrQueue.GetFrontPriorityClass());
+}
+
+void QueueProcessor::SetPreviewSize (
+ const Size& rPreviewSize,
+ const bool bDoSuperSampling)
+{
+ maPreviewSize = rPreviewSize;
+ mbDoSuperSampling = bDoSuperSampling;
+}
+
+IMPL_LINK_NOARG(QueueProcessor, ProcessRequestHdl, Timer *, void)
+{
+ ProcessRequests();
+}
+
+void QueueProcessor::ProcessRequests()
+{
+ assert(mpCacheContext);
+
+ // Never process more than one request at a time in order to prevent the
+ // lock up of the edit view.
+ if ( ! mrQueue.IsEmpty()
+ && ! mbIsPaused
+ && mpCacheContext->IsIdle())
+ {
+ CacheKey aKey = nullptr;
+ RequestPriorityClass ePriorityClass (NOT_VISIBLE);
+ {
+ ::osl::MutexGuard aGuard (mrQueue.GetMutex());
+
+ if ( ! mrQueue.IsEmpty())
+ {
+ // Get the request with the highest priority from the queue.
+ ePriorityClass = mrQueue.GetFrontPriorityClass();
+ aKey = mrQueue.GetFront();
+ mrQueue.PopFront();
+ }
+ }
+
+ if (aKey != nullptr)
+ ProcessOneRequest(aKey, ePriorityClass);
+ }
+
+ // Schedule the processing of the next element(s).
+ {
+ ::osl::MutexGuard aGuard (mrQueue.GetMutex());
+ if ( ! mrQueue.IsEmpty())
+ Start(mrQueue.GetFrontPriorityClass());
+ else
+ {
+ comphelper::ProfileZone aZone("QueueProcessor finished processing all elements");
+ }
+ }
+}
+
+void QueueProcessor::ProcessOneRequest (
+ CacheKey aKey,
+ const RequestPriorityClass ePriorityClass)
+{
+ try
+ {
+ std::scoped_lock aGuard (maMutex);
+
+ // Create a new preview bitmap and store it in the cache.
+ if (mpCache != nullptr && mpCacheContext)
+ {
+ const SdPage* pSdPage = dynamic_cast<const SdPage*>(mpCacheContext->GetPage(aKey));
+ if (pSdPage != nullptr)
+ {
+ const BitmapEx aPreview (
+ maBitmapFactory.CreateBitmap(*pSdPage, maPreviewSize, mbDoSuperSampling));
+ mpCache->SetBitmap (pSdPage, aPreview, ePriorityClass!=NOT_VISIBLE);
+
+ // Initiate a repaint of the new preview.
+ mpCacheContext->NotifyPreviewCreation(aKey);
+ }
+ }
+ }
+ catch (css::uno::Exception &)
+ {
+ TOOLS_WARN_EXCEPTION( "sd", "QueueProcessor");
+ }
+}
+
+void QueueProcessor::SetBitmapCache (
+ const std::shared_ptr<BitmapCache>& rpCache)
+{
+ mpCache = rpCache;
+}
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsQueueProcessor.hxx b/sd/source/ui/slidesorter/cache/SlsQueueProcessor.hxx
new file mode 100644
index 000000000..0035bcbce
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsQueueProcessor.hxx
@@ -0,0 +1,98 @@
+/* -*- 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 "SlsRequestPriorityClass.hxx"
+#include "SlsBitmapFactory.hxx"
+
+#include <vcl/timer.hxx>
+#include <mutex>
+
+namespace sd::slidesorter::cache {
+
+class BitmapCache;
+class RequestQueue;
+
+/** This queue processor is timer based, i.e. when an entry is added to the
+ queue and the processor is started with Start() in the base class a
+ timer is started that eventually calls ProcessRequest(). This is
+ repeated until the queue is empty or Stop() is called.
+*/
+class QueueProcessor final
+{
+public:
+ QueueProcessor (
+ RequestQueue& rQueue,
+ const std::shared_ptr<BitmapCache>& rpCache,
+ const Size& rPreviewSize,
+ const bool bDoSuperSampling,
+ const SharedCacheContext& rpCacheContext);
+ ~QueueProcessor();
+
+ /** Start the processor. This implementation is timer based and waits
+ a defined amount of time that depends on the given argument before
+ the next entry in the queue is processed.
+ @param nPriorityClass
+ A priority class of 0 tells the processor that a high priority
+ request is waiting in the queue. The time to wait is thus
+ shorter then that for a low priority request (denoted by a value
+ of 1.) When the timer is already running it is not modified.
+ */
+ void Start (int nPriorityClass);
+ void Stop();
+ void Pause();
+ void Resume();
+
+ void SetPreviewSize (
+ const Size& rSize,
+ const bool bDoSuperSampling);
+
+ /** Use this method when the page cache is (maybe) using a different
+ BitmapCache. This is usually necessary after calling
+ PageCacheManager::ChangeSize().
+ */
+ void SetBitmapCache (const std::shared_ptr<BitmapCache>& rpCache);
+
+private:
+ /** This mutex is used to guard the queue processor. Be careful not to
+ mix its use with that of the solar mutex.
+ */
+ std::mutex maMutex;
+
+ Timer maTimer;
+ DECL_LINK(ProcessRequestHdl, Timer *, void);
+ Size maPreviewSize;
+ bool mbDoSuperSampling;
+ SharedCacheContext mpCacheContext;
+ RequestQueue& mrQueue;
+ std::shared_ptr<BitmapCache> mpCache;
+ BitmapFactory maBitmapFactory;
+ bool mbIsPaused;
+
+ void ProcessRequests();
+ void ProcessOneRequest (
+ CacheKey aKey,
+ const RequestPriorityClass ePriorityClass);
+};
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsRequestFactory.cxx b/sd/source/ui/slidesorter/cache/SlsRequestFactory.cxx
new file mode 100644
index 000000000..6fc6cabc9
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsRequestFactory.cxx
@@ -0,0 +1,50 @@
+/* -*- 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 "SlsRequestFactory.hxx"
+#include "SlsRequestQueue.hxx"
+
+namespace sd::slidesorter::cache {
+
+void RequestFactory::operator()(
+ RequestQueue& rRequestQueue,
+ const SharedCacheContext& rpCacheContext)
+{
+ std::shared_ptr<std::vector<CacheKey> > aKeys;
+
+ // Add the requests for the visible pages.
+ aKeys = rpCacheContext->GetEntryList(true);
+ if (aKeys != nullptr)
+ {
+ for (const auto& rKey : *aKeys)
+ rRequestQueue.AddRequest(rKey, VISIBLE_NO_PREVIEW);
+ }
+
+ // Add the requests for the non-visible pages.
+ aKeys = rpCacheContext->GetEntryList(false);
+ if (aKeys != nullptr)
+ {
+ for (const auto& rKey : *aKeys)
+ rRequestQueue.AddRequest(rKey, NOT_VISIBLE);
+ }
+}
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsRequestFactory.hxx b/sd/source/ui/slidesorter/cache/SlsRequestFactory.hxx
new file mode 100644
index 000000000..3f4207725
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsRequestFactory.hxx
@@ -0,0 +1,36 @@
+/* -*- 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>
+
+namespace sd::slidesorter::cache
+{
+class RequestQueue;
+
+class RequestFactory
+{
+public:
+ void operator()(RequestQueue& rRequestQueue, const SharedCacheContext& rpCacheContext);
+};
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsRequestPriorityClass.hxx b/sd/source/ui/slidesorter/cache/SlsRequestPriorityClass.hxx
new file mode 100644
index 000000000..2c84ecbcf
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsRequestPriorityClass.hxx
@@ -0,0 +1,44 @@
+/* -*- 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
+
+namespace sd::slidesorter::cache
+{
+/** Each request for a preview creation has a priority. This enum defines
+ the available priorities. The special values MIN_CLASS and MAX_CLASS
+ are/can be used for validation and have to be kept up-to-date.
+*/
+enum RequestPriorityClass
+{
+ MIN_CLASS = 0,
+
+ // The slide is visible. A preview does not yet exist.
+ VISIBLE_NO_PREVIEW = MIN_CLASS,
+ // The slide is visible. A preview exists but is not up-to-date anymore.
+ VISIBLE_OUTDATED_PREVIEW,
+ // The slide is not visible.
+ NOT_VISIBLE,
+
+ MAX_CLASS = NOT_VISIBLE
+};
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsRequestQueue.cxx b/sd/source/ui/slidesorter/cache/SlsRequestQueue.cxx
new file mode 100644
index 000000000..931c1a8f6
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsRequestQueue.cxx
@@ -0,0 +1,275 @@
+/* -*- 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 "SlsRequestQueue.hxx"
+
+#include <sal/log.hxx>
+
+#include <svx/svdpage.hxx>
+
+#include <set>
+
+namespace sd::slidesorter::cache {
+
+namespace {
+
+/** This class extends the actual request data with additional information
+ that is used by the priority queues.
+*/
+class Request
+{
+public:
+ Request (
+ CacheKey aKey, sal_Int32 nPriority, RequestPriorityClass eClass)
+ : maKey(aKey), mnPriorityInClass(nPriority), meClass(eClass)
+ {}
+ /** Sort requests according to priority classes and then to priorities.
+ */
+ class Comparator { public:
+ bool operator() (const Request& rRequest1, const Request& rRequest2)
+ const
+ {
+ if (rRequest1.meClass == rRequest2.meClass)
+ {
+ if (rRequest1.mnPriorityInClass == rRequest2.mnPriorityInClass)
+ {
+ return rRequest1.maKey < rRequest2.maKey;
+ }
+ return rRequest1.mnPriorityInClass > rRequest2.mnPriorityInClass;
+ }
+ return rRequest1.meClass < rRequest2.meClass;
+ }
+ };
+ /** Request data is compared arbitrarily by their addresses in memory.
+ This just establishes an order so that the STL containers are happy.
+ The order is not semantically interpreted.
+ */
+ class DataComparator
+ {
+ public:
+ explicit DataComparator (const CacheKey aKey)
+ : maKey(aKey)
+ {
+ }
+ bool operator() (const Request& rRequest) const
+ {
+ return maKey == rRequest.maKey;
+ }
+ private:
+ const CacheKey maKey;
+ };
+
+ CacheKey maKey;
+ sal_Int32 mnPriorityInClass;
+ RequestPriorityClass meClass;
+};
+
+}
+
+class RequestQueue::Container
+ : public ::std::set<
+ Request,
+ Request::Comparator>
+{
+};
+
+//===== GenericRequestQueue =================================================
+
+RequestQueue::RequestQueue (const SharedCacheContext& rpCacheContext)
+ : mpRequestQueue(new Container),
+ mpCacheContext(rpCacheContext),
+ mnMinimumPriority(0),
+ mnMaximumPriority(1)
+{
+}
+
+RequestQueue::~RequestQueue()
+{
+ Clear();
+}
+
+void RequestQueue::AddRequest (
+ CacheKey aKey,
+ RequestPriorityClass eRequestClass)
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ assert(eRequestClass>=MIN_CLASS && eRequestClass<=MAX_CLASS);
+
+ // If the request is already a member of the queue then remove it so
+ // that the following insertion will use the new prioritization.
+#if OSL_DEBUG_LEVEL >=2
+ bool bRemoved =
+#endif
+ RemoveRequest(aKey);
+
+ // The priority of the request inside its priority class is defined by
+ // the page number. This ensures a strict top-to-bottom, left-to-right
+ // order.
+ sal_Int32 nPriority (mpCacheContext->GetPriority(aKey));
+ Request aRequest (aKey, nPriority, eRequestClass);
+
+ std::pair<Container::iterator,bool> ret = mpRequestQueue->insert(aRequest);
+ bool bInserted = ret.second;
+
+ if (bInserted)
+ {
+ SdrPage *pPage = const_cast<SdrPage*>(aRequest.maKey);
+ pPage->AddPageUser(*this);
+ }
+
+#if OSL_DEBUG_LEVEL >=2
+ SAL_INFO("sd.sls", __func__ << ": " << (bRemoved?"replaced":"added")
+ << " request for page " << ((aKey->GetPageNum()-1)/2)
+ << " with priority class " << static_cast<int>(eRequestClass));
+#endif
+}
+
+void RequestQueue::PageInDestruction(const SdrPage& rPage)
+{
+ //remove any requests pending for this page which is going away now
+ RemoveRequest(&rPage);
+}
+
+#if OSL_DEBUG_LEVEL >=2
+bool
+#else
+void
+#endif
+RequestQueue::RemoveRequest(
+ CacheKey aKey)
+{
+ ::osl::MutexGuard aGuard (maMutex);
+#if OSL_DEBUG_LEVEL >=2
+ bool bIsRemoved = false;
+#endif
+ while(true)
+ {
+ Container::const_iterator aRequestIterator = ::std::find_if (
+ mpRequestQueue->begin(),
+ mpRequestQueue->end(),
+ Request::DataComparator(aKey));
+ if (aRequestIterator != mpRequestQueue->end())
+ {
+ if (aRequestIterator->mnPriorityInClass == mnMinimumPriority+1)
+ mnMinimumPriority++;
+ else if (aRequestIterator->mnPriorityInClass == mnMaximumPriority-1)
+ mnMaximumPriority--;
+
+ SdrPage *pPage = const_cast<SdrPage*>(aRequestIterator->maKey);
+ pPage->RemovePageUser(*this);
+ mpRequestQueue->erase(aRequestIterator);
+#if OSL_DEBUG_LEVEL >=2
+ bIsRemoved = true;
+#endif
+ }
+ else
+ break;
+ }
+#if OSL_DEBUG_LEVEL >=2
+ return bIsRemoved;
+#endif
+
+}
+
+void RequestQueue::ChangeClass (
+ CacheKey aKey,
+ RequestPriorityClass eNewRequestClass)
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ assert(eNewRequestClass>=MIN_CLASS && eNewRequestClass<=MAX_CLASS);
+
+ Container::const_iterator iRequest (
+ ::std::find_if (
+ mpRequestQueue->begin(),
+ mpRequestQueue->end(),
+ Request::DataComparator(aKey)));
+ if (iRequest!=mpRequestQueue->end() && iRequest->meClass!=eNewRequestClass)
+ {
+ AddRequest(aKey, eNewRequestClass);
+ }
+}
+
+CacheKey RequestQueue::GetFront()
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ if (mpRequestQueue->empty())
+ throw css::uno::RuntimeException("RequestQueue::GetFront(): queue is empty",
+ nullptr);
+
+ return mpRequestQueue->begin()->maKey;
+}
+
+RequestPriorityClass RequestQueue::GetFrontPriorityClass()
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ if (mpRequestQueue->empty())
+ throw css::uno::RuntimeException("RequestQueue::GetFrontPriorityClass(): queue is empty",
+ nullptr);
+
+ return mpRequestQueue->begin()->meClass;
+}
+
+void RequestQueue::PopFront()
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ if ( mpRequestQueue->empty())
+ return;
+
+ Container::const_iterator aIter(mpRequestQueue->begin());
+ SdrPage *pPage = const_cast<SdrPage*>(aIter->maKey);
+ pPage->RemovePageUser(*this);
+ mpRequestQueue->erase(aIter);
+
+ // Reset the priority counter if possible.
+ if (mpRequestQueue->empty())
+ {
+ mnMinimumPriority = 0;
+ mnMaximumPriority = 1;
+ }
+}
+
+bool RequestQueue::IsEmpty()
+{
+ ::osl::MutexGuard aGuard (maMutex);
+ return mpRequestQueue->empty();
+}
+
+void RequestQueue::Clear()
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ for (const auto& rItem : *mpRequestQueue)
+ {
+ SdrPage *pPage = const_cast<SdrPage*>(rItem.maKey);
+ pPage->RemovePageUser(*this);
+ }
+
+ mpRequestQueue->clear();
+ mnMinimumPriority = 0;
+ mnMaximumPriority = 1;
+}
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/cache/SlsRequestQueue.hxx b/sd/source/ui/slidesorter/cache/SlsRequestQueue.hxx
new file mode 100644
index 000000000..618ba5801
--- /dev/null
+++ b/sd/source/ui/slidesorter/cache/SlsRequestQueue.hxx
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include "SlsRequestPriorityClass.hxx"
+#include <cache/SlsCacheContext.hxx>
+#include <osl/mutex.hxx>
+#include <svx/sdrpageuser.hxx>
+
+#include <memory>
+
+namespace sd::slidesorter::cache
+{
+/** The request queue stores requests that are described by the Request
+ sorted according to priority class and then priority.
+*/
+class RequestQueue : public sdr::PageUser
+{
+public:
+ explicit RequestQueue(const SharedCacheContext& rpCacheContext);
+ virtual ~RequestQueue();
+
+ /** Insert a request with highest or lowest priority in its priority
+ class. When the request is already present then it is first
+ removed. This effect is then a re-prioritization.
+ @param aKey
+ The request.
+ @param eRequestClass
+ The priority class in which to insert the request with highest
+ or lowest priority.
+ @param bInsertWithHighestPriority
+ When this flag is <TRUE/> the request is inserted with highest
+ priority in its class. When <FALSE/> the request is inserted
+ with lowest priority.
+ */
+ void AddRequest(CacheKey aKey, RequestPriorityClass eRequestClass);
+
+ /** Remove the specified request from the queue.
+ @param aKey
+ It is OK when the specified request is not a member of the
+ queue.
+ */
+#if OSL_DEBUG_LEVEL >= 2
+ bool
+#else
+ void
+#endif
+ RemoveRequest(CacheKey aKey);
+
+ /** Change the priority class of the specified request.
+ */
+ void ChangeClass(CacheKey aKey, RequestPriorityClass eNewRequestClass);
+
+ /** Get the request with the highest priority int the highest priority class.
+ */
+ CacheKey GetFront();
+
+ // For debugging.
+ RequestPriorityClass GetFrontPriorityClass();
+
+ /** Really a synonym for RemoveRequest(GetFront());
+ */
+ void PopFront();
+
+ /** Returns <TRUE/> when there is no element in the queue.
+ */
+ bool IsEmpty();
+
+ /** Remove all requests from the queue. This resets the minimum and
+ maximum priorities to their default values.
+ */
+ void Clear();
+
+ /** Return the mutex that guards the access to the priority queue.
+ */
+ ::osl::Mutex& GetMutex() { return maMutex; }
+
+ /** Ensure we don't hand out a page deleted before anyone got a
+ chance to process it
+ */
+ virtual void PageInDestruction(const SdrPage& rPage) override;
+
+private:
+ ::osl::Mutex maMutex;
+ class Container;
+ std::unique_ptr<Container> mpRequestQueue;
+ SharedCacheContext mpCacheContext;
+
+ /** A lower bound of the lowest priority of all elements in the queues.
+ The start value is 0. It is assigned and then decreased every time
+ when an element is inserted or marked as the request with lowest
+ priority.
+ */
+ int mnMinimumPriority;
+ /** An upper bound of the highest priority of all elements in the queues.
+ The start value is 1. It is assigned and then increased every time
+ when an element is inserted or marked as the request with highest
+ priority.
+ */
+ int mnMaximumPriority;
+};
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlideSorterController.cxx b/sd/source/ui/slidesorter/controller/SlideSorterController.cxx
new file mode 100644
index 000000000..5c851f183
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlideSorterController.cxx
@@ -0,0 +1,910 @@
+/* -*- 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 <controller/SlideSorterController.hxx>
+
+#include <SlideSorter.hxx>
+#include <controller/SlsPageSelector.hxx>
+#include <controller/SlsSelectionFunction.hxx>
+#include <controller/SlsProperties.hxx>
+#include <controller/SlsCurrentSlideManager.hxx>
+#include "SlsListener.hxx"
+#include <controller/SlsFocusManager.hxx>
+#include <controller/SlsAnimator.hxx>
+#include <controller/SlsClipboard.hxx>
+#include <controller/SlsInsertionIndicatorHandler.hxx>
+#include <controller/SlsScrollBarManager.hxx>
+#include <controller/SlsSelectionManager.hxx>
+#include <controller/SlsSlotManager.hxx>
+#include <controller/SlsVisibleAreaManager.hxx>
+#include <model/SlideSorterModel.hxx>
+#include <model/SlsPageEnumerationProvider.hxx>
+#include <model/SlsPageDescriptor.hxx>
+#include <view/SlideSorterView.hxx>
+#include <view/SlsLayouter.hxx>
+#include <view/SlsPageObjectLayouter.hxx>
+#include <view/SlsTheme.hxx>
+#include <view/SlsToolTip.hxx>
+#include <cache/SlsPageCache.hxx>
+#include <cache/SlsPageCacheManager.hxx>
+#include <tools/diagnose_ex.h>
+
+#include <drawdoc.hxx>
+#include <ViewShellBase.hxx>
+#include <Window.hxx>
+#include <FrameView.hxx>
+#include <sdpage.hxx>
+
+#include <app.hrc>
+#include <sdmod.hxx>
+#include <ViewShellHint.hxx>
+#include <AccessibleSlideSorterView.hxx>
+#include <AccessibleSlideSorterObject.hxx>
+
+#include <vcl/window.hxx>
+#include <svx/svxids.hrc>
+#include <sfx2/request.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/dispatch.hxx>
+#include <tools/debug.hxx>
+#include <vcl/commandevent.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+
+#include <com/sun/star/lang/XComponent.hpp>
+#include <com/sun/star/accessibility/AccessibleEventId.hpp>
+
+#include <memory>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::sd::slidesorter::model;
+using namespace ::sd::slidesorter::view;
+using namespace ::sd::slidesorter::controller;
+using namespace ::basegfx;
+
+namespace sd::slidesorter::controller {
+
+SlideSorterController::SlideSorterController (SlideSorter& rSlideSorter)
+ : mrSlideSorter(rSlideSorter),
+ mrModel(mrSlideSorter.GetModel()),
+ mrView(mrSlideSorter.GetView()),
+ mpInsertionIndicatorHandler(std::make_shared<InsertionIndicatorHandler>(rSlideSorter)),
+ mpAnimator(std::make_shared<Animator>(rSlideSorter)),
+ mpVisibleAreaManager(new VisibleAreaManager(rSlideSorter)),
+ mnModelChangeLockCount(0),
+ mbIsForcedRearrangePending(false),
+ mbContextMenuOpen(false),
+ mbPostModelChangePending(false),
+ mnCurrentPageBeforeSwitch(0),
+ mpEditModeChangeMasterPage(nullptr),
+ mnPaintEntranceCount(0)
+{
+ sd::Window *pWindow (mrSlideSorter.GetContentWindow().get());
+ OSL_ASSERT(pWindow);
+ if (!pWindow)
+ return;
+
+ // The whole background is painted by the view and controls.
+ vcl::Window* pParentWindow = pWindow->GetParent();
+ OSL_ASSERT(pParentWindow!=nullptr);
+ pParentWindow->SetBackground (Wallpaper());
+
+ // Connect the view with the window that has been created by our base
+ // class.
+ pWindow->SetBackground(Wallpaper());
+ pWindow->SetCenterAllowed(false);
+ pWindow->SetMapMode(MapMode(MapUnit::MapPixel));
+ pWindow->SetViewSize(mrView.GetModelArea().GetSize());
+}
+
+void SlideSorterController::Init()
+{
+ mpCurrentSlideManager = std::make_shared<CurrentSlideManager>(mrSlideSorter);
+ mpPageSelector.reset(new PageSelector(mrSlideSorter));
+ mpFocusManager.reset(new FocusManager(mrSlideSorter));
+ mpSlotManager = std::make_shared<SlotManager>(mrSlideSorter);
+ mpScrollBarManager.reset(new ScrollBarManager(mrSlideSorter));
+ mpSelectionManager = std::make_shared<SelectionManager>(mrSlideSorter);
+ mpClipboard.reset(new Clipboard(mrSlideSorter));
+
+ // Create the selection function.
+ SfxRequest aRequest (
+ SID_OBJECT_SELECT,
+ SfxCallMode::SLOT,
+ mrModel.GetDocument()->GetItemPool());
+ mrSlideSorter.SetCurrentFunction(CreateSelectionFunction(aRequest));
+
+ mpListener = new Listener(mrSlideSorter);
+
+ mpPageSelector->GetCoreSelection();
+ GetSelectionManager()->SelectionHasChanged();
+}
+
+SlideSorterController::~SlideSorterController()
+{
+ try
+ {
+ uno::Reference<lang::XComponent> xComponent = mpListener;
+ if (xComponent.is())
+ xComponent->dispose();
+ }
+ catch( uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sd", "sd::SlideSorterController::~SlideSorterController()" );
+ }
+
+ // dispose should have been called by now so that nothing is to be done
+ // to shut down cleanly.
+}
+
+void SlideSorterController::Dispose()
+{
+ mpInsertionIndicatorHandler->End(Animator::AM_Immediate);
+ mpClipboard.reset();
+ mpSelectionManager.reset();
+ mpAnimator->Dispose();
+}
+
+model::SharedPageDescriptor SlideSorterController::GetPageAt (
+ const Point& aWindowPosition)
+{
+ sal_Int32 nHitPageIndex (mrView.GetPageIndexAtPoint(aWindowPosition));
+ model::SharedPageDescriptor pDescriptorAtPoint;
+ if (nHitPageIndex >= 0)
+ {
+ pDescriptorAtPoint = mrModel.GetPageDescriptor(nHitPageIndex);
+
+ // Depending on a property we may have to check that the mouse is no
+ // just over the page object but over the preview area.
+ if (pDescriptorAtPoint
+ && ! pDescriptorAtPoint->HasState(PageDescriptor::ST_Selected))
+ {
+ // Make sure that the mouse is over the preview area.
+ if ( ! mrView.GetLayouter().GetPageObjectLayouter()->GetBoundingBox(
+ pDescriptorAtPoint,
+ view::PageObjectLayouter::Part::Preview,
+ view::PageObjectLayouter::WindowCoordinateSystem).Contains(aWindowPosition))
+ {
+ pDescriptorAtPoint.reset();
+ }
+ }
+ }
+
+ return pDescriptorAtPoint;
+}
+
+PageSelector& SlideSorterController::GetPageSelector()
+{
+ OSL_ASSERT(mpPageSelector != nullptr);
+ return *mpPageSelector;
+}
+
+FocusManager& SlideSorterController::GetFocusManager()
+{
+ OSL_ASSERT(mpFocusManager != nullptr);
+ return *mpFocusManager;
+}
+
+Clipboard& SlideSorterController::GetClipboard()
+{
+ OSL_ASSERT(mpClipboard != nullptr);
+ return *mpClipboard;
+}
+
+ScrollBarManager& SlideSorterController::GetScrollBarManager()
+{
+ OSL_ASSERT(mpScrollBarManager != nullptr);
+ return *mpScrollBarManager;
+}
+
+std::shared_ptr<CurrentSlideManager> const & SlideSorterController::GetCurrentSlideManager() const
+{
+ OSL_ASSERT(mpCurrentSlideManager != nullptr);
+ return mpCurrentSlideManager;
+}
+
+std::shared_ptr<SlotManager> const & SlideSorterController::GetSlotManager() const
+{
+ OSL_ASSERT(mpSlotManager != nullptr);
+ return mpSlotManager;
+}
+
+std::shared_ptr<SelectionManager> const & SlideSorterController::GetSelectionManager() const
+{
+ OSL_ASSERT(mpSelectionManager != nullptr);
+ return mpSelectionManager;
+}
+
+std::shared_ptr<InsertionIndicatorHandler> const &
+ SlideSorterController::GetInsertionIndicatorHandler() const
+{
+ OSL_ASSERT(mpInsertionIndicatorHandler != nullptr);
+ return mpInsertionIndicatorHandler;
+}
+
+void SlideSorterController::Paint (
+ const ::tools::Rectangle& rBBox,
+ vcl::Window* pWindow)
+{
+ if (mnPaintEntranceCount != 0)
+ return;
+
+ ++mnPaintEntranceCount;
+
+ try
+ {
+ mrView.CompleteRedraw(pWindow->GetOutDev(), vcl::Region(rBBox));
+ }
+ catch (const Exception&)
+ {
+ // Ignore all exceptions.
+ }
+
+ --mnPaintEntranceCount;
+}
+
+void SlideSorterController::FuTemporary (SfxRequest& rRequest)
+{
+ mpSlotManager->FuTemporary (rRequest);
+}
+
+void SlideSorterController::FuPermanent (SfxRequest &rRequest)
+{
+ mpSlotManager->FuPermanent (rRequest);
+}
+
+void SlideSorterController::FuSupport (SfxRequest &rRequest)
+{
+ mpSlotManager->FuSupport (rRequest);
+}
+
+bool SlideSorterController::Command (
+ const CommandEvent& rEvent,
+ ::sd::Window* pWindow)
+{
+ bool bEventHasBeenHandled = false;
+
+ if (pWindow == nullptr)
+ return false;
+
+ ViewShell* pViewShell = mrSlideSorter.GetViewShell();
+ if (pViewShell == nullptr)
+ return false;
+
+ switch (rEvent.GetCommand())
+ {
+ case CommandEventId::ContextMenu:
+ {
+ SdPage* pPage = nullptr;
+ OUString aPopupId;
+
+ model::PageEnumeration aSelectedPages (
+ PageEnumerationProvider::CreateSelectedPagesEnumeration(mrModel));
+ if (aSelectedPages.HasMoreElements())
+ pPage = aSelectedPages.GetNextElement()->GetPage();
+
+ if (mrModel.GetEditMode() == EditMode::Page)
+ {
+ if (pPage != nullptr)
+ aPopupId = "pagepane";
+ else
+ aPopupId = "pagepanenosel";
+ }
+ else if (pPage != nullptr)
+ aPopupId = "pagepanemaster";
+ else
+ aPopupId = "pagepanenoselmaster";
+
+ std::unique_ptr<InsertionIndicatorHandler::ForceShowContext, o3tl::default_delete<InsertionIndicatorHandler::ForceShowContext>> xContext;
+ if (pPage == nullptr)
+ {
+ // When there is no selection, then we show the insertion
+ // indicator so that the user knows where a page insertion
+ // would take place.
+ mpInsertionIndicatorHandler->Start(false);
+ mpInsertionIndicatorHandler->UpdateIndicatorIcon(SD_MOD()->pTransferClip);
+ mpInsertionIndicatorHandler->UpdatePosition(
+ pWindow->PixelToLogic(rEvent.GetMousePosPixel()),
+ InsertionIndicatorHandler::MoveMode);
+ xContext.reset(new InsertionIndicatorHandler::ForceShowContext(
+ mpInsertionIndicatorHandler));
+ }
+
+ pWindow->ReleaseMouse();
+
+ Point aMenuLocation (0,0);
+ if (!rEvent.IsMouseEvent())
+ {
+ // The event is not a mouse event. Use the center of the
+ // focused page as top left position of the context menu.
+ model::SharedPageDescriptor pDescriptor (
+ GetFocusManager().GetFocusedPageDescriptor());
+ if (pDescriptor)
+ {
+ ::tools::Rectangle aBBox (
+ mrView.GetLayouter().GetPageObjectLayouter()->GetBoundingBox (
+ pDescriptor,
+ PageObjectLayouter::Part::PageObject,
+ PageObjectLayouter::ModelCoordinateSystem));
+ aMenuLocation = aBBox.Center();
+ }
+ }
+
+ if (SfxDispatcher* pDispatcher = pViewShell->GetDispatcher())
+ {
+ mbContextMenuOpen = true;
+ if (!rEvent.IsMouseEvent())
+ pDispatcher->ExecutePopup(aPopupId, pWindow, &aMenuLocation);
+ else
+ pDispatcher->ExecutePopup(aPopupId, pWindow);
+ mbContextMenuOpen = false;
+ mrSlideSorter.GetView().UpdatePageUnderMouse();
+ ::rtl::Reference<SelectionFunction> pFunction(GetCurrentSelectionFunction());
+ if (pFunction.is())
+ pFunction->ResetMouseAnchor();
+ }
+ if (pPage == nullptr)
+ {
+ // Remember the position of the insertion indicator before
+ // it is hidden, so that a pending slide insertion slot call
+ // finds the right place to insert a new slide.
+ GetSelectionManager()->SetInsertionPosition(
+ GetInsertionIndicatorHandler()->GetInsertionPageIndex());
+ }
+ xContext.reset();
+ bEventHasBeenHandled = true;
+ }
+ break;
+
+ case CommandEventId::Wheel:
+ {
+ const CommandWheelData* pData = rEvent.GetWheelData();
+ if (pData == nullptr)
+ return false;
+ if (pData->IsMod1())
+ {
+ // We do not support zooming with control+mouse wheel.
+ return false;
+ }
+ // Determine whether to scroll horizontally or vertically. This
+ // depends on the orientation of the scroll bar and the
+ // IsHoriz() flag of the event.
+ if ((mrSlideSorter.GetView().GetOrientation()==view::Layouter::HORIZONTAL)
+ == pData->IsHorz())
+ {
+ GetScrollBarManager().Scroll(
+ ScrollBarManager::Orientation_Vertical,
+ -pData->GetNotchDelta());
+ }
+ else
+ {
+ GetScrollBarManager().Scroll(
+ ScrollBarManager::Orientation_Horizontal,
+ -pData->GetNotchDelta());
+ }
+ mrSlideSorter.GetView().UpdatePageUnderMouse(rEvent.GetMousePosPixel());
+
+ bEventHasBeenHandled = true;
+ }
+ break;
+
+ default: break;
+ }
+
+ return bEventHasBeenHandled;
+}
+
+void SlideSorterController::LockModelChange()
+{
+ mnModelChangeLockCount += 1;
+}
+
+void SlideSorterController::UnlockModelChange()
+{
+ mnModelChangeLockCount -= 1;
+ if (mnModelChangeLockCount==0 && mbPostModelChangePending)
+ {
+ PostModelChange();
+ }
+}
+
+void SlideSorterController::PreModelChange()
+{
+ // Prevent PreModelChange to execute more than once per model lock.
+ if (mbPostModelChangePending)
+ return;
+
+ if (mrSlideSorter.GetViewShell() != nullptr)
+ mrSlideSorter.GetViewShell()->Broadcast(
+ ViewShellHint(ViewShellHint::HINT_COMPLEX_MODEL_CHANGE_START));
+
+ GetCurrentSlideManager()->PrepareModelChange();
+
+ if (mrSlideSorter.GetContentWindow())
+ mrView.PreModelChange();
+
+ mbPostModelChangePending = true;
+}
+
+void SlideSorterController::PostModelChange()
+{
+ mbPostModelChangePending = false;
+ mrModel.Resync();
+
+ sd::Window *pWindow (mrSlideSorter.GetContentWindow().get());
+ if (pWindow)
+ {
+ GetCurrentSlideManager()->HandleModelChange();
+
+ mrView.PostModelChange ();
+
+ pWindow->SetViewOrigin (Point (0,0));
+ pWindow->SetViewSize (mrView.GetModelArea().GetSize());
+
+ // The visibility of the scroll bars may have to be changed. Then
+ // the size of the view has to change, too. Let Rearrange() handle
+ // that.
+ Rearrange(mbIsForcedRearrangePending);
+ }
+
+ if (mrSlideSorter.GetViewShell() != nullptr)
+ mrSlideSorter.GetViewShell()->Broadcast(
+ ViewShellHint(ViewShellHint::HINT_COMPLEX_MODEL_CHANGE_END));
+}
+
+void SlideSorterController::HandleModelChange()
+{
+ // Ignore this call when the document is not in a valid state, i.e. has
+ // not the same number of regular and notes pages.
+ bool bIsDocumentValid = (mrModel.GetDocument()->GetPageCount() % 2 == 1);
+
+ if (bIsDocumentValid)
+ {
+ ModelChangeLock aLock (*this);
+ PreModelChange();
+ }
+}
+
+IMPL_LINK(SlideSorterController, ApplicationEventHandler, VclSimpleEvent&, rEvent, void)
+{
+ auto windowEvent = dynamic_cast<VclWindowEvent *>(&rEvent);
+ if (windowEvent != nullptr) {
+ WindowEventHandler(*windowEvent);
+ }
+}
+IMPL_LINK(SlideSorterController, WindowEventHandler, VclWindowEvent&, rEvent, void)
+{
+ vcl::Window* pWindow = rEvent.GetWindow();
+ sd::Window *pActiveWindow (mrSlideSorter.GetContentWindow().get());
+ switch (rEvent.GetId())
+ {
+ case VclEventId::WindowActivate:
+ case VclEventId::WindowShow:
+ if (pActiveWindow && pWindow == pActiveWindow->GetParent())
+ mrView.RequestRepaint();
+ break;
+
+ case VclEventId::WindowHide:
+ if (pActiveWindow && pWindow == pActiveWindow->GetParent())
+ mrView.SetPageUnderMouse(SharedPageDescriptor());
+ break;
+
+ case VclEventId::WindowGetFocus:
+ if (pActiveWindow)
+ if (pWindow == pActiveWindow)
+ GetFocusManager().ShowFocus(false);
+ break;
+
+ case VclEventId::WindowLoseFocus:
+ if (pActiveWindow && pWindow == pActiveWindow)
+ {
+ GetFocusManager().HideFocus();
+ mrView.GetToolTip().Hide();
+
+ //don't scroll back to the selected slide when we lose
+ //focus due to a temporary active context menu
+ if (!mbContextMenuOpen)
+ {
+ // Select the current slide so that it is properly
+ // visualized when the focus is moved to the edit view.
+ GetPageSelector().SelectPage(GetCurrentSlideManager()->GetCurrentSlide());
+ }
+ }
+ break;
+
+ case VclEventId::ApplicationDataChanged:
+ {
+ // Invalidate the preview cache.
+ cache::PageCacheManager::Instance()->InvalidateAllCaches();
+
+ // Update the draw mode.
+ DrawModeFlags nDrawMode (Application::GetSettings().GetStyleSettings().GetHighContrastMode()
+ ? sd::OUTPUT_DRAWMODE_CONTRAST
+ : sd::OUTPUT_DRAWMODE_COLOR);
+ if (mrSlideSorter.GetViewShell() != nullptr)
+ mrSlideSorter.GetViewShell()->GetFrameView()->SetDrawMode(nDrawMode);
+ if (pActiveWindow != nullptr)
+ pActiveWindow->GetOutDev()->SetDrawMode(nDrawMode);
+ mrView.HandleDrawModeChange();
+
+ // When the system font has changed a layout has to be done.
+ mrView.Resize();
+
+ // Update theme colors.
+ mrSlideSorter.GetProperties()->HandleDataChangeEvent();
+ mrSlideSorter.GetTheme()->Update(mrSlideSorter.GetProperties());
+ mrView.HandleDataChangeEvent();
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+void SlideSorterController::GetCtrlState (SfxItemSet& rSet)
+{
+ if (rSet.GetItemState(SID_RELOAD) != SfxItemState::UNKNOWN)
+ {
+ // let SFx en-/disable "last version"
+ SfxViewFrame* pSlideViewFrame = SfxViewFrame::Current();
+ DBG_ASSERT(pSlideViewFrame!=nullptr,
+ "SlideSorterController::GetCtrlState: ViewFrame not found");
+ if (pSlideViewFrame)
+ {
+ pSlideViewFrame->GetSlotState (SID_RELOAD, nullptr, &rSet);
+ }
+ else // MI says: no MDIFrame --> disable
+ {
+ rSet.DisableItem(SID_RELOAD);
+ }
+ }
+
+ // Output quality.
+ if (rSet.GetItemState(SID_OUTPUT_QUALITY_COLOR)==SfxItemState::DEFAULT
+ ||rSet.GetItemState(SID_OUTPUT_QUALITY_GRAYSCALE)==SfxItemState::DEFAULT
+ ||rSet.GetItemState(SID_OUTPUT_QUALITY_BLACKWHITE)==SfxItemState::DEFAULT
+ ||rSet.GetItemState(SID_OUTPUT_QUALITY_CONTRAST)==SfxItemState::DEFAULT)
+ {
+ if (mrSlideSorter.GetContentWindow())
+ {
+ DrawModeFlags nMode = mrSlideSorter.GetContentWindow()->GetOutDev()->GetDrawMode();
+ sal_uInt16 nQuality = 0;
+
+ if (nMode == sd::OUTPUT_DRAWMODE_COLOR) {
+ nQuality = 0;
+ } else if (nMode == sd::OUTPUT_DRAWMODE_GRAYSCALE) {
+ nQuality = 1;
+ } else if (nMode == sd::OUTPUT_DRAWMODE_BLACKWHITE) {
+ nQuality = 2;
+ } else if (nMode == sd::OUTPUT_DRAWMODE_CONTRAST) {
+ nQuality = 3;
+ }
+
+ rSet.Put (SfxBoolItem (SID_OUTPUT_QUALITY_COLOR, nQuality==0));
+ rSet.Put (SfxBoolItem (SID_OUTPUT_QUALITY_GRAYSCALE, nQuality==1));
+ rSet.Put (SfxBoolItem (SID_OUTPUT_QUALITY_BLACKWHITE, nQuality==2));
+ rSet.Put (SfxBoolItem (SID_OUTPUT_QUALITY_CONTRAST, nQuality==3));
+ }
+ }
+
+ if (rSet.GetItemState(SID_MAIL_SCROLLBODY_PAGEDOWN) == SfxItemState::DEFAULT)
+ {
+ rSet.Put (SfxBoolItem( SID_MAIL_SCROLLBODY_PAGEDOWN, true));
+ }
+}
+
+void SlideSorterController::GetStatusBarState (SfxItemSet& rSet)
+{
+ mpSlotManager->GetStatusBarState (rSet);
+}
+
+void SlideSorterController::ExecCtrl (SfxRequest& rRequest)
+{
+ mpSlotManager->ExecCtrl (rRequest);
+}
+
+void SlideSorterController::GetAttrState (SfxItemSet& rSet)
+{
+ mpSlotManager->GetAttrState (rSet);
+}
+
+void SlideSorterController::UpdateAllPages()
+{
+ // Do a redraw.
+ mrSlideSorter.GetContentWindow()->Invalidate();
+}
+
+void SlideSorterController::Resize (const ::tools::Rectangle& rAvailableSpace)
+{
+ if (maTotalWindowArea != rAvailableSpace)
+ {
+ maTotalWindowArea = rAvailableSpace;
+ Rearrange(true);
+ }
+}
+
+void SlideSorterController::Rearrange (bool bForce)
+{
+ if (maTotalWindowArea.IsEmpty())
+ return;
+
+ if (mnModelChangeLockCount>0)
+ {
+ mbIsForcedRearrangePending |= bForce;
+ return;
+ }
+ else
+ mbIsForcedRearrangePending = false;
+
+ sd::Window *pWindow (mrSlideSorter.GetContentWindow().get());
+ if (!pWindow)
+ return;
+
+ if (bForce)
+ mrView.UpdateOrientation();
+
+ // Place the scroll bars.
+ ::tools::Rectangle aNewContentArea = GetScrollBarManager().PlaceScrollBars(
+ maTotalWindowArea,
+ mrView.GetOrientation() != view::Layouter::VERTICAL,
+ mrView.GetOrientation() != view::Layouter::HORIZONTAL);
+
+ bool bSizeHasChanged (false);
+ // Only when bForce is not true we have to test for a size change in
+ // order to determine whether the window and the view have to be resized.
+ if ( ! bForce)
+ {
+ ::tools::Rectangle aCurrentContentArea (pWindow->GetPosPixel(), pWindow->GetOutputSizePixel());
+ bSizeHasChanged = (aNewContentArea != aCurrentContentArea);
+ }
+ if (bForce || bSizeHasChanged)
+ {
+ // The browser window gets the remaining space.
+ pWindow->SetPosSizePixel (aNewContentArea.TopLeft(), aNewContentArea.GetSize());
+ mrView.Resize();
+ }
+
+ // Adapt the scroll bars to the new zoom factor of the browser
+ // window and the arrangement of the page objects.
+ GetScrollBarManager().UpdateScrollBars(!bForce);
+
+ // Keep the current slide in the visible area.
+ GetVisibleAreaManager().RequestCurrentSlideVisible();
+
+ mrView.RequestRepaint();
+}
+
+rtl::Reference<FuPoor> SlideSorterController::CreateSelectionFunction (SfxRequest& rRequest)
+{
+ rtl::Reference<FuPoor> xFunc( SelectionFunction::Create(mrSlideSorter, rRequest) );
+ return xFunc;
+}
+
+::rtl::Reference<SelectionFunction> SlideSorterController::GetCurrentSelectionFunction() const
+{
+ rtl::Reference<FuPoor> pFunction (mrSlideSorter.GetViewShell()->GetCurrentFunction());
+ return ::rtl::Reference<SelectionFunction>(dynamic_cast<SelectionFunction*>(pFunction.get()));
+}
+
+void SlideSorterController::PrepareEditModeChange()
+{
+ // Before we throw away the page descriptors we prepare for selecting
+ // descriptors in the other mode and for restoring the current
+ // selection when switching back to the current mode.
+ if (mrModel.GetEditMode() != EditMode::Page)
+ return;
+
+ maSelectionBeforeSwitch.clear();
+
+ // Search for the first selected page and determine the master page
+ // used by its page object. It will be selected after the switch.
+ // In the same loop the current selection is stored.
+ PageEnumeration aSelectedPages (
+ PageEnumerationProvider::CreateSelectedPagesEnumeration(mrModel));
+ while (aSelectedPages.HasMoreElements())
+ {
+ SharedPageDescriptor pDescriptor (aSelectedPages.GetNextElement());
+ SdPage* pPage = pDescriptor->GetPage();
+ // Remember the master page of the first selected descriptor.
+ if (pPage!=nullptr && mpEditModeChangeMasterPage==nullptr)
+ mpEditModeChangeMasterPage = &static_cast<SdPage&>(
+ pPage->TRG_GetMasterPage());
+
+ maSelectionBeforeSwitch.push_back(pPage);
+ }
+
+ // Remember the current page.
+ if (mrSlideSorter.GetViewShell() != nullptr)
+ mnCurrentPageBeforeSwitch = (mrSlideSorter.GetViewShell()->GetViewShellBase()
+ .GetMainViewShell()->GetActualPage()->GetPageNum()-1)/2;
+}
+
+void SlideSorterController::ChangeEditMode (EditMode eEditMode)
+{
+ if (mrModel.GetEditMode() != eEditMode)
+ {
+ ModelChangeLock aLock (*this);
+ PreModelChange();
+ // Do the actual edit mode switching.
+ bool bResult = mrModel.SetEditMode(eEditMode);
+ if (bResult)
+ HandleModelChange();
+ }
+}
+
+void SlideSorterController::FinishEditModeChange()
+{
+ if (mrModel.GetEditMode() == EditMode::MasterPage)
+ {
+ mpPageSelector->DeselectAllPages();
+
+ // Search for the master page that was determined in
+ // PrepareEditModeChange() and make it the current page.
+ PageEnumeration aAllPages (PageEnumerationProvider::CreateAllPagesEnumeration(mrModel));
+ while (aAllPages.HasMoreElements())
+ {
+ SharedPageDescriptor pDescriptor (aAllPages.GetNextElement());
+ if (pDescriptor->GetPage() == mpEditModeChangeMasterPage)
+ {
+ GetCurrentSlideManager()->SwitchCurrentSlide(pDescriptor);
+ mpPageSelector->SelectPage(pDescriptor);
+ break;
+ }
+ }
+ }
+ else
+ {
+ PageSelector::BroadcastLock aBroadcastLock (*mpPageSelector);
+
+ SharedPageDescriptor pDescriptor (mrModel.GetPageDescriptor(mnCurrentPageBeforeSwitch));
+ GetCurrentSlideManager()->SwitchCurrentSlide(pDescriptor);
+
+ // Restore the selection.
+ mpPageSelector->DeselectAllPages();
+ for (const auto& rpPage : maSelectionBeforeSwitch)
+ {
+ mpPageSelector->SelectPage(rpPage);
+ }
+ maSelectionBeforeSwitch.clear( );
+ }
+ mpEditModeChangeMasterPage = nullptr;
+}
+
+void SlideSorterController::PageNameHasChanged (int nPageIndex, const OUString& rsOldName)
+{
+ // Request a repaint for the page object whose name has changed.
+ model::SharedPageDescriptor pDescriptor (mrModel.GetPageDescriptor(nPageIndex));
+ if (pDescriptor)
+ mrView.RequestRepaint(pDescriptor);
+
+ // Get a pointer to the corresponding accessible object and notify
+ // that of the name change.
+ sd::Window *pWindow (mrSlideSorter.GetContentWindow().get());
+ if ( ! pWindow)
+ return;
+
+ css::uno::Reference< css::accessibility::XAccessible >
+ xAccessible (pWindow->GetAccessible(false));
+ if ( ! xAccessible.is())
+ return;
+
+ // Now comes a small hack. We assume that the accessible object is
+ // an instantiation of AccessibleSlideSorterView and cast it to that
+ // class. The cleaner alternative to this cast would be a new member
+ // in which we would store the last AccessibleSlideSorterView object
+ // created by SlideSorterViewShell::CreateAccessibleDocumentView().
+ // But then there is no guaranty that the accessible object obtained
+ // from the window really is that instance last created by
+ // CreateAccessibleDocumentView().
+ // However, the dynamic cast together with the check of the result
+ // being NULL should be safe enough.
+ ::accessibility::AccessibleSlideSorterView* pAccessibleView
+ = dynamic_cast< ::accessibility::AccessibleSlideSorterView*>(xAccessible.get());
+ if (pAccessibleView == nullptr)
+ return;
+
+ ::accessibility::AccessibleSlideSorterObject* pChild
+ = pAccessibleView->GetAccessibleChildImplementation(nPageIndex);
+ if (pChild == nullptr || pChild->GetPage() == nullptr)
+ return;
+
+ OUString sNewName (pChild->GetPage()->GetName());
+ pChild->FireAccessibleEvent(
+ css::accessibility::AccessibleEventId::NAME_CHANGED,
+ Any(rsOldName),
+ Any(sNewName));
+}
+
+void SlideSorterController::SetDocumentSlides (const Reference<container::XIndexAccess>& rxSlides)
+{
+ if (mrModel.GetDocumentSlides() != rxSlides)
+ {
+ ModelChangeLock aLock (*this);
+ PreModelChange();
+
+ mrModel.SetDocumentSlides(rxSlides);
+ }
+}
+
+VisibleAreaManager& SlideSorterController::GetVisibleAreaManager() const
+{
+ OSL_ASSERT(mpVisibleAreaManager);
+ return *mpVisibleAreaManager;
+}
+
+void SlideSorterController::CheckForMasterPageAssignment()
+{
+ if (mrModel.GetPageCount()%2==0)
+ return;
+ PageEnumeration aAllPages (PageEnumerationProvider::CreateAllPagesEnumeration(mrModel));
+ while (aAllPages.HasMoreElements())
+ {
+ SharedPageDescriptor pDescriptor (aAllPages.GetNextElement());
+ if (pDescriptor->UpdateMasterPage())
+ {
+ mrView.GetPreviewCache()->InvalidatePreviewBitmap (
+ pDescriptor->GetPage());
+ }
+ }
+}
+
+void SlideSorterController::CheckForSlideTransitionAssignment()
+{
+ if (mrModel.GetPageCount()%2==0)
+ return;
+ PageEnumeration aAllPages (PageEnumerationProvider::CreateAllPagesEnumeration(mrModel));
+ while (aAllPages.HasMoreElements())
+ {
+ SharedPageDescriptor pDescriptor (aAllPages.GetNextElement());
+ if (pDescriptor->UpdateTransitionFlag())
+ {
+ mrView.GetPreviewCache()->InvalidatePreviewBitmap (
+ pDescriptor->GetPage());
+ }
+ }
+}
+
+//===== SlideSorterController::ModelChangeLock ================================
+
+SlideSorterController::ModelChangeLock::ModelChangeLock (
+ SlideSorterController& rController)
+ : mpController(&rController)
+{
+ mpController->LockModelChange();
+}
+
+SlideSorterController::ModelChangeLock::~ModelChangeLock() COVERITY_NOEXCEPT_FALSE
+{
+ Release();
+}
+
+void SlideSorterController::ModelChangeLock::Release()
+{
+ if (mpController != nullptr)
+ {
+ mpController->UnlockModelChange();
+ mpController = nullptr;
+ }
+}
+
+} // end of namespace ::sd::slidesorter
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsAnimationFunction.cxx b/sd/source/ui/slidesorter/controller/SlsAnimationFunction.cxx
new file mode 100644
index 000000000..31978baf7
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsAnimationFunction.cxx
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <o3tl/safeint.hxx>
+
+#include <controller/SlsAnimationFunction.hxx>
+
+namespace sd::slidesorter::controller {
+
+//===== AnimationBezierFunction ===============================================
+
+AnimationBezierFunction::AnimationBezierFunction (
+ const double nX1,
+ const double nY1)
+ : mnX1(nX1),
+ mnY1(nY1),
+ mnX2(1-nY1),
+ mnY2(1-nX1)
+{
+}
+
+::basegfx::B2DPoint AnimationBezierFunction::operator() (const double nT)
+{
+ return ::basegfx::B2DPoint(
+ EvaluateComponent(nT, mnX1, mnX2),
+ EvaluateComponent(nT, mnY1, mnY2));
+}
+
+double AnimationBezierFunction::EvaluateComponent (
+ const double nT,
+ const double nV1,
+ const double nV2)
+{
+ const double nS (1-nT);
+
+ // While the control point values 1 and 2 are explicitly given the start
+ // and end values are implicitly given.
+ const double nV0 (0);
+ const double nV3 (1);
+
+ const double nV01 (nS*nV0 + nT*nV1);
+ const double nV12 (nS*nV1 + nT*nV2);
+ const double nV23 (nS*nV2 + nT*nV3);
+
+ const double nV012 (nS*nV01 + nT*nV12);
+ const double nV123 (nS*nV12 + nT*nV23);
+
+ const double nV0123 (nS*nV012 + nT*nV123);
+
+ return nV0123;
+}
+
+//===== AnimationParametricFunction ===========================================
+
+AnimationParametricFunction::AnimationParametricFunction (const ParametricFunction& rFunction)
+{
+ const sal_Int32 nSampleCount (64);
+
+ // Sample the given parametric function.
+ ::std::vector<basegfx::B2DPoint> aPoints;
+ aPoints.reserve(nSampleCount);
+ for (sal_Int32 nIndex=0; nIndex<nSampleCount; ++nIndex)
+ {
+ const double nT (nIndex/double(nSampleCount-1));
+ aPoints.emplace_back(rFunction(nT));
+ }
+
+ // Interpolate at evenly spaced points.
+ maY.clear();
+ maY.reserve(nSampleCount);
+ double nX0 (aPoints[0].getX());
+ double nY0 (aPoints[0].getY());
+ double nX1 (aPoints[1].getX());
+ double nY1 (aPoints[1].getY());
+ sal_Int32 nIndex (1);
+ for (sal_Int32 nIndex2=0; nIndex2<nSampleCount; ++nIndex2)
+ {
+ const double nX (nIndex2 / double(nSampleCount-1));
+ while (nX > nX1 && nIndex<nSampleCount)
+ {
+ nX0 = nX1;
+ nY0 = nY1;
+ nX1 = aPoints[nIndex].getX();
+ nY1 = aPoints[nIndex].getY();
+ ++nIndex;
+ }
+ const double nU ((nX-nX1) / (nX0 - nX1));
+ const double nY (nY0*nU + nY1*(1-nU));
+ maY.push_back(nY);
+ }
+}
+
+double AnimationParametricFunction::operator() (const double nX)
+{
+ const sal_Int32 nIndex0 (static_cast<sal_Int32>(nX * maY.size()));
+ const double nX0 (nIndex0 / double(maY.size()-1));
+ const sal_uInt32 nIndex1 (nIndex0 + 1);
+ const double nX1 (nIndex1 / double(maY.size()-1));
+
+ if (nIndex0<=0)
+ return maY[0];
+ else if (o3tl::make_unsigned(nIndex0)>=maY.size() || nIndex1>=maY.size())
+ return maY[maY.size()-1];
+
+ const double nU ((nX-nX1) / (nX0 - nX1));
+ return maY[nIndex0]*nU + maY[nIndex1]*(1-nU);
+}
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsAnimator.cxx b/sd/source/ui/slidesorter/controller/SlsAnimator.cxx
new file mode 100644
index 000000000..b400ec4dc
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsAnimator.cxx
@@ -0,0 +1,280 @@
+/* -*- 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 <controller/SlsAnimator.hxx>
+#include <view/SlideSorterView.hxx>
+#include <osl/diagnose.h>
+
+namespace sd::slidesorter::controller {
+
+/** Handle one animation function by using a timer for frequent calls to
+ the animations operator().
+*/
+class Animator::Animation
+{
+public:
+ Animation (
+ const Animator::AnimationFunctor& rAnimation,
+ const double nStartOffset,
+ const double nDuration,
+ const double nGlobalTime,
+ const Animator::AnimationId nAnimationId,
+ const Animator::FinishFunctor& rFinishFunctor);
+ /** Run next animation step. If animation has reached its end it is
+ expired.
+ */
+ bool Run (const double nGlobalTime);
+
+ /** Typically called when an animation has finished, but also from
+ Animator::Disposed(). The finish functor is called and the
+ animation is marked as expired to prevent another run.
+ */
+ void Expire();
+ bool IsExpired() const { return mbIsExpired;}
+
+ Animator::AnimationFunctor maAnimation;
+ Animator::FinishFunctor maFinishFunctor;
+ const Animator::AnimationId mnAnimationId;
+ const double mnDuration;
+ const double mnEnd;
+ const double mnGlobalTimeAtStart;
+ bool mbIsExpired;
+};
+
+Animator::Animator (SlideSorter& rSlideSorter)
+ : mrSlideSorter(rSlideSorter),
+ maIdle("sd slidesorter controller Animator"),
+ mbIsDisposed(false),
+ mnNextAnimationId(0)
+{
+ maIdle.SetPriority(TaskPriority::REPAINT);
+ maIdle.SetInvokeHandler(LINK(this,Animator,TimeoutHandler));
+}
+
+Animator::~Animator()
+{
+ if ( ! mbIsDisposed)
+ {
+ OSL_ASSERT(mbIsDisposed);
+ Dispose();
+ }
+}
+
+void Animator::Dispose()
+{
+ mbIsDisposed = true;
+
+ AnimationList aCopy (maAnimations);
+ for (const auto& rxAnimation : aCopy)
+ rxAnimation->Expire();
+
+ maIdle.Stop();
+ if (mpDrawLock)
+ {
+ mpDrawLock->Dispose();
+ mpDrawLock.reset();
+ }
+}
+
+Animator::AnimationId Animator::AddAnimation (
+ const AnimationFunctor& rAnimation,
+ const FinishFunctor& rFinishFunctor)
+{
+ // When the animator is already disposed then ignore this call
+ // silently (well, we show an assertion, but do not throw an exception.)
+ OSL_ASSERT( ! mbIsDisposed);
+ if (mbIsDisposed)
+ return -1;
+
+ std::shared_ptr<Animation> pAnimation =
+ std::make_shared<Animation>(
+ rAnimation,
+ 0,
+ 300 / 1000.0,
+ maElapsedTime.getElapsedTime(),
+ ++mnNextAnimationId,
+ rFinishFunctor);
+ maAnimations.push_back(pAnimation);
+
+ RequestNextFrame();
+
+ return pAnimation->mnAnimationId;
+}
+
+void Animator::RemoveAnimation (const Animator::AnimationId nId)
+{
+ OSL_ASSERT( ! mbIsDisposed);
+
+ const AnimationList::iterator iAnimation (::std::find_if(
+ maAnimations.begin(),
+ maAnimations.end(),
+ [nId] (std::shared_ptr<Animation> const& pAnim)
+ { return nId == pAnim->mnAnimationId; }));
+ if (iAnimation != maAnimations.end())
+ {
+ OSL_ASSERT((*iAnimation)->mnAnimationId == nId);
+ (*iAnimation)->Expire();
+ maAnimations.erase(iAnimation);
+ }
+
+ if (maAnimations.empty())
+ {
+ // Reset the animation id when we can.
+ mnNextAnimationId = 0;
+
+ // No more animations => we do not have to suppress painting
+ // anymore.
+ mpDrawLock.reset();
+ }
+}
+
+void Animator::RemoveAllAnimations()
+{
+ for (auto const& it : maAnimations)
+ {
+ it->Expire();
+ }
+ maAnimations.clear();
+ mnNextAnimationId = 0;
+
+ // No more animations => we do not have to suppress painting
+ // anymore.
+ mpDrawLock.reset();
+}
+
+bool Animator::ProcessAnimations (const double nTime)
+{
+ bool bExpired (false);
+
+ OSL_ASSERT( ! mbIsDisposed);
+ if (mbIsDisposed)
+ return bExpired;
+
+ AnimationList aCopy (maAnimations);
+ for (const auto& rxAnimation : aCopy)
+ {
+ bExpired |= rxAnimation->Run(nTime);
+ }
+
+ return bExpired;
+}
+
+void Animator::CleanUpAnimationList()
+{
+ OSL_ASSERT( ! mbIsDisposed);
+ if (mbIsDisposed)
+ return;
+
+ AnimationList aActiveAnimations;
+
+ for (const auto& rxAnimation : maAnimations)
+ {
+ if ( ! rxAnimation->IsExpired())
+ aActiveAnimations.push_back(rxAnimation);
+ }
+
+ maAnimations.swap(aActiveAnimations);
+}
+
+void Animator::RequestNextFrame ()
+{
+ if ( ! maIdle.IsActive())
+ {
+ // Prevent redraws except for the ones in TimeoutHandler. While the
+ // Animator is active it will schedule repaints regularly. Repaints
+ // in between would only lead to visual artifacts.
+ mpDrawLock.reset(new view::SlideSorterView::DrawLock(mrSlideSorter));
+ maIdle.Start();
+ }
+}
+
+IMPL_LINK_NOARG(Animator, TimeoutHandler, Timer *, void)
+{
+ if (mbIsDisposed)
+ return;
+
+ if (ProcessAnimations(maElapsedTime.getElapsedTime()))
+ CleanUpAnimationList();
+
+ // Unlock the draw lock. This should lead to a repaint.
+ mpDrawLock.reset();
+
+ if (!maAnimations.empty())
+ RequestNextFrame();
+}
+
+//===== Animator::Animation ===================================================
+
+Animator::Animation::Animation (
+ const Animator::AnimationFunctor& rAnimation,
+ const double nStartOffset,
+ const double nDuration,
+ const double nGlobalTime,
+ const Animator::AnimationId nId,
+ const Animator::FinishFunctor& rFinishFunctor)
+ : maAnimation(rAnimation),
+ maFinishFunctor(rFinishFunctor),
+ mnAnimationId(nId),
+ mnDuration(nDuration),
+ mnEnd(nGlobalTime + nDuration + nStartOffset),
+ mnGlobalTimeAtStart(nGlobalTime + nStartOffset),
+ mbIsExpired(false)
+{
+ Run(nGlobalTime);
+}
+
+bool Animator::Animation::Run (const double nGlobalTime)
+{
+ if ( ! mbIsExpired)
+ {
+ if (mnDuration > 0)
+ {
+ if (nGlobalTime >= mnEnd)
+ {
+ maAnimation(1.0);
+ Expire();
+ }
+ else if (nGlobalTime >= mnGlobalTimeAtStart)
+ {
+ maAnimation((nGlobalTime - mnGlobalTimeAtStart) / mnDuration);
+ }
+ }
+ else if (mnDuration < 0)
+ {
+ // Animations without end have to be expired by their owner.
+ maAnimation(nGlobalTime);
+ }
+ }
+
+ return mbIsExpired;
+}
+
+void Animator::Animation::Expire()
+{
+ if ( ! mbIsExpired)
+ {
+ mbIsExpired = true;
+ if (maFinishFunctor)
+ maFinishFunctor();
+ }
+}
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsClipboard.cxx b/sd/source/ui/slidesorter/controller/SlsClipboard.cxx
new file mode 100644
index 000000000..160077e64
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsClipboard.cxx
@@ -0,0 +1,918 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <cassert>
+
+#include <controller/SlsClipboard.hxx>
+
+#include <SlideSorterViewShell.hxx>
+#include <SlideSorter.hxx>
+#include <model/SlideSorterModel.hxx>
+#include <model/SlsPageDescriptor.hxx>
+#include <model/SlsPageEnumerationProvider.hxx>
+#include <view/SlideSorterView.hxx>
+#include <controller/SlideSorterController.hxx>
+#include <controller/SlsInsertionIndicatorHandler.hxx>
+#include <controller/SlsPageSelector.hxx>
+#include <controller/SlsSelectionFunction.hxx>
+#include <controller/SlsCurrentSlideManager.hxx>
+#include <controller/SlsFocusManager.hxx>
+#include <controller/SlsSelectionManager.hxx>
+#include <controller/SlsTransferableData.hxx>
+#include <controller/SlsSelectionObserver.hxx>
+#include <controller/SlsVisibleAreaManager.hxx>
+#include <cache/SlsPageCache.hxx>
+
+#include <ViewShellBase.hxx>
+#include <DrawViewShell.hxx>
+#include <Window.hxx>
+#include <fupoor.hxx>
+#include <strings.hrc>
+#include <sdresid.hxx>
+#include <sdxfer.hxx>
+#include <sdmod.hxx>
+#include <ins_paste.hxx>
+#include <drawdoc.hxx>
+#include <DrawDocShell.hxx>
+#include <sdpage.hxx>
+#include <sdtreelb.hxx>
+
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <sfx2/request.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/docfile.hxx>
+#include <svx/svxids.hrc>
+#include <tools/urlobj.hxx>
+#include <rtl/ustring.hxx>
+#include <vcl/svapp.hxx>
+
+namespace sd::slidesorter::controller {
+
+namespace {
+/** Temporarily deactivate slide tracking of the VisibleAreaManager.
+ This is used as a workaround to avoid unwanted repositioning of
+ the visible area when the selection of slides is copied to the
+ clipboard (cloning of slides leads to model change notifications
+ for the original model.)
+*/
+class TemporarySlideTrackingDeactivator
+{
+public:
+ explicit TemporarySlideTrackingDeactivator (SlideSorterController& rController)
+ : mrController(rController),
+ mbIsCurrentSlideTrackingActive (
+ mrController.GetVisibleAreaManager().IsCurrentSlideTrackingActive())
+ {
+ if (mbIsCurrentSlideTrackingActive)
+ mrController.GetVisibleAreaManager().DeactivateCurrentSlideTracking();
+ }
+ ~TemporarySlideTrackingDeactivator()
+ {
+ if (mbIsCurrentSlideTrackingActive)
+ mrController.GetVisibleAreaManager().ActivateCurrentSlideTracking();
+ }
+
+private:
+ SlideSorterController& mrController;
+ const bool mbIsCurrentSlideTrackingActive;
+};
+} // end of anonymous namespace
+
+class Clipboard::UndoContext
+{
+public:
+ UndoContext (
+ SdDrawDocument* pDocument,
+ const std::shared_ptr<ViewShell>& rpMainViewShell)
+ : mpDocument(pDocument),
+ mpMainViewShell(rpMainViewShell)
+ {
+ if (mpDocument!=nullptr && mpDocument->IsUndoEnabled())
+ {
+ if (mpMainViewShell && mpMainViewShell->GetShellType() == ViewShell::ST_DRAW)
+ mpDocument->BegUndo(SdResId(STRING_DRAG_AND_DROP_PAGES));
+ else
+ mpDocument->BegUndo(SdResId(STRING_DRAG_AND_DROP_SLIDES));
+ }
+ }
+
+ ~UndoContext()
+ {
+ if (mpDocument!=nullptr && mpDocument->IsUndoEnabled())
+ mpDocument->EndUndo();
+ if (mpMainViewShell && mpMainViewShell->GetViewFrame()!=nullptr)
+ {
+ SfxBindings& rBindings = mpMainViewShell->GetViewFrame()->GetBindings();
+ rBindings.Invalidate(SID_UNDO);
+ rBindings.Invalidate(SID_REDO);
+ }
+ }
+private:
+ SdDrawDocument* mpDocument;
+ std::shared_ptr<ViewShell> mpMainViewShell;
+};
+
+Clipboard::Clipboard (SlideSorter& rSlideSorter)
+ : ViewClipboard(rSlideSorter.GetView()),
+ mrSlideSorter(rSlideSorter),
+ mrController(mrSlideSorter.GetController()),
+ mnDragFinishedUserEventId(nullptr)
+{
+}
+
+Clipboard::~Clipboard()
+{
+ if (mnDragFinishedUserEventId != nullptr)
+ Application::RemoveUserEvent(mnDragFinishedUserEventId);
+}
+
+/** With the current implementation the forwarded calls to the current
+ function will come back eventually to call the local Do(Cut|Copy|Paste)
+ methods. A shortcut is possible but would be an unclean hack.
+*/
+void Clipboard::HandleSlotCall (SfxRequest& rRequest)
+{
+ ViewShell* pViewShell = mrSlideSorter.GetViewShell();
+ rtl::Reference<FuPoor> xFunc;
+ if (pViewShell != nullptr)
+ xFunc = pViewShell->GetCurrentFunction();
+ switch (rRequest.GetSlot())
+ {
+ case SID_CUT:
+ if (mrSlideSorter.GetModel().GetEditMode() != EditMode::MasterPage)
+ {
+ if(xFunc.is())
+ xFunc->DoCut();
+ else
+ DoCut();
+ }
+ rRequest.Done();
+ break;
+
+ case SID_COPY:
+ if (mrSlideSorter.GetModel().GetEditMode() != EditMode::MasterPage)
+ {
+ if(xFunc.is())
+ xFunc->DoCopy();
+ else
+ DoCopy();
+ }
+ rRequest.Done();
+ break;
+
+ case SID_PASTE:
+ // Prevent redraws while inserting pages from the clipboard
+ // because the intermediate inconsistent state might lead to
+ // a crash.
+ if (mrSlideSorter.GetModel().GetEditMode() != EditMode::MasterPage)
+ {
+ view::SlideSorterView::DrawLock aLock (mrSlideSorter);
+ SelectionObserver::Context aContext (mrSlideSorter);
+ if(xFunc.is())
+ xFunc->DoPaste();
+ else
+ DoPaste();
+ }
+ rRequest.Done();
+ break;
+
+ case SID_DELETE:
+ DoDelete();
+ rRequest.Done();
+ break;
+ }
+}
+
+void Clipboard::DoCut ()
+{
+ if (mrSlideSorter.GetModel().GetPageCount() > 1)
+ {
+ DoCopy();
+ DoDelete();
+ }
+}
+
+void Clipboard::DoDelete()
+{
+ if (mrSlideSorter.GetModel().GetPageCount() > 1)
+ {
+ mrController.GetSelectionManager()->DeleteSelectedPages();
+ }
+}
+
+void Clipboard::DoCopy ()
+{
+ CreateSlideTransferable( nullptr, false );
+}
+
+void Clipboard::DoPaste ()
+{
+ SdTransferable* pClipTransferable = SD_MOD()->pTransferClip;
+
+ if (pClipTransferable==nullptr || !pClipTransferable->IsPageTransferable())
+ return;
+
+ sal_Int32 nInsertPosition = GetInsertionPosition();
+
+ if (nInsertPosition >= 0)
+ {
+ // Paste the pages from the clipboard.
+ sal_Int32 nInsertPageCount = PasteTransferable(nInsertPosition);
+ // Select the pasted pages and make the first of them the
+ // current page.
+ mrSlideSorter.GetContentWindow()->GrabFocus();
+ SelectPageRange(nInsertPosition, nInsertPageCount);
+ }
+}
+
+sal_Int32 Clipboard::GetInsertionPosition ()
+{
+ sal_Int32 nInsertPosition = -1;
+
+ // Determine the insertion position:
+ // a) When the insertion indicator is visible, then at that position.
+ // b) When the focus indicator is visible, then before or after the
+ // focused page, depending on user input to a dialog.
+ // c) When there is a selection but no focus, then after the
+ // selection.
+ // d) After the last page when there is no selection and no focus.
+
+ std::shared_ptr<controller::InsertionIndicatorHandler> pInsertionIndicatorHandler (
+ mrController.GetInsertionIndicatorHandler());
+ if (pInsertionIndicatorHandler->IsActive())
+ {
+ // Use the insertion index of an active insertion indicator.
+ nInsertPosition = pInsertionIndicatorHandler->GetInsertionPageIndex();
+ }
+ else if (mrController.GetSelectionManager()->GetInsertionPosition() >= 0)
+ {
+ // Use the insertion index of an insertion indicator that has been
+ // deactivated a short while ago.
+ nInsertPosition = mrController.GetSelectionManager()->GetInsertionPosition();
+ }
+ else if (mrController.GetFocusManager().IsFocusShowing())
+ {
+ // Use the focus to determine the insertion position.
+ vcl::Window* pWin = mrSlideSorter.GetContentWindow();
+ SdInsertPasteDlg aDialog(pWin ? pWin->GetFrameWeld() : nullptr);
+ if (aDialog.run() == RET_OK)
+ {
+ nInsertPosition = mrController.GetFocusManager().GetFocusedPageIndex();
+ if (!aDialog.IsInsertBefore())
+ nInsertPosition ++;
+ }
+ }
+
+ return nInsertPosition;
+}
+
+sal_Int32 Clipboard::PasteTransferable (sal_Int32 nInsertPosition)
+{
+ SdTransferable* pClipTransferable = SD_MOD()->pTransferClip;
+ model::SlideSorterModel& rModel (mrSlideSorter.GetModel());
+ bool bMergeMasterPages = !pClipTransferable->HasSourceDoc (rModel.GetDocument());
+ sal_uInt16 nInsertIndex (rModel.GetCoreIndex(nInsertPosition));
+ sal_Int32 nInsertPageCount (0);
+ if (pClipTransferable->HasPageBookmarks())
+ {
+ const std::vector<OUString> &rBookmarkList = pClipTransferable->GetPageBookmarks();
+ const SolarMutexGuard aGuard;
+
+ nInsertPageCount = static_cast<sal_uInt16>(rBookmarkList.size());
+ rModel.GetDocument()->InsertBookmarkAsPage(
+ rBookmarkList,
+ nullptr,
+ false,
+ false,
+ nInsertIndex,
+ false,
+ pClipTransferable->GetPageDocShell(),
+ true,
+ bMergeMasterPages,
+ false);
+ }
+ else
+ {
+ SfxObjectShell* pShell = pClipTransferable->GetDocShell().get();
+ DrawDocShell* pDataDocSh = static_cast<DrawDocShell*>(pShell);
+ SdDrawDocument* pDataDoc = pDataDocSh->GetDoc();
+
+ if (pDataDoc!=nullptr
+ && pDataDoc->GetSdPageCount(PageKind::Standard))
+ {
+ const SolarMutexGuard aGuard;
+
+ bMergeMasterPages = (pDataDoc != rModel.GetDocument());
+ nInsertPageCount = pDataDoc->GetSdPageCount( PageKind::Standard );
+ rModel.GetDocument()->InsertBookmarkAsPage(
+ std::vector<OUString>(),
+ nullptr,
+ false,
+ false,
+ nInsertIndex,
+ false,
+ pDataDocSh,
+ true,
+ bMergeMasterPages,
+ false);
+ }
+ }
+ mrController.HandleModelChange();
+ return nInsertPageCount;
+}
+
+void Clipboard::SelectPageRange (sal_Int32 nFirstIndex, sal_Int32 nPageCount)
+{
+ // Select the newly inserted pages. That are the nInsertPageCount pages
+ // after the nInsertIndex position.
+ PageSelector& rSelector (mrController.GetPageSelector());
+ rSelector.DeselectAllPages();
+ for (sal_Int32 i=0; i<nPageCount; i++)
+ {
+ model::SharedPageDescriptor pDescriptor (
+ mrSlideSorter.GetModel().GetPageDescriptor(nFirstIndex + i));
+ if (pDescriptor)
+ {
+ rSelector.SelectPage(pDescriptor);
+ // The first page of the new selection is made the current page.
+ if (i == 0)
+ {
+ mrController.GetCurrentSlideManager()->SwitchCurrentSlide(pDescriptor);
+ }
+ }
+ }
+}
+
+void Clipboard::CreateSlideTransferable (
+ vcl::Window* pWindow,
+ bool bDrag)
+{
+ std::vector<OUString> aBookmarkList;
+
+ // Insert all selected pages into a bookmark list and remember them in
+ // maPagesToRemove for possible later removal.
+ model::PageEnumeration aSelectedPages
+ (model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
+ mrSlideSorter.GetModel()));
+ SdDrawDocument* const pDocument = mrSlideSorter.GetModel().GetDocument();
+ DrawDocShell* const pDataDocSh = pDocument->GetDocSh();
+
+ sal_Int32 nUniqueID = 0;
+ while (aSelectedPages.HasMoreElements())
+ {
+ model::SharedPageDescriptor pDescriptor (aSelectedPages.GetNextElement());
+
+ //ensure that the slides have unique names
+ const OUString sOrigName = pDescriptor->GetPage()->GetName();
+ if ( pDataDocSh && !pDataDocSh->IsPageNameUnique( sOrigName ) )
+ {
+ OUString sUniqueName;
+ bool bUnique = false;
+ while ( !bUnique )
+ {
+ sUniqueName = sOrigName + "_clipboard" + OUString::number(nUniqueID++);
+ bUnique = pDataDocSh->IsNewPageNameValid( sUniqueName );
+ if ( bUnique )
+ pDescriptor->GetPage()->SetName(sUniqueName);
+ }
+ }
+
+ aBookmarkList.push_back(pDescriptor->GetPage()->GetName());
+ maPagesToRemove.push_back (pDescriptor->GetPage());
+ }
+
+ // Create a small set of representatives of the selection for which
+ // previews are included into the transferable so that an insertion
+ // indicator can be rendered.
+ aSelectedPages.Rewind();
+ ::std::vector<TransferableData::Representative> aRepresentatives;
+ aRepresentatives.reserve(3);
+ std::shared_ptr<cache::PageCache> pPreviewCache (
+ mrSlideSorter.GetView().GetPreviewCache());
+ while (aSelectedPages.HasMoreElements())
+ {
+ model::SharedPageDescriptor pDescriptor (aSelectedPages.GetNextElement());
+ if ( ! pDescriptor || pDescriptor->GetPage()==nullptr)
+ continue;
+ BitmapEx aPreview (pPreviewCache->GetPreviewBitmap(pDescriptor->GetPage(), false));
+ aRepresentatives.emplace_back(
+ aPreview,
+ pDescriptor->HasState(model::PageDescriptor::ST_Excluded));
+ if (aRepresentatives.size() >= 3)
+ break;
+ }
+
+ if (aBookmarkList.empty())
+ return;
+
+ mrSlideSorter.GetView().BrkAction();
+ rtl::Reference<SdTransferable> pTransferable = TransferableData::CreateTransferable (
+ pDocument,
+ dynamic_cast<SlideSorterViewShell*>(mrSlideSorter.GetViewShell()),
+ std::move(aRepresentatives));
+
+ if (bDrag)
+ SD_MOD()->pTransferDrag = pTransferable.get();
+ else
+ SD_MOD()->pTransferClip = pTransferable.get();
+
+ pDocument->CreatingDataObj (pTransferable.get());
+ pTransferable->SetWorkDocument(pDocument->AllocSdDrawDocument());
+ std::unique_ptr<TransferableObjectDescriptor> pObjDesc(new TransferableObjectDescriptor);
+ pTransferable->GetWorkDocument()->GetDocSh()
+ ->FillTransferableObjectDescriptor (*pObjDesc);
+
+ if (pDataDocSh != nullptr)
+ pObjDesc->maDisplayName = pDataDocSh->GetMedium()->GetURLObject().GetURLNoPass();
+
+ vcl::Window* pActionWindow = pWindow;
+ if (pActionWindow == nullptr)
+ {
+ ViewShell* pViewShell = mrSlideSorter.GetViewShell();
+ if (pViewShell != nullptr)
+ pActionWindow = pViewShell->GetActiveWindow();
+ }
+
+ assert(pActionWindow);
+
+ pTransferable->SetStartPos (pActionWindow->PixelToLogic(
+ pActionWindow->GetPointerPosPixel()));
+ pTransferable->SetObjectDescriptor (std::move(pObjDesc));
+
+ {
+ TemporarySlideTrackingDeactivator aDeactivator (mrController);
+ pTransferable->SetPageBookmarks (std::move(aBookmarkList), !bDrag);
+ }
+
+ if (bDrag)
+ {
+ pTransferable->SetView (&mrSlideSorter.GetView());
+ pTransferable->StartDrag (pActionWindow, DND_ACTION_COPY | DND_ACTION_MOVE);
+ }
+ else
+ pTransferable->CopyToClipboard (pActionWindow);
+
+ pDocument->CreatingDataObj(nullptr);
+}
+
+std::shared_ptr<SdTransferable::UserData> Clipboard::CreateTransferableUserData (SdTransferable* pTransferable)
+{
+ do
+ {
+ SdPageObjsTLV::SdPageObjsTransferable* pTreeListBoxTransferable
+ = dynamic_cast<SdPageObjsTLV::SdPageObjsTransferable*>(pTransferable);
+ if (pTreeListBoxTransferable == nullptr)
+ break;
+
+ // Find view shell for the document of the transferable.
+ ::sd::ViewShell* pViewShell
+ = SdPageObjsTLV::GetViewShellForDocShell(pTreeListBoxTransferable->GetDocShell());
+ if (pViewShell == nullptr)
+ break;
+
+ // Find slide sorter for the document of the transferable.
+ SlideSorterViewShell* pSlideSorterViewShell
+ = SlideSorterViewShell::GetSlideSorter(pViewShell->GetViewShellBase());
+ if (pSlideSorterViewShell == nullptr)
+ break;
+ SlideSorter& rSlideSorter (pSlideSorterViewShell->GetSlideSorter());
+
+ // Get bookmark from transferable.
+ TransferableDataHelper aDataHelper (pTransferable);
+ INetBookmark aINetBookmark;
+ if ( ! aDataHelper.GetINetBookmark(SotClipboardFormatId::NETSCAPE_BOOKMARK, aINetBookmark))
+ break;
+ const OUString sURL (aINetBookmark.GetURL());
+ const sal_Int32 nIndex (sURL.indexOf('#'));
+ if (nIndex == -1)
+ break;
+ OUString sBookmark (sURL.copy(nIndex+1));
+
+ // Make sure that the bookmark points to a page.
+ SdDrawDocument* pTransferableDocument = rSlideSorter.GetModel().GetDocument();
+ if (pTransferableDocument == nullptr)
+ break;
+ bool bIsMasterPage = false;
+ const sal_uInt16 nPageIndex (pTransferableDocument->GetPageByName(sBookmark, bIsMasterPage));
+ if (nPageIndex == SDRPAGE_NOTFOUND)
+ break;
+
+ // Create preview.
+ ::std::vector<TransferableData::Representative> aRepresentatives;
+ aRepresentatives.reserve(1);
+ std::shared_ptr<cache::PageCache> pPreviewCache (
+ rSlideSorter.GetView().GetPreviewCache());
+ model::SharedPageDescriptor pDescriptor (rSlideSorter.GetModel().GetPageDescriptor((nPageIndex-1)/2));
+ if ( ! pDescriptor || pDescriptor->GetPage()==nullptr)
+ break;
+ BitmapEx aPreview (pPreviewCache->GetPreviewBitmap(pDescriptor->GetPage(), false));
+ aRepresentatives.emplace_back(
+ aPreview,
+ pDescriptor->HasState(model::PageDescriptor::ST_Excluded));
+
+ // Remember the page in maPagesToRemove so that it can be removed
+ // when drag and drop action is "move".
+ Clipboard& rOtherClipboard (pSlideSorterViewShell->GetSlideSorter().GetController().GetClipboard());
+ rOtherClipboard.maPagesToRemove.clear();
+ rOtherClipboard.maPagesToRemove.push_back(pDescriptor->GetPage());
+
+ // Create the new transferable.
+ std::shared_ptr<SdTransferable::UserData> pNewTransferable =
+ std::make_shared<TransferableData>(
+ pSlideSorterViewShell,
+ std::move(aRepresentatives));
+ pTransferable->SetWorkDocument(pTreeListBoxTransferable->GetSourceDoc()->AllocSdDrawDocument());
+ // pTransferable->SetView(&mrSlideSorter.GetView());
+
+ // Set page bookmark list.
+ std::vector<OUString> aPageBookmarks { sBookmark };
+ pTransferable->SetPageBookmarks(std::move(aPageBookmarks), false);
+
+ // Replace the view referenced by the transferable with the
+ // corresponding slide sorter view.
+ pTransferable->SetView(&pSlideSorterViewShell->GetSlideSorter().GetView());
+
+ return pNewTransferable;
+ }
+ while (false);
+
+ return std::shared_ptr<SdTransferable::UserData>();
+}
+
+void Clipboard::StartDrag (
+ const Point& rPosition,
+ vcl::Window* pWindow)
+{
+ maPagesToRemove.clear();
+ CreateSlideTransferable(pWindow, true);
+
+ mrController.GetInsertionIndicatorHandler()->UpdatePosition(
+ rPosition,
+ InsertionIndicatorHandler::UnknownMode);
+}
+
+void Clipboard::DragFinished (sal_Int8 nDropAction)
+{
+ if (mnDragFinishedUserEventId == nullptr)
+ {
+ mnDragFinishedUserEventId = Application::PostUserEvent(
+ LINK(this, Clipboard, ProcessDragFinished),
+ reinterpret_cast<void*>(nDropAction));
+ }
+}
+
+IMPL_LINK(Clipboard, ProcessDragFinished, void*, pUserData, void)
+{
+ const sal_Int8 nDropAction (static_cast<sal_Int8>(reinterpret_cast<sal_IntPtr>(pUserData)));
+
+ mnDragFinishedUserEventId = nullptr;
+
+ // Hide the substitution display and insertion indicator.
+ ::rtl::Reference<SelectionFunction> pFunction (mrController.GetCurrentSelectionFunction());
+ if (pFunction.is())
+ pFunction->NotifyDragFinished();
+
+ PageSelector& rSelector (mrController.GetPageSelector());
+ if ((nDropAction & DND_ACTION_MOVE) != 0
+ && ! maPagesToRemove.empty())
+ {
+ // Remove the pages that have been moved to another place (possibly
+ // in the same document.)
+ rSelector.DeselectAllPages();
+ for (const auto& rpDraggedPage : maPagesToRemove)
+ {
+ rSelector.SelectPage(rpDraggedPage);
+ }
+ mrController.GetSelectionManager()->DeleteSelectedPages();
+ }
+ mxUndoContext.reset();
+ mxSelectionObserverContext.reset();
+}
+
+sal_Int8 Clipboard::AcceptDrop (
+ const AcceptDropEvent& rEvent,
+ DropTargetHelper& rTargetHelper,
+ ::sd::Window* pTargetWindow,
+ sal_uInt16 nPage,
+ SdrLayerID nLayer)
+{
+ sal_Int8 nAction (DND_ACTION_NONE);
+
+ const Clipboard::DropType eDropType (IsDropAccepted());
+
+ switch (eDropType)
+ {
+ case DT_PAGE:
+ case DT_PAGE_FROM_NAVIGATOR:
+ {
+ // Accept a drop.
+ nAction = rEvent.mnAction;
+
+ // Use the copy action when the drop action is the default, i.e. not
+ // explicitly set to move or link, and when the source and
+ // target models are not the same.
+ SdTransferable* pDragTransferable = SD_MOD()->pTransferDrag;
+ if (pDragTransferable != nullptr
+ && pDragTransferable->IsPageTransferable()
+ && ((rEvent.maDragEvent.DropAction
+ & css::datatransfer::dnd::DNDConstants::ACTION_DEFAULT) != 0)
+ && (mrSlideSorter.GetModel().GetDocument()->GetDocSh()
+ != pDragTransferable->GetPageDocShell()))
+ {
+ nAction = DND_ACTION_COPY;
+ }
+ else if (IsInsertionTrivial(pDragTransferable, nAction))
+ {
+ nAction = DND_ACTION_NONE;
+ }
+
+ // Show the insertion marker and the substitution for a drop.
+ SelectionFunction* pSelectionFunction = dynamic_cast<SelectionFunction*>(
+ mrSlideSorter.GetViewShell()->GetCurrentFunction().get());
+ if (pSelectionFunction != nullptr)
+ pSelectionFunction->MouseDragged(rEvent, nAction);
+
+ // Scroll the window when the mouse reaches the window border.
+ // mrController.GetScrollBarManager().AutoScroll (rEvent.maPosPixel);
+ }
+ break;
+
+ case DT_SHAPE:
+ nAction = ExecuteOrAcceptShapeDrop(
+ DC_ACCEPT,
+ rEvent.maPosPixel,
+ &rEvent,
+ rTargetHelper,
+ pTargetWindow,
+ nPage,
+ nLayer);
+ break;
+
+ default:
+ case DT_NONE:
+ nAction = DND_ACTION_NONE;
+ break;
+ }
+
+ return nAction;
+}
+
+sal_Int8 Clipboard::ExecuteDrop (
+ const ExecuteDropEvent& rEvent,
+ DropTargetHelper& rTargetHelper,
+ ::sd::Window* pTargetWindow,
+ sal_uInt16 nPage,
+ SdrLayerID nLayer)
+{
+ sal_Int8 nResult = DND_ACTION_NONE;
+ mxUndoContext.reset();
+ const Clipboard::DropType eDropType (IsDropAccepted());
+
+ switch (eDropType)
+ {
+ case DT_PAGE:
+ case DT_PAGE_FROM_NAVIGATOR:
+ {
+ SdTransferable* pDragTransferable = SD_MOD()->pTransferDrag;
+ const Point aEventModelPosition (
+ pTargetWindow->PixelToLogic (rEvent.maPosPixel));
+ const sal_Int32 nXOffset (std::abs (pDragTransferable->GetStartPos().X()
+ - aEventModelPosition.X()));
+ const sal_Int32 nYOffset (std::abs (pDragTransferable->GetStartPos().Y()
+ - aEventModelPosition.Y()));
+ bool bContinue =
+ ( pDragTransferable->GetView() != &mrSlideSorter.GetView() )
+ || ( nXOffset >= 2 && nYOffset >= 2 );
+
+ std::shared_ptr<InsertionIndicatorHandler> pInsertionIndicatorHandler(
+ mrController.GetInsertionIndicatorHandler());
+ // Get insertion position and then turn off the insertion indicator.
+ pInsertionIndicatorHandler->UpdatePosition(aEventModelPosition, rEvent.mnAction);
+ // sal_uInt16 nIndex = DetermineInsertPosition(*pDragTransferable);
+
+ // Do not process the insertion when it is trivial,
+ // i.e. would insert pages at their original place.
+ if (IsInsertionTrivial(pDragTransferable, rEvent.mnAction))
+ bContinue = false;
+
+ // Tell the insertion indicator handler to hide before the model
+ // is modified. Doing it later may result in page objects whose
+ // animation state is not properly reset because they are then
+ // in another run then before the model change.
+ pInsertionIndicatorHandler->End(Animator::AM_Immediate);
+
+ if (bContinue)
+ {
+ SlideSorterController::ModelChangeLock aModelChangeLock (mrController);
+
+ // Handle a general drop operation.
+ mxUndoContext.reset(new UndoContext (
+ mrSlideSorter.GetModel().GetDocument(),
+ mrSlideSorter.GetViewShell()->GetViewShellBase().GetMainViewShell()));
+ mxSelectionObserverContext.reset(new SelectionObserver::Context(mrSlideSorter));
+
+ if (rEvent.mnAction == DND_ACTION_MOVE)
+ {
+ SdDrawDocument* pDoc = mrSlideSorter.GetModel().GetDocument();
+ const bool bDoesMakePageObjectsNamesUnique = pDoc->DoesMakePageObjectsNamesUnique();
+ pDoc->DoMakePageObjectsNamesUnique(false);
+ HandlePageDrop(*pDragTransferable);
+ pDoc->DoMakePageObjectsNamesUnique(bDoesMakePageObjectsNamesUnique);
+ }
+ else
+ HandlePageDrop(*pDragTransferable);
+
+ nResult = rEvent.mnAction;
+
+ // We leave the undo context alive for when moving or
+ // copying inside one view then the actions in
+ // NotifyDragFinished should be covered as well as
+ // well as the ones above.
+ }
+
+ // When the pages originated in another slide sorter then
+ // only that is notified automatically about the drag
+ // operation being finished. Because the target slide sorter
+ // has be notified, too, add a callback for that.
+ std::shared_ptr<TransferableData> pSlideSorterTransferable (
+ TransferableData::GetFromTransferable(pDragTransferable));
+ assert(pSlideSorterTransferable);
+ if (pSlideSorterTransferable
+ && pSlideSorterTransferable->GetSourceViewShell() != mrSlideSorter.GetViewShell())
+ {
+ DragFinished(nResult);
+ }
+
+ // Notify the receiving selection function that drag-and-drop is
+ // finished and the substitution handler can be released.
+ ::rtl::Reference<SelectionFunction> pFunction (
+ mrController.GetCurrentSelectionFunction());
+ if (pFunction.is())
+ pFunction->NotifyDragFinished();
+ }
+ break;
+
+ case DT_SHAPE:
+ nResult = ExecuteOrAcceptShapeDrop(
+ DC_EXECUTE,
+ rEvent.maPosPixel,
+ &rEvent,
+ rTargetHelper,
+ pTargetWindow,
+ nPage,
+ nLayer);
+ break;
+
+ default:
+ case DT_NONE:
+ break;
+ }
+
+ return nResult;
+}
+
+bool Clipboard::IsInsertionTrivial (
+ SdTransferable const * pTransferable,
+ const sal_Int8 nDndAction) const
+{
+ std::shared_ptr<TransferableData> pSlideSorterTransferable (
+ TransferableData::GetFromTransferable(pTransferable));
+ if (pSlideSorterTransferable
+ && pSlideSorterTransferable->GetSourceViewShell() != mrSlideSorter.GetViewShell())
+ return false;
+ return mrController.GetInsertionIndicatorHandler()->IsInsertionTrivial(nDndAction);
+}
+
+void Clipboard::Abort()
+{
+ if (mxSelectionObserverContext)
+ {
+ mxSelectionObserverContext->Abort();
+ mxSelectionObserverContext.reset();
+ }
+}
+
+sal_uInt16 Clipboard::DetermineInsertPosition ()
+{
+ // Tell the model to move the dragged pages behind the one with the
+ // index nInsertionIndex which first has to be transformed into an index
+ // understandable by the document.
+ const sal_Int32 nInsertionIndex (
+ mrController.GetInsertionIndicatorHandler()->GetInsertionPageIndex());
+
+ // Convert to insertion index to that of an SdModel.
+ if (nInsertionIndex >= 0)
+ return mrSlideSorter.GetModel().GetCoreIndex(nInsertionIndex);
+ else
+ return 0;
+}
+
+Clipboard::DropType Clipboard::IsDropAccepted() const
+{
+ const SdTransferable* pDragTransferable = SD_MOD()->pTransferDrag;
+ if (pDragTransferable == nullptr)
+ return DT_NONE;
+
+ if (pDragTransferable->IsPageTransferable())
+ {
+ if (mrSlideSorter.GetModel().GetEditMode() != EditMode::MasterPage)
+ return DT_PAGE;
+ else
+ return DT_NONE;
+ }
+
+ const SdPageObjsTLV::SdPageObjsTransferable* pPageObjsTransferable
+ = dynamic_cast<const SdPageObjsTLV::SdPageObjsTransferable*>(pDragTransferable);
+ if (pPageObjsTransferable != nullptr)
+ return DT_PAGE_FROM_NAVIGATOR;
+
+ return DT_SHAPE;
+}
+
+sal_Int8 Clipboard::ExecuteOrAcceptShapeDrop (
+ DropCommand eCommand,
+ const Point& rPosition,
+ const void* pDropEvent,
+ DropTargetHelper& rTargetHelper,
+ ::sd::Window* pTargetWindow,
+ sal_uInt16 nPage,
+ SdrLayerID nLayer)
+{
+ sal_Int8 nResult = 0;
+
+ // The dropping of a shape is accepted or executed only when there is
+ // DrawViewShell available to which we can forward this call. This has
+ // technical reasons: The actual code to accept or execute a shape drop
+ // is implemented in the ViewShell class and uses the page view of the
+ // main edit view. This is not possible without a DrawViewShell.
+ std::shared_ptr<DrawViewShell> pDrawViewShell;
+ if (mrSlideSorter.GetViewShell() != nullptr)
+ pDrawViewShell = std::dynamic_pointer_cast<DrawViewShell>(
+ mrSlideSorter.GetViewShell()->GetViewShellBase().GetMainViewShell());
+ if (pDrawViewShell != nullptr
+ && (pDrawViewShell->GetShellType() == ViewShell::ST_IMPRESS
+ || pDrawViewShell->GetShellType() == ViewShell::ST_DRAW))
+ {
+ // The drop is only accepted or executed when it takes place over a
+ // page object. Therefore we replace a missing page number by the
+ // number of the page under the mouse.
+ if (nPage == SDRPAGE_NOTFOUND)
+ {
+ model::SharedPageDescriptor pDescriptor (
+ mrSlideSorter.GetModel().GetPageDescriptor(
+ mrSlideSorter.GetView().GetPageIndexAtPoint(rPosition)));
+ if (pDescriptor)
+ nPage = pDescriptor->GetPageIndex();
+ }
+
+ // Now comes the code that is different for the Execute and Accept:
+ // We simply forward the call to the AcceptDrop() or ExecuteDrop()
+ // methods of the DrawViewShell in the center pane.
+ if (nPage != SDRPAGE_NOTFOUND)
+ switch (eCommand)
+ {
+ case DC_ACCEPT:
+ nResult = pDrawViewShell->AcceptDrop(
+ *static_cast<const AcceptDropEvent*>(pDropEvent),
+ rTargetHelper,
+ pTargetWindow,
+ nPage,
+ nLayer);
+ break;
+
+ case DC_EXECUTE:
+ nResult = pDrawViewShell->ExecuteDrop(
+ *static_cast<const ExecuteDropEvent*>(pDropEvent),
+ rTargetHelper,
+ pTargetWindow,
+ nPage,
+ nLayer);
+ break;
+ }
+ }
+
+ return nResult;
+}
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsCurrentSlideManager.cxx b/sd/source/ui/slidesorter/controller/SlsCurrentSlideManager.cxx
new file mode 100644
index 000000000..9203c06e8
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsCurrentSlideManager.cxx
@@ -0,0 +1,256 @@
+/* -*- 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 <SlideSorter.hxx>
+#include <model/SlideSorterModel.hxx>
+#include <model/SlsPageDescriptor.hxx>
+#include <controller/SlsPageSelector.hxx>
+#include <controller/SlideSorterController.hxx>
+#include <controller/SlsCurrentSlideManager.hxx>
+#include <controller/SlsFocusManager.hxx>
+#include <view/SlideSorterView.hxx>
+#include <ViewShellBase.hxx>
+#include <ViewShell.hxx>
+#include <DrawViewShell.hxx>
+#include <sdpage.hxx>
+#include <FrameView.hxx>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/frame/XController.hpp>
+#include <osl/diagnose.h>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+using namespace ::sd::slidesorter::model;
+
+namespace sd::slidesorter::controller {
+
+CurrentSlideManager::CurrentSlideManager (SlideSorter& rSlideSorter)
+ : mrSlideSorter(rSlideSorter),
+ mnCurrentSlideIndex(-1),
+ maSwitchPageDelayTimer("sd CurrentSlideManager maSwitchPageDelayTimer")
+{
+ maSwitchPageDelayTimer.SetTimeout(100);
+ maSwitchPageDelayTimer.SetInvokeHandler(LINK(this,CurrentSlideManager,SwitchPageCallback));
+}
+
+CurrentSlideManager::~CurrentSlideManager()
+{
+}
+
+void CurrentSlideManager::NotifyCurrentSlideChange (const SdPage* pPage)
+{
+ if (pPage != nullptr)
+ NotifyCurrentSlideChange(
+ mrSlideSorter.GetModel().GetIndex(
+ Reference<drawing::XDrawPage>(
+ const_cast<SdPage*>(pPage)->getUnoPage(),
+ UNO_QUERY)));
+ else
+ NotifyCurrentSlideChange(-1);
+}
+
+void CurrentSlideManager::NotifyCurrentSlideChange (const sal_Int32 nSlideIndex)
+{
+ if (mnCurrentSlideIndex == nSlideIndex)
+ return;
+
+ PageSelector::BroadcastLock aBroadcastLock (mrSlideSorter.GetController().GetPageSelector());
+
+ mrSlideSorter.GetController().GetPageSelector().DeselectAllPages();
+
+ ReleaseCurrentSlide();
+ AcquireCurrentSlide(nSlideIndex);
+
+ // Update the selection.
+ if (mpCurrentSlide)
+ {
+ mrSlideSorter.GetController().GetPageSelector().SelectPage(mpCurrentSlide);
+ mrSlideSorter.GetController().GetFocusManager().SetFocusedPage(mpCurrentSlide);
+ }
+}
+
+void CurrentSlideManager::ReleaseCurrentSlide()
+{
+ if (mpCurrentSlide)
+ mrSlideSorter.GetView().SetState(mpCurrentSlide, PageDescriptor::ST_Current, false);
+
+ mpCurrentSlide.reset();
+ mnCurrentSlideIndex = -1;
+}
+
+void CurrentSlideManager::AcquireCurrentSlide (const sal_Int32 nSlideIndex)
+{
+ mnCurrentSlideIndex = nSlideIndex;
+
+ // if current slide valid
+ if (mnCurrentSlideIndex >= 0 && mnCurrentSlideIndex<mrSlideSorter.GetModel().GetPageCount())
+ {
+ // Get a descriptor for the XDrawPage reference. Note that the
+ // given XDrawPage may or may not be member of the slide sorter
+ // document.
+ mpCurrentSlide = mrSlideSorter.GetModel().GetPageDescriptor(mnCurrentSlideIndex);
+ if (mpCurrentSlide)
+ mrSlideSorter.GetView().SetState(mpCurrentSlide, PageDescriptor::ST_Current, true);
+ }
+}
+
+void CurrentSlideManager::SwitchCurrentSlide (
+ const sal_Int32 nSlideIndex)
+{
+ SwitchCurrentSlide(mrSlideSorter.GetModel().GetPageDescriptor(nSlideIndex), true/*bUpdateSelection*/);
+}
+
+void CurrentSlideManager::SwitchCurrentSlide (
+ const SharedPageDescriptor& rpDescriptor,
+ const bool bUpdateSelection)
+{
+ if (!rpDescriptor || mpCurrentSlide==rpDescriptor)
+ return;
+
+ ReleaseCurrentSlide();
+ AcquireCurrentSlide((rpDescriptor->GetPage()->GetPageNum()-1)/2);
+
+ ViewShell* pViewShell = mrSlideSorter.GetViewShell();
+ if (pViewShell != nullptr && pViewShell->IsMainViewShell())
+ {
+ // The slide sorter is the main view.
+ FrameView* pFrameView = pViewShell->GetFrameView();
+ if (pFrameView != nullptr)
+ pFrameView->SetSelectedPage(sal::static_int_cast<sal_uInt16>(mnCurrentSlideIndex));
+ mrSlideSorter.GetController().GetPageSelector().SetCoreSelection();
+ }
+
+ // We do not tell the XController/ViewShellBase about the new
+ // slide right away. This is done asynchronously after a short
+ // delay to allow for more slide switches in the slide sorter.
+ // This goes under the assumption that slide switching inside
+ // the slide sorter is fast (no expensive redraw of the new page
+ // (unless the preview of the new slide is not yet preset)) and
+ // that slide switching in the edit view is slow (all shapes of
+ // the new slide have to be repainted.)
+ maSwitchPageDelayTimer.Start();
+
+ // We have to store the (index of the) new current slide at
+ // the tab control because there are other asynchronous
+ // notifications of the slide switching that otherwise
+ // overwrite the correct value.
+ SetCurrentSlideAtTabControl(mpCurrentSlide);
+
+ if (bUpdateSelection)
+ {
+ mrSlideSorter.GetController().GetPageSelector().DeselectAllPages();
+ mrSlideSorter.GetController().GetPageSelector().SelectPage(rpDescriptor);
+ }
+ mrSlideSorter.GetController().GetFocusManager().SetFocusedPage(rpDescriptor);
+}
+
+void CurrentSlideManager::SetCurrentSlideAtViewShellBase (const SharedPageDescriptor& rpDescriptor)
+{
+ OSL_ASSERT(rpDescriptor);
+
+ ViewShellBase* pBase = mrSlideSorter.GetViewShellBase();
+ if (pBase != nullptr)
+ {
+ DrawViewShell* pDrawViewShell = dynamic_cast<DrawViewShell*>(
+ pBase->GetMainViewShell().get());
+ if (pDrawViewShell != nullptr)
+ {
+ sal_uInt16 nPageNumber = (rpDescriptor->GetPage()->GetPageNum()-1)/2;
+ pDrawViewShell->SwitchPage(nPageNumber);
+ TabControl& rPageTabControl = pDrawViewShell->GetPageTabControl();
+ rPageTabControl.SetCurPageId(rPageTabControl.GetPageId(nPageNumber));
+ }
+ }
+}
+
+void CurrentSlideManager::SetCurrentSlideAtTabControl (const SharedPageDescriptor& rpDescriptor)
+{
+ OSL_ASSERT(rpDescriptor);
+
+ ViewShellBase* pBase = mrSlideSorter.GetViewShellBase();
+ if (pBase != nullptr)
+ {
+ std::shared_ptr<DrawViewShell> pDrawViewShell (
+ std::dynamic_pointer_cast<DrawViewShell>(pBase->GetMainViewShell()));
+ if (pDrawViewShell)
+ {
+ sal_uInt16 nPageNumber = (rpDescriptor->GetPage()->GetPageNum()-1)/2;
+ TabControl& rPageTabControl = pDrawViewShell->GetPageTabControl();
+ rPageTabControl.SetCurPageId(rPageTabControl.GetPageId(nPageNumber));
+ }
+ }
+}
+
+void CurrentSlideManager::SetCurrentSlideAtXController (const SharedPageDescriptor& rpDescriptor)
+{
+ OSL_ASSERT(rpDescriptor);
+
+ try
+ {
+ Reference<beans::XPropertySet> xSet (mrSlideSorter.GetXController(), UNO_QUERY);
+ if (xSet.is())
+ {
+ Any aPage;
+ aPage <<= rpDescriptor->GetPage()->getUnoPage();
+ xSet->setPropertyValue( "CurrentPage", aPage );
+ }
+ }
+ catch (const Exception&)
+ {
+ // We have not been able to set the current page at the main view.
+ // This is sad but still leaves us in a valid state. Therefore,
+ // this exception is silently ignored.
+ }
+}
+
+void CurrentSlideManager::PrepareModelChange()
+{
+ mpCurrentSlide.reset();
+}
+
+void CurrentSlideManager::HandleModelChange()
+{
+ if (mnCurrentSlideIndex >= 0)
+ {
+ mpCurrentSlide = mrSlideSorter.GetModel().GetPageDescriptor(mnCurrentSlideIndex);
+ if (mpCurrentSlide)
+ mrSlideSorter.GetView().SetState(mpCurrentSlide, PageDescriptor::ST_Current, true);
+ }
+}
+
+IMPL_LINK_NOARG(CurrentSlideManager, SwitchPageCallback, Timer *, void)
+{
+ if (mpCurrentSlide)
+ {
+ // Set current page. At the moment we have to do this in two
+ // different ways. The UNO way is the preferable one but, alas,
+ // it does not work always correctly (after some kinds of model
+ // changes). Therefore, we call DrawViewShell::SwitchPage(),
+ // too.
+ ViewShell* pViewShell = mrSlideSorter.GetViewShell();
+ if (pViewShell==nullptr || ! pViewShell->IsMainViewShell())
+ SetCurrentSlideAtViewShellBase(mpCurrentSlide);
+ SetCurrentSlideAtXController(mpCurrentSlide);
+ }
+}
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsDragAndDropContext.cxx b/sd/source/ui/slidesorter/controller/SlsDragAndDropContext.cxx
new file mode 100644
index 000000000..f447c5656
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsDragAndDropContext.cxx
@@ -0,0 +1,120 @@
+/* -*- 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 "SlsDragAndDropContext.hxx"
+
+#include <SlideSorter.hxx>
+#include <model/SlideSorterModel.hxx>
+#include <controller/SlideSorterController.hxx>
+#include <controller/SlsInsertionIndicatorHandler.hxx>
+#include <controller/SlsScrollBarManager.hxx>
+#include <controller/SlsProperties.hxx>
+#include <controller/SlsClipboard.hxx>
+#include <controller/SlsTransferableData.hxx>
+#include <Window.hxx>
+#include <sdtreelb.hxx>
+#include <sdmod.hxx>
+
+namespace sd::slidesorter::controller {
+
+DragAndDropContext::DragAndDropContext (SlideSorter& rSlideSorter)
+ : mpTargetSlideSorter(&rSlideSorter),
+ mnInsertionIndex(-1)
+{
+ // No Drag-and-Drop for master pages.
+ if (rSlideSorter.GetModel().GetEditMode() != EditMode::Page)
+ return;
+
+ // For properly handling transferables created by the navigator we
+ // need additional information. For this a user data object is
+ // created that contains the necessary information.
+ SdTransferable* pTransferable = SD_MOD()->pTransferDrag;
+ SdPageObjsTLV::SdPageObjsTransferable* pTreeListBoxTransferable
+ = dynamic_cast<SdPageObjsTLV::SdPageObjsTransferable*>(pTransferable);
+ if (pTreeListBoxTransferable!=nullptr && !TransferableData::GetFromTransferable(pTransferable))
+ {
+ pTransferable->AddUserData(
+ sd::slidesorter::controller::Clipboard::CreateTransferableUserData(pTransferable));
+ }
+
+ rSlideSorter.GetController().GetInsertionIndicatorHandler()->UpdateIndicatorIcon(pTransferable);
+}
+
+DragAndDropContext::~DragAndDropContext() COVERITY_NOEXCEPT_FALSE
+{
+ SetTargetSlideSorter();
+}
+
+void DragAndDropContext::Dispose()
+{
+ mnInsertionIndex = -1;
+}
+
+void DragAndDropContext::UpdatePosition (
+ const Point& rMousePosition,
+ const InsertionIndicatorHandler::Mode eMode,
+ const bool bAllowAutoScroll)
+{
+ if (mpTargetSlideSorter == nullptr)
+ return;
+
+ if (mpTargetSlideSorter->GetProperties()->IsUIReadOnly())
+ return;
+
+ // Convert window coordinates into model coordinates (we need the
+ // window coordinates for auto-scrolling because that remains
+ // constant while scrolling.)
+ sd::Window *pWindow = mpTargetSlideSorter->GetContentWindow().get();
+ const Point aMouseModelPosition (pWindow->PixelToLogic(rMousePosition));
+ std::shared_ptr<InsertionIndicatorHandler> pInsertionIndicatorHandler (
+ mpTargetSlideSorter->GetController().GetInsertionIndicatorHandler());
+
+ bool bDoAutoScroll = bAllowAutoScroll
+ && mpTargetSlideSorter->GetController().GetScrollBarManager().AutoScroll(
+ rMousePosition,
+ [this, eMode, rMousePosition] () {
+ return this->UpdatePosition(rMousePosition, eMode, false);
+ });
+
+ if (!bDoAutoScroll)
+ {
+ pInsertionIndicatorHandler->UpdatePosition(aMouseModelPosition, eMode);
+
+ // Remember the new insertion index.
+ mnInsertionIndex = pInsertionIndicatorHandler->GetInsertionPageIndex();
+ if (pInsertionIndicatorHandler->IsInsertionTrivial(mnInsertionIndex, eMode))
+ mnInsertionIndex = -1;
+ }
+}
+
+void DragAndDropContext::SetTargetSlideSorter()
+{
+ if (mpTargetSlideSorter != nullptr)
+ {
+ mpTargetSlideSorter->GetController().GetScrollBarManager().StopAutoScroll();
+ mpTargetSlideSorter->GetController().GetInsertionIndicatorHandler()->End(
+ Animator::AM_Animated);
+ }
+
+ mpTargetSlideSorter = nullptr;
+}
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsDragAndDropContext.hxx b/sd/source/ui/slidesorter/controller/SlsDragAndDropContext.hxx
new file mode 100644
index 000000000..cbeb11f8b
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsDragAndDropContext.hxx
@@ -0,0 +1,68 @@
+/* -*- 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 <controller/SlsInsertionIndicatorHandler.hxx>
+
+class Point;
+
+namespace sd::slidesorter
+{
+class SlideSorter;
+}
+
+namespace sd::slidesorter::controller
+{
+/** A DragAndDropContext object handles an active drag and drop operation.
+ When the mouse is moved from one slide sorter window to another the
+ target SlideSorter object is exchanged accordingly.
+*/
+class DragAndDropContext
+{
+public:
+ /** Create a substitution display of the currently selected pages or,
+ when provided, the pages in the transferable.
+ */
+ explicit DragAndDropContext(SlideSorter& rSlideSorter);
+ ~DragAndDropContext() COVERITY_NOEXCEPT_FALSE;
+
+ /** Call this method (for example as reaction to ESC key press) to avoid
+ processing (ie moving or inserting) the substitution when the called
+ DragAndDropContext object is destroyed.
+ */
+ void Dispose();
+
+ /** Move the substitution display by the distance the mouse has
+ travelled since the last call to this method or to
+ CreateSubstitution(). The given point becomes the new anchor.
+ */
+ void UpdatePosition(const Point& rMousePosition, const InsertionIndicatorHandler::Mode eMode,
+ const bool bAllowAutoScroll);
+
+ void SetTargetSlideSorter();
+
+private:
+ SlideSorter* mpTargetSlideSorter;
+ sal_Int32 mnInsertionIndex;
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsFocusManager.cxx b/sd/source/ui/slidesorter/controller/SlsFocusManager.cxx
new file mode 100644
index 000000000..59027f5a8
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsFocusManager.cxx
@@ -0,0 +1,245 @@
+/* -*- 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 <controller/SlsFocusManager.hxx>
+
+#include <SlideSorter.hxx>
+#include <controller/SlideSorterController.hxx>
+#include <controller/SlsCurrentSlideManager.hxx>
+#include <controller/SlsVisibleAreaManager.hxx>
+#include <model/SlideSorterModel.hxx>
+#include <model/SlsPageDescriptor.hxx>
+#include <view/SlideSorterView.hxx>
+#include <view/SlsLayouter.hxx>
+#include <osl/diagnose.h>
+
+#include <Window.hxx>
+#include <sdpage.hxx>
+
+namespace sd::slidesorter::controller {
+
+FocusManager::FocusManager (SlideSorter& rSlideSorter)
+ : mrSlideSorter(rSlideSorter),
+ mnPageIndex(0),
+ mbPageIsFocused(false)
+{
+ if (mrSlideSorter.GetModel().GetPageCount() > 0)
+ mnPageIndex = 0;
+}
+
+FocusManager::~FocusManager()
+{
+}
+
+void FocusManager::MoveFocus (FocusMoveDirection eDirection)
+{
+ if (!(mnPageIndex >= 0 && mbPageIsFocused))
+ return;
+
+ HideFocusIndicator (GetFocusedPageDescriptor());
+
+ const sal_Int32 nColumnCount (mrSlideSorter.GetView().GetLayouter().GetColumnCount());
+ const sal_Int32 nPageCount (mrSlideSorter.GetModel().GetPageCount());
+ switch (eDirection)
+ {
+ case FocusMoveDirection::Left:
+ if (mnPageIndex > 0)
+ mnPageIndex -= 1;
+ break;
+
+ case FocusMoveDirection::Right:
+ if (mnPageIndex < nPageCount-1)
+ mnPageIndex += 1;
+ break;
+
+ case FocusMoveDirection::Up:
+ {
+ const sal_Int32 nCandidate (mnPageIndex - nColumnCount);
+ if (nCandidate >= 0)
+ {
+ // Move the focus the previous row.
+ mnPageIndex = nCandidate;
+ }
+ }
+ break;
+
+ case FocusMoveDirection::Down:
+ {
+ const sal_Int32 nCandidate (mnPageIndex + nColumnCount);
+ if (nCandidate < nPageCount)
+ {
+ // Move the focus to the next row.
+ mnPageIndex = nCandidate;
+ }
+ }
+ break;
+ }
+
+ if (mnPageIndex < 0)
+ {
+ OSL_ASSERT(mnPageIndex>=0);
+ mnPageIndex = 0;
+ }
+ else if (mnPageIndex >= nPageCount)
+ {
+ OSL_ASSERT(mnPageIndex<nPageCount);
+ mnPageIndex = nPageCount - 1;
+ }
+
+ if (mbPageIsFocused)
+ {
+ ShowFocusIndicator(GetFocusedPageDescriptor(), true);
+ }
+}
+
+void FocusManager::ShowFocus (const bool bScrollToFocus)
+{
+ mbPageIsFocused = true;
+ ShowFocusIndicator(GetFocusedPageDescriptor(), bScrollToFocus);
+}
+
+void FocusManager::HideFocus()
+{
+ mbPageIsFocused = false;
+ HideFocusIndicator(GetFocusedPageDescriptor());
+}
+
+bool FocusManager::ToggleFocus()
+{
+ if (mnPageIndex >= 0)
+ {
+ if (mbPageIsFocused)
+ HideFocus ();
+ else
+ ShowFocus ();
+ }
+ return mbPageIsFocused;
+}
+
+bool FocusManager::HasFocus() const
+{
+ return mrSlideSorter.GetContentWindow()->HasFocus();
+}
+
+model::SharedPageDescriptor FocusManager::GetFocusedPageDescriptor() const
+{
+ return mrSlideSorter.GetModel().GetPageDescriptor(mnPageIndex);
+}
+
+bool FocusManager::SetFocusedPage (const model::SharedPageDescriptor& rpDescriptor)
+{
+ if (rpDescriptor)
+ {
+ FocusHider aFocusHider (*this);
+ mnPageIndex = (rpDescriptor->GetPage()->GetPageNum()-1)/2;
+ return true;
+ }
+ return false;
+}
+
+void FocusManager::SetFocusedPage (sal_Int32 nPageIndex)
+{
+ FocusHider aFocusHider (*this);
+ mnPageIndex = nPageIndex;
+}
+
+bool FocusManager::SetFocusedPageToCurrentPage()
+{
+ return SetFocusedPage(mrSlideSorter.GetController().GetCurrentSlideManager()->GetCurrentSlide());
+}
+
+bool FocusManager::IsFocusShowing() const
+{
+ return HasFocus() && mbPageIsFocused;
+}
+
+void FocusManager::HideFocusIndicator (const model::SharedPageDescriptor& rpDescriptor)
+{
+ if (rpDescriptor)
+ {
+ mrSlideSorter.GetView().SetState(rpDescriptor, model::PageDescriptor::ST_Focused, false);
+
+ // Hide focus should also fire the focus event, Currently, only accessibility add the focus listener
+ NotifyFocusChangeListeners();
+ }
+}
+
+void FocusManager::ShowFocusIndicator (
+ const model::SharedPageDescriptor& rpDescriptor,
+ const bool bScrollToFocus)
+{
+ if (!rpDescriptor)
+ return;
+
+ mrSlideSorter.GetView().SetState(rpDescriptor, model::PageDescriptor::ST_Focused, true);
+
+ if (bScrollToFocus)
+ {
+ // Scroll the focused page object into the visible area and repaint
+ // it, so that the focus indicator becomes visible.
+ mrSlideSorter.GetController().GetVisibleAreaManager().RequestVisible(rpDescriptor,true);
+ }
+ mrSlideSorter.GetView().RequestRepaint(rpDescriptor);
+
+ NotifyFocusChangeListeners();
+}
+
+void FocusManager::AddFocusChangeListener (const Link<LinkParamNone*,void>& rListener)
+{
+ if (::std::find (maFocusChangeListeners.begin(), maFocusChangeListeners.end(), rListener)
+ == maFocusChangeListeners.end())
+ {
+ maFocusChangeListeners.push_back (rListener);
+ }
+}
+
+void FocusManager::RemoveFocusChangeListener (const Link<LinkParamNone*,void>& rListener)
+{
+ maFocusChangeListeners.erase (
+ ::std::find (maFocusChangeListeners.begin(), maFocusChangeListeners.end(), rListener));
+}
+
+void FocusManager::NotifyFocusChangeListeners() const
+{
+ // Create a copy of the listener list to be safe when that is modified.
+ ::std::vector<Link<LinkParamNone*,void>> aListeners (maFocusChangeListeners);
+
+ // Tell the selection change listeners that the selection has changed.
+ for (const auto& rListener : aListeners)
+ {
+ rListener.Call(nullptr);
+ }
+}
+
+FocusManager::FocusHider::FocusHider (FocusManager& rManager)
+: mbFocusVisible(rManager.IsFocusShowing())
+, mrManager(rManager)
+{
+ mrManager.HideFocus();
+}
+
+FocusManager::FocusHider::~FocusHider() COVERITY_NOEXCEPT_FALSE
+{
+ if (mbFocusVisible)
+ mrManager.ShowFocus();
+}
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsInsertionIndicatorHandler.cxx b/sd/source/ui/slidesorter/controller/SlsInsertionIndicatorHandler.cxx
new file mode 100644
index 000000000..ff1a05ef1
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsInsertionIndicatorHandler.cxx
@@ -0,0 +1,243 @@
+/* -*- 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 <controller/SlsInsertionIndicatorHandler.hxx>
+#include <view/SlideSorterView.hxx>
+#include <view/SlsLayouter.hxx>
+#include <view/SlsInsertAnimator.hxx>
+#include <view/SlsInsertionIndicatorOverlay.hxx>
+#include <model/SlideSorterModel.hxx>
+#include <model/SlsPageEnumerationProvider.hxx>
+#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
+#include <osl/diagnose.h>
+
+#include <SlideSorter.hxx>
+
+using namespace ::com::sun::star::datatransfer::dnd::DNDConstants;
+
+namespace sd::slidesorter::controller {
+
+InsertionIndicatorHandler::InsertionIndicatorHandler (SlideSorter& rSlideSorter)
+ : mrSlideSorter(rSlideSorter),
+ mpInsertionIndicatorOverlay(std::make_shared<view::InsertionIndicatorOverlay>(rSlideSorter)),
+ meMode(MoveMode),
+ mbIsInsertionTrivial(false),
+ mbIsActive(false),
+ mbIsReadOnly(mrSlideSorter.GetModel().IsReadOnly()),
+ mbIsOverSourceView(true),
+ maIconSize(0,0),
+ mbIsForcedShow(false)
+{
+}
+
+InsertionIndicatorHandler::~InsertionIndicatorHandler() COVERITY_NOEXCEPT_FALSE
+{
+}
+
+void InsertionIndicatorHandler::Start (const bool bIsOverSourceView)
+{
+ if (mbIsActive)
+ {
+ OSL_ASSERT(!mbIsActive);
+ }
+
+ mbIsReadOnly = mrSlideSorter.GetModel().IsReadOnly();
+ if (mbIsReadOnly)
+ return;
+
+ mbIsActive = true;
+ mbIsOverSourceView = bIsOverSourceView;
+}
+
+void InsertionIndicatorHandler::End (const controller::Animator::AnimationMode eMode)
+{
+ if (mbIsForcedShow || ! mbIsActive || mbIsReadOnly)
+ return;
+
+ GetInsertAnimator()->Reset(eMode);
+
+ mbIsActive = false;
+ // maInsertPosition = view::InsertPosition();
+ meMode = UnknownMode;
+
+ mpInsertionIndicatorOverlay->Hide();
+ mpInsertionIndicatorOverlay = std::make_shared<view::InsertionIndicatorOverlay>(mrSlideSorter);
+}
+
+void InsertionIndicatorHandler::ForceShow()
+{
+ mbIsForcedShow = true;
+}
+
+void InsertionIndicatorHandler::ForceEnd()
+{
+ mbIsForcedShow = false;
+ End(Animator::AM_Immediate);
+}
+
+void InsertionIndicatorHandler::UpdateIndicatorIcon (const SdTransferable* pTransferable)
+{
+ mpInsertionIndicatorOverlay->Create(pTransferable);
+ maIconSize = mpInsertionIndicatorOverlay->GetSize();
+}
+
+InsertionIndicatorHandler::Mode InsertionIndicatorHandler::GetModeFromDndAction (
+ const sal_Int8 nDndAction)
+{
+ if ((nDndAction & ACTION_MOVE) != 0)
+ return MoveMode;
+ else if ((nDndAction & ACTION_COPY) != 0)
+ return CopyMode;
+ else
+ return UnknownMode;
+}
+
+void InsertionIndicatorHandler::UpdatePosition (
+ const Point& rMouseModelPosition,
+ const Mode eMode)
+{
+ if ( ! mbIsActive)
+ return;
+
+ if (mbIsReadOnly)
+ return;
+
+ SetPosition(rMouseModelPosition, eMode);
+}
+
+void InsertionIndicatorHandler::UpdatePosition (
+ const Point& rMouseModelPosition,
+ const sal_Int8 nDndAction)
+{
+ UpdatePosition(rMouseModelPosition, GetModeFromDndAction(nDndAction));
+}
+
+sal_Int32 InsertionIndicatorHandler::GetInsertionPageIndex() const
+{
+ if (mbIsReadOnly)
+ return -1;
+ else
+ return maInsertPosition.GetIndex();
+}
+
+void InsertionIndicatorHandler::SetPosition (
+ const Point& rPoint,
+ const Mode eMode)
+{
+ view::Layouter& rLayouter (mrSlideSorter.GetView().GetLayouter());
+
+ const view::InsertPosition aInsertPosition (rLayouter.GetInsertPosition(
+ rPoint,
+ maIconSize,
+ mrSlideSorter.GetModel()));
+
+ if (maInsertPosition == aInsertPosition && meMode == eMode)
+ return;
+
+ maInsertPosition = aInsertPosition;
+ meMode = eMode;
+ mbIsInsertionTrivial = IsInsertionTrivial(maInsertPosition.GetIndex(), eMode);
+ if (maInsertPosition.GetIndex()>=0 && ! mbIsInsertionTrivial)
+ {
+ mpInsertionIndicatorOverlay->SetLocation(maInsertPosition.GetLocation());
+
+ GetInsertAnimator()->SetInsertPosition(maInsertPosition);
+ mpInsertionIndicatorOverlay->Show();
+ }
+ else
+ {
+ GetInsertAnimator()->Reset(Animator::AM_Animated);
+ mpInsertionIndicatorOverlay->Hide();
+ }
+}
+
+std::shared_ptr<view::InsertAnimator> const & InsertionIndicatorHandler::GetInsertAnimator()
+{
+ if ( ! mpInsertAnimator)
+ mpInsertAnimator = std::make_shared<view::InsertAnimator>(mrSlideSorter);
+ return mpInsertAnimator;
+}
+
+bool InsertionIndicatorHandler::IsInsertionTrivial (
+ const sal_Int32 nInsertionIndex,
+ const Mode eMode) const
+{
+ if (eMode == CopyMode)
+ return false;
+ else if (eMode == UnknownMode)
+ return true;
+
+ if ( ! mbIsOverSourceView)
+ return false;
+
+ // Iterate over all selected pages and check whether there are
+ // holes. While we do this we remember the indices of the first and
+ // last selected page as preparation for the next step.
+ sal_Int32 nCurrentIndex = -1;
+ sal_Int32 nFirstIndex = -1;
+ sal_Int32 nLastIndex = -1;
+ model::PageEnumeration aSelectedPages (
+ model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
+ mrSlideSorter.GetModel()));
+ while (aSelectedPages.HasMoreElements())
+ {
+ model::SharedPageDescriptor pDescriptor (aSelectedPages.GetNextElement());
+
+ // Get the page number and compare it to the last one.
+ const sal_Int32 nPageNumber (pDescriptor->GetPageIndex());
+ if (nCurrentIndex>=0 && nPageNumber>(nCurrentIndex+1))
+ return false;
+ else
+ nCurrentIndex = nPageNumber;
+
+ // Remember indices of the first and last page of the selection.
+ if (nFirstIndex == -1)
+ nFirstIndex = nPageNumber;
+ nLastIndex = nPageNumber;
+ }
+
+ // When we come here then the selection has no holes. We still have
+ // to check that the insertion position is not directly in front or
+ // directly behind the selection and thus moving the selection there
+ // would not change the model.
+ return nInsertionIndex >= nFirstIndex && nInsertionIndex <= (nLastIndex+1);
+}
+
+bool InsertionIndicatorHandler::IsInsertionTrivial (const sal_Int8 nDndAction)
+{
+ return IsInsertionTrivial(GetInsertionPageIndex(), GetModeFromDndAction(nDndAction));
+}
+
+//===== InsertionIndicatorHandler::ForceShowContext ===========================
+
+InsertionIndicatorHandler::ForceShowContext::ForceShowContext (
+ const std::shared_ptr<InsertionIndicatorHandler>& rpHandler)
+ : mpHandler(rpHandler)
+{
+ mpHandler->ForceShow();
+}
+
+InsertionIndicatorHandler::ForceShowContext::~ForceShowContext() COVERITY_NOEXCEPT_FALSE
+{
+ mpHandler->ForceEnd();
+}
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsListener.cxx b/sd/source/ui/slidesorter/controller/SlsListener.cxx
new file mode 100644
index 000000000..000f42da2
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsListener.cxx
@@ -0,0 +1,597 @@
+/* -*- 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 "SlsListener.hxx"
+
+#include <SlideSorter.hxx>
+#include <ViewShell.hxx>
+#include <ViewShellHint.hxx>
+#include <controller/SlideSorterController.hxx>
+#include <controller/SlsPageSelector.hxx>
+#include <controller/SlsCurrentSlideManager.hxx>
+#include <controller/SlsSelectionManager.hxx>
+#include <controller/SlsSelectionObserver.hxx>
+#include <model/SlideSorterModel.hxx>
+#include <view/SlideSorterView.hxx>
+#include <cache/SlsPageCache.hxx>
+#include <cache/SlsPageCacheManager.hxx>
+#include <drawdoc.hxx>
+#include <sdpage.hxx>
+#include <DrawDocShell.hxx>
+#include <svx/svdpage.hxx>
+
+#include <ViewShellBase.hxx>
+#include <EventMultiplexer.hxx>
+#include <com/sun/star/document/XEventBroadcaster.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/frame/FrameActionEvent.hpp>
+#include <com/sun/star/frame/FrameAction.hpp>
+#include <tools/debug.hxx>
+#include <tools/diagnose_ex.h>
+
+using namespace ::com::sun::star::accessibility;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star;
+
+namespace sd::slidesorter::controller {
+
+Listener::Listener (
+ SlideSorter& rSlideSorter)
+ : mrSlideSorter(rSlideSorter),
+ mrController(mrSlideSorter.GetController()),
+ mpBase(mrSlideSorter.GetViewShellBase()),
+ mbListeningToDocument (false),
+ mbListeningToUNODocument (false),
+ mbListeningToController (false),
+ mbListeningToFrame (false),
+ mbIsMainViewChangePending(false)
+{
+ StartListening(*mrSlideSorter.GetModel().GetDocument());
+ StartListening(*mrSlideSorter.GetModel().GetDocument()->GetDocSh());
+ mbListeningToDocument = true;
+
+ // Connect to the UNO document.
+ Reference<document::XEventBroadcaster> xBroadcaster (
+ mrSlideSorter.GetModel().GetDocument()->getUnoModel(), uno::UNO_QUERY);
+ if (xBroadcaster.is())
+ {
+ xBroadcaster->addEventListener (this);
+ mbListeningToUNODocument = true;
+ }
+
+ // Listen for disposing events from the document.
+ Reference<XComponent> xComponent (xBroadcaster, UNO_QUERY);
+ if (xComponent.is())
+ xComponent->addEventListener (
+ Reference<lang::XEventListener>(
+ static_cast<XWeak*>(this), UNO_QUERY));
+
+ // Connect to the frame to listen for controllers being exchanged.
+ bool bIsMainViewShell (false);
+ ViewShell* pViewShell = mrSlideSorter.GetViewShell();
+ if (pViewShell != nullptr)
+ bIsMainViewShell = pViewShell->IsMainViewShell();
+ if ( ! bIsMainViewShell)
+ {
+ // Listen to changes of certain properties.
+ Reference<frame::XFrame> xFrame;
+ Reference<frame::XController> xController (mrSlideSorter.GetXController());
+ if (xController.is())
+ xFrame = xController->getFrame();
+ mxFrameWeak = xFrame;
+ if (xFrame.is())
+ {
+ xFrame->addFrameActionListener(Reference<frame::XFrameActionListener>(this));
+ mbListeningToFrame = true;
+ }
+
+ // Connect to the current controller.
+ ConnectToController ();
+ }
+
+ // Listen for hints of the MainViewShell as well. If that is not yet
+ // present then the EventMultiplexer will tell us when it is available.
+ if (mpBase != nullptr)
+ {
+ ViewShell* pMainViewShell = mpBase->GetMainViewShell().get();
+ if (pMainViewShell != nullptr
+ && pMainViewShell!=pViewShell)
+ {
+ StartListening(*pMainViewShell);
+ }
+
+ Link<tools::EventMultiplexerEvent&,void> aLink (LINK(this, Listener, EventMultiplexerCallback));
+ mpBase->GetEventMultiplexer()->AddEventListener(aLink);
+ }
+}
+
+Listener::~Listener()
+{
+ DBG_ASSERT( !mbListeningToDocument && !mbListeningToUNODocument && !mbListeningToFrame,
+ "sd::Listener::~Listener(), disposing() was not called, ask DBO!" );
+}
+
+void Listener::ReleaseListeners()
+{
+ if (mbListeningToDocument)
+ {
+ EndListening(*mrSlideSorter.GetModel().GetDocument()->GetDocSh());
+ EndListening(*mrSlideSorter.GetModel().GetDocument());
+ mbListeningToDocument = false;
+ }
+
+ if (mbListeningToUNODocument)
+ {
+ Reference<document::XEventBroadcaster> xBroadcaster (
+ mrSlideSorter.GetModel().GetDocument()->getUnoModel(), UNO_QUERY);
+ if (xBroadcaster.is())
+ xBroadcaster->removeEventListener (this);
+
+ // Remove the dispose listener.
+ Reference<XComponent> xComponent (xBroadcaster, UNO_QUERY);
+ if (xComponent.is())
+ xComponent->removeEventListener (
+ Reference<lang::XEventListener>(
+ static_cast<XWeak*>(this), UNO_QUERY));
+
+ mbListeningToUNODocument = false;
+ }
+
+ if (mbListeningToFrame)
+ {
+ // Listen to changes of certain properties.
+ Reference<frame::XFrame> xFrame (mxFrameWeak);
+ if (xFrame.is())
+ {
+ xFrame->removeFrameActionListener(Reference<frame::XFrameActionListener>(this));
+ mbListeningToFrame = false;
+ }
+ }
+
+ DisconnectFromController ();
+
+ if (mpBase != nullptr)
+ {
+ Link<sd::tools::EventMultiplexerEvent&,void> aLink (LINK(this, Listener, EventMultiplexerCallback));
+ mpBase->GetEventMultiplexer()->RemoveEventListener(aLink);
+ }
+}
+
+void Listener::ConnectToController()
+{
+ ViewShell* pShell = mrSlideSorter.GetViewShell();
+
+ // Register at the controller of the main view shell (if we are that not
+ // ourself).
+ if (pShell!=nullptr && pShell->IsMainViewShell())
+ return;
+
+ Reference<frame::XController> xController (mrSlideSorter.GetXController());
+
+ // Listen to changes of certain properties.
+ Reference<beans::XPropertySet> xSet (xController, UNO_QUERY);
+ if (xSet.is())
+ {
+ try
+ {
+ xSet->addPropertyChangeListener("CurrentPage", this);
+ }
+ catch (beans::UnknownPropertyException&)
+ {
+ DBG_UNHANDLED_EXCEPTION("sd");
+ }
+ try
+ {
+ xSet->addPropertyChangeListener("IsMasterPageMode", this);
+ }
+ catch (beans::UnknownPropertyException&)
+ {
+ DBG_UNHANDLED_EXCEPTION("sd");
+ }
+ }
+
+ // Listen for disposing events.
+ if (xController.is())
+ {
+ xController->addEventListener (
+ Reference<lang::XEventListener>(static_cast<XWeak*>(this), UNO_QUERY));
+
+ mxControllerWeak = xController;
+ mbListeningToController = true;
+ }
+}
+
+void Listener::DisconnectFromController()
+{
+ if (!mbListeningToController)
+ return;
+
+ Reference<frame::XController> xController = mxControllerWeak;
+ Reference<beans::XPropertySet> xSet (xController, UNO_QUERY);
+ try
+ {
+ // Remove the property listener.
+ if (xSet.is())
+ {
+ xSet->removePropertyChangeListener( "CurrentPage", this );
+ xSet->removePropertyChangeListener( "IsMasterPageMode", this);
+ }
+
+ // Remove the dispose listener.
+ if (xController.is())
+ xController->removeEventListener (
+ Reference<lang::XEventListener>(
+ static_cast<XWeak*>(this), UNO_QUERY));
+ }
+ catch (beans::UnknownPropertyException&)
+ {
+ DBG_UNHANDLED_EXCEPTION("sd");
+ }
+
+ mbListeningToController = false;
+ mxControllerWeak = Reference<frame::XController>();
+}
+
+void Listener::Notify (
+ SfxBroadcaster& rBroadcaster,
+ const SfxHint& rHint)
+{
+ if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint)
+ {
+ const SdrHint* pSdrHint = static_cast<const SdrHint*>(&rHint);
+ switch (pSdrHint->GetKind())
+ {
+ case SdrHintKind::ModelCleared:
+ if (&rBroadcaster == mrSlideSorter.GetModel().GetDocument())
+ { // rhbz#965646 stop listening to dying document
+ EndListening(rBroadcaster);
+ return;
+ }
+ break;
+ case SdrHintKind::PageOrderChange:
+ if (&rBroadcaster == mrSlideSorter.GetModel().GetDocument())
+ HandleModelChange(pSdrHint->GetPage());
+ break;
+
+ default:
+ break;
+ }
+ }
+ else if (rHint.GetId() == SfxHintId::DocChanged)
+ {
+ mrController.CheckForMasterPageAssignment();
+ mrController.CheckForSlideTransitionAssignment();
+ }
+ else if (auto pViewShellHint = dynamic_cast<const ViewShellHint*>(&rHint))
+ {
+ switch (pViewShellHint->GetHintId())
+ {
+ case ViewShellHint::HINT_PAGE_RESIZE_START:
+ // Initiate a model change but do nothing (well, not much)
+ // until we are told that all slides have been resized.
+ mpModelChangeLock.reset(new SlideSorterController::ModelChangeLock(mrController),
+ o3tl::default_delete<SlideSorterController::ModelChangeLock>());
+ mrController.HandleModelChange();
+ break;
+
+ case ViewShellHint::HINT_PAGE_RESIZE_END:
+ // All slides have been resized. The model has to be updated.
+ mpModelChangeLock.reset();
+ break;
+
+ case ViewShellHint::HINT_CHANGE_EDIT_MODE_START:
+ mrController.PrepareEditModeChange();
+ break;
+
+ case ViewShellHint::HINT_CHANGE_EDIT_MODE_END:
+ mrController.FinishEditModeChange();
+ break;
+
+ case ViewShellHint::HINT_COMPLEX_MODEL_CHANGE_START:
+ mpModelChangeLock.reset(new SlideSorterController::ModelChangeLock(mrController),
+ o3tl::default_delete<SlideSorterController::ModelChangeLock>());
+ break;
+
+ case ViewShellHint::HINT_COMPLEX_MODEL_CHANGE_END:
+ mpModelChangeLock.reset();
+ break;
+ }
+ }
+}
+
+IMPL_LINK(Listener, EventMultiplexerCallback, ::sd::tools::EventMultiplexerEvent&, rEvent, void)
+{
+ switch (rEvent.meEventId)
+ {
+ case EventMultiplexerEventId::MainViewRemoved:
+ {
+ if (mpBase != nullptr)
+ {
+ ViewShell* pMainViewShell = mpBase->GetMainViewShell().get();
+ if (pMainViewShell != nullptr)
+ EndListening(*pMainViewShell);
+ }
+ }
+ break;
+
+ case EventMultiplexerEventId::MainViewAdded:
+ mbIsMainViewChangePending = true;
+ break;
+
+ case EventMultiplexerEventId::ConfigurationUpdated:
+ if (mbIsMainViewChangePending && mpBase != nullptr)
+ {
+ mbIsMainViewChangePending = false;
+ ViewShell* pMainViewShell = mpBase->GetMainViewShell().get();
+ if (pMainViewShell != nullptr
+ && pMainViewShell!=mrSlideSorter.GetViewShell())
+ {
+ StartListening (*pMainViewShell);
+ }
+ }
+ break;
+
+ case EventMultiplexerEventId::ControllerAttached:
+ {
+ ConnectToController();
+ // mrController.GetPageSelector().GetCoreSelection();
+ UpdateEditMode();
+ }
+ break;
+
+ case EventMultiplexerEventId::ControllerDetached:
+ DisconnectFromController();
+ break;
+
+ case EventMultiplexerEventId::ShapeChanged:
+ case EventMultiplexerEventId::ShapeInserted:
+ case EventMultiplexerEventId::ShapeRemoved:
+ HandleShapeModification(static_cast<const SdrPage*>(rEvent.mpUserData));
+ break;
+
+ case EventMultiplexerEventId::EndTextEdit:
+ if (rEvent.mpUserData != nullptr)
+ {
+ const SdrObject* pObject = static_cast<const SdrObject*>(rEvent.mpUserData);
+ HandleShapeModification(pObject->getSdrPageFromSdrObject());
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+//===== lang::XEventListener ================================================
+
+void SAL_CALL Listener::disposing (
+ const lang::EventObject& rEventObject)
+{
+ if ((mbListeningToDocument || mbListeningToUNODocument)
+ && mrSlideSorter.GetModel().GetDocument()!=nullptr
+ && rEventObject.Source
+ == mrSlideSorter.GetModel().GetDocument()->getUnoModel())
+ {
+ mbListeningToDocument = false;
+ mbListeningToUNODocument = false;
+ }
+ else if (mbListeningToController)
+ {
+ Reference<frame::XController> xController (mxControllerWeak);
+ if (rEventObject.Source == xController)
+ {
+ mbListeningToController = false;
+ }
+ }
+}
+
+//===== document::XEventListener ============================================
+
+void SAL_CALL Listener::notifyEvent (
+ const document::EventObject& )
+{
+}
+
+//===== beans::XPropertySetListener =========================================
+
+void SAL_CALL Listener::propertyChange (
+ const PropertyChangeEvent& rEvent)
+{
+ if (m_bDisposed)
+ {
+ throw lang::DisposedException ("SlideSorterController object has already been disposed",
+ static_cast<uno::XWeak*>(this));
+ }
+
+ if (rEvent.PropertyName == "CurrentPage")
+ {
+ Any aCurrentPage = rEvent.NewValue;
+ Reference<beans::XPropertySet> xPageSet (aCurrentPage, UNO_QUERY);
+ if (xPageSet.is())
+ {
+ try
+ {
+ Any aPageNumber = xPageSet->getPropertyValue ("Number");
+ sal_Int32 nCurrentPage = 0;
+ aPageNumber >>= nCurrentPage;
+ // The selection is already set but we call SelectPage()
+ // nevertheless in order to make the new current page the
+ // last recently selected page of the PageSelector. This is
+ // used when making the selection visible.
+ mrController.GetCurrentSlideManager()->NotifyCurrentSlideChange(nCurrentPage-1);
+ mrController.GetPageSelector().SelectPage(nCurrentPage-1);
+ }
+ catch (beans::UnknownPropertyException&)
+ {
+ DBG_UNHANDLED_EXCEPTION("sd");
+ }
+ catch (lang::DisposedException&)
+ {
+ // Something is already disposed. There is not much we can
+ // do, except not to crash.
+ }
+ }
+ }
+ else if (rEvent.PropertyName == "IsMasterPageMode")
+ {
+ bool bIsMasterPageMode = false;
+ rEvent.NewValue >>= bIsMasterPageMode;
+ mrController.ChangeEditMode (
+ bIsMasterPageMode ? EditMode::MasterPage : EditMode::Page);
+ }
+}
+
+//===== frame::XFrameActionListener ==========================================
+
+void SAL_CALL Listener::frameAction (const frame::FrameActionEvent& rEvent)
+{
+ switch (rEvent.Action)
+ {
+ case frame::FrameAction_COMPONENT_DETACHING:
+ DisconnectFromController();
+ break;
+
+ case frame::FrameAction_COMPONENT_REATTACHED:
+ {
+ ConnectToController();
+ mrController.GetPageSelector().GetCoreSelection();
+ UpdateEditMode();
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+//===== accessibility::XAccessibleEventListener ==============================
+
+void SAL_CALL Listener::notifyEvent (
+ const AccessibleEventObject& )
+{
+}
+
+void Listener::disposing(std::unique_lock<std::mutex>&)
+{
+ ReleaseListeners();
+}
+
+void Listener::UpdateEditMode()
+{
+ // When there is a new controller then the edit mode may have changed at
+ // the same time.
+ Reference<frame::XController> xController (mxControllerWeak);
+ Reference<beans::XPropertySet> xSet (xController, UNO_QUERY);
+ bool bIsMasterPageMode = false;
+ if (xSet != nullptr)
+ {
+ try
+ {
+ Any aValue (xSet->getPropertyValue( "IsMasterPageMode" ));
+ aValue >>= bIsMasterPageMode;
+ }
+ catch (beans::UnknownPropertyException&)
+ {
+ // When the property is not supported then the master page mode
+ // is not supported, too.
+ bIsMasterPageMode = false;
+ }
+ }
+ mrController.ChangeEditMode (
+ bIsMasterPageMode ? EditMode::MasterPage : EditMode::Page);
+}
+
+void Listener::HandleModelChange (const SdrPage* pPage)
+{
+ // Notify model and selection observer about the page. The return value
+ // of the model call acts as filter as to which events to pass to the
+ // selection observer.
+ if (mrSlideSorter.GetModel().NotifyPageEvent(pPage))
+ {
+ // The page of the hint belongs (or belonged) to the model.
+
+ // Tell the cache manager that the preview bitmaps for a deleted
+ // page can be removed from all caches.
+ if (pPage!=nullptr && ! pPage->IsInserted())
+ cache::PageCacheManager::Instance()->ReleasePreviewBitmap(pPage);
+
+ mrController.GetSelectionManager()->GetSelectionObserver()->NotifyPageEvent(pPage);
+ }
+
+ // Tell the controller about the model change only when the document is
+ // in a sane state, not just in the middle of a larger change.
+ SdDrawDocument* pDocument (mrSlideSorter.GetModel().GetDocument());
+ if (pDocument != nullptr
+ && pDocument->GetMasterSdPageCount(PageKind::Standard) == pDocument->GetMasterSdPageCount(PageKind::Notes))
+ {
+ // A model change can make updates of some text fields necessary
+ // (like page numbers and page count.) Invalidate all previews in
+ // the cache to cope with this. Doing this on demand would be a
+ // nice optimization.
+ cache::PageCacheManager::Instance()->InvalidateAllPreviewBitmaps(pDocument->getUnoModel());
+
+ mrController.HandleModelChange();
+ }
+}
+
+void Listener::HandleShapeModification (const SdrPage* pPage)
+{
+ if (pPage == nullptr)
+ return;
+
+ // Invalidate the preview of the page (in all slide sorters that display
+ // it.)
+ std::shared_ptr<cache::PageCacheManager> pCacheManager (cache::PageCacheManager::Instance());
+ if ( ! pCacheManager)
+ return;
+ SdDrawDocument* pDocument = mrSlideSorter.GetModel().GetDocument();
+ if (pDocument == nullptr)
+ {
+ OSL_ASSERT(pDocument!=nullptr);
+ return;
+ }
+ pCacheManager->InvalidatePreviewBitmap(pDocument->getUnoModel(), pPage);
+ mrSlideSorter.GetView().GetPreviewCache()->RequestPreviewBitmap(pPage);
+
+ // When the page is a master page then invalidate the previews of all
+ // pages that are linked to this master page.
+ if (!pPage->IsMasterPage())
+ return;
+
+ for (sal_uInt16 nIndex=0,nCount=pDocument->GetSdPageCount(PageKind::Standard);
+ nIndex<nCount;
+ ++nIndex)
+ {
+ const SdPage* pCandidate = pDocument->GetSdPage(nIndex, PageKind::Standard);
+ if (pCandidate!=nullptr && pCandidate->TRG_HasMasterPage())
+ {
+ if (&pCandidate->TRG_GetMasterPage() == pPage)
+ pCacheManager->InvalidatePreviewBitmap(pDocument->getUnoModel(), pCandidate);
+ }
+ else
+ {
+ OSL_ASSERT(pCandidate!=nullptr && pCandidate->TRG_HasMasterPage());
+ }
+ }
+}
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsListener.hxx b/sd/source/ui/slidesorter/controller/SlsListener.hxx
new file mode 100644
index 000000000..eff02cf19
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsListener.hxx
@@ -0,0 +1,164 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <controller/SlideSorterController.hxx>
+#include <com/sun/star/document/XEventListener.hpp>
+#include <com/sun/star/beans/XPropertyChangeListener.hpp>
+#include <com/sun/star/accessibility/XAccessibleEventListener.hpp>
+#include <com/sun/star/frame/XFrameActionListener.hpp>
+#include <comphelper/compbase.hxx>
+#include <cppuhelper/weakref.hxx>
+
+#include <svl/lstner.hxx>
+#include <tools/link.hxx>
+#include <memory>
+
+class SdrPage;
+
+namespace sd {
+class ViewShellBase;
+}
+
+namespace sd::tools { class EventMultiplexerEvent; }
+namespace sd::slidesorter { class SlideSorter; }
+
+namespace sd::slidesorter::controller {
+
+typedef comphelper::WeakComponentImplHelper<
+ css::document::XEventListener,
+ css::beans::XPropertyChangeListener,
+ css::accessibility::XAccessibleEventListener,
+ css::frame::XFrameActionListener
+ > ListenerInterfaceBase;
+
+/** Listen for events of various types and sources and react to them. This
+ class is a part of the controller.
+
+ When the view shell in the center pane is replaced by another the
+ associated controller is replaced as well. Therefore we have to
+ register at the frame and on certain FrameActionEvents to stop listening
+ to the old controller and register as listener at the new one.
+*/
+class Listener
+ : public ListenerInterfaceBase,
+ public SfxListener
+{
+public:
+ explicit Listener (SlideSorter& rSlideSorter);
+ virtual ~Listener() override;
+
+ /** Connect to the current controller of the view shell as listener.
+ This method is called once during initialization and every time a
+ FrameActionEvent signals the current controller being exchanged.
+ When the connection is successful then the flag
+ mbListeningToController is set to <TRUE/>.
+ */
+ void ConnectToController();
+
+ /** Disconnect from the current controller of the view shell as
+ listener. This method is called once during initialization and
+ every time a FrameActionEvent signals the current controller being
+ exchanged. When this method terminates then mbListeningToController
+ is <FALSE/>.
+ */
+ void DisconnectFromController();
+
+ virtual void Notify (
+ SfxBroadcaster& rBroadcaster,
+ const SfxHint& rHint) override;
+
+ //===== lang::XEventListener ============================================
+ virtual void SAL_CALL
+ disposing (const css::lang::EventObject& rEventObject) override;
+
+ //===== document::XEventListener ========================================
+ virtual void SAL_CALL
+ notifyEvent (
+ const css::document::EventObject& rEventObject) override;
+
+ //===== beans::XPropertySetListener =====================================
+ virtual void SAL_CALL
+ propertyChange (
+ const css::beans::PropertyChangeEvent& rEvent) override;
+
+ //===== accessibility::XAccessibleEventListener ==========================
+ virtual void SAL_CALL
+ notifyEvent (
+ const css::accessibility::AccessibleEventObject&
+ rEvent) override;
+
+ //===== frame::XFrameActionListener ======================================
+ /** For certain actions the listener connects to a new controller of the
+ frame it is listening to. This usually happens when the view shell
+ in the center pane is replaced by another view shell.
+ */
+ virtual void SAL_CALL
+ frameAction (const css::frame::FrameActionEvent& rEvent) override;
+
+ virtual void disposing(std::unique_lock<std::mutex>&) override;
+
+private:
+ SlideSorter& mrSlideSorter;
+ SlideSorterController& mrController;
+ ViewShellBase* mpBase;
+
+ /// Remember whether we are listening to the document.
+ bool mbListeningToDocument;
+ /// Remember whether we are listening to the UNO document.
+ bool mbListeningToUNODocument;
+ /// Remember whether we are listening to the UNO controller.
+ bool mbListeningToController;
+ /// Remember whether we are listening to the frame.
+ bool mbListeningToFrame;
+ bool mbIsMainViewChangePending;
+
+ css::uno::WeakReference< css::frame::XController> mxControllerWeak;
+ css::uno::WeakReference< css::frame::XFrame> mxFrameWeak;
+
+ /** This object is used to lock the model between some
+ events. It is references counted in order to cope with events that
+ are expected but never sent.
+ */
+ std::shared_ptr<SlideSorterController::ModelChangeLock> mpModelChangeLock;
+
+ void ReleaseListeners();
+
+ /** Called when the edit mode has changed. Update model accordingly.
+ */
+ void UpdateEditMode();
+
+ /** Handle a change in the order of slides or when the set of slides has
+ changed, i.e. a slide has been created.
+ */
+ void HandleModelChange (const SdrPage* pPage);
+
+ /** Handle a modification to a shape on the given page. When this is a
+ regular page then update its preview. When it is a master page then
+ additionally update the previews of all pages linked to it.
+ */
+ void HandleShapeModification (const SdrPage* pPage);
+
+ DECL_LINK(EventMultiplexerCallback, tools::EventMultiplexerEvent&, void);
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsPageSelector.cxx b/sd/source/ui/slidesorter/controller/SlsPageSelector.cxx
new file mode 100644
index 000000000..21affcf2f
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsPageSelector.cxx
@@ -0,0 +1,386 @@
+/* -*- 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 <controller/SlsPageSelector.hxx>
+
+#include <SlideSorter.hxx>
+#include <controller/SlideSorterController.hxx>
+#include <controller/SlsSelectionManager.hxx>
+#include <controller/SlsCurrentSlideManager.hxx>
+#include <controller/SlsVisibleAreaManager.hxx>
+#include <model/SlsPageDescriptor.hxx>
+#include <model/SlsPageEnumerationProvider.hxx>
+#include <model/SlideSorterModel.hxx>
+#include <view/SlideSorterView.hxx>
+#include <osl/diagnose.h>
+
+#include <sdpage.hxx>
+#include <tools/debug.hxx>
+#include <memory>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::sd::slidesorter::model;
+using namespace ::sd::slidesorter::view;
+
+namespace sd::slidesorter::controller {
+
+PageSelector::PageSelector (SlideSorter& rSlideSorter)
+ : mrModel(rSlideSorter.GetModel()),
+ mrSlideSorter(rSlideSorter),
+ mrController(mrSlideSorter.GetController()),
+ mnSelectedPageCount(0),
+ mnBroadcastDisableLevel(0),
+ mbSelectionChangeBroadcastPending(false),
+ mnUpdateLockCount(0),
+ mbIsUpdateCurrentPagePending(true)
+{
+ CountSelectedPages ();
+}
+
+void PageSelector::SelectAllPages()
+{
+ VisibleAreaManager::TemporaryDisabler aDisabler (mrSlideSorter);
+ PageSelector::UpdateLock aLock (*this);
+
+ int nPageCount = mrModel.GetPageCount();
+ for (int nPageIndex=0; nPageIndex<nPageCount; nPageIndex++)
+ SelectPage(nPageIndex);
+}
+
+void PageSelector::DeselectAllPages()
+{
+ VisibleAreaManager::TemporaryDisabler aDisabler (mrSlideSorter);
+ PageSelector::UpdateLock aLock (*this);
+
+ int nPageCount = mrModel.GetPageCount();
+ for (int nPageIndex=0; nPageIndex<nPageCount; nPageIndex++)
+ DeselectPage(nPageIndex);
+
+ DBG_ASSERT (mnSelectedPageCount==0,
+ "PageSelector::DeselectAllPages: the selected pages counter is not 0");
+ mnSelectedPageCount = 0;
+ mpSelectionAnchor.reset();
+}
+
+void PageSelector::GetCoreSelection()
+{
+ PageSelector::UpdateLock aLock (*this);
+
+ bool bSelectionHasChanged (true);
+ mnSelectedPageCount = 0;
+ model::PageEnumeration aAllPages (
+ model::PageEnumerationProvider::CreateAllPagesEnumeration(mrModel));
+ while (aAllPages.HasMoreElements())
+ {
+ model::SharedPageDescriptor pDescriptor (aAllPages.GetNextElement());
+ if (pDescriptor->GetCoreSelection())
+ {
+ mrSlideSorter.GetController().GetVisibleAreaManager().RequestVisible(pDescriptor);
+ mrSlideSorter.GetView().RequestRepaint(pDescriptor);
+ bSelectionHasChanged = true;
+ }
+
+ if (pDescriptor->HasState(PageDescriptor::ST_Selected))
+ mnSelectedPageCount++;
+ }
+
+ if (bSelectionHasChanged)
+ {
+ if (mnBroadcastDisableLevel > 0)
+ mbSelectionChangeBroadcastPending = true;
+ else
+ mrController.GetSelectionManager()->SelectionHasChanged();
+ }
+}
+
+void PageSelector::SetCoreSelection()
+{
+ model::PageEnumeration aAllPages (
+ model::PageEnumerationProvider::CreateAllPagesEnumeration(mrModel));
+ while (aAllPages.HasMoreElements())
+ {
+ model::SharedPageDescriptor pDescriptor (aAllPages.GetNextElement());
+ pDescriptor->SetCoreSelection();
+ }
+}
+
+void PageSelector::SelectPage (int nPageIndex)
+{
+ SharedPageDescriptor pDescriptor (mrModel.GetPageDescriptor(nPageIndex));
+ if (pDescriptor)
+ SelectPage(pDescriptor);
+}
+
+void PageSelector::SelectPage (const SdPage* pPage)
+{
+ const sal_Int32 nPageIndex (mrModel.GetIndex(pPage));
+ SharedPageDescriptor pDescriptor (mrModel.GetPageDescriptor(nPageIndex));
+ if (pDescriptor && pDescriptor->GetPage()==pPage)
+ SelectPage(pDescriptor);
+}
+
+void PageSelector::SelectPage (const SharedPageDescriptor& rpDescriptor)
+{
+ if (!rpDescriptor
+ || !mrSlideSorter.GetView().SetState(rpDescriptor, PageDescriptor::ST_Selected, true))
+ return;
+
+ ++mnSelectedPageCount;
+ mrSlideSorter.GetController().GetVisibleAreaManager().RequestVisible(rpDescriptor,true);
+ mrSlideSorter.GetView().RequestRepaint(rpDescriptor);
+
+ mpMostRecentlySelectedPage = rpDescriptor;
+ if (mpSelectionAnchor == nullptr)
+ mpSelectionAnchor = rpDescriptor;
+
+ if (mnBroadcastDisableLevel > 0)
+ mbSelectionChangeBroadcastPending = true;
+ else
+ mrController.GetSelectionManager()->SelectionHasChanged();
+ UpdateCurrentPage();
+
+ CheckConsistency();
+}
+
+void PageSelector::DeselectPage (int nPageIndex)
+{
+ model::SharedPageDescriptor pDescriptor (mrModel.GetPageDescriptor(nPageIndex));
+ if (pDescriptor)
+ DeselectPage(pDescriptor);
+}
+
+void PageSelector::DeselectPage (
+ const SharedPageDescriptor& rpDescriptor,
+ const bool bUpdateCurrentPage)
+{
+ if (!rpDescriptor
+ || !mrSlideSorter.GetView().SetState(rpDescriptor, PageDescriptor::ST_Selected, false))
+ return;
+
+ --mnSelectedPageCount;
+ mrSlideSorter.GetController().GetVisibleAreaManager().RequestVisible(rpDescriptor);
+ mrSlideSorter.GetView().RequestRepaint(rpDescriptor);
+ if (mpMostRecentlySelectedPage == rpDescriptor)
+ mpMostRecentlySelectedPage.reset();
+ if (mnBroadcastDisableLevel > 0)
+ mbSelectionChangeBroadcastPending = true;
+ else
+ mrController.GetSelectionManager()->SelectionHasChanged();
+ if (bUpdateCurrentPage)
+ UpdateCurrentPage();
+
+ CheckConsistency();
+}
+
+void PageSelector::CheckConsistency() const
+{
+ int nSelectionCount (0);
+ for (int nPageIndex=0,nPageCount=mrModel.GetPageCount(); nPageIndex<nPageCount; nPageIndex++)
+ {
+ SharedPageDescriptor pDescriptor (mrModel.GetPageDescriptor(nPageIndex));
+ assert(pDescriptor);
+ if (pDescriptor->HasState(PageDescriptor::ST_Selected))
+ ++nSelectionCount;
+ }
+ if (nSelectionCount!=mnSelectedPageCount)
+ {
+ // #i120020# The former call to assert(..) internally calls
+ // SlideSorterModel::GetPageDescriptor which will crash in this situation
+ // (only in non-pro code). All what is wanted there is to assert it (the
+ // error is already detected), so do this directly.
+ OSL_ENSURE(false, "PageSelector: Consistency error (!)");
+ }
+}
+
+bool PageSelector::IsPageSelected(int nPageIndex)
+{
+ SharedPageDescriptor pDescriptor (mrModel.GetPageDescriptor(nPageIndex));
+ if (pDescriptor)
+ return pDescriptor->HasState(PageDescriptor::ST_Selected);
+ else
+ return false;
+}
+
+bool PageSelector::IsPageVisible(int nPageIndex)
+{
+ SharedPageDescriptor pDescriptor (mrModel.GetPageDescriptor(nPageIndex));
+ if (pDescriptor)
+ return pDescriptor->HasState(PageDescriptor::ST_Visible);
+ else
+ return false;
+}
+
+int PageSelector::GetPageCount() const
+{
+ return mrModel.GetPageCount();
+}
+
+void PageSelector::CountSelectedPages()
+{
+ mnSelectedPageCount = 0;
+ model::PageEnumeration aSelectedPages (
+ model::PageEnumerationProvider::CreateSelectedPagesEnumeration(mrModel));
+ while (aSelectedPages.HasMoreElements())
+ {
+ mnSelectedPageCount++;
+ aSelectedPages.GetNextElement();
+ }
+}
+
+void PageSelector::EnableBroadcasting()
+{
+ if (mnBroadcastDisableLevel > 0)
+ mnBroadcastDisableLevel --;
+ if (mnBroadcastDisableLevel==0 && mbSelectionChangeBroadcastPending)
+ {
+ mrController.GetSelectionManager()->SelectionHasChanged();
+ mbSelectionChangeBroadcastPending = false;
+ }
+}
+
+void PageSelector::DisableBroadcasting()
+{
+ mnBroadcastDisableLevel ++;
+}
+
+std::shared_ptr<PageSelector::PageSelection> PageSelector::GetPageSelection() const
+{
+ auto pSelection = std::make_shared<PageSelection>();
+ pSelection->reserve(GetSelectedPageCount());
+
+ int nPageCount = GetPageCount();
+ for (int nIndex=0; nIndex<nPageCount; nIndex++)
+ {
+ SharedPageDescriptor pDescriptor (mrModel.GetPageDescriptor(nIndex));
+ if (pDescriptor && pDescriptor->HasState(PageDescriptor::ST_Selected))
+ pSelection->push_back(pDescriptor->GetPage());
+ }
+
+ return pSelection;
+}
+
+void PageSelector::SetPageSelection (
+ const std::shared_ptr<PageSelection>& rpSelection,
+ const bool bUpdateCurrentPage)
+{
+ for (const auto& rpPage : *rpSelection)
+ SelectPage(rpPage);
+ if (bUpdateCurrentPage)
+ UpdateCurrentPage();
+}
+
+void PageSelector::UpdateCurrentPage (const bool bUpdateOnlyWhenPending)
+{
+ if (mnUpdateLockCount > 0)
+ {
+ mbIsUpdateCurrentPagePending = true;
+ return;
+ }
+
+ if ( ! mbIsUpdateCurrentPagePending && bUpdateOnlyWhenPending)
+ return;
+
+ mbIsUpdateCurrentPagePending = false;
+
+ // Make the first selected page the current page.
+ SharedPageDescriptor pCurrentPageDescriptor;
+ const sal_Int32 nPageCount (GetPageCount());
+ for (sal_Int32 nIndex=0; nIndex<nPageCount; ++nIndex)
+ {
+ SharedPageDescriptor pDescriptor (mrModel.GetPageDescriptor(nIndex));
+ if ( ! pDescriptor)
+ continue;
+ if (pDescriptor->HasState(PageDescriptor::ST_Selected))
+ {
+ pCurrentPageDescriptor = pDescriptor;
+ break;
+ }
+ }
+
+ if (!pCurrentPageDescriptor)
+ return;
+
+ // Switching the current slide normally sets also the
+ // selection to just the new current slide. To prevent that,
+ // we store (and at the end of this scope restore) the current
+ // selection.
+ std::shared_ptr<PageSelection> pSelection (GetPageSelection());
+
+ mrController.GetCurrentSlideManager()->SwitchCurrentSlide(pCurrentPageDescriptor);
+
+ // Restore the selection and prevent a recursive call to
+ // UpdateCurrentPage().
+ SetPageSelection(pSelection, false);
+}
+
+//===== PageSelector::UpdateLock ==============================================
+
+PageSelector::UpdateLock::UpdateLock (SlideSorter const & rSlideSorter)
+ : mpSelector(&rSlideSorter.GetController().GetPageSelector())
+{
+ ++mpSelector->mnUpdateLockCount;
+}
+
+PageSelector::UpdateLock::UpdateLock (PageSelector& rSelector)
+ : mpSelector(&rSelector)
+{
+ ++mpSelector->mnUpdateLockCount;
+}
+
+PageSelector::UpdateLock::~UpdateLock()
+{
+ Release();
+}
+
+void PageSelector::UpdateLock::Release()
+{
+ if (mpSelector != nullptr)
+ {
+ --mpSelector->mnUpdateLockCount;
+ OSL_ASSERT(mpSelector->mnUpdateLockCount >= 0);
+ if (mpSelector->mnUpdateLockCount == 0)
+ mpSelector->UpdateCurrentPage(true);
+
+ mpSelector = nullptr;
+ }
+}
+
+//===== PageSelector::BroadcastLock ==============================================
+
+PageSelector::BroadcastLock::BroadcastLock (SlideSorter const & rSlideSorter)
+ : mrSelector(rSlideSorter.GetController().GetPageSelector())
+{
+ mrSelector.DisableBroadcasting();
+}
+
+PageSelector::BroadcastLock::BroadcastLock (PageSelector& rSelector)
+ : mrSelector(rSelector)
+{
+ mrSelector.DisableBroadcasting();
+}
+
+PageSelector::BroadcastLock::~BroadcastLock()
+{
+ mrSelector.EnableBroadcasting();
+}
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsProperties.cxx b/sd/source/ui/slidesorter/controller/SlsProperties.cxx
new file mode 100644
index 000000000..f1152a373
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsProperties.cxx
@@ -0,0 +1,106 @@
+/* -*- 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 <controller/SlsProperties.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+
+namespace sd::slidesorter::controller {
+
+Properties::Properties()
+ : mbIsHighlightCurrentSlide(false),
+ mbIsShowSelection(true),
+ mbIsShowFocus(true),
+ mbIsCenterSelection(false),
+ mbIsSmoothSelectionScrolling(true),
+ mbIsSuspendPreviewUpdatesDuringFullScreenPresentation(true),
+ maBackgroundColor(Application::GetSettings().GetStyleSettings().GetWindowColor()),
+ maTextColor(Application::GetSettings().GetStyleSettings().GetActiveTextColor()),
+ maSelectionColor(Application::GetSettings().GetStyleSettings().GetHighlightColor()),
+ maHighlightColor(Application::GetSettings().GetStyleSettings().GetMenuHighlightColor()),
+ mbIsUIReadOnly(false)
+{
+}
+
+void Properties::HandleDataChangeEvent()
+{
+ maBackgroundColor = Application::GetSettings().GetStyleSettings().GetWindowColor();
+ maTextColor = Application::GetSettings().GetStyleSettings().GetActiveTextColor();
+ maSelectionColor = Application::GetSettings().GetStyleSettings().GetHighlightColor();
+ maHighlightColor = Application::GetSettings().GetStyleSettings().GetMenuHighlightColor();
+}
+
+void Properties::SetHighlightCurrentSlide (const bool bIsHighlightCurrentSlide)
+{
+ mbIsHighlightCurrentSlide = bIsHighlightCurrentSlide;
+}
+
+void Properties::SetShowSelection (const bool bIsShowSelection)
+{
+ mbIsShowSelection = bIsShowSelection;
+}
+
+void Properties::SetShowFocus (const bool bIsShowFocus)
+{
+ mbIsShowFocus = bIsShowFocus;
+}
+
+void Properties::SetCenterSelection (const bool bIsCenterSelection)
+{
+ mbIsCenterSelection = bIsCenterSelection;
+}
+
+void Properties::SetSmoothSelectionScrolling (const bool bIsSmoothSelectionScrolling)
+{
+ mbIsSmoothSelectionScrolling = bIsSmoothSelectionScrolling;
+}
+
+void Properties::SetSuspendPreviewUpdatesDuringFullScreenPresentation (const bool bFlag)
+{
+ mbIsSuspendPreviewUpdatesDuringFullScreenPresentation = bFlag;
+}
+
+void Properties::SetBackgroundColor (const Color& rColor)
+{
+ maBackgroundColor = rColor;
+}
+
+void Properties::SetTextColor (const Color& rColor)
+{
+ maTextColor = rColor;
+}
+
+void Properties::SetSelectionColor (const Color& rColor)
+{
+ maSelectionColor = rColor;
+}
+
+void Properties::SetHighlightColor (const Color& rColor)
+{
+ maHighlightColor = rColor;
+}
+
+void Properties::SetUIReadOnly (const bool bIsUIReadOnly)
+{
+ mbIsUIReadOnly = bIsUIReadOnly;
+}
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsScrollBarManager.cxx b/sd/source/ui/slidesorter/controller/SlsScrollBarManager.cxx
new file mode 100644
index 000000000..83192414f
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsScrollBarManager.cxx
@@ -0,0 +1,608 @@
+/* -*- 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 <controller/SlsScrollBarManager.hxx>
+
+#include <SlideSorter.hxx>
+#include <ViewShell.hxx>
+#include <controller/SlideSorterController.hxx>
+#include <controller/SlsVisibleAreaManager.hxx>
+#include <model/SlideSorterModel.hxx>
+#include <model/SlsPageDescriptor.hxx>
+#include <view/SlideSorterView.hxx>
+#include <view/SlsLayouter.hxx>
+#include <Window.hxx>
+#include <sdpage.hxx>
+#include <osl/diagnose.h>
+
+#include <vcl/scrbar.hxx>
+
+namespace sd::slidesorter::controller {
+
+constexpr double gnHorizontalScrollFactor(0.15);
+constexpr double gnVerticalScrollFactor(0.25);
+
+ScrollBarManager::ScrollBarManager (SlideSorter& rSlideSorter)
+ : mrSlideSorter(rSlideSorter),
+ mpHorizontalScrollBar(mrSlideSorter.GetHorizontalScrollBar()),
+ mpVerticalScrollBar(mrSlideSorter.GetVerticalScrollBar()),
+ mnHorizontalPosition (0),
+ mnVerticalPosition (0),
+ maScrollBorder (20,20),
+ mpScrollBarFiller(mrSlideSorter.GetScrollBarFiller()),
+ maAutoScrollTimer("sd ScrollBarManager maAutoScrollTimer"),
+ maAutoScrollOffset(0,0),
+ mbIsAutoScrollActive(false),
+ mpContentWindow(mrSlideSorter.GetContentWindow())
+{
+ // Hide the scroll bars by default to prevent display errors while
+ // switching between view shells: In the short time between initiating
+ // such a switch and the final rearrangement of UI controls the scroll
+ // bars and the filler where displayed in the upper left corner of the
+ // ViewTabBar.
+ mpHorizontalScrollBar->Hide();
+ mpVerticalScrollBar->Hide();
+ mpScrollBarFiller->Hide();
+
+ maAutoScrollTimer.SetTimeout(25);
+ maAutoScrollTimer.SetInvokeHandler (
+ LINK(this, ScrollBarManager, AutoScrollTimeoutHandler));
+}
+
+ScrollBarManager::~ScrollBarManager()
+{
+}
+
+void ScrollBarManager::Connect()
+{
+ if (mpVerticalScrollBar != nullptr)
+ {
+ mpVerticalScrollBar->SetScrollHdl (
+ LINK(this, ScrollBarManager, VerticalScrollBarHandler));
+ }
+ if (mpHorizontalScrollBar != nullptr)
+ {
+ mpHorizontalScrollBar->SetScrollHdl(
+ LINK(this, ScrollBarManager, HorizontalScrollBarHandler));
+ }
+}
+
+void ScrollBarManager::Disconnect()
+{
+ if (mpVerticalScrollBar != nullptr)
+ {
+ mpVerticalScrollBar->SetScrollHdl( Link<ScrollBar*,void>() );
+ }
+ if (mpHorizontalScrollBar != nullptr)
+ {
+ mpHorizontalScrollBar->SetScrollHdl( Link<ScrollBar*,void>() );
+ }
+}
+
+/** Placing the scroll bars is an iterative process. The visibility of one
+ scroll bar affects the remaining size and thus may lead to the other
+ scroll bar becoming visible.
+
+ First we determine the visibility of the horizontal scroll bar. After
+ that we do the same for the vertical scroll bar. To have an initial
+ value for the required size we call the layouter before that. When one
+ of the two scroll bars is made visible then the size of the browser
+ window changes and a second call to the layouter becomes necessary.
+ That call is made anyway after this method returns.
+*/
+::tools::Rectangle ScrollBarManager::PlaceScrollBars (
+ const ::tools::Rectangle& rAvailableArea,
+ const bool bIsHorizontalScrollBarAllowed,
+ const bool bIsVerticalScrollBarAllowed)
+{
+ ::tools::Rectangle aRemainingSpace (DetermineScrollBarVisibilities(
+ rAvailableArea,
+ bIsHorizontalScrollBarAllowed,
+ bIsVerticalScrollBarAllowed));
+
+ if (mpHorizontalScrollBar!=nullptr && mpHorizontalScrollBar->IsVisible())
+ PlaceHorizontalScrollBar (rAvailableArea);
+
+ if (mpVerticalScrollBar!=nullptr && mpVerticalScrollBar->IsVisible())
+ PlaceVerticalScrollBar (rAvailableArea);
+
+ if (mpScrollBarFiller!=nullptr && mpScrollBarFiller->IsVisible())
+ PlaceFiller (rAvailableArea);
+
+ return aRemainingSpace;
+}
+
+void ScrollBarManager::PlaceHorizontalScrollBar (const ::tools::Rectangle& aAvailableArea)
+{
+ // Save the current relative position.
+ mnHorizontalPosition = double(mpHorizontalScrollBar->GetThumbPos())
+ / double(mpHorizontalScrollBar->GetRange().Len());
+
+ // Place the scroll bar.
+ Size aScrollBarSize (mpHorizontalScrollBar->GetSizePixel());
+ mpHorizontalScrollBar->SetPosSizePixel (
+ Point(aAvailableArea.Left(),
+ aAvailableArea.Bottom()-aScrollBarSize.Height()+1),
+ Size (aAvailableArea.GetWidth() - GetVerticalScrollBarWidth(),
+ aScrollBarSize.Height()));
+
+ // Restore the relative position.
+ mpHorizontalScrollBar->SetThumbPos(
+ static_cast<::tools::Long>(0.5 + mnHorizontalPosition * mpHorizontalScrollBar->GetRange().Len()));
+}
+
+void ScrollBarManager::PlaceVerticalScrollBar (const ::tools::Rectangle& aArea)
+{
+ const sal_Int32 nThumbPosition (mpVerticalScrollBar->GetThumbPos());
+
+ // Place the scroll bar.
+ Size aScrollBarSize (mpVerticalScrollBar->GetSizePixel());
+ Point aPosition (aArea.Right()-aScrollBarSize.Width()+1, aArea.Top());
+ Size aSize (aScrollBarSize.Width(), aArea.GetHeight() - GetHorizontalScrollBarHeight());
+ mpVerticalScrollBar->SetPosSizePixel(aPosition, aSize);
+
+ // Restore the position.
+ mpVerticalScrollBar->SetThumbPos(static_cast<::tools::Long>(nThumbPosition));
+ mnVerticalPosition = nThumbPosition / double(mpVerticalScrollBar->GetRange().Len());
+}
+
+void ScrollBarManager::PlaceFiller (const ::tools::Rectangle& aArea)
+{
+ mpScrollBarFiller->SetPosSizePixel(
+ Point(
+ aArea.Right()-mpVerticalScrollBar->GetSizePixel().Width()+1,
+ aArea.Bottom()-mpHorizontalScrollBar->GetSizePixel().Height()+1),
+ Size (
+ mpVerticalScrollBar->GetSizePixel().Width(),
+ mpHorizontalScrollBar->GetSizePixel().Height()));
+}
+
+void ScrollBarManager::UpdateScrollBars(bool bUseScrolling)
+{
+ ::tools::Rectangle aModelArea (mrSlideSorter.GetView().GetModelArea());
+ sd::Window *pWindow (mrSlideSorter.GetContentWindow().get());
+ Size aWindowModelSize (pWindow->PixelToLogic(pWindow->GetSizePixel()));
+
+ // The horizontal scroll bar is only shown when the window is
+ // horizontally smaller than the view.
+ if (mpHorizontalScrollBar != nullptr && mpHorizontalScrollBar->IsVisible())
+ {
+ mpHorizontalScrollBar->Show();
+ mpHorizontalScrollBar->SetRange (
+ Range(aModelArea.Left(), aModelArea.Right()));
+ mnHorizontalPosition =
+ double(mpHorizontalScrollBar->GetThumbPos())
+ / double(mpHorizontalScrollBar->GetRange().Len());
+
+ mpHorizontalScrollBar->SetVisibleSize (aWindowModelSize.Width());
+
+ const ::tools::Long nWidth (mpContentWindow->PixelToLogic(
+ mpContentWindow->GetSizePixel()).Width());
+ // Make the line size about 10% of the visible width.
+ mpHorizontalScrollBar->SetLineSize (nWidth / 10);
+ // Make the page size about 90% of the visible width.
+ mpHorizontalScrollBar->SetPageSize ((nWidth * 9) / 10);
+ }
+ else
+ {
+ mnHorizontalPosition = 0;
+ }
+
+ // The vertical scroll bar is always shown.
+ if (mpVerticalScrollBar != nullptr && mpVerticalScrollBar->IsVisible())
+ {
+ mpVerticalScrollBar->SetRange (
+ Range(aModelArea.Top(), aModelArea.Bottom()));
+ mnVerticalPosition =
+ double(mpVerticalScrollBar->GetThumbPos())
+ / double(mpVerticalScrollBar->GetRange().Len());
+
+ mpVerticalScrollBar->SetVisibleSize (aWindowModelSize.Height());
+
+ const ::tools::Long nHeight (mpContentWindow->PixelToLogic(
+ mpContentWindow->GetSizePixel()).Height());
+ // Make the line size about 10% of the visible height.
+ mpVerticalScrollBar->SetLineSize (nHeight / 10);
+ // Make the page size about 90% of the visible height.
+ mpVerticalScrollBar->SetPageSize ((nHeight * 9) / 10);
+ }
+ else
+ {
+ mnVerticalPosition = 0;
+ }
+
+ double nEps (::std::numeric_limits<double>::epsilon());
+ if (fabs(mnHorizontalPosition-pWindow->GetVisibleX()) > nEps
+ || fabs(mnVerticalPosition-pWindow->GetVisibleY()) > nEps)
+ {
+ mrSlideSorter.GetView().InvalidatePageObjectVisibilities();
+ if (bUseScrolling)
+ pWindow->SetVisibleXY(mnHorizontalPosition, mnVerticalPosition);
+ else
+ SetWindowOrigin(mnHorizontalPosition, mnVerticalPosition);
+ }
+}
+
+IMPL_LINK(ScrollBarManager, VerticalScrollBarHandler, ScrollBar*, pScrollBar, void)
+{
+ if (pScrollBar!=nullptr
+ && pScrollBar==mpVerticalScrollBar.get()
+ && pScrollBar->IsVisible()
+ && mrSlideSorter.GetContentWindow())
+ {
+ double nRelativePosition = double(pScrollBar->GetThumbPos())
+ / double(pScrollBar->GetRange().Len());
+ mrSlideSorter.GetView().InvalidatePageObjectVisibilities();
+ mrSlideSorter.GetContentWindow()->SetVisibleXY(-1, nRelativePosition);
+ mrSlideSorter.GetController().GetVisibleAreaManager().DeactivateCurrentSlideTracking();
+ }
+}
+
+IMPL_LINK(ScrollBarManager, HorizontalScrollBarHandler, ScrollBar*, pScrollBar, void)
+{
+ if (pScrollBar!=nullptr
+ && pScrollBar==mpHorizontalScrollBar.get()
+ && pScrollBar->IsVisible()
+ && mrSlideSorter.GetContentWindow())
+ {
+ double nRelativePosition = double(pScrollBar->GetThumbPos())
+ / double(pScrollBar->GetRange().Len());
+ mrSlideSorter.GetView().InvalidatePageObjectVisibilities();
+ mrSlideSorter.GetContentWindow()->SetVisibleXY(nRelativePosition, -1);
+ mrSlideSorter.GetController().GetVisibleAreaManager().DeactivateCurrentSlideTracking();
+ }
+}
+
+void ScrollBarManager::SetWindowOrigin (
+ double nHorizontalPosition,
+ double nVerticalPosition)
+{
+ mnHorizontalPosition = nHorizontalPosition;
+ mnVerticalPosition = nVerticalPosition;
+
+ sd::Window *pWindow (mrSlideSorter.GetContentWindow().get());
+ Size aViewSize (pWindow->GetViewSize());
+ Point aOrigin (
+ static_cast<::tools::Long>(mnHorizontalPosition * aViewSize.Width()),
+ static_cast<::tools::Long>(mnVerticalPosition * aViewSize.Height()));
+
+ pWindow->SetWinViewPos (aOrigin);
+ pWindow->UpdateMapMode ();
+ pWindow->Invalidate ();
+}
+
+/** Determining the visibility of the scroll bars is quite complicated. The
+ visibility of one influences that of the other because showing a scroll
+ bar makes the available space smaller and may lead to the need of
+ displaying the other.
+ To solve this we test all four combinations of showing or hiding each
+ scroll bar and use the best one. The best one is that combination that
+ a) shows the least number of scroll bars with preference of showing the
+ vertical over showing the horizontal and
+ b) when not showing a scroll bar the area used by the page objects fits
+ into the available area in the scroll bars orientation.
+*/
+::tools::Rectangle ScrollBarManager::DetermineScrollBarVisibilities (
+ const ::tools::Rectangle& rAvailableArea,
+ const bool bIsHorizontalScrollBarAllowed,
+ const bool bIsVerticalScrollBarAllowed)
+{
+ // Test which combination of scroll bars is the best.
+ bool bShowHorizontal = false;
+ bool bShowVertical = false;
+ if (mrSlideSorter.GetModel().GetPageCount() == 0)
+ {
+ // No pages => no scroll bars.
+ }
+ else if (TestScrollBarVisibilities(false, false, rAvailableArea))
+ {
+ // Nothing to be done.
+ }
+ else if (bIsHorizontalScrollBarAllowed
+ && TestScrollBarVisibilities(true, false, rAvailableArea))
+ {
+ bShowHorizontal = true;
+ }
+ else if (bIsVerticalScrollBarAllowed
+ && TestScrollBarVisibilities(false, true, rAvailableArea))
+ {
+ bShowVertical = true;
+ }
+ else
+ {
+ bShowHorizontal = true;
+ bShowVertical = true;
+ }
+
+ // Make the visibility of the scroll bars permanent.
+ mpVerticalScrollBar->Show(bShowVertical);
+ mpHorizontalScrollBar->Show(bShowHorizontal);
+ mpScrollBarFiller->Show(bShowVertical && bShowHorizontal);
+
+ // Adapt the remaining space accordingly.
+ ::tools::Rectangle aRemainingSpace (rAvailableArea);
+ if (bShowVertical)
+ aRemainingSpace.AdjustRight( -(mpVerticalScrollBar->GetSizePixel().Width()) );
+ if (bShowHorizontal)
+ aRemainingSpace.AdjustBottom( -(mpHorizontalScrollBar->GetSizePixel().Height()) );
+
+ return aRemainingSpace;
+}
+
+bool ScrollBarManager::TestScrollBarVisibilities (
+ bool bHorizontalScrollBarVisible,
+ bool bVerticalScrollBarVisible,
+ const ::tools::Rectangle& rAvailableArea)
+{
+ model::SlideSorterModel& rModel (mrSlideSorter.GetModel());
+
+ // Adapt the available size by subtracting the sizes of the scroll bars
+ // visible in this combination.
+ Size aBrowserSize (rAvailableArea.GetSize());
+ if (bHorizontalScrollBarVisible)
+ aBrowserSize.AdjustHeight( -(mpHorizontalScrollBar->GetSizePixel().Height()) );
+ if (bVerticalScrollBarVisible)
+ aBrowserSize.AdjustWidth( -(mpVerticalScrollBar->GetSizePixel().Width()) );
+
+ // Tell the view to rearrange its page objects and check whether the
+ // page objects can be shown without clipping.
+ bool bRearrangeSuccess (mrSlideSorter.GetView().GetLayouter().Rearrange (
+ mrSlideSorter.GetView().GetOrientation(),
+ aBrowserSize,
+ rModel.GetPageDescriptor(0)->GetPage()->GetSize(),
+ rModel.GetPageCount()));
+
+ if (bRearrangeSuccess)
+ {
+ Size aPageSize = mrSlideSorter.GetView().GetLayouter().GetTotalBoundingBox().GetSize();
+ Size aWindowModelSize = mpContentWindow->PixelToLogic(aBrowserSize);
+
+ // The content may be clipped, i.e. not fully visible, in one
+ // direction only when the scroll bar is visible in that direction.
+ if (aPageSize.Width() > aWindowModelSize.Width())
+ if ( ! bHorizontalScrollBarVisible)
+ return false;
+ if (aPageSize.Height() > aWindowModelSize.Height())
+ if ( ! bVerticalScrollBarVisible)
+ return false;
+
+ return true;
+ }
+ else
+ return false;
+}
+
+void ScrollBarManager::SetTopLeft(const Point& rNewTopLeft)
+{
+ if (( ! mpVerticalScrollBar
+ || mpVerticalScrollBar->GetThumbPos() == rNewTopLeft.Y())
+ && ( ! mpHorizontalScrollBar
+ || mpHorizontalScrollBar->GetThumbPos() == rNewTopLeft.X()))
+ return;
+
+ // Flush pending repaints before scrolling to avoid temporary artifacts.
+ mrSlideSorter.GetContentWindow()->PaintImmediately();
+
+ if (mpVerticalScrollBar)
+ {
+ mpVerticalScrollBar->SetThumbPos(rNewTopLeft.Y());
+ mnVerticalPosition = rNewTopLeft.Y() / double(mpVerticalScrollBar->GetRange().Len());
+ }
+ if (mpHorizontalScrollBar)
+ {
+ mpHorizontalScrollBar->SetThumbPos(rNewTopLeft.X());
+ mnHorizontalPosition = rNewTopLeft.X() / double(mpHorizontalScrollBar->GetRange().Len());
+ }
+
+ mrSlideSorter.GetContentWindow()->SetVisibleXY(mnHorizontalPosition, mnVerticalPosition);
+ mrSlideSorter.GetView().InvalidatePageObjectVisibilities();
+}
+
+int ScrollBarManager::GetVerticalScrollBarWidth() const
+{
+ if (mpVerticalScrollBar != nullptr && mpVerticalScrollBar->IsVisible())
+ return mpVerticalScrollBar->GetSizePixel().Width();
+ else
+ return 0;
+}
+
+int ScrollBarManager::GetHorizontalScrollBarHeight() const
+{
+ if (mpHorizontalScrollBar != nullptr && mpHorizontalScrollBar->IsVisible())
+ return mpHorizontalScrollBar->GetSizePixel().Height();
+ else
+ return 0;
+}
+
+void ScrollBarManager::CalcAutoScrollOffset (const Point& rMouseWindowPosition)
+{
+ sd::Window *pWindow (mrSlideSorter.GetContentWindow().get());
+
+ int nDx = 0;
+ int nDy = 0;
+
+ Size aWindowSize = pWindow->GetOutputSizePixel();
+ ::tools::Rectangle aWindowArea (pWindow->GetPosPixel(), aWindowSize);
+ ::tools::Rectangle aViewPixelArea (
+ pWindow->LogicToPixel(mrSlideSorter.GetView().GetModelArea()));
+
+ if (aWindowSize.Width() > maScrollBorder.Width() * 3
+ && mpHorizontalScrollBar != nullptr
+ && mpHorizontalScrollBar->IsVisible())
+ {
+ if (rMouseWindowPosition.X() < maScrollBorder.Width()
+ && aWindowArea.Left() > aViewPixelArea.Left())
+ {
+ nDx = -1 + static_cast<int>(gnHorizontalScrollFactor
+ * (rMouseWindowPosition.X() - maScrollBorder.Width()));
+ }
+
+ if (rMouseWindowPosition.X() >= (aWindowSize.Width() - maScrollBorder.Width())
+ && aWindowArea.Right() < aViewPixelArea.Right())
+ {
+ nDx = 1 + static_cast<int>(gnHorizontalScrollFactor
+ * (rMouseWindowPosition.X() - aWindowSize.Width()
+ + maScrollBorder.Width()));
+ }
+ }
+
+ if (aWindowSize.Height() > maScrollBorder.Height() * 3
+ && aWindowSize.Height() < aViewPixelArea.GetHeight())
+ {
+ if (rMouseWindowPosition.Y() < maScrollBorder.Height()
+ && aWindowArea.Top() > aViewPixelArea.Top())
+ {
+ nDy = -1 + static_cast<int>(gnVerticalScrollFactor
+ * (rMouseWindowPosition.Y() - maScrollBorder.Height()));
+ }
+
+ if (rMouseWindowPosition.Y() >= (aWindowSize.Height() - maScrollBorder.Height())
+ && aWindowArea.Bottom() < aViewPixelArea.Bottom())
+ {
+ nDy = 1 + static_cast<int>(gnVerticalScrollFactor
+ * (rMouseWindowPosition.Y() - aWindowSize.Height()
+ + maScrollBorder.Height()));
+ }
+ }
+
+ maAutoScrollOffset = Size(nDx,nDy);
+}
+
+bool ScrollBarManager::AutoScroll (
+ const Point& rMouseWindowPosition,
+ const ::std::function<void ()>& rAutoScrollFunctor)
+{
+ maAutoScrollFunctor = rAutoScrollFunctor;
+ CalcAutoScrollOffset(rMouseWindowPosition);
+ bool bResult (true);
+ if ( ! mbIsAutoScrollActive)
+ bResult = RepeatAutoScroll();
+
+ return bResult;
+}
+
+void ScrollBarManager::StopAutoScroll()
+{
+ maAutoScrollTimer.Stop();
+ mbIsAutoScrollActive = false;
+}
+
+bool ScrollBarManager::RepeatAutoScroll()
+{
+ if (maAutoScrollOffset != Size(0,0))
+ {
+ if (mrSlideSorter.GetViewShell() != nullptr)
+ {
+ mrSlideSorter.GetViewShell()->Scroll(
+ maAutoScrollOffset.Width(),
+ maAutoScrollOffset.Height());
+ mrSlideSorter.GetView().InvalidatePageObjectVisibilities();
+
+ if (maAutoScrollFunctor)
+ maAutoScrollFunctor();
+
+ mbIsAutoScrollActive = true;
+ maAutoScrollTimer.Start();
+
+ return true;
+ }
+ }
+
+ clearAutoScrollFunctor();
+ mbIsAutoScrollActive = false;
+ return false;
+}
+
+void ScrollBarManager::clearAutoScrollFunctor()
+{
+ maAutoScrollFunctor = ::std::function<void ()>();
+}
+
+IMPL_LINK_NOARG(ScrollBarManager, AutoScrollTimeoutHandler, Timer *, void)
+{
+ RepeatAutoScroll();
+}
+
+void ScrollBarManager::Scroll(
+ const Orientation eOrientation,
+ const sal_Int32 nDistance)
+{
+ bool bIsVertical (false);
+ switch (eOrientation)
+ {
+ case Orientation_Horizontal: bIsVertical = false; break;
+ case Orientation_Vertical: bIsVertical = true; break;
+ default:
+ OSL_ASSERT(eOrientation==Orientation_Horizontal || eOrientation==Orientation_Vertical);
+ return;
+ }
+
+ Point aNewTopLeft (
+ mpHorizontalScrollBar ? mpHorizontalScrollBar->GetThumbPos() : 0,
+ mpVerticalScrollBar ? mpVerticalScrollBar->GetThumbPos() : 0);
+
+ view::Layouter& rLayouter (mrSlideSorter.GetView().GetLayouter());
+
+ // Calculate estimate of new location.
+ if (bIsVertical)
+ aNewTopLeft.AdjustY(nDistance * rLayouter.GetPageObjectSize().Height() );
+ else
+ aNewTopLeft.AdjustX(nDistance * rLayouter.GetPageObjectSize().Width() );
+
+ // Adapt location to show whole slides.
+ if (bIsVertical)
+ if (nDistance > 0)
+ {
+ const sal_Int32 nIndex (rLayouter.GetIndexAtPoint(
+ Point(aNewTopLeft.X(), aNewTopLeft.Y()+mpVerticalScrollBar->GetVisibleSize()),
+ true));
+ aNewTopLeft.setY( rLayouter.GetPageObjectBox(nIndex,true).Bottom()
+ - mpVerticalScrollBar->GetVisibleSize() );
+ }
+ else
+ {
+ const sal_Int32 nIndex (rLayouter.GetIndexAtPoint(
+ Point(aNewTopLeft.X(), aNewTopLeft.Y()),
+ true));
+ aNewTopLeft.setY( rLayouter.GetPageObjectBox(nIndex,true).Top() );
+ }
+ else
+ if (nDistance > 0)
+ {
+ const sal_Int32 nIndex (rLayouter.GetIndexAtPoint(
+ Point(aNewTopLeft.X()+mpVerticalScrollBar->GetVisibleSize(), aNewTopLeft.Y()),
+ true));
+ aNewTopLeft.setX( rLayouter.GetPageObjectBox(nIndex,true).Right()
+ - mpVerticalScrollBar->GetVisibleSize() );
+ }
+ else
+ {
+ const sal_Int32 nIndex (rLayouter.GetIndexAtPoint(
+ Point(aNewTopLeft.X(), aNewTopLeft.Y()),
+ true));
+ aNewTopLeft.setX( rLayouter.GetPageObjectBox(nIndex,true).Left() );
+ }
+
+ mrSlideSorter.GetController().GetVisibleAreaManager().DeactivateCurrentSlideTracking();
+ SetTopLeft(aNewTopLeft);
+}
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsSelectionFunction.cxx b/sd/source/ui/slidesorter/controller/SlsSelectionFunction.cxx
new file mode 100644
index 000000000..c710a4c1b
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsSelectionFunction.cxx
@@ -0,0 +1,1485 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <sal/config.h>
+
+#include <controller/SlsSelectionFunction.hxx>
+
+#include <SlideSorter.hxx>
+#include <SlideSorterViewShell.hxx>
+#include "SlsDragAndDropContext.hxx"
+#include <controller/SlideSorterController.hxx>
+#include <controller/SlsPageSelector.hxx>
+#include <controller/SlsFocusManager.hxx>
+#include <controller/SlsScrollBarManager.hxx>
+#include <controller/SlsClipboard.hxx>
+#include <controller/SlsCurrentSlideManager.hxx>
+#include <controller/SlsInsertionIndicatorHandler.hxx>
+#include <controller/SlsSelectionManager.hxx>
+#include <controller/SlsProperties.hxx>
+#include <controller/SlsVisibleAreaManager.hxx>
+#include <model/SlideSorterModel.hxx>
+#include <model/SlsPageDescriptor.hxx>
+#include <model/SlsPageEnumerationProvider.hxx>
+#include <view/SlideSorterView.hxx>
+#include <view/SlsLayouter.hxx>
+#include <framework/FrameworkHelper.hxx>
+#include <osl/diagnose.h>
+#include <Window.hxx>
+#include <sdpage.hxx>
+#include <drawdoc.hxx>
+#include <sdxfer.hxx>
+#include <ViewShell.hxx>
+#include <FrameView.hxx>
+#include <app.hrc>
+#include <o3tl/deleter.hxx>
+#include <sfx2/dispatch.hxx>
+#include <vcl/ptrstyle.hxx>
+#include <optional>
+#include <sdmod.hxx>
+
+namespace {
+const sal_uInt32 SINGLE_CLICK (0x00000001);
+const sal_uInt32 DOUBLE_CLICK (0x00000002);
+const sal_uInt32 LEFT_BUTTON (0x00000010);
+const sal_uInt32 RIGHT_BUTTON (0x00000020);
+const sal_uInt32 MIDDLE_BUTTON (0x00000040);
+const sal_uInt32 BUTTON_DOWN (0x00000100);
+const sal_uInt32 BUTTON_UP (0x00000200);
+const sal_uInt32 MOUSE_MOTION (0x00000400);
+const sal_uInt32 MOUSE_DRAG (0x00000800);
+// The rest leaves the lower 16 bit untouched so that it can be used with
+// key codes.
+const sal_uInt32 OVER_SELECTED_PAGE (0x00010000);
+const sal_uInt32 OVER_UNSELECTED_PAGE (0x00020000);
+const sal_uInt32 SHIFT_MODIFIER (0x00200000);
+const sal_uInt32 CONTROL_MODIFIER (0x00400000);
+
+// Some absent events are defined so they can be expressed explicitly.
+const sal_uInt32 NO_MODIFIER (0x00000000);
+const sal_uInt32 NOT_OVER_PAGE (0x00000000);
+
+// Masks
+const sal_uInt32 MODIFIER_MASK (SHIFT_MODIFIER | CONTROL_MODIFIER);
+
+} // end of anonymous namespace
+
+// Define some macros to make the following switch statement more readable.
+#define ANY_MODIFIER(code) \
+ code|NO_MODIFIER: \
+ case code|SHIFT_MODIFIER: \
+ case code|CONTROL_MODIFIER
+
+namespace sd::slidesorter::controller {
+
+//===== SelectionFunction::EventDescriptor ====================================
+
+class SelectionFunction::EventDescriptor
+{
+public:
+ Point maMousePosition;
+ Point maMouseModelPosition;
+ model::SharedPageDescriptor mpHitDescriptor;
+ SdrPage* mpHitPage;
+ sal_uInt32 mnEventCode;
+ InsertionIndicatorHandler::Mode meDragMode;
+ bool mbIsLeaving;
+
+ EventDescriptor (
+ sal_uInt32 nEventType,
+ const MouseEvent& rEvent,
+ SlideSorter const & rSlideSorter);
+ EventDescriptor (
+ sal_uInt32 nEventType,
+ const AcceptDropEvent& rEvent,
+ const sal_Int8 nDragAction,
+ SlideSorter const & rSlideSorter);
+
+private:
+ /** Compute a numerical code that describes a mouse event and that can
+ be used for fast look up of the appropriate reaction.
+ */
+ sal_uInt32 EncodeMouseEvent (const MouseEvent& rEvent) const;
+
+ /** Compute a numerical code that describes the current state like
+ whether the selection rectangle is visible or whether the page under
+ the mouse or the one that has the focus is selected.
+ */
+ sal_uInt32 EncodeState() const;
+};
+
+//===== SelectionFunction::ModeHandler ========================================
+
+class SelectionFunction::ModeHandler
+{
+public:
+ ModeHandler (
+ SlideSorter& rSlideSorter,
+ SelectionFunction& rSelectionFunction,
+ const bool bIsMouseOverIndicatorAllowed);
+ virtual ~ModeHandler() COVERITY_NOEXCEPT_FALSE;
+
+ virtual Mode GetMode() const = 0;
+ virtual void Abort() = 0;
+ virtual void ProcessEvent (EventDescriptor& rDescriptor);
+
+ /** Set the selection to exactly the specified page and also set it as
+ the current page.
+ */
+ void SetCurrentPage (const model::SharedPageDescriptor& rpDescriptor);
+
+ /// Deselect all pages.
+ void DeselectAllPages();
+ void SelectOnePage (const model::SharedPageDescriptor& rpDescriptor);
+
+ /** When the view on which this selection function is working is the
+ main view then the view is switched to the regular editing view.
+ */
+ void SwitchView (const model::SharedPageDescriptor& rpDescriptor);
+
+ void StartDrag (
+ const Point& rMousePosition);
+
+ bool IsMouseOverIndicatorAllowed() const { return mbIsMouseOverIndicatorAllowed;}
+
+protected:
+ SlideSorter& mrSlideSorter;
+ SelectionFunction& mrSelectionFunction;
+
+ virtual bool ProcessButtonDownEvent (EventDescriptor& rDescriptor);
+ virtual bool ProcessButtonUpEvent (EventDescriptor& rDescriptor);
+ virtual bool ProcessMotionEvent (EventDescriptor& rDescriptor);
+ virtual bool ProcessDragEvent (EventDescriptor& rDescriptor);
+ virtual bool HandleUnprocessedEvent (EventDescriptor& rDescriptor);
+
+ void ReprocessEvent (EventDescriptor& rDescriptor);
+
+private:
+ const bool mbIsMouseOverIndicatorAllowed;
+};
+
+namespace {
+
+/** This is the default handler for processing events. It activates the
+ multi selection or drag-and-drop when the right conditions are met.
+*/
+class NormalModeHandler : public SelectionFunction::ModeHandler
+{
+public:
+ NormalModeHandler (
+ SlideSorter& rSlideSorter,
+ SelectionFunction& rSelectionFunction);
+
+ virtual SelectionFunction::Mode GetMode() const override;
+ virtual void Abort() override;
+
+ void ResetButtonDownLocation();
+
+protected:
+ virtual bool ProcessButtonDownEvent (SelectionFunction::EventDescriptor& rDescriptor) override;
+ virtual bool ProcessButtonUpEvent (SelectionFunction::EventDescriptor& rDescriptor) override;
+ virtual bool ProcessMotionEvent (SelectionFunction::EventDescriptor& rDescriptor) override;
+ virtual bool ProcessDragEvent (SelectionFunction::EventDescriptor& rDescriptor) override;
+
+private:
+ ::std::optional<Point> maButtonDownLocation;
+
+ /** Select all pages between and including the selection anchor and the
+ specified page.
+ */
+ void RangeSelect (const model::SharedPageDescriptor& rpDescriptor);
+};
+
+/** Handle events during a multi selection, which typically is started by
+ pressing the left mouse button when not over a page.
+*/
+class MultiSelectionModeHandler : public SelectionFunction::ModeHandler
+{
+public:
+ /** Start a rectangle selection at the given position.
+ */
+ MultiSelectionModeHandler (
+ SlideSorter& rSlideSorter,
+ SelectionFunction& rSelectionFunction,
+ const Point& rMouseModelPosition,
+ const sal_uInt32 nEventCode);
+
+ virtual ~MultiSelectionModeHandler() override;
+
+ virtual SelectionFunction::Mode GetMode() const override;
+ virtual void Abort() override;
+ virtual void ProcessEvent (SelectionFunction::EventDescriptor& rDescriptor) override;
+
+ enum SelectionMode { SM_Normal, SM_Add, SM_Toggle };
+
+ void SetSelectionMode (const SelectionMode eSelectionMode);
+ void SetSelectionModeFromModifier (const sal_uInt32 nEventCode);
+
+protected:
+ virtual bool ProcessButtonUpEvent (SelectionFunction::EventDescriptor& rDescriptor) override;
+ virtual bool ProcessMotionEvent (SelectionFunction::EventDescriptor& rDescriptor) override;
+ virtual bool HandleUnprocessedEvent (SelectionFunction::EventDescriptor& rDescriptor) override;
+
+private:
+ SelectionMode meSelectionMode;
+ Point maSecondCorner;
+ PointerStyle maSavedPointer;
+ bool mbAutoScrollInstalled;
+ sal_Int32 mnAnchorIndex;
+ sal_Int32 mnSecondIndex;
+
+ void UpdateModelPosition (const Point& rMouseModelPosition);
+ void UpdateSelection();
+
+ /** Update the rectangle selection so that the given position becomes
+ the new second point of the selection rectangle.
+ */
+ void UpdatePosition (
+ const Point& rMousePosition,
+ const bool bAllowAutoScroll);
+
+ void UpdateSelectionState (
+ const model::SharedPageDescriptor& rpDescriptor,
+ const bool bIsInSelection) const;
+};
+
+/** Handle events during drag-and-drop.
+*/
+class DragAndDropModeHandler : public SelectionFunction::ModeHandler
+{
+public:
+ DragAndDropModeHandler (
+ SlideSorter& rSlideSorter,
+ SelectionFunction& rSelectionFunction,
+ const Point& rMousePosition,
+ vcl::Window* pWindow);
+ virtual ~DragAndDropModeHandler() override;
+
+ virtual SelectionFunction::Mode GetMode() const override;
+ virtual void Abort() override;
+
+protected:
+ virtual bool ProcessButtonUpEvent (SelectionFunction::EventDescriptor& rDescriptor) override;
+ virtual bool ProcessDragEvent (SelectionFunction::EventDescriptor& rDescriptor) override;
+
+private:
+ std::unique_ptr<DragAndDropContext, o3tl::default_delete<DragAndDropContext>> mpDragAndDropContext;
+};
+
+}
+
+//===== SelectionFunction =====================================================
+
+
+SelectionFunction::SelectionFunction (
+ SlideSorter& rSlideSorter,
+ SfxRequest& rRequest)
+ : FuPoor (
+ rSlideSorter.GetViewShell(),
+ rSlideSorter.GetContentWindow(),
+ &rSlideSorter.GetView(),
+ rSlideSorter.GetModel().GetDocument(),
+ rRequest),
+ mrSlideSorter(rSlideSorter),
+ mrController(mrSlideSorter.GetController()),
+ mnShiftKeySelectionAnchor(-1),
+ mpModeHandler(std::make_shared<NormalModeHandler>(rSlideSorter, *this))
+{
+}
+
+SelectionFunction::~SelectionFunction()
+{
+ mpModeHandler.reset();
+}
+
+rtl::Reference<FuPoor> SelectionFunction::Create(
+ SlideSorter& rSlideSorter,
+ SfxRequest& rRequest)
+{
+ rtl::Reference<FuPoor> xFunc( new SelectionFunction( rSlideSorter, rRequest ) );
+ return xFunc;
+}
+
+bool SelectionFunction::MouseButtonDown (const MouseEvent& rEvent)
+{
+ // remember button state for creation of own MouseEvents
+ SetMouseButtonCode (rEvent.GetButtons());
+ aMDPos = rEvent.GetPosPixel();
+
+ // mpWindow->CaptureMouse();
+
+ ProcessMouseEvent(BUTTON_DOWN, rEvent);
+
+ return true;
+}
+
+bool SelectionFunction::MouseMove (const MouseEvent& rEvent)
+{
+ ProcessMouseEvent(MOUSE_MOTION, rEvent);
+ return true;
+}
+
+bool SelectionFunction::MouseButtonUp (const MouseEvent& rEvent)
+{
+ mrController.GetScrollBarManager().StopAutoScroll ();
+
+ ProcessMouseEvent(BUTTON_UP, rEvent);
+
+ return true;
+}
+
+void SelectionFunction::NotifyDragFinished()
+{
+ SwitchToNormalMode();
+}
+
+bool SelectionFunction::KeyInput (const KeyEvent& rEvent)
+{
+ view::SlideSorterView::DrawLock aDrawLock (mrSlideSorter);
+ PageSelector::BroadcastLock aBroadcastLock (mrSlideSorter);
+ PageSelector::UpdateLock aLock (mrSlideSorter);
+ FocusManager& rFocusManager (mrController.GetFocusManager());
+ bool bResult = false;
+
+ const vcl::KeyCode& rCode (rEvent.GetKeyCode());
+ switch (rCode.GetCode())
+ {
+ case KEY_RETURN:
+ {
+ model::SharedPageDescriptor pDescriptor (rFocusManager.GetFocusedPageDescriptor());
+ ViewShell* pViewShell = mrSlideSorter.GetViewShell();
+ if (rFocusManager.HasFocus() && pDescriptor && pViewShell!=nullptr)
+ {
+ // The Return key triggers different functions depending on
+ // whether the slide sorter is the main view or displayed in
+ // the right pane.
+ if (pViewShell->IsMainViewShell())
+ {
+ mpModeHandler->SetCurrentPage(pDescriptor);
+ mpModeHandler->SwitchView(pDescriptor);
+ }
+ else if (pViewShell->GetDispatcher() != nullptr)
+ {
+ pViewShell->GetDispatcher()->Execute(
+ SID_INSERTPAGE,
+ SfxCallMode::ASYNCHRON | SfxCallMode::RECORD);
+ }
+ bResult = true;
+ }
+ break;
+ }
+
+ case KEY_TAB:
+ if ( ! rFocusManager.IsFocusShowing())
+ {
+ rFocusManager.ShowFocus();
+ bResult = true;
+ }
+ break;
+
+ case KEY_ESCAPE:
+ // When there is an active multiselection or drag-and-drop
+ // operation then stop that.
+ mpModeHandler->Abort();
+ SwitchToNormalMode();
+ bResult = true;
+ break;
+
+ case KEY_SPACE:
+ {
+ // Toggle the selection state.
+ model::SharedPageDescriptor pDescriptor (rFocusManager.GetFocusedPageDescriptor());
+ if (pDescriptor && rCode.IsMod1())
+ {
+ if (pDescriptor->HasState(model::PageDescriptor::ST_Selected))
+ mrController.GetPageSelector().DeselectPage(pDescriptor, false);
+ else
+ mrController.GetPageSelector().SelectPage(pDescriptor);
+ }
+ bResult = true;
+ }
+ break;
+
+ // Move the focus indicator left.
+ case KEY_LEFT:
+ MoveFocus(FocusManager::FocusMoveDirection::Left, rCode.IsShift(), rCode.IsMod1());
+ bResult = true;
+ break;
+
+ // Move the focus indicator right.
+ case KEY_RIGHT:
+ MoveFocus(FocusManager::FocusMoveDirection::Right, rCode.IsShift(), rCode.IsMod1());
+ bResult = true;
+ break;
+
+ // Move the focus indicator up.
+ case KEY_UP:
+ MoveFocus(FocusManager::FocusMoveDirection::Up, rCode.IsShift(), rCode.IsMod1());
+ bResult = true;
+ break;
+
+ // Move the focus indicator down.
+ case KEY_DOWN:
+ MoveFocus(FocusManager::FocusMoveDirection::Down, rCode.IsShift(), rCode.IsMod1());
+ bResult = true;
+ break;
+
+ // Go to previous page. No wrap around.
+ case KEY_PAGEUP:
+ GotoNextPage(-1);
+ bResult = true;
+ break;
+
+ // Go to next page. No wrap around...
+ case KEY_PAGEDOWN:
+ GotoNextPage(+1);
+ bResult = true;
+ break;
+
+ case KEY_HOME:
+ GotoPage(0);
+ bResult = true;
+ break;
+
+ case KEY_END:
+ GotoPage(mrSlideSorter.GetModel().GetPageCount()-1);
+ bResult = true;
+ break;
+
+ case KEY_DELETE:
+ case KEY_BACKSPACE:
+ {
+ if (mrSlideSorter.GetProperties()->IsUIReadOnly())
+ break;
+
+ mrController.GetSelectionManager()->DeleteSelectedPages(rCode.GetCode()==KEY_DELETE);
+
+ mnShiftKeySelectionAnchor = -1;
+ bResult = true;
+ }
+ break;
+
+ case KEY_F10:
+ if (rCode.IsShift())
+ {
+ mpModeHandler->SelectOnePage(
+ mrSlideSorter.GetController().GetFocusManager().GetFocusedPageDescriptor());
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ if ( ! bResult)
+ bResult = FuPoor::KeyInput(rEvent);
+
+ return bResult;
+}
+
+void SelectionFunction::MoveFocus (
+ const FocusManager::FocusMoveDirection eDirection,
+ const bool bIsShiftDown,
+ const bool bIsControlDown)
+{
+ // Remember the anchor of shift key multi selection.
+ if (bIsShiftDown)
+ {
+ if (mnShiftKeySelectionAnchor<0)
+ {
+ model::SharedPageDescriptor pFocusedDescriptor (
+ mrController.GetFocusManager().GetFocusedPageDescriptor());
+ mnShiftKeySelectionAnchor = pFocusedDescriptor->GetPageIndex();
+ }
+ }
+ else if ( ! bIsControlDown)
+ ResetShiftKeySelectionAnchor();
+
+ mrController.GetFocusManager().MoveFocus(eDirection);
+
+ PageSelector& rSelector (mrController.GetPageSelector());
+ model::SharedPageDescriptor pFocusedDescriptor (
+ mrController.GetFocusManager().GetFocusedPageDescriptor());
+ if (bIsShiftDown)
+ {
+ // When shift is pressed then select all pages in the range between
+ // the currently and the previously focused pages, including them.
+ if (pFocusedDescriptor)
+ {
+ sal_Int32 nPageRangeEnd (pFocusedDescriptor->GetPageIndex());
+ model::PageEnumeration aPages (
+ model::PageEnumerationProvider::CreateAllPagesEnumeration(
+ mrSlideSorter.GetModel()));
+ while (aPages.HasMoreElements())
+ {
+ model::SharedPageDescriptor pDescriptor (aPages.GetNextElement());
+ if (pDescriptor)
+ {
+ const sal_Int32 nPageIndex(pDescriptor->GetPageIndex());
+ if ((nPageIndex>=mnShiftKeySelectionAnchor && nPageIndex<=nPageRangeEnd)
+ || (nPageIndex<=mnShiftKeySelectionAnchor && nPageIndex>=nPageRangeEnd))
+ {
+ rSelector.SelectPage(pDescriptor);
+ }
+ else
+ {
+ rSelector.DeselectPage(pDescriptor);
+ }
+ }
+ }
+ }
+ }
+ else if (bIsControlDown)
+ {
+ // When control is pressed then do not alter the selection or the
+ // current page, just move the focus.
+ }
+ else
+ {
+ // Without shift just select the focused page.
+ mpModeHandler->SelectOnePage(pFocusedDescriptor);
+ }
+}
+
+void SelectionFunction::DoCut()
+{
+ if ( ! mrSlideSorter.GetProperties()->IsUIReadOnly())
+ {
+ mrController.GetClipboard().DoCut();
+ }
+}
+
+void SelectionFunction::DoCopy()
+{
+ mrController.GetClipboard().DoCopy();
+}
+
+void SelectionFunction::DoPaste()
+{
+ if ( ! mrSlideSorter.GetProperties()->IsUIReadOnly())
+ {
+ mrController.GetClipboard().DoPaste();
+ }
+}
+
+bool SelectionFunction::cancel()
+{
+ mrController.GetFocusManager().ToggleFocus();
+ return true;
+}
+
+void SelectionFunction::GotoNextPage (int nOffset)
+{
+ model::SharedPageDescriptor pDescriptor
+ = mrController.GetCurrentSlideManager()->GetCurrentSlide();
+ if (pDescriptor)
+ {
+ SdPage* pPage = pDescriptor->GetPage();
+ OSL_ASSERT(pPage!=nullptr);
+ sal_Int32 nIndex = (pPage->GetPageNum()-1) / 2;
+ GotoPage(nIndex + nOffset);
+ }
+ ResetShiftKeySelectionAnchor();
+}
+
+void SelectionFunction::GotoPage (int nIndex)
+{
+ sal_uInt16 nPageCount = static_cast<sal_uInt16>(mrSlideSorter.GetModel().GetPageCount());
+
+ if (nIndex >= nPageCount)
+ nIndex = nPageCount - 1;
+ if (nIndex < 0)
+ nIndex = 0;
+
+ mrController.GetFocusManager().SetFocusedPage(nIndex);
+ model::SharedPageDescriptor pNextPageDescriptor (
+ mrSlideSorter.GetModel().GetPageDescriptor (nIndex));
+ if (pNextPageDescriptor)
+ mpModeHandler->SetCurrentPage(pNextPageDescriptor);
+ else
+ {
+ OSL_ASSERT(pNextPageDescriptor);
+ }
+ ResetShiftKeySelectionAnchor();
+}
+
+void SelectionFunction::ProcessMouseEvent (sal_uInt32 nEventType, const MouseEvent& rEvent)
+{
+ // #95491# remember button state for creation of own MouseEvents
+ SetMouseButtonCode (rEvent.GetButtons());
+
+ EventDescriptor aEventDescriptor (nEventType, rEvent, mrSlideSorter);
+ ProcessEvent(aEventDescriptor);
+}
+
+void SelectionFunction::MouseDragged (
+ const AcceptDropEvent& rEvent,
+ const sal_Int8 nDragAction)
+{
+ EventDescriptor aEventDescriptor (MOUSE_DRAG, rEvent, nDragAction, mrSlideSorter);
+ ProcessEvent(aEventDescriptor);
+}
+
+void SelectionFunction::ProcessEvent (EventDescriptor& rDescriptor)
+{
+ // The call to ProcessEvent may switch to another mode handler.
+ // Prevent the untimely destruction of the called handler by acquiring a
+ // temporary reference here.
+ std::shared_ptr<ModeHandler> pModeHandler (mpModeHandler);
+ pModeHandler->ProcessEvent(rDescriptor);
+}
+
+static bool Match (
+ const sal_uInt32 nEventCode,
+ const sal_uInt32 nPositivePattern)
+{
+ return (nEventCode & nPositivePattern)==nPositivePattern;
+}
+
+void SelectionFunction::SwitchToNormalMode()
+{
+ if (mpModeHandler->GetMode() != NormalMode)
+ SwitchMode(std::make_shared<NormalModeHandler>(mrSlideSorter, *this));
+}
+
+void SelectionFunction::SwitchToDragAndDropMode (const Point& rMousePosition)
+{
+ if (mpModeHandler->GetMode() == DragAndDropMode)
+ return;
+
+ SwitchMode(std::make_shared<DragAndDropModeHandler>(mrSlideSorter, *this, rMousePosition, mpWindow));
+}
+
+void SelectionFunction::SwitchToMultiSelectionMode (
+ const Point& rMousePosition,
+ const sal_uInt32 nEventCode)
+{
+ if (mpModeHandler->GetMode() != MultiSelectionMode)
+ SwitchMode(std::make_shared<MultiSelectionModeHandler>(mrSlideSorter, *this, rMousePosition, nEventCode));
+}
+
+void SelectionFunction::SwitchMode (const std::shared_ptr<ModeHandler>& rpHandler)
+{
+ // Not all modes allow mouse over indicator.
+ if (mpModeHandler->IsMouseOverIndicatorAllowed() != rpHandler->IsMouseOverIndicatorAllowed())
+ {
+ if ( ! rpHandler->IsMouseOverIndicatorAllowed())
+ {
+ mrSlideSorter.GetView().SetPageUnderMouse(model::SharedPageDescriptor());
+ }
+ else
+ mrSlideSorter.GetView().UpdatePageUnderMouse();
+ }
+
+ mpModeHandler = rpHandler;
+}
+
+void SelectionFunction::ResetShiftKeySelectionAnchor()
+{
+ mnShiftKeySelectionAnchor = -1;
+}
+
+void SelectionFunction::ResetMouseAnchor()
+{
+ if (mpModeHandler && mpModeHandler->GetMode() == NormalMode)
+ {
+ std::shared_ptr<NormalModeHandler> pHandler (
+ std::dynamic_pointer_cast<NormalModeHandler>(mpModeHandler));
+ if (pHandler)
+ pHandler->ResetButtonDownLocation();
+ }
+}
+
+//===== EventDescriptor =======================================================
+
+SelectionFunction::EventDescriptor::EventDescriptor (
+ const sal_uInt32 nEventType,
+ const MouseEvent& rEvent,
+ SlideSorter const & rSlideSorter)
+ : maMousePosition(rEvent.GetPosPixel()),
+ mpHitPage(),
+ mnEventCode(nEventType),
+ meDragMode(InsertionIndicatorHandler::MoveMode),
+ mbIsLeaving(false)
+{
+ maMouseModelPosition = rSlideSorter.GetContentWindow()->PixelToLogic(maMousePosition);
+ mpHitDescriptor = rSlideSorter.GetController().GetPageAt(maMousePosition);
+ if (mpHitDescriptor)
+ {
+ mpHitPage = mpHitDescriptor->GetPage();
+ }
+
+ mnEventCode |= EncodeMouseEvent(rEvent);
+ mnEventCode |= EncodeState();
+
+ // Detect the mouse leaving the window. When not button is pressed then
+ // we can call IsLeaveWindow at the event. Otherwise we have to make an
+ // explicit test.
+ mbIsLeaving = rEvent.IsLeaveWindow()
+ || ! ::tools::Rectangle(Point(0,0),
+ rSlideSorter.GetContentWindow()->GetOutputSizePixel()).Contains(maMousePosition);
+}
+
+SelectionFunction::EventDescriptor::EventDescriptor (
+ const sal_uInt32 nEventType,
+ const AcceptDropEvent& rEvent,
+ const sal_Int8 nDragAction,
+ SlideSorter const & rSlideSorter)
+ : maMousePosition(rEvent.maPosPixel),
+ mpHitPage(),
+ mnEventCode(nEventType),
+ meDragMode(InsertionIndicatorHandler::GetModeFromDndAction(nDragAction)),
+ mbIsLeaving(false)
+{
+ maMouseModelPosition = rSlideSorter.GetContentWindow()->PixelToLogic(maMousePosition);
+ mpHitDescriptor = rSlideSorter.GetController().GetPageAt(maMousePosition);
+ if (mpHitDescriptor)
+ {
+ mpHitPage = mpHitDescriptor->GetPage();
+ }
+
+ mnEventCode |= EncodeState();
+
+ // Detect the mouse leaving the window. When not button is pressed then
+ // we can call IsLeaveWindow at the event. Otherwise we have to make an
+ // explicit test.
+ mbIsLeaving = rEvent.mbLeaving
+ || ! ::tools::Rectangle(Point(0,0),
+ rSlideSorter.GetContentWindow()->GetOutputSizePixel()).Contains(maMousePosition);
+}
+
+sal_uInt32 SelectionFunction::EventDescriptor::EncodeMouseEvent (
+ const MouseEvent& rEvent) const
+{
+ // Initialize with the type of mouse event.
+ sal_uInt32 nEventCode (mnEventCode & (BUTTON_DOWN | BUTTON_UP | MOUSE_MOTION));
+
+ // Detect the affected button.
+ switch (rEvent.GetButtons())
+ {
+ case MOUSE_LEFT: nEventCode |= LEFT_BUTTON; break;
+ case MOUSE_RIGHT: nEventCode |= RIGHT_BUTTON; break;
+ case MOUSE_MIDDLE: nEventCode |= MIDDLE_BUTTON; break;
+ }
+
+ // Detect the number of clicks.
+ switch (rEvent.GetClicks())
+ {
+ case 1: nEventCode |= SINGLE_CLICK; break;
+ case 2: nEventCode |= DOUBLE_CLICK; break;
+ }
+
+ // Detect pressed modifier keys.
+ if (rEvent.IsShift())
+ nEventCode |= SHIFT_MODIFIER;
+ if (rEvent.IsMod1())
+ nEventCode |= CONTROL_MODIFIER;
+
+ return nEventCode;
+}
+
+sal_uInt32 SelectionFunction::EventDescriptor::EncodeState() const
+{
+ sal_uInt32 nEventCode (0);
+
+ // Detect whether the event has happened over a page object.
+ if (mpHitPage!=nullptr && mpHitDescriptor)
+ {
+ if (mpHitDescriptor->HasState(model::PageDescriptor::ST_Selected))
+ nEventCode |= OVER_SELECTED_PAGE;
+ else
+ nEventCode |= OVER_UNSELECTED_PAGE;
+ }
+
+ return nEventCode;
+}
+
+//===== SelectionFunction::ModeHandler ========================================
+
+SelectionFunction::ModeHandler::ModeHandler (
+ SlideSorter& rSlideSorter,
+ SelectionFunction& rSelectionFunction,
+ const bool bIsMouseOverIndicatorAllowed)
+ : mrSlideSorter(rSlideSorter),
+ mrSelectionFunction(rSelectionFunction),
+ mbIsMouseOverIndicatorAllowed(bIsMouseOverIndicatorAllowed)
+{
+}
+
+SelectionFunction::ModeHandler::~ModeHandler() COVERITY_NOEXCEPT_FALSE
+{
+}
+
+void SelectionFunction::ModeHandler::ReprocessEvent (EventDescriptor& rDescriptor)
+{
+ mrSelectionFunction.ProcessEvent(rDescriptor);
+}
+
+void SelectionFunction::ModeHandler::ProcessEvent (
+ SelectionFunction::EventDescriptor& rDescriptor)
+{
+ PageSelector::BroadcastLock aBroadcastLock (mrSlideSorter);
+ PageSelector::UpdateLock aUpdateLock (mrSlideSorter);
+
+ bool bIsProcessed (false);
+ switch (rDescriptor.mnEventCode & (BUTTON_DOWN | BUTTON_UP | MOUSE_MOTION | MOUSE_DRAG))
+ {
+ case BUTTON_DOWN:
+ bIsProcessed = ProcessButtonDownEvent(rDescriptor);
+ break;
+
+ case BUTTON_UP:
+ bIsProcessed = ProcessButtonUpEvent(rDescriptor);
+ break;
+
+ case MOUSE_MOTION:
+ bIsProcessed = ProcessMotionEvent(rDescriptor);
+ break;
+
+ case MOUSE_DRAG:
+ bIsProcessed = ProcessDragEvent(rDescriptor);
+ break;
+ }
+
+ if ( ! bIsProcessed)
+ HandleUnprocessedEvent(rDescriptor);
+}
+
+bool SelectionFunction::ModeHandler::ProcessButtonDownEvent (EventDescriptor&)
+{
+ return false;
+}
+
+bool SelectionFunction::ModeHandler::ProcessButtonUpEvent (EventDescriptor&)
+{
+ mrSelectionFunction.SwitchToNormalMode();
+ return false;
+}
+
+bool SelectionFunction::ModeHandler::ProcessMotionEvent (EventDescriptor& rDescriptor)
+{
+ if (mbIsMouseOverIndicatorAllowed)
+ mrSlideSorter.GetView().UpdatePageUnderMouse(rDescriptor.maMousePosition);
+
+ if (rDescriptor.mbIsLeaving)
+ {
+ mrSelectionFunction.SwitchToNormalMode();
+ mrSlideSorter.GetView().SetPageUnderMouse(model::SharedPageDescriptor());
+
+ return true;
+ }
+ else
+ return false;
+}
+
+bool SelectionFunction::ModeHandler::ProcessDragEvent (EventDescriptor&)
+{
+ return false;
+}
+
+bool SelectionFunction::ModeHandler::HandleUnprocessedEvent (EventDescriptor&)
+{
+ return false;
+}
+
+void SelectionFunction::ModeHandler::SetCurrentPage (
+ const model::SharedPageDescriptor& rpDescriptor)
+{
+ SelectOnePage(rpDescriptor);
+ mrSlideSorter.GetController().GetCurrentSlideManager()->SwitchCurrentSlide(rpDescriptor);
+}
+
+void SelectionFunction::ModeHandler::DeselectAllPages()
+{
+ mrSlideSorter.GetController().GetPageSelector().DeselectAllPages();
+ mrSelectionFunction.ResetShiftKeySelectionAnchor();
+}
+
+void SelectionFunction::ModeHandler::SelectOnePage (
+ const model::SharedPageDescriptor& rpDescriptor)
+{
+ DeselectAllPages();
+ mrSlideSorter.GetController().GetPageSelector().SelectPage(rpDescriptor);
+}
+
+void SelectionFunction::ModeHandler::SwitchView (const model::SharedPageDescriptor& rpDescriptor)
+{
+ // Switch to the draw view. This is done only when the current
+ // view is the main view.
+ ViewShell* pViewShell = mrSlideSorter.GetViewShell();
+ if (pViewShell==nullptr || !pViewShell->IsMainViewShell())
+ return;
+
+ if (rpDescriptor && rpDescriptor->GetPage()!=nullptr)
+ {
+ mrSlideSorter.GetModel().GetDocument()->SetSelected(rpDescriptor->GetPage(), true);
+ pViewShell->GetFrameView()->SetSelectedPage(
+ (rpDescriptor->GetPage()->GetPageNum()-1)/2);
+ }
+ if (mrSlideSorter.GetViewShellBase() != nullptr)
+ framework::FrameworkHelper::Instance(*mrSlideSorter.GetViewShellBase())->RequestView(
+ framework::FrameworkHelper::msImpressViewURL,
+ framework::FrameworkHelper::msCenterPaneURL);
+}
+
+void SelectionFunction::ModeHandler::StartDrag (
+ const Point& rMousePosition)
+{
+ // Do not start a drag-and-drop operation when one is already active.
+ // (when dragging pages from one document into another, pressing a
+ // modifier key can trigger a MouseMotion event in the originating
+ // window (focus still in there). Together with the mouse button pressed
+ // (drag-and-drop is active) this triggers the start of drag-and-drop.)
+ if (SD_MOD()->pTransferDrag != nullptr)
+ return;
+
+ if ( ! mrSlideSorter.GetProperties()->IsUIReadOnly())
+ {
+ mrSelectionFunction.SwitchToDragAndDropMode(rMousePosition);
+ }
+}
+
+//===== NormalModeHandler =====================================================
+
+NormalModeHandler::NormalModeHandler (
+ SlideSorter& rSlideSorter,
+ SelectionFunction& rSelectionFunction)
+ : ModeHandler(rSlideSorter, rSelectionFunction, true)
+{
+}
+
+SelectionFunction::Mode NormalModeHandler::GetMode() const
+{
+ return SelectionFunction::NormalMode;
+}
+
+void NormalModeHandler::Abort()
+{
+}
+
+bool NormalModeHandler::ProcessButtonDownEvent (
+ SelectionFunction::EventDescriptor& rDescriptor)
+{
+ // Remember the location where the left button is pressed. With
+ // that we can filter away motion events that are caused by key
+ // presses. We also can tune the minimal motion distance that
+ // triggers a drag-and-drop operation.
+ if ((rDescriptor.mnEventCode & BUTTON_DOWN) != 0)
+ maButtonDownLocation = rDescriptor.maMousePosition;
+
+ switch (rDescriptor.mnEventCode)
+ {
+ case BUTTON_DOWN | LEFT_BUTTON | SINGLE_CLICK | OVER_UNSELECTED_PAGE:
+ SetCurrentPage(rDescriptor.mpHitDescriptor);
+ break;
+
+ case BUTTON_DOWN | LEFT_BUTTON | SINGLE_CLICK | OVER_SELECTED_PAGE:
+ break;
+
+ case BUTTON_DOWN | LEFT_BUTTON | DOUBLE_CLICK | OVER_SELECTED_PAGE:
+ case BUTTON_DOWN | LEFT_BUTTON | DOUBLE_CLICK | OVER_UNSELECTED_PAGE:
+ // A double click always shows the selected slide in the center
+ // pane in an edit view.
+ SetCurrentPage(rDescriptor.mpHitDescriptor);
+ SwitchView(rDescriptor.mpHitDescriptor);
+ break;
+
+ case BUTTON_DOWN | LEFT_BUTTON | SINGLE_CLICK | OVER_SELECTED_PAGE | SHIFT_MODIFIER:
+ case BUTTON_DOWN | LEFT_BUTTON | SINGLE_CLICK | OVER_UNSELECTED_PAGE | SHIFT_MODIFIER:
+ // Range selection with the shift modifier.
+ RangeSelect(rDescriptor.mpHitDescriptor);
+ break;
+
+ // Right button for context menu.
+ case BUTTON_DOWN | RIGHT_BUTTON | SINGLE_CLICK | OVER_UNSELECTED_PAGE:
+ // Single right click and shift+F10 select as preparation to
+ // show the context menu. Change the selection only when the
+ // page under the mouse is not selected. In this case the
+ // selection is set to this single page. Otherwise the
+ // selection is not modified.
+ SetCurrentPage(rDescriptor.mpHitDescriptor);
+ break;
+
+ case BUTTON_DOWN | RIGHT_BUTTON | SINGLE_CLICK | OVER_SELECTED_PAGE:
+ // Do not change the selection. Just adjust the insertion indicator.
+ break;
+
+ case BUTTON_DOWN | RIGHT_BUTTON | SINGLE_CLICK | NOT_OVER_PAGE:
+ // Remember the current selection so that when a multi selection
+ // is started, we can restore the previous selection.
+ mrSlideSorter.GetModel().SaveCurrentSelection();
+ DeselectAllPages();
+ break;
+
+ case ANY_MODIFIER(BUTTON_DOWN | LEFT_BUTTON | SINGLE_CLICK | NOT_OVER_PAGE):
+ // Remember the current selection so that when a multi selection
+ // is started, we can restore the previous selection.
+ mrSlideSorter.GetModel().SaveCurrentSelection();
+ DeselectAllPages();
+ break;
+
+ case BUTTON_DOWN | LEFT_BUTTON | DOUBLE_CLICK | NOT_OVER_PAGE:
+ {
+ // Insert a new slide:
+ // First of all we need to set the insertion indicator which sets the
+ // position where the new slide will be inserted.
+ std::shared_ptr<InsertionIndicatorHandler> pInsertionIndicatorHandler
+ = mrSlideSorter.GetController().GetInsertionIndicatorHandler();
+
+ pInsertionIndicatorHandler->Start(false);
+ pInsertionIndicatorHandler->UpdatePosition(
+ rDescriptor.maMousePosition,
+ InsertionIndicatorHandler::MoveMode);
+
+ mrSlideSorter.GetController().GetSelectionManager()->SetInsertionPosition(
+ pInsertionIndicatorHandler->GetInsertionPageIndex());
+
+ mrSlideSorter.GetViewShell()->GetDispatcher()->Execute(
+ SID_INSERTPAGE,
+ SfxCallMode::ASYNCHRON | SfxCallMode::RECORD);
+
+ pInsertionIndicatorHandler->End(Animator::AM_Immediate);
+
+ break;
+ }
+
+ default:
+ return false;
+ }
+ return true;
+}
+
+bool NormalModeHandler::ProcessButtonUpEvent (
+ SelectionFunction::EventDescriptor& rDescriptor)
+{
+ bool bIsProcessed (true);
+ switch (rDescriptor.mnEventCode)
+ {
+ case BUTTON_UP | LEFT_BUTTON | SINGLE_CLICK | OVER_SELECTED_PAGE:
+ SetCurrentPage(rDescriptor.mpHitDescriptor);
+ break;
+
+ // Multi selection with the control modifier.
+ case BUTTON_UP | LEFT_BUTTON | SINGLE_CLICK | OVER_SELECTED_PAGE | CONTROL_MODIFIER:
+ mrSlideSorter.GetController().GetPageSelector().DeselectPage(
+ rDescriptor.mpHitDescriptor);
+ break;
+
+ case BUTTON_UP | LEFT_BUTTON | SINGLE_CLICK | OVER_UNSELECTED_PAGE | CONTROL_MODIFIER:
+ mrSlideSorter.GetController().GetPageSelector().SelectPage(
+ rDescriptor.mpHitDescriptor);
+ mrSlideSorter.GetView().SetPageUnderMouse(rDescriptor.mpHitDescriptor);
+ break;
+ case BUTTON_UP | LEFT_BUTTON | SINGLE_CLICK | NOT_OVER_PAGE:
+ break;
+
+ default:
+ bIsProcessed = false;
+ break;
+ }
+ mrSelectionFunction.SwitchToNormalMode();
+ return bIsProcessed;
+}
+
+bool NormalModeHandler::ProcessMotionEvent (
+ SelectionFunction::EventDescriptor& rDescriptor)
+{
+ if (ModeHandler::ProcessMotionEvent(rDescriptor))
+ return true;
+
+ bool bIsProcessed (true);
+ switch (rDescriptor.mnEventCode)
+ {
+ // A mouse motion without visible substitution starts that.
+ case ANY_MODIFIER(MOUSE_MOTION | LEFT_BUTTON | SINGLE_CLICK | OVER_UNSELECTED_PAGE):
+ case ANY_MODIFIER(MOUSE_MOTION | LEFT_BUTTON | SINGLE_CLICK | OVER_SELECTED_PAGE):
+ {
+ if (maButtonDownLocation)
+ {
+ const sal_Int32 nDistance(std::max(
+ std::abs(maButtonDownLocation->X() - rDescriptor.maMousePosition.X()),
+ std::abs(maButtonDownLocation->Y() - rDescriptor.maMousePosition.Y())));
+ if (nDistance > 3)
+ StartDrag(rDescriptor.maMousePosition);
+ }
+ break;
+ }
+
+ // A mouse motion not over a page starts a rectangle selection.
+ case ANY_MODIFIER(MOUSE_MOTION | LEFT_BUTTON | SINGLE_CLICK | NOT_OVER_PAGE):
+ mrSelectionFunction.SwitchToMultiSelectionMode(
+ rDescriptor.maMouseModelPosition,
+ rDescriptor.mnEventCode);
+ break;
+
+ default:
+ bIsProcessed = false;
+ break;
+ }
+ return bIsProcessed;
+}
+
+bool NormalModeHandler::ProcessDragEvent (SelectionFunction::EventDescriptor& rDescriptor)
+{
+ mrSelectionFunction.SwitchToDragAndDropMode(rDescriptor.maMousePosition);
+ ReprocessEvent(rDescriptor);
+ return true;
+}
+
+void NormalModeHandler::RangeSelect (const model::SharedPageDescriptor& rpDescriptor)
+{
+ PageSelector::UpdateLock aLock (mrSlideSorter);
+ PageSelector& rSelector (mrSlideSorter.GetController().GetPageSelector());
+
+ model::SharedPageDescriptor pAnchor (rSelector.GetSelectionAnchor());
+ DeselectAllPages();
+
+ if (!pAnchor)
+ return;
+
+ // Select all pages between the anchor and the given one, including
+ // the two.
+ const sal_uInt16 nAnchorIndex ((pAnchor->GetPage()->GetPageNum()-1) / 2);
+ const sal_uInt16 nOtherIndex ((rpDescriptor->GetPage()->GetPageNum()-1) / 2);
+
+ // Iterate over all pages in the range. Start with the anchor
+ // page. This way the PageSelector will recognize it again as
+ // anchor (the first selected page after a DeselectAllPages()
+ // becomes the anchor.)
+ const sal_uInt16 nStep ((nAnchorIndex < nOtherIndex) ? +1 : -1);
+ sal_uInt16 nIndex (nAnchorIndex);
+ while (true)
+ {
+ rSelector.SelectPage(nIndex);
+ if (nIndex == nOtherIndex)
+ break;
+ nIndex = nIndex + nStep;
+ }
+}
+
+void NormalModeHandler::ResetButtonDownLocation()
+{
+ maButtonDownLocation = ::std::optional<Point>();
+}
+
+//===== MultiSelectionModeHandler =============================================
+
+MultiSelectionModeHandler::MultiSelectionModeHandler (
+ SlideSorter& rSlideSorter,
+ SelectionFunction& rSelectionFunction,
+ const Point& rMouseModelPosition,
+ const sal_uInt32 nEventCode)
+ : ModeHandler(rSlideSorter, rSelectionFunction, false),
+ meSelectionMode(SM_Normal),
+ maSecondCorner(rMouseModelPosition),
+ maSavedPointer(mrSlideSorter.GetContentWindow()->GetPointer()),
+ mbAutoScrollInstalled(false),
+ mnAnchorIndex(-1),
+ mnSecondIndex(-1)
+{
+
+ mrSlideSorter.GetContentWindow()->SetPointer(PointerStyle::Text);
+ SetSelectionModeFromModifier(nEventCode);
+}
+
+MultiSelectionModeHandler::~MultiSelectionModeHandler()
+{
+ if (mbAutoScrollInstalled)
+ {
+ //a call to this handler's MultiSelectionModeHandler::UpdatePosition
+ //may be still waiting to be called back
+ mrSlideSorter.GetController().GetScrollBarManager().clearAutoScrollFunctor();
+ }
+ mrSlideSorter.GetContentWindow()->SetPointer(maSavedPointer);
+}
+
+SelectionFunction::Mode MultiSelectionModeHandler::GetMode() const
+{
+ return SelectionFunction::MultiSelectionMode;
+}
+
+void MultiSelectionModeHandler::Abort()
+{
+ mrSlideSorter.GetView().RequestRepaint(mrSlideSorter.GetModel().RestoreSelection());
+}
+
+void MultiSelectionModeHandler::ProcessEvent (
+ SelectionFunction::EventDescriptor& rDescriptor)
+{
+ // During a multi selection we do not want sudden jumps of the
+ // visible area caused by moving newly selected pages into view.
+ // Therefore disable that temporarily. The disabled object is
+ // released at the end of the event processing, after the focus and
+ // current slide have been updated.
+ VisibleAreaManager::TemporaryDisabler aDisabler (mrSlideSorter);
+
+ ModeHandler::ProcessEvent(rDescriptor);
+}
+
+bool MultiSelectionModeHandler::ProcessButtonUpEvent (
+ SelectionFunction::EventDescriptor& rDescriptor)
+{
+ if (mbAutoScrollInstalled)
+ {
+ //a call to this handler's MultiSelectionModeHandler::UpdatePosition
+ //may be still waiting to be called back
+ mrSlideSorter.GetController().GetScrollBarManager().clearAutoScrollFunctor();
+ mbAutoScrollInstalled = false;
+ }
+
+ if (Match(rDescriptor.mnEventCode, BUTTON_UP | LEFT_BUTTON | SINGLE_CLICK))
+ {
+ mrSelectionFunction.SwitchToNormalMode();
+ return true;
+ }
+ else
+ return false;
+}
+
+bool MultiSelectionModeHandler::ProcessMotionEvent (
+ SelectionFunction::EventDescriptor& rDescriptor)
+{
+ // The selection rectangle is visible. Handle events accordingly.
+ if (Match(rDescriptor.mnEventCode, MOUSE_MOTION | LEFT_BUTTON | SINGLE_CLICK))
+ {
+ SetSelectionModeFromModifier(rDescriptor.mnEventCode);
+ UpdatePosition(rDescriptor.maMousePosition, true);
+ return true;
+ }
+ else
+ return false;
+}
+
+bool MultiSelectionModeHandler::HandleUnprocessedEvent (
+ SelectionFunction::EventDescriptor& rDescriptor)
+{
+ if ( ! ModeHandler::HandleUnprocessedEvent(rDescriptor))
+ {
+ // If the event has not been processed then stop multi selection.
+ mrSelectionFunction.SwitchToNormalMode();
+ ReprocessEvent(rDescriptor);
+ }
+ return true;
+}
+
+void MultiSelectionModeHandler::UpdatePosition (
+ const Point& rMousePosition,
+ const bool bAllowAutoScroll)
+{
+ VisibleAreaManager::TemporaryDisabler aDisabler (mrSlideSorter);
+
+ // Convert window coordinates into model coordinates (we need the
+ // window coordinates for auto-scrolling because that remains
+ // constant while scrolling.)
+ sd::Window *pWindow (mrSlideSorter.GetContentWindow().get());
+ const Point aMouseModelPosition (pWindow->PixelToLogic(rMousePosition));
+
+ bool bDoAutoScroll = bAllowAutoScroll && mrSlideSorter.GetController().GetScrollBarManager().AutoScroll(
+ rMousePosition,
+ [this, &rMousePosition] () { return this->UpdatePosition(rMousePosition, false); });
+
+ if (!bDoAutoScroll)
+ UpdateModelPosition(aMouseModelPosition);
+
+ mbAutoScrollInstalled |= bDoAutoScroll;
+}
+
+void MultiSelectionModeHandler::SetSelectionModeFromModifier (
+ const sal_uInt32 nEventCode)
+{
+ switch (nEventCode & MODIFIER_MASK)
+ {
+ case NO_MODIFIER:
+ SetSelectionMode(SM_Normal);
+ break;
+
+ case SHIFT_MODIFIER:
+ SetSelectionMode(SM_Add);
+ break;
+
+ case CONTROL_MODIFIER:
+ SetSelectionMode(SM_Toggle);
+ break;
+ }
+}
+
+void MultiSelectionModeHandler::SetSelectionMode (const SelectionMode eSelectionMode)
+{
+ if (meSelectionMode != eSelectionMode)
+ {
+ meSelectionMode = eSelectionMode;
+ UpdateSelection();
+ }
+}
+
+void MultiSelectionModeHandler::UpdateSelectionState (
+ const model::SharedPageDescriptor& rpDescriptor,
+ const bool bIsInSelection) const
+{
+ // Determine whether the page was selected before the rectangle
+ // selection was started.
+ const bool bWasSelected (rpDescriptor->HasState(model::PageDescriptor::ST_WasSelected));
+
+ // Combine the two selection states depending on the selection mode.
+ bool bSelect (false);
+ switch(meSelectionMode)
+ {
+ case SM_Normal:
+ bSelect = bIsInSelection;
+ break;
+
+ case SM_Add:
+ bSelect = bIsInSelection || bWasSelected;
+ break;
+
+ case SM_Toggle:
+ if (bIsInSelection)
+ bSelect = !bWasSelected;
+ else
+ bSelect = bWasSelected;
+ break;
+ }
+
+ // Set the new selection state.
+ if (bSelect)
+ mrSlideSorter.GetController().GetPageSelector().SelectPage(rpDescriptor);
+ else
+ mrSlideSorter.GetController().GetPageSelector().DeselectPage(rpDescriptor);
+}
+
+void MultiSelectionModeHandler::UpdateModelPosition (const Point& rMouseModelPosition)
+{
+ maSecondCorner = rMouseModelPosition;
+ UpdateSelection();
+}
+
+void MultiSelectionModeHandler::UpdateSelection()
+{
+ view::SlideSorterView::DrawLock aLock (mrSlideSorter);
+
+ model::SlideSorterModel& rModel (mrSlideSorter.GetModel());
+ const sal_Int32 nPageCount (rModel.GetPageCount());
+
+ const sal_Int32 nIndexUnderMouse (
+ mrSlideSorter.GetView().GetLayouter().GetIndexAtPoint (
+ maSecondCorner,
+ false,
+ false));
+ if (nIndexUnderMouse < 0 || nIndexUnderMouse >= nPageCount)
+ return;
+
+ if (mnAnchorIndex < 0)
+ mnAnchorIndex = nIndexUnderMouse;
+ mnSecondIndex = nIndexUnderMouse;
+
+ Range aRange (mnAnchorIndex, mnSecondIndex);
+ aRange.Justify();
+
+ for (sal_Int32 nIndex=0; nIndex<nPageCount; ++nIndex)
+ {
+ UpdateSelectionState(rModel.GetPageDescriptor(nIndex), aRange.Contains(nIndex));
+ }
+}
+
+//===== DragAndDropModeHandler ================================================
+
+DragAndDropModeHandler::DragAndDropModeHandler (
+ SlideSorter& rSlideSorter,
+ SelectionFunction& rSelectionFunction,
+ const Point& rMousePosition,
+ vcl::Window* pWindow)
+ : ModeHandler(rSlideSorter, rSelectionFunction, false)
+{
+ SdTransferable* pDragTransferable = SD_MOD()->pTransferDrag;
+ if (pDragTransferable==nullptr && mrSlideSorter.GetViewShell() != nullptr)
+ {
+ SlideSorterViewShell* pSlideSorterViewShell
+ = dynamic_cast<SlideSorterViewShell*>(mrSlideSorter.GetViewShell());
+ if (pSlideSorterViewShell != nullptr)
+ pSlideSorterViewShell->StartDrag(rMousePosition, pWindow);
+ pDragTransferable = SD_MOD()->pTransferDrag;
+ }
+
+ mpDragAndDropContext.reset(new DragAndDropContext(mrSlideSorter));
+ mrSlideSorter.GetController().GetInsertionIndicatorHandler()->Start(
+ pDragTransferable != nullptr
+ && pDragTransferable->GetView()==&mrSlideSorter.GetView());
+}
+
+DragAndDropModeHandler::~DragAndDropModeHandler()
+{
+ if (mpDragAndDropContext)
+ {
+ // Disconnect the substitution handler from this selection function.
+ mpDragAndDropContext->SetTargetSlideSorter();
+ mpDragAndDropContext.reset();
+ }
+ mrSlideSorter.GetController().GetInsertionIndicatorHandler()->End(Animator::AM_Animated);
+}
+
+SelectionFunction::Mode DragAndDropModeHandler::GetMode() const
+{
+ return SelectionFunction::DragAndDropMode;
+}
+
+void DragAndDropModeHandler::Abort()
+{
+ mrSlideSorter.GetController().GetClipboard().Abort();
+ if (mpDragAndDropContext)
+ mpDragAndDropContext->Dispose();
+ // mrSlideSorter.GetView().RequestRepaint(mrSlideSorter.GetModel().RestoreSelection());
+}
+
+bool DragAndDropModeHandler::ProcessButtonUpEvent (
+ SelectionFunction::EventDescriptor& rDescriptor)
+{
+ if (Match(rDescriptor.mnEventCode, BUTTON_UP | LEFT_BUTTON))
+ {
+ // The following Process() call may lead to the destruction
+ // of rDescriptor.mpHitDescriptor so release our reference to it.
+ rDescriptor.mpHitDescriptor.reset();
+ mrSelectionFunction.SwitchToNormalMode();
+ return true;
+ }
+ else
+ return false;
+}
+
+bool DragAndDropModeHandler::ProcessDragEvent (SelectionFunction::EventDescriptor& rDescriptor)
+{
+ OSL_ASSERT(mpDragAndDropContext);
+
+ if (rDescriptor.mbIsLeaving)
+ {
+ mrSelectionFunction.SwitchToNormalMode();
+ }
+ else if (mpDragAndDropContext)
+ {
+ mpDragAndDropContext->UpdatePosition(
+ rDescriptor.maMousePosition,
+ rDescriptor.meDragMode, true);
+ }
+
+ return true;
+}
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsSelectionManager.cxx b/sd/source/ui/slidesorter/controller/SlsSelectionManager.cxx
new file mode 100644
index 000000000..e1f75b21c
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsSelectionManager.cxx
@@ -0,0 +1,309 @@
+/* -*- 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 <controller/SlsSelectionManager.hxx>
+
+#include <SlideSorter.hxx>
+#include <controller/SlideSorterController.hxx>
+#include <controller/SlsCurrentSlideManager.hxx>
+#include <controller/SlsFocusManager.hxx>
+#include <controller/SlsPageSelector.hxx>
+#include <controller/SlsSelectionObserver.hxx>
+#include <model/SlideSorterModel.hxx>
+#include <model/SlsPageEnumerationProvider.hxx>
+#include <model/SlsPageDescriptor.hxx>
+#include <view/SlideSorterView.hxx>
+#include <tools/diagnose_ex.h>
+#include <drawdoc.hxx>
+#include <sdpage.hxx>
+#include <drawview.hxx>
+#include <DrawViewShell.hxx>
+#include <ViewShellBase.hxx>
+#include <svx/svxids.hrc>
+#include <com/sun/star/drawing/XMasterPagesSupplier.hpp>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+
+
+#include <sdresid.hxx>
+#include <strings.hrc>
+#include <app.hrc>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::drawing;
+using namespace ::com::sun::star::uno;
+using namespace ::sd::slidesorter::model;
+using namespace ::sd::slidesorter::view;
+using namespace ::sd::slidesorter::controller;
+
+namespace sd::slidesorter::controller {
+
+SelectionManager::SelectionManager (SlideSorter& rSlideSorter)
+ : mrSlideSorter(rSlideSorter),
+ mrController(rSlideSorter.GetController()),
+ mnInsertionPosition(-1),
+ mpSelectionObserver(std::make_shared<SelectionObserver>(rSlideSorter))
+{
+}
+
+SelectionManager::~SelectionManager()
+{
+}
+
+void SelectionManager::DeleteSelectedPages (const bool bSelectFollowingPage)
+{
+ // Create some locks to prevent updates of the model, view, selection
+ // state while modifying any of them.
+ SlideSorterController::ModelChangeLock aLock (mrController);
+ SlideSorterView::DrawLock aDrawLock (mrSlideSorter);
+ PageSelector::UpdateLock aSelectionLock (mrSlideSorter);
+
+ // Hide focus.
+ bool bIsFocusShowing = mrController.GetFocusManager().IsFocusShowing();
+ if (bIsFocusShowing)
+ mrController.GetFocusManager().ToggleFocus();
+
+ // Store pointers to all selected page descriptors. This is necessary
+ // because the pages get deselected when the first one is deleted.
+ model::PageEnumeration aPageEnumeration (
+ PageEnumerationProvider::CreateSelectedPagesEnumeration(mrSlideSorter.GetModel()));
+ ::std::vector<SdPage*> aSelectedPages;
+ sal_Int32 nNewCurrentSlide (-1);
+ while (aPageEnumeration.HasMoreElements())
+ {
+ SharedPageDescriptor pDescriptor (aPageEnumeration.GetNextElement());
+ aSelectedPages.push_back(pDescriptor->GetPage());
+ if (bSelectFollowingPage || nNewCurrentSlide<0)
+ nNewCurrentSlide = pDescriptor->GetPageIndex();
+ }
+ if (aSelectedPages.empty())
+ return;
+
+ // Determine the slide to select (and thereby make the current slide)
+ // after the deletion.
+ if (bSelectFollowingPage)
+ nNewCurrentSlide -= aSelectedPages.size() - 1;
+ else
+ --nNewCurrentSlide;
+
+ const auto pViewShell = mrSlideSorter.GetViewShell();
+ const auto pDrawViewShell = pViewShell ? std::dynamic_pointer_cast<sd::DrawViewShell>(pViewShell->GetViewShellBase().GetMainViewShell()) : nullptr;
+ const auto pDrawView = pDrawViewShell ? pDrawViewShell->GetDrawView() : nullptr;
+
+ if (pDrawView)
+ pDrawView->BlockPageOrderChangedHint(true);
+
+ // Proper naming for the undo action
+ OUString sUndoComment(SdResId(STR_UNDO_DELETEPAGES));
+ if (mrSlideSorter.GetView().GetDoc().GetDocumentType() == DocumentType::Draw)
+ sUndoComment = SdResId(STR_UNDO_DELETEPAGES_DRAW);
+
+ // The actual deletion of the selected pages is done in one of two
+ // helper functions. They are specialized for normal respectively for
+ // master pages.
+ mrSlideSorter.GetView().BegUndo (sUndoComment);
+ if (mrSlideSorter.GetModel().GetEditMode() == EditMode::Page)
+ DeleteSelectedNormalPages(aSelectedPages);
+ else
+ DeleteSelectedMasterPages(aSelectedPages);
+ mrSlideSorter.GetView().EndUndo ();
+
+ mrController.HandleModelChange();
+ aLock.Release();
+ if (pDrawView)
+ {
+ assert(pDrawViewShell);
+ pDrawView->BlockPageOrderChangedHint(false);
+ pDrawViewShell->ResetActualPage();
+ }
+
+ // Show focus and move it to next valid location.
+ if (bIsFocusShowing)
+ mrController.GetFocusManager().ToggleFocus();
+
+ // Set the new current slide.
+ if (nNewCurrentSlide < 0)
+ nNewCurrentSlide = 0;
+ else if (nNewCurrentSlide >= mrSlideSorter.GetModel().GetPageCount())
+ nNewCurrentSlide = mrSlideSorter.GetModel().GetPageCount()-1;
+ mrController.GetPageSelector().CountSelectedPages();
+ mrController.GetPageSelector().SelectPage(nNewCurrentSlide);
+ mrController.GetFocusManager().SetFocusedPage(nNewCurrentSlide);
+}
+
+void SelectionManager::DeleteSelectedNormalPages (const ::std::vector<SdPage*>& rSelectedPages)
+{
+ // Prepare the deletion via the UNO API.
+ OSL_ASSERT(mrSlideSorter.GetModel().GetEditMode() == EditMode::Page);
+
+ try
+ {
+ Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier( mrSlideSorter.GetModel().GetDocument()->getUnoModel(), UNO_QUERY_THROW );
+ Reference<drawing::XDrawPages> xPages( xDrawPagesSupplier->getDrawPages(), UNO_SET_THROW );
+
+ // Iterate over all pages that were selected when this method was called
+ // and delete the draw page the notes page. The iteration is done in
+ // reverse order so that when one slide is not deleted (to avoid an
+ // empty document) the remaining slide is the first one.
+ ::std::vector<SdPage*>::const_reverse_iterator aI;
+ for (aI=rSelectedPages.rbegin(); aI!=rSelectedPages.rend(); ++aI)
+ {
+ // Do not delete the last slide in the document.
+ if (xPages->getCount() <= 1)
+ break;
+
+ const sal_uInt16 nPage (model::FromCoreIndex((*aI)->GetPageNum()));
+
+ Reference< XDrawPage > xPage( xPages->getByIndex( nPage ), UNO_QUERY_THROW );
+ xPages->remove(xPage);
+ }
+ }
+ catch( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sd", "SelectionManager::DeleteSelectedNormalPages()");
+ }
+}
+
+void SelectionManager::DeleteSelectedMasterPages (const ::std::vector<SdPage*>& rSelectedPages)
+{
+ // Prepare the deletion via the UNO API.
+ OSL_ASSERT(mrSlideSorter.GetModel().GetEditMode() == EditMode::MasterPage);
+
+ try
+ {
+ Reference<drawing::XMasterPagesSupplier> xDrawPagesSupplier( mrSlideSorter.GetModel().GetDocument()->getUnoModel(), UNO_QUERY_THROW );
+ Reference<drawing::XDrawPages> xPages( xDrawPagesSupplier->getMasterPages(), UNO_SET_THROW );
+
+ // Iterate over all pages that were selected when this method was called
+ // and delete the draw page the notes page. The iteration is done in
+ // reverse order so that when one slide is not deleted (to avoid an
+ // empty document) the remaining slide is the first one.
+ ::std::vector<SdPage*>::const_reverse_iterator aI;
+ for (aI=rSelectedPages.rbegin(); aI!=rSelectedPages.rend(); ++aI)
+ {
+ // Do not delete the last slide in the document.
+ if (xPages->getCount() <= 1)
+ break;
+
+ const sal_uInt16 nPage (model::FromCoreIndex((*aI)->GetPageNum()));
+
+ Reference< XDrawPage > xPage( xPages->getByIndex( nPage ), UNO_QUERY_THROW );
+ xPages->remove(xPage);
+ }
+ }
+ catch( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sd", "SelectionManager::DeleteSelectedMasterPages()");
+ }
+}
+
+void SelectionManager::SelectionHasChanged ()
+{
+ ViewShell* pViewShell = mrSlideSorter.GetViewShell();
+ if (pViewShell == nullptr)
+ return;
+
+ pViewShell->Invalidate (SID_EXPAND_PAGE);
+ pViewShell->Invalidate (SID_SUMMARY_PAGE);
+ pViewShell->Invalidate(SID_SHOW_SLIDE);
+ pViewShell->Invalidate(SID_HIDE_SLIDE);
+ pViewShell->Invalidate(SID_DELETE_PAGE);
+ pViewShell->Invalidate(SID_DELETE_MASTER_PAGE);
+ pViewShell->Invalidate(SID_ASSIGN_LAYOUT);
+
+ // StatusBar
+ pViewShell->Invalidate (SID_STATUS_PAGE);
+ pViewShell->Invalidate (SID_STATUS_LAYOUT);
+ pViewShell->Invalidate (SID_SCALE);
+
+ OSL_ASSERT(mrController.GetCurrentSlideManager());
+ SharedPageDescriptor pDescriptor(mrController.GetCurrentSlideManager()->GetCurrentSlide());
+ if (pDescriptor)
+ pViewShell->UpdatePreview(pDescriptor->GetPage());
+
+ // Tell the selection change listeners that the selection has changed.
+ for (const auto& rLink : maSelectionChangeListeners)
+ {
+ rLink.Call(nullptr);
+ }
+
+ // Reset the insertion position: until set again it is calculated from
+ // the current selection.
+ mnInsertionPosition = -1;
+}
+
+void SelectionManager::AddSelectionChangeListener (const Link<LinkParamNone*,void>& rListener)
+{
+ if (::std::find (
+ maSelectionChangeListeners.begin(),
+ maSelectionChangeListeners.end(),
+ rListener) == maSelectionChangeListeners.end())
+ {
+ maSelectionChangeListeners.push_back (rListener);
+ }
+}
+
+void SelectionManager::RemoveSelectionChangeListener(const Link<LinkParamNone*,void>& rListener)
+{
+ maSelectionChangeListeners.erase (
+ ::std::find (
+ maSelectionChangeListeners.begin(),
+ maSelectionChangeListeners.end(),
+ rListener));
+}
+
+sal_Int32 SelectionManager::GetInsertionPosition() const
+{
+ sal_Int32 nInsertionPosition (mnInsertionPosition);
+ if (nInsertionPosition < 0)
+ {
+ model::PageEnumeration aSelectedPages
+ (model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
+ mrSlideSorter.GetModel()));
+ // Initialize (for the case of an empty selection) with the position
+ // at the end of the document.
+ nInsertionPosition = mrSlideSorter.GetModel().GetPageCount();
+ while (aSelectedPages.HasMoreElements())
+ {
+ const sal_Int32 nPosition (aSelectedPages.GetNextElement()->GetPage()->GetPageNum());
+ // Convert *2+1 index to straight index (n-1)/2 after the page
+ // (+1).
+ nInsertionPosition = model::FromCoreIndex(nPosition) + 1;
+ }
+
+ }
+ return nInsertionPosition;
+}
+
+void SelectionManager::SetInsertionPosition (const sal_Int32 nInsertionPosition)
+{
+ if (nInsertionPosition < 0)
+ mnInsertionPosition = -1;
+ else if (nInsertionPosition > mrSlideSorter.GetModel().GetPageCount())
+ {
+ // Assert but then ignore invalid values.
+ OSL_ASSERT(nInsertionPosition<=mrSlideSorter.GetModel().GetPageCount());
+ return;
+ }
+ else
+ mnInsertionPosition = nInsertionPosition;
+}
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsSelectionObserver.cxx b/sd/source/ui/slidesorter/controller/SlsSelectionObserver.cxx
new file mode 100644
index 000000000..8fb0493a0
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsSelectionObserver.cxx
@@ -0,0 +1,139 @@
+/* -*- 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 <SlideSorter.hxx>
+#include <controller/SlideSorterController.hxx>
+#include <controller/SlsSelectionManager.hxx>
+#include <controller/SlsSelectionObserver.hxx>
+#include <controller/SlsPageSelector.hxx>
+#include <controller/SlsFocusManager.hxx>
+#include <sdpage.hxx>
+#include <osl/diagnose.h>
+
+namespace sd::slidesorter::controller
+{
+SelectionObserver::Context::Context(SlideSorter const& rSlideSorter)
+ : mpSelectionObserver(
+ rSlideSorter.GetController().GetSelectionManager()->GetSelectionObserver())
+{
+ if (mpSelectionObserver)
+ mpSelectionObserver->StartObservation();
+}
+
+SelectionObserver::Context::~Context() COVERITY_NOEXCEPT_FALSE
+{
+ if (mpSelectionObserver)
+ mpSelectionObserver->EndObservation();
+}
+
+void SelectionObserver::Context::Abort()
+{
+ if (mpSelectionObserver)
+ {
+ mpSelectionObserver->AbortObservation();
+ mpSelectionObserver.reset();
+ }
+}
+
+//===== SelectionObserver =====================================================
+
+SelectionObserver::SelectionObserver(SlideSorter& rSlideSorter)
+ : mrSlideSorter(rSlideSorter)
+ , mbIsObservationActive(false)
+ , mbPageEventOccurred(false)
+{
+}
+
+SelectionObserver::~SelectionObserver() {}
+
+void SelectionObserver::NotifyPageEvent(const SdrPage* pSdrPage)
+{
+ if (!mbIsObservationActive)
+ return;
+
+ mbPageEventOccurred = true;
+
+ const SdPage* pPage = dynamic_cast<const SdPage*>(pSdrPage);
+ if (pPage == nullptr)
+ return;
+
+ //NotifyPageEvent is called for add, remove, *and* change position so for
+ //the change position case we must ensure we don't end up with the slide
+ //duplicated in our list
+ std::vector<const SdPage*>::iterator iPage(
+ std::find(maInsertedPages.begin(), maInsertedPages.end(), pPage));
+ if (iPage != maInsertedPages.end())
+ maInsertedPages.erase(iPage);
+
+ if (pPage->IsInserted())
+ maInsertedPages.push_back(pPage);
+}
+
+void SelectionObserver::StartObservation()
+{
+ OSL_ASSERT(!mbIsObservationActive);
+ maInsertedPages.clear();
+ mbIsObservationActive = true;
+}
+
+void SelectionObserver::AbortObservation()
+{
+ OSL_ASSERT(mbIsObservationActive);
+ mbIsObservationActive = false;
+ maInsertedPages.clear();
+}
+
+void SelectionObserver::EndObservation()
+{
+ OSL_ASSERT(mbIsObservationActive);
+ mbIsObservationActive = false;
+
+ if (!mbPageEventOccurred)
+ return;
+
+ PageSelector& rSelector(mrSlideSorter.GetController().GetPageSelector());
+ PageSelector::UpdateLock aUpdateLock(mrSlideSorter);
+ rSelector.DeselectAllPages();
+ if (!maInsertedPages.empty())
+ {
+ // Select the inserted pages.
+ for (const auto& rpPage : maInsertedPages)
+ {
+ rSelector.SelectPage(rpPage);
+ }
+ maInsertedPages.clear();
+ }
+
+ aUpdateLock.Release();
+ FocusManager& rFocusManager = mrSlideSorter.GetController().GetFocusManager();
+ bool bSuccess = rFocusManager.SetFocusedPageToCurrentPage();
+ // tdf#129346 nothing currently selected, select something, if possible
+ // but (tdf#129346) only if setting focus to current page failed
+ if (rSelector.GetPageCount() && rSelector.GetSelectedPageCount() == 0)
+ {
+ if (bSuccess)
+ rSelector.SelectPage(rFocusManager.GetFocusedPageDescriptor());
+ else
+ rSelector.SelectPage(0);
+ }
+}
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsSlotManager.cxx b/sd/source/ui/slidesorter/controller/SlsSlotManager.cxx
new file mode 100644
index 000000000..52e05557e
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsSlotManager.cxx
@@ -0,0 +1,1284 @@
+/* -*- 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 <controller/SlsSlotManager.hxx>
+#include <SlideSorter.hxx>
+#include <SlideSorterViewShell.hxx>
+#include <controller/SlideSorterController.hxx>
+#include <controller/SlsClipboard.hxx>
+#include <controller/SlsCurrentSlideManager.hxx>
+#include <controller/SlsInsertionIndicatorHandler.hxx>
+#include <controller/SlsPageSelector.hxx>
+#include <controller/SlsSelectionFunction.hxx>
+#include <controller/SlsSelectionManager.hxx>
+#include <model/SlideSorterModel.hxx>
+#include <model/SlsPageEnumerationProvider.hxx>
+#include <model/SlsPageDescriptor.hxx>
+#include <view/SlideSorterView.hxx>
+#include <view/SlsLayouter.hxx>
+#include <framework/FrameworkHelper.hxx>
+#include <Window.hxx>
+#include <fupoor.hxx>
+#include <fucushow.hxx>
+#include <fusldlg.hxx>
+#include <fuexpand.hxx>
+#include <fusumry.hxx>
+#include <slideshow.hxx>
+#include <app.hrc>
+#include <strings.hrc>
+#include <sdresid.hxx>
+#include <unokywds.hxx>
+#include <drawdoc.hxx>
+#include <DrawDocShell.hxx>
+#include <ViewShellBase.hxx>
+#include <ViewShellImplementation.hxx>
+#include <sdpage.hxx>
+#include <sdxfer.hxx>
+#include <helpids.h>
+#include <unmodpg.hxx>
+#include <DrawViewShell.hxx>
+#include <sdabstdlg.hxx>
+#include <sdmod.hxx>
+
+#include <vcl/uitest/logger.hxx>
+#include <vcl/uitest/eventdescription.hxx>
+
+#include <sfx2/request.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/sidebar/Sidebar.hxx>
+#include <svx/svxids.hrc>
+#include <svx/svxdlg.hxx>
+#include <svl/intitem.hxx>
+#include <svl/stritem.hxx>
+#include <svl/whiter.hxx>
+#include <svl/itempool.hxx>
+#include <com/sun/star/drawing/XMasterPagesSupplier.hpp>
+#include <com/sun/star/drawing/XDrawPages.hpp>
+#include <osl/diagnose.h>
+
+#include <memory>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::beans;
+
+namespace sd::slidesorter::controller {
+
+namespace {
+
+/** The state of a set of slides with respect to being excluded from the
+ slide show.
+*/
+enum SlideExclusionState {UNDEFINED, EXCLUDED, INCLUDED, MIXED};
+
+/** Return for the given set of slides whether they included are
+ excluded from the slide show.
+*/
+SlideExclusionState GetSlideExclusionState (model::PageEnumeration& rPageSet);
+
+} // end of anonymous namespace
+
+
+namespace {
+
+void collectUIInformation(std::map<OUString, OUString>&& aParameters, const OUString& rAction)
+{
+ EventDescription aDescription;
+ aDescription.aID = "impress_win_or_draw_win";
+ aDescription.aParameters = std::move(aParameters);
+ aDescription.aAction = rAction;
+ aDescription.aKeyWord = "ImpressWindowUIObject";
+ aDescription.aParent = "MainWindow";
+
+ UITestLogger::getInstance().logEvent(aDescription);
+}
+
+}
+
+SlotManager::SlotManager (SlideSorter& rSlideSorter)
+ : mrSlideSorter(rSlideSorter)
+{
+}
+
+void SlotManager::FuTemporary (SfxRequest& rRequest)
+{
+ SdDrawDocument* pDocument = mrSlideSorter.GetModel().GetDocument();
+
+ SlideSorterViewShell* pShell
+ = dynamic_cast<SlideSorterViewShell*>(mrSlideSorter.GetViewShell());
+ if (pShell == nullptr)
+ return;
+
+ switch (rRequest.GetSlot())
+ {
+ case SID_PRESENTATION:
+ case SID_PRESENTATION_CURRENT_SLIDE:
+ case SID_REHEARSE_TIMINGS:
+ slideshowhelp::ShowSlideShow(rRequest, *mrSlideSorter.GetModel().GetDocument());
+ pShell->Cancel();
+ rRequest.Done();
+ break;
+
+ case SID_HIDE_SLIDE:
+ ChangeSlideExclusionState(model::SharedPageDescriptor(), true);
+ break;
+
+ case SID_SHOW_SLIDE:
+ ChangeSlideExclusionState(model::SharedPageDescriptor(), false);
+ break;
+
+ case SID_PAGES_PER_ROW:
+ if (rRequest.GetArgs() != nullptr)
+ {
+ const SfxUInt16Item* pPagesPerRow = rRequest.GetArg<SfxUInt16Item>(SID_PAGES_PER_ROW);
+ if (pPagesPerRow != nullptr)
+ {
+ sal_Int32 nColumnCount = pPagesPerRow->GetValue();
+ // Force the given number of columns by setting
+ // the minimal and maximal number of columns to
+ // the same value.
+ mrSlideSorter.GetView().GetLayouter().SetColumnCount (
+ nColumnCount, nColumnCount);
+ // Force a repaint and re-layout.
+ pShell->ArrangeGUIElements ();
+ // Rearrange the UI-elements controlled by the
+ // controller and force a rearrangement of the
+ // view.
+ mrSlideSorter.GetController().Rearrange(true);
+ }
+ }
+ rRequest.Done();
+ break;
+
+ case SID_SELECTALL:
+ mrSlideSorter.GetController().GetPageSelector().SelectAllPages();
+ rRequest.Done();
+ break;
+
+ case SID_SLIDE_TRANSITIONS_PANEL:
+ {
+ // First make sure that the sidebar is visible
+ pShell->GetViewFrame()->ShowChildWindow(SID_SIDEBAR);
+ ::sfx2::sidebar::Sidebar::ShowPanel(
+ u"SdSlideTransitionPanel",
+ pShell->GetViewFrame()->GetFrame().GetFrameInterface());
+ rRequest.Ignore ();
+ break;
+ }
+
+ case SID_MASTER_SLIDES_PANEL:
+ {
+ // First make sure that the sidebar is visible
+ pShell->GetViewFrame()->ShowChildWindow(SID_SIDEBAR);
+ ::sfx2::sidebar::Sidebar::ShowPanel(
+ u"SdAllMasterPagesPanel",
+ pShell->GetViewFrame()->GetFrame().GetFrameInterface());
+ rRequest.Ignore ();
+ break;
+ }
+
+ case SID_PRESENTATION_DLG:
+ FuSlideShowDlg::Create (
+ pShell,
+ mrSlideSorter.GetContentWindow(),
+ &mrSlideSorter.GetView(),
+ pDocument,
+ rRequest);
+ break;
+
+ case SID_CUSTOMSHOW_DLG:
+ FuCustomShowDlg::Create (
+ pShell,
+ mrSlideSorter.GetContentWindow(),
+ &mrSlideSorter.GetView(),
+ pDocument,
+ rRequest);
+ break;
+
+ case SID_EXPAND_PAGE:
+ FuExpandPage::Create (
+ pShell,
+ mrSlideSorter.GetContentWindow(),
+ &mrSlideSorter.GetView(),
+ pDocument,
+ rRequest);
+ break;
+
+ case SID_SUMMARY_PAGE:
+ FuSummaryPage::Create (
+ pShell,
+ mrSlideSorter.GetContentWindow(),
+ &mrSlideSorter.GetView(),
+ pDocument,
+ rRequest);
+ break;
+
+ case SID_INSERTPAGE:
+ case SID_INSERT_MASTER_PAGE:
+ InsertSlide(rRequest);
+ rRequest.Done();
+ break;
+
+ case SID_DUPLICATE_PAGE:
+ DuplicateSelectedSlides(rRequest);
+ rRequest.Done();
+ break;
+
+ case SID_DELETE_PAGE:
+ case SID_DELETE_MASTER_PAGE:
+ case SID_DELETE: // we need SID_CUT to handle the delete key
+ // (DEL -> accelerator -> SID_CUT).
+ if (mrSlideSorter.GetModel().GetPageCount() > 1)
+ {
+ mrSlideSorter.GetView().EndTextEditAllViews();
+ mrSlideSorter.GetController().GetSelectionManager()->DeleteSelectedPages();
+ }
+
+ rRequest.Done();
+ break;
+
+ case SID_RENAMEPAGE:
+ case SID_RENAME_MASTER_PAGE:
+ RenameSlide (rRequest);
+ rRequest.Done ();
+ break;
+
+ case SID_ASSIGN_LAYOUT:
+ {
+ pShell->mpImpl->AssignLayout( rRequest, PageKind::Standard );
+ rRequest.Done ();
+ }
+ break;
+
+ case SID_PHOTOALBUM:
+ {
+ SdAbstractDialogFactory* pFact = SdAbstractDialogFactory::Create();
+ vcl::Window* pWin = mrSlideSorter.GetContentWindow();
+ ScopedVclPtr<VclAbstractDialog> pDlg(pFact->CreateSdPhotoAlbumDialog(
+ pWin ? pWin->GetFrameWeld() : nullptr,
+ pDocument));
+ pDlg->Execute();
+ rRequest.Done ();
+ }
+ break;
+
+ case SID_REMOTE_DLG:
+ {
+#ifdef ENABLE_SDREMOTE
+ SdAbstractDialogFactory* pFact = SdAbstractDialogFactory::Create();
+ vcl::Window* pWin = mrSlideSorter.GetContentWindow();
+ ScopedVclPtr<VclAbstractDialog> pDlg(pFact->CreateRemoteDialog(pWin ? pWin->GetFrameWeld() : nullptr));
+ pDlg->Execute();
+#endif
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+void SlotManager::FuPermanent (SfxRequest& rRequest)
+{
+ ViewShell* pShell = mrSlideSorter.GetViewShell();
+ if (pShell == nullptr)
+ return;
+
+ if(pShell->GetCurrentFunction().is())
+ {
+ rtl::Reference<FuPoor> xEmpty;
+ if (pShell->GetOldFunction() == pShell->GetCurrentFunction())
+ pShell->SetOldFunction(xEmpty);
+
+ pShell->GetCurrentFunction()->Deactivate();
+ pShell->SetCurrentFunction(xEmpty);
+ }
+
+ switch(rRequest.GetSlot())
+ {
+ case SID_OBJECT_SELECT:
+ pShell->SetCurrentFunction( SelectionFunction::Create(mrSlideSorter, rRequest) );
+ rRequest.Done();
+ break;
+
+ default:
+ break;
+ }
+
+ if(pShell->GetOldFunction().is())
+ {
+ pShell->GetOldFunction()->Deactivate();
+ rtl::Reference<FuPoor> xEmpty;
+ pShell->SetOldFunction(xEmpty);
+ }
+
+ if(pShell->GetCurrentFunction().is())
+ {
+ pShell->GetCurrentFunction()->Activate();
+ pShell->SetOldFunction(pShell->GetCurrentFunction());
+ }
+
+ //! that's only until ENUM-Slots ?are
+ // Invalidate( SID_OBJECT_SELECT );
+}
+
+void SlotManager::FuSupport (SfxRequest& rRequest)
+{
+ switch (rRequest.GetSlot())
+ {
+ case SID_STYLE_FAMILY:
+ if (rRequest.GetArgs() != nullptr)
+ {
+ SdDrawDocument* pDocument
+ = mrSlideSorter.GetModel().GetDocument();
+ if (pDocument != nullptr)
+ {
+ const SfxPoolItem& rItem (
+ rRequest.GetArgs()->Get(SID_STYLE_FAMILY));
+ pDocument->GetDocSh()->SetStyleFamily(
+ static_cast<SfxStyleFamily>(static_cast<const SfxUInt16Item&>(rItem).GetValue()));
+ }
+ }
+ break;
+
+ case SID_PASTE:
+ {
+ SdTransferable* pTransferClip = SD_MOD()->pTransferClip;
+ if( pTransferClip )
+ {
+ SfxObjectShell* pTransferDocShell = pTransferClip->GetDocShell().get();
+
+ DrawDocShell* pDocShell = dynamic_cast<DrawDocShell*>(pTransferDocShell);
+ if (pDocShell && pDocShell->GetDoc()->GetPageCount() > 1)
+ {
+ mrSlideSorter.GetController().GetClipboard().HandleSlotCall(rRequest);
+ break;
+ }
+ }
+ ViewShellBase* pBase = mrSlideSorter.GetViewShellBase();
+ if (pBase != nullptr)
+ {
+ std::shared_ptr<DrawViewShell> pDrawViewShell (
+ std::dynamic_pointer_cast<DrawViewShell>(pBase->GetMainViewShell()));
+ if (pDrawViewShell != nullptr)
+ pDrawViewShell->FuSupport(rRequest);
+ }
+ }
+ break;
+
+ case SID_CUT:
+ case SID_COPY:
+ case SID_DELETE:
+ mrSlideSorter.GetView().EndTextEditAllViews();
+ mrSlideSorter.GetController().GetClipboard().HandleSlotCall(rRequest);
+ break;
+
+ case SID_DRAWINGMODE:
+ case SID_NOTES_MODE:
+ case SID_HANDOUT_MASTER_MODE:
+ case SID_SLIDE_SORTER_MODE:
+ case SID_OUTLINE_MODE:
+ {
+ ViewShellBase* pBase = mrSlideSorter.GetViewShellBase();
+ if (pBase != nullptr)
+ {
+ framework::FrameworkHelper::Instance(*pBase)->HandleModeChangeSlot(
+ rRequest.GetSlot(), rRequest);
+ rRequest.Done();
+ }
+ break;
+ }
+
+ case SID_UNDO:
+ {
+ SlideSorterViewShell* pViewShell
+ = dynamic_cast<SlideSorterViewShell*>(mrSlideSorter.GetViewShell());
+ if (pViewShell != nullptr)
+ {
+ pViewShell->ImpSidUndo (rRequest);
+ }
+ break;
+ }
+
+ case SID_REDO:
+ {
+ SlideSorterViewShell* pViewShell
+ = dynamic_cast<SlideSorterViewShell*>(mrSlideSorter.GetViewShell());
+ if (pViewShell != nullptr)
+ {
+ pViewShell->ImpSidRedo (rRequest);
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+void SlotManager::ExecCtrl (SfxRequest& rRequest)
+{
+ ViewShell* pViewShell = mrSlideSorter.GetViewShell();
+ sal_uInt16 nSlot = rRequest.GetSlot();
+ switch (nSlot)
+ {
+ case SID_RELOAD:
+ {
+ // empty Undo-Manager
+ mrSlideSorter.GetModel().GetDocument()->GetDocSh()->ClearUndoBuffer();
+
+ // normal forwarding to ViewFrame for execution
+ if (pViewShell != nullptr)
+ pViewShell->GetViewFrame()->ExecuteSlot(rRequest);
+
+ // has to be finished right away
+ return;
+ }
+
+ case SID_OUTPUT_QUALITY_COLOR:
+ case SID_OUTPUT_QUALITY_GRAYSCALE:
+ case SID_OUTPUT_QUALITY_BLACKWHITE:
+ case SID_OUTPUT_QUALITY_CONTRAST:
+ {
+ // flush page cache
+ if (pViewShell != nullptr)
+ pViewShell->ExecReq (rRequest);
+ break;
+ }
+
+ case SID_MAIL_SCROLLBODY_PAGEDOWN:
+ {
+ if (pViewShell != nullptr)
+ pViewShell->ExecReq (rRequest);
+ break;
+ }
+
+ case SID_OPT_LOCALE_CHANGED:
+ {
+ mrSlideSorter.GetController().UpdateAllPages();
+ if (pViewShell != nullptr)
+ pViewShell->UpdatePreview (pViewShell->GetActualPage());
+ rRequest.Done();
+ break;
+ }
+
+ case SID_SEARCH_DLG:
+ // We have to handle the SID_SEARCH_DLG slot explicitly because
+ // in some cases (when the slide sorter is displayed in the
+ // center pane) we want to disable the search dialog. Therefore
+ // we have to handle the execution of that slot as well.
+ // We try to do that by forwarding the request to the view frame
+ // of the view shell.
+ if (pViewShell != nullptr)
+ pViewShell->GetViewFrame()->ExecuteSlot(rRequest);
+ break;
+
+ default:
+ break;
+ }
+}
+
+void SlotManager::GetAttrState (SfxItemSet& rSet)
+{
+ // Iterate over all items.
+ SfxWhichIter aIter (rSet);
+ sal_uInt16 nWhich = aIter.FirstWhich();
+ while (nWhich)
+ {
+ sal_uInt16 nSlotId (nWhich);
+ if (SfxItemPool::IsWhich(nWhich) && mrSlideSorter.GetViewShell()!=nullptr)
+ nSlotId = mrSlideSorter.GetViewShell()->GetPool().GetSlotId(nWhich);
+ switch (nSlotId)
+ {
+ case SID_PAGES_PER_ROW:
+ rSet.Put (
+ SfxUInt16Item (
+ nSlotId,
+ static_cast<sal_uInt16>(mrSlideSorter.GetView().GetLayouter().GetColumnCount())
+ )
+ );
+ break;
+ }
+ nWhich = aIter.NextWhich();
+ }
+}
+
+void SlotManager::GetMenuState (SfxItemSet& rSet)
+{
+ EditMode eEditMode = mrSlideSorter.GetModel().GetEditMode();
+ ViewShell* pShell = mrSlideSorter.GetViewShell();
+ DrawDocShell* pDocShell = mrSlideSorter.GetModel().GetDocument()->GetDocSh();
+
+ if (pShell!=nullptr && pShell->GetCurrentFunction().is())
+ {
+ sal_uInt16 nSId = pShell->GetCurrentFunction()->GetSlotID();
+
+ rSet.Put( SfxBoolItem( nSId, true ) );
+ }
+ rSet.Put( SfxBoolItem( SID_DRAWINGMODE, false ) );
+ rSet.Put( SfxBoolItem( SID_SLIDE_SORTER_MODE, true ) );
+ rSet.Put( SfxBoolItem( SID_OUTLINE_MODE, false ) );
+ rSet.Put( SfxBoolItem( SID_NOTES_MODE, false ) );
+ rSet.Put( SfxBoolItem( SID_HANDOUT_MASTER_MODE, false ) );
+
+ if (pShell!=nullptr && pShell->IsMainViewShell())
+ {
+ rSet.DisableItem(SID_SPELL_DIALOG);
+ rSet.DisableItem(SID_SEARCH_DLG);
+ }
+
+ if (SfxItemState::DEFAULT == rSet.GetItemState(SID_EXPAND_PAGE))
+ {
+ bool bDisable = true;
+ if (eEditMode == EditMode::Page)
+ {
+ // At least one of the selected pages has to contain an outline
+ // presentation objects in order to enable the expand page menu
+ // entry.
+ model::PageEnumeration aSelectedPages (
+ model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
+ mrSlideSorter.GetModel()));
+ while (aSelectedPages.HasMoreElements())
+ {
+ SdPage* pPage = aSelectedPages.GetNextElement()->GetPage();
+ SdrObject* pObj = pPage->GetPresObj(PresObjKind::Outline);
+ if (pObj!=nullptr )
+ {
+ if( !pObj->IsEmptyPresObj() )
+ {
+ bDisable = false;
+ }
+ else
+ {
+ // check if the object is in edit, then if it's temporarily not empty
+ SdrTextObj* pTextObj = dynamic_cast< SdrTextObj* >( pObj );
+ if( pTextObj )
+ {
+ if( pTextObj->CanCreateEditOutlinerParaObject() )
+ {
+ bDisable = false;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (bDisable)
+ rSet.DisableItem (SID_EXPAND_PAGE);
+ }
+
+ if (SfxItemState::DEFAULT == rSet.GetItemState(SID_SUMMARY_PAGE))
+ {
+ bool bDisable = true;
+ if (eEditMode == EditMode::Page)
+ {
+ // At least one of the selected pages has to contain a title
+ // presentation objects in order to enable the summary page menu
+ // entry.
+ model::PageEnumeration aSelectedPages (
+ model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
+ mrSlideSorter.GetModel()));
+ while (aSelectedPages.HasMoreElements())
+ {
+ SdPage* pPage = aSelectedPages.GetNextElement()->GetPage();
+ SdrObject* pObj = pPage->GetPresObj(PresObjKind::Title);
+
+ if (pObj!=nullptr && !pObj->IsEmptyPresObj())
+ bDisable = false;
+ }
+ }
+ if (bDisable)
+ rSet.DisableItem (SID_SUMMARY_PAGE);
+ }
+
+ // starting of presentation possible?
+ if( SfxItemState::DEFAULT == rSet.GetItemState( SID_PRESENTATION ) ||
+ SfxItemState::DEFAULT == rSet.GetItemState( SID_REHEARSE_TIMINGS ) )
+ {
+ bool bDisable = true;
+ model::PageEnumeration aAllPages (
+ model::PageEnumerationProvider::CreateAllPagesEnumeration(mrSlideSorter.GetModel()));
+ while (aAllPages.HasMoreElements())
+ {
+ SdPage* pPage = aAllPages.GetNextElement()->GetPage();
+
+ if( !pPage->IsExcluded() )
+ bDisable = false;
+ }
+ if( bDisable || pDocShell->IsPreview())
+ {
+ rSet.DisableItem( SID_PRESENTATION );
+ rSet.DisableItem( SID_REHEARSE_TIMINGS );
+ }
+ }
+
+ // Disable the rename slots when there are no or more than one slides/master
+ // pages selected; disable the duplicate slot when there are no slides
+ // selected:
+ if (rSet.GetItemState(SID_RENAMEPAGE) == SfxItemState::DEFAULT
+ || rSet.GetItemState(SID_RENAME_MASTER_PAGE) == SfxItemState::DEFAULT
+ || rSet.GetItemState(SID_DUPLICATE_PAGE) == SfxItemState::DEFAULT)
+ {
+ int n = mrSlideSorter.GetController().GetPageSelector()
+ .GetSelectedPageCount();
+ if (n != 1)
+ {
+ rSet.DisableItem(SID_RENAMEPAGE);
+ rSet.DisableItem(SID_RENAME_MASTER_PAGE);
+ }
+ if (n == 0)
+ {
+ rSet.DisableItem(SID_DUPLICATE_PAGE);
+ }
+ }
+
+ if (rSet.GetItemState(SID_HIDE_SLIDE) == SfxItemState::DEFAULT
+ || rSet.GetItemState(SID_SHOW_SLIDE) == SfxItemState::DEFAULT)
+ {
+ model::PageEnumeration aSelectedPages (
+ model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
+ mrSlideSorter.GetModel()));
+ const SlideExclusionState eState (GetSlideExclusionState(aSelectedPages));
+ switch (eState)
+ {
+ case MIXED:
+ // Show both entries.
+ break;
+
+ case EXCLUDED:
+ rSet.DisableItem(SID_HIDE_SLIDE);
+ break;
+
+ case INCLUDED:
+ rSet.DisableItem(SID_SHOW_SLIDE);
+ break;
+
+ case UNDEFINED:
+ rSet.DisableItem(SID_HIDE_SLIDE);
+ rSet.DisableItem(SID_SHOW_SLIDE);
+ break;
+ }
+ }
+
+ if (eEditMode == EditMode::MasterPage)
+ {
+ // Disable some slots when in master page mode.
+ rSet.DisableItem(SID_ASSIGN_LAYOUT);
+ rSet.DisableItem(SID_INSERTPAGE);
+
+ if (rSet.GetItemState(SID_DUPLICATE_PAGE) == SfxItemState::DEFAULT)
+ rSet.DisableItem(SID_DUPLICATE_PAGE);
+ }
+}
+
+void SlotManager::GetClipboardState ( SfxItemSet& rSet)
+{
+ SdTransferable* pTransferClip = SD_MOD()->pTransferClip;
+
+ if (rSet.GetItemState(SID_PASTE) == SfxItemState::DEFAULT
+ || rSet.GetItemState(SID_PASTE_SPECIAL) == SfxItemState::DEFAULT)
+ {
+ // no own clipboard data?
+ if ( !pTransferClip || !pTransferClip->GetDocShell().is() )
+ {
+ rSet.DisableItem(SID_PASTE);
+ rSet.DisableItem(SID_PASTE_SPECIAL);
+ }
+ else
+ {
+ SfxObjectShell* pTransferDocShell = pTransferClip->GetDocShell().get();
+
+ if( !pTransferDocShell || static_cast<DrawDocShell*>(pTransferDocShell)->GetDoc()->GetPageCount() <= 1 )
+ {
+ bool bIsPastingSupported (false);
+
+ // No or just one page. Check if there is anything that can be
+ // pasted via a DrawViewShell.
+ ViewShellBase* pBase = mrSlideSorter.GetViewShellBase();
+ if (pBase != nullptr)
+ {
+ std::shared_ptr<DrawViewShell> pDrawViewShell (
+ std::dynamic_pointer_cast<DrawViewShell>(pBase->GetMainViewShell()));
+ if (pDrawViewShell != nullptr)
+ {
+ TransferableDataHelper aDataHelper (
+ TransferableDataHelper::CreateFromSystemClipboard(
+ pDrawViewShell->GetActiveWindow()));
+ if (aDataHelper.GetFormatCount() > 0)
+ bIsPastingSupported = true;
+ }
+ }
+
+ if ( ! bIsPastingSupported)
+ {
+ rSet.DisableItem(SID_PASTE);
+ rSet.DisableItem(SID_PASTE_SPECIAL);
+ }
+ }
+ }
+ }
+
+ // Cut, copy and paste of master pages is not yet implemented properly
+ if (rSet.GetItemState(SID_COPY) == SfxItemState::DEFAULT
+ || rSet.GetItemState(SID_PASTE) == SfxItemState::DEFAULT
+ || rSet.GetItemState(SID_PASTE_SPECIAL) == SfxItemState::DEFAULT
+ || rSet.GetItemState(SID_CUT) == SfxItemState::DEFAULT)
+ {
+ if (mrSlideSorter.GetModel().GetEditMode() == EditMode::MasterPage)
+ {
+ if (rSet.GetItemState(SID_CUT) == SfxItemState::DEFAULT)
+ rSet.DisableItem(SID_CUT);
+ if (rSet.GetItemState(SID_COPY) == SfxItemState::DEFAULT)
+ rSet.DisableItem(SID_COPY);
+ if (rSet.GetItemState(SID_PASTE) == SfxItemState::DEFAULT)
+ rSet.DisableItem(SID_PASTE);
+ if (rSet.GetItemState(SID_PASTE_SPECIAL) == SfxItemState::DEFAULT)
+ rSet.DisableItem(SID_PASTE_SPECIAL);
+ }
+ }
+
+ ViewShellBase* pBase = mrSlideSorter.GetViewShellBase();
+ if (pBase && pBase->GetObjectShell()->isContentExtractionLocked())
+ {
+ rSet.DisableItem(SID_COPY);
+ rSet.DisableItem(SID_CUT);
+ }
+
+ // Cut, copy, and delete page are disabled when there is no selection.
+ if (!(rSet.GetItemState(SID_CUT) == SfxItemState::DEFAULT
+ || rSet.GetItemState(SID_COPY) == SfxItemState::DEFAULT
+ || rSet.GetItemState(SID_DELETE) == SfxItemState::DEFAULT
+ || rSet.GetItemState(SID_DELETE_PAGE) == SfxItemState::DEFAULT
+ || rSet.GetItemState(SID_DELETE_MASTER_PAGE) == SfxItemState::DEFAULT))
+ return;
+
+ model::PageEnumeration aSelectedPages (
+ model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
+ mrSlideSorter.GetModel()));
+
+ // For copy to work we have to have at least one selected page.
+ if ( ! aSelectedPages.HasMoreElements())
+ rSet.DisableItem(SID_COPY);
+
+ bool bDisable = false;
+ // The operations that lead to the deletion of a page are valid if
+ // a) there is at least one selected page
+ // b) deleting the selected pages leaves at least one page in the
+ // document
+ // c) selected master pages must not be used by slides.
+
+ // Test a).
+ if ( ! aSelectedPages.HasMoreElements())
+ bDisable = true;
+ // Test b): Count the number of selected pages. It has to be less
+ // than the number of all pages.
+ else if (mrSlideSorter.GetController().GetPageSelector().GetSelectedPageCount()
+ >= mrSlideSorter.GetController().GetPageSelector().GetPageCount())
+ bDisable = true;
+ // Test c): Iterate over the selected pages and look for a master
+ // page that is used by at least one page.
+ else while (aSelectedPages.HasMoreElements())
+ {
+ SdPage* pPage = aSelectedPages.GetNextElement()->GetPage();
+ int nUseCount (mrSlideSorter.GetModel().GetDocument()
+ ->GetMasterPageUserCount(pPage));
+ if (nUseCount > 0)
+ {
+ bDisable = true;
+ break;
+ }
+ }
+
+ if (bDisable)
+ {
+ rSet.DisableItem(SID_CUT);
+ rSet.DisableItem(SID_DELETE_PAGE);
+ rSet.DisableItem(SID_DELETE_MASTER_PAGE);
+ }
+}
+
+void SlotManager::GetStatusBarState (SfxItemSet& rSet)
+{
+ // page view and layout
+ SdPage* pPage = nullptr;
+ sal_uInt16 nSelectedPages = mrSlideSorter.GetController().GetPageSelector().GetSelectedPageCount();
+
+ //Set number of slides
+ if (nSelectedPages > 0)
+ {
+ model::PageEnumeration aSelectedPages (
+ model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
+ mrSlideSorter.GetModel()));
+ model::SharedPageDescriptor pDescriptor (aSelectedPages.GetNextElement());
+ OUString aPageStr;
+ if (pDescriptor)
+ {
+ pPage = pDescriptor->GetPage();
+ sal_uInt16 nFirstPage = (pPage->GetPageNum()/2) + 1;
+ sal_Int32 nPageCount = mrSlideSorter.GetModel().GetPageCount();
+ sal_Int32 nActivePageCount = static_cast<sal_Int32>(mrSlideSorter.GetModel().GetDocument()->GetActiveSdPageCount());
+
+ aPageStr = (nPageCount == nActivePageCount) ? SdResId(STR_SD_PAGE_COUNT) : SdResId(STR_SD_PAGE_COUNT_CUSTOM);
+
+ aPageStr = aPageStr.replaceFirst("%1", OUString::number(nFirstPage));
+ aPageStr = aPageStr.replaceFirst("%2", OUString::number(nPageCount));
+ if(nPageCount != nActivePageCount)
+ aPageStr = aPageStr.replaceFirst("%3", OUString::number(nActivePageCount));
+ }
+ rSet.Put( SfxStringItem( SID_STATUS_PAGE, aPageStr ) );
+ }
+ //Set layout
+ if (nSelectedPages == 1 && pPage != nullptr)
+ {
+ SdPage* pFirstPage = pPage;
+ OUString aLayoutStr = pFirstPage->GetLayoutName();
+ sal_Int32 nIndex = aLayoutStr.indexOf( SD_LT_SEPARATOR );
+ if( nIndex != -1 )
+ aLayoutStr = aLayoutStr.copy(0, nIndex);
+ rSet.Put( SfxStringItem( SID_STATUS_LAYOUT, aLayoutStr ) );
+ }
+ //Scale value
+ const Fraction& aUIScale = mrSlideSorter.GetModel().GetDocument()->GetUIScale();
+ OUString aString = OUString::number(aUIScale.GetNumerator()) +
+ ":" + OUString::number(aUIScale.GetDenominator());
+ rSet.Put( SfxStringItem( SID_SCALE, aString ) );
+}
+
+void SlotManager::RenameSlide(const SfxRequest& rRequest)
+{
+ View* pDrView = &mrSlideSorter.GetView();
+
+ if ( pDrView->IsTextEdit() )
+ {
+ pDrView->SdrEndTextEdit();
+ }
+
+ SdPage* pSelectedPage = nullptr;
+ model::PageEnumeration aSelectedPages (
+ model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
+ mrSlideSorter.GetModel()));
+ if (aSelectedPages.HasMoreElements())
+ pSelectedPage = aSelectedPages.GetNextElement()->GetPage();
+ if (pSelectedPage == nullptr)
+ return;
+
+ // tdf#107183 Set different dialog titles when renaming
+ // master slides or normal ones
+ OUString aTitle;
+ if( rRequest.GetSlot() == SID_RENAME_MASTER_PAGE )
+ aTitle = SdResId( STR_TITLE_RENAMEMASTER );
+ else if (pDrView->GetDoc().GetDocumentType() == DocumentType::Draw)
+ aTitle = SdResId( STR_TITLE_RENAMEPAGE );
+ else
+ aTitle = SdResId( STR_TITLE_RENAMESLIDE );
+
+ OUString aDescr( SdResId( STR_DESC_RENAMESLIDE ) );
+ OUString aPageName = pSelectedPage->GetName();
+
+ if(rRequest.GetArgs())
+ {
+ OUString aName = rRequest.GetArgs()->GetItem<const SfxStringItem>(SID_RENAMEPAGE)->GetValue();
+
+ bool bResult = RenameSlideFromDrawViewShell(pSelectedPage->GetPageNum()/2, aName );
+ DBG_ASSERT( bResult, "Couldn't rename slide or page" );
+ }
+ else
+ {
+ SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create();
+ vcl::Window* pWin = mrSlideSorter.GetContentWindow();
+ ScopedVclPtr<AbstractSvxNameDialog> aNameDlg(pFact->CreateSvxNameDialog(
+ pWin ? pWin->GetFrameWeld() : nullptr,
+ aPageName, aDescr));
+ OUString aOldName;
+ aNameDlg->GetName( aOldName );
+ aNameDlg->SetText( aTitle );
+ aNameDlg->SetCheckNameHdl( LINK( this, SlotManager, RenameSlideHdl ), true );
+ aNameDlg->SetCheckNameTooltipHdl( LINK( this, SlotManager, RenameSlideTooltipHdl ) );
+ aNameDlg->SetEditHelpId( HID_SD_NAMEDIALOG_PAGE );
+
+ if( aNameDlg->Execute() == RET_OK )
+ {
+ OUString aNewName;
+ aNameDlg->GetName( aNewName );
+ if (aNewName != aPageName)
+ {
+ bool bResult =
+ RenameSlideFromDrawViewShell(
+ pSelectedPage->GetPageNum()/2, aNewName );
+ DBG_ASSERT( bResult, "Couldn't rename slide or page" );
+ }
+ }
+ OUString aNewName;
+ aNameDlg->GetName( aNewName );
+ collectUIInformation({{"OldName", aOldName}, {"NewName", aNewName}}, "RENAME");
+ aNameDlg.disposeAndClear();
+ }
+ // Tell the slide sorter about the name change (necessary for
+ // accessibility.)
+ mrSlideSorter.GetController().PageNameHasChanged(
+ (pSelectedPage->GetPageNum()-1)/2, aPageName);
+}
+
+IMPL_LINK(SlotManager, RenameSlideHdl, AbstractSvxNameDialog&, rDialog, bool)
+{
+ OUString aNewName;
+ rDialog.GetName( aNewName );
+
+ model::SharedPageDescriptor pDescriptor (
+ mrSlideSorter.GetController().GetCurrentSlideManager()->GetCurrentSlide());
+ SdPage* pCurrentPage = nullptr;
+ if (pDescriptor)
+ pCurrentPage = pDescriptor->GetPage();
+
+ return (pCurrentPage!=nullptr && aNewName == pCurrentPage->GetName())
+ || (mrSlideSorter.GetViewShell()
+ && mrSlideSorter.GetViewShell()->GetDocSh()->IsNewPageNameValid( aNewName ) );
+}
+
+IMPL_STATIC_LINK_NOARG(SlotManager, RenameSlideTooltipHdl, AbstractSvxNameDialog&, OUString)
+{
+ return SdResId(STR_TOOLTIP_RENAME);
+}
+
+bool SlotManager::RenameSlideFromDrawViewShell( sal_uInt16 nPageId, const OUString & rName )
+{
+ bool bOutDummy;
+ SdDrawDocument* pDocument = mrSlideSorter.GetModel().GetDocument();
+ if( pDocument->GetPageByName( rName, bOutDummy ) != SDRPAGE_NOTFOUND )
+ return false;
+
+ SdPage* pPageToRename = nullptr;
+
+ SfxUndoManager* pManager = pDocument->GetDocSh()->GetUndoManager();
+
+ if( mrSlideSorter.GetModel().GetEditMode() == EditMode::Page )
+ {
+ model::SharedPageDescriptor pDescriptor (
+ mrSlideSorter.GetController().GetCurrentSlideManager()->GetCurrentSlide());
+ if (pDescriptor)
+ pPageToRename = pDescriptor->GetPage();
+
+ if (pPageToRename != nullptr)
+ {
+ // Undo
+ SdPage* pUndoPage = pPageToRename;
+ SdrLayerAdmin & rLayerAdmin = pDocument->GetLayerAdmin();
+ SdrLayerID nBackground = rLayerAdmin.GetLayerID(sUNO_LayerName_background);
+ SdrLayerID nBgObj = rLayerAdmin.GetLayerID(sUNO_LayerName_background_objects);
+ SdrLayerIDSet aVisibleLayers = pPageToRename->TRG_GetMasterPageVisibleLayers();
+
+ // (#67720#)
+ pManager->AddUndoAction(
+ std::make_unique<ModifyPageUndoAction>(
+ pDocument, pUndoPage, rName, pUndoPage->GetAutoLayout(),
+ aVisibleLayers.IsSet( nBackground ),
+ aVisibleLayers.IsSet( nBgObj )));
+
+ // rename
+ pPageToRename->SetName( rName );
+
+ // also rename notes-page
+ SdPage* pNotesPage = pDocument->GetSdPage( nPageId, PageKind::Notes );
+ if (pNotesPage != nullptr)
+ pNotesPage->SetName (rName);
+ }
+ }
+ else
+ {
+ // rename MasterPage -> rename LayoutTemplate
+ pPageToRename = pDocument->GetMasterSdPage( nPageId, PageKind::Standard );
+ if (pPageToRename != nullptr)
+ {
+ const OUString aOldLayoutName( pPageToRename->GetLayoutName() );
+ pManager->AddUndoAction( std::make_unique<RenameLayoutTemplateUndoAction>( pDocument, aOldLayoutName, rName ) );
+ pDocument->RenameLayoutTemplate( aOldLayoutName, rName );
+ }
+ }
+
+ bool bSuccess = pPageToRename!=nullptr && ( rName == pPageToRename->GetName() );
+
+ if( bSuccess )
+ {
+ // user edited page names may be changed by the page so update control
+ // aTabControl.SetPageText( nPageId, rName );
+
+ // set document to modified state
+ pDocument->SetChanged();
+
+ // inform navigator about change
+ if (mrSlideSorter.GetViewShell() && mrSlideSorter.GetViewShell()->GetViewFrame())
+ mrSlideSorter.GetViewShell()->GetViewFrame()->GetBindings().Invalidate(
+ SID_NAVIGATOR_STATE, true);
+ }
+
+ return bSuccess;
+}
+
+/** Insert a slide. The insertion position depends on a) the selection and
+ b) the mouse position when there is no selection.
+
+ When there is a selection then insertion takes place after the last
+ slide of the selection. For this to work all but the last selected
+ slide are deselected first.
+
+ Otherwise, when there is no selection but the insertion marker is visible
+ the slide is inserted at that position. The slide before that marker is
+ selected first.
+
+ When both the selection and the insertion marker are not visible--can
+ that happen?--the new slide is inserted after the last slide.
+*/
+void SlotManager::InsertSlide (SfxRequest& rRequest)
+{
+ const sal_Int32 nInsertionIndex (GetInsertionPosition());
+
+ PageSelector::BroadcastLock aBroadcastLock (mrSlideSorter);
+
+ SdPage* pNewPage = nullptr;
+ if (mrSlideSorter.GetModel().GetEditMode() == EditMode::Page)
+ {
+ SlideSorterViewShell* pShell = dynamic_cast<SlideSorterViewShell*>(
+ mrSlideSorter.GetViewShell());
+ if (pShell != nullptr)
+ {
+ pNewPage = pShell->CreateOrDuplicatePage (
+ rRequest,
+ PageKind::Standard,
+ nInsertionIndex>=0
+ ? mrSlideSorter.GetModel().GetPageDescriptor(nInsertionIndex)->GetPage()
+ : nullptr);
+ }
+ }
+ else
+ {
+ // Use the API to create a new page.
+ SdDrawDocument* pDocument = mrSlideSorter.GetModel().GetDocument();
+ Reference<drawing::XMasterPagesSupplier> xMasterPagesSupplier (
+ pDocument->getUnoModel(), UNO_QUERY);
+ if (xMasterPagesSupplier.is())
+ {
+ Reference<drawing::XDrawPages> xMasterPages (
+ xMasterPagesSupplier->getMasterPages());
+ if (xMasterPages.is())
+ {
+ xMasterPages->insertNewByIndex (nInsertionIndex+1);
+
+ // Create shapes for the default layout.
+ pNewPage = pDocument->GetMasterSdPage(
+ static_cast<sal_uInt16>(nInsertionIndex+1), PageKind::Standard);
+ pNewPage->CreateTitleAndLayout (true,true);
+ }
+ }
+ }
+ if (pNewPage == nullptr)
+ return;
+
+ // When a new page has been inserted then select it, make it the
+ // current page, and focus it.
+ view::SlideSorterView::DrawLock aDrawLock (mrSlideSorter);
+ PageSelector::UpdateLock aUpdateLock (mrSlideSorter);
+ mrSlideSorter.GetController().GetPageSelector().DeselectAllPages();
+ mrSlideSorter.GetController().GetPageSelector().SelectPage(pNewPage);
+ collectUIInformation({{"POS", OUString::number(nInsertionIndex + 2)}}, "Insert_New_Page_or_Slide");
+}
+
+void SlotManager::DuplicateSelectedSlides (SfxRequest& rRequest)
+{
+ // Create a list of the pages that are to be duplicated. The process of
+ // duplication alters the selection.
+ sal_Int32 nInsertPosition (0);
+ ::std::vector<SdPage*> aPagesToDuplicate;
+ model::PageEnumeration aSelectedPages (
+ model::PageEnumerationProvider::CreateSelectedPagesEnumeration(mrSlideSorter.GetModel()));
+ while (aSelectedPages.HasMoreElements())
+ {
+ model::SharedPageDescriptor pDescriptor (aSelectedPages.GetNextElement());
+ if (pDescriptor && pDescriptor->GetPage())
+ {
+ aPagesToDuplicate.push_back(pDescriptor->GetPage());
+ nInsertPosition = pDescriptor->GetPage()->GetPageNum()+2;
+ }
+ }
+
+ // Duplicate the pages in aPagesToDuplicate and collect the newly
+ // created pages in aPagesToSelect.
+ const bool bUndo (aPagesToDuplicate.size()>1 && mrSlideSorter.GetView().IsUndoEnabled());
+ if (bUndo)
+ mrSlideSorter.GetView().BegUndo(SdResId(STR_INSERTPAGE));
+
+ ::std::vector<SdPage*> aPagesToSelect;
+ for(const auto& rpPage : aPagesToDuplicate)
+ {
+ aPagesToSelect.push_back(
+ mrSlideSorter.GetViewShell()->CreateOrDuplicatePage(
+ rRequest, PageKind::Standard, rpPage, nInsertPosition));
+ nInsertPosition += 2;
+ }
+ aPagesToDuplicate.clear();
+
+ if (bUndo)
+ mrSlideSorter.GetView().EndUndo();
+
+ // Set the selection to the pages in aPagesToSelect.
+ PageSelector& rSelector (mrSlideSorter.GetController().GetPageSelector());
+ rSelector.DeselectAllPages();
+ for (auto const& it: aPagesToSelect)
+ {
+ rSelector.SelectPage(it);
+ }
+
+ collectUIInformation({{"POS", OUString::number(nInsertPosition + 2)}}, "Duplicate");
+}
+
+void SlotManager::ChangeSlideExclusionState (
+ const model::SharedPageDescriptor& rpDescriptor,
+ const bool bExcludeSlide)
+{
+ if (rpDescriptor)
+ {
+ mrSlideSorter.GetView().SetState(
+ rpDescriptor,
+ model::PageDescriptor::ST_Excluded,
+ bExcludeSlide);
+ }
+ else
+ {
+ model::PageEnumeration aSelectedPages (
+ model::PageEnumerationProvider::CreateSelectedPagesEnumeration(
+ mrSlideSorter.GetModel()));
+ while (aSelectedPages.HasMoreElements())
+ {
+ model::SharedPageDescriptor pDescriptor (aSelectedPages.GetNextElement());
+ mrSlideSorter.GetView().SetState(
+ pDescriptor,
+ model::PageDescriptor::ST_Excluded,
+ bExcludeSlide);
+ }
+ }
+
+ SfxBindings& rBindings (mrSlideSorter.GetViewShell()->GetViewFrame()->GetBindings());
+ rBindings.Invalidate(SID_PRESENTATION);
+ rBindings.Invalidate(SID_REHEARSE_TIMINGS);
+ rBindings.Invalidate(SID_HIDE_SLIDE);
+ rBindings.Invalidate(SID_SHOW_SLIDE);
+ mrSlideSorter.GetModel().GetDocument()->SetChanged();
+}
+
+sal_Int32 SlotManager::GetInsertionPosition() const
+{
+ PageSelector& rSelector (mrSlideSorter.GetController().GetPageSelector());
+
+ // The insertion indicator is preferred. After all the user explicitly
+ // used it to define the insertion position.
+ if (mrSlideSorter.GetController().GetInsertionIndicatorHandler()->IsActive())
+ {
+ // Select the page before the insertion indicator.
+ return mrSlideSorter.GetController().GetInsertionIndicatorHandler()->GetInsertionPageIndex()
+ - 1;
+ }
+
+ // Is there a stored insertion position?
+ else if (mrSlideSorter.GetController().GetSelectionManager()->GetInsertionPosition() >= 0)
+ {
+ return mrSlideSorter.GetController().GetSelectionManager()->GetInsertionPosition() - 1;
+ }
+
+ // Use the index of the last selected slide.
+ else if (rSelector.GetSelectedPageCount() > 0)
+ {
+ for (int nIndex=rSelector.GetPageCount()-1; nIndex>=0; --nIndex)
+ if (rSelector.IsPageSelected(nIndex))
+ return nIndex;
+
+ // We should never get here.
+ OSL_ASSERT(false);
+ return rSelector.GetPageCount() - 1;
+ }
+
+ // Select the last page when there is at least one page.
+ else if (rSelector.GetPageCount() > 0)
+ {
+ return rSelector.GetPageCount() - 1;
+ }
+
+ // Hope for the best that CreateOrDuplicatePage() can cope with an empty
+ // selection.
+ else
+ {
+ // We should never get here because there has to be at least one page.
+ OSL_ASSERT(false);
+ return -1;
+ }
+}
+
+void SlotManager::NotifyEditModeChange()
+{
+ SfxBindings& rBindings (mrSlideSorter.GetViewShell()->GetViewFrame()->GetBindings());
+ rBindings.Invalidate(SID_PRESENTATION);
+ rBindings.Invalidate(SID_INSERTPAGE);
+ rBindings.Invalidate(SID_DUPLICATE_PAGE);
+}
+
+namespace {
+
+SlideExclusionState GetSlideExclusionState (model::PageEnumeration& rPageSet)
+{
+ SlideExclusionState eState (UNDEFINED);
+
+ // Get toggle state of the selected pages.
+ while (rPageSet.HasMoreElements() && eState!=MIXED)
+ {
+ const bool bState = rPageSet.GetNextElement()->GetPage()->IsExcluded();
+ switch (eState)
+ {
+ case UNDEFINED:
+ // Use the first selected page to set the initial value.
+ eState = bState ? EXCLUDED : INCLUDED;
+ break;
+
+ case EXCLUDED:
+ // The pages before where all not part of the show,
+ // this one is.
+ if ( ! bState)
+ eState = MIXED;
+ break;
+
+ case INCLUDED:
+ // The pages before where all part of the show,
+ // this one is not.
+ if (bState)
+ eState = MIXED;
+ break;
+
+ default:
+ // No need to change anything.
+ break;
+ }
+ }
+
+ return eState;
+}
+
+} // end of anonymous namespace
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsTransferableData.cxx b/sd/source/ui/slidesorter/controller/SlsTransferableData.cxx
new file mode 100644
index 000000000..f4b89a5ab
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsTransferableData.cxx
@@ -0,0 +1,86 @@
+/* -*- 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 <controller/SlsTransferableData.hxx>
+
+#include <SlideSorterViewShell.hxx>
+
+namespace sd::slidesorter::controller {
+
+rtl::Reference<SdTransferable> TransferableData::CreateTransferable (
+ SdDrawDocument* pSrcDoc,
+ SlideSorterViewShell* pViewShell,
+ ::std::vector<Representative>&& rRepresentatives)
+{
+ rtl::Reference<SdTransferable> pTransferable = new SdTransferable (pSrcDoc, nullptr, false/*bInitOnGetData*/);
+ auto pData = std::make_shared<TransferableData>(pViewShell, std::move(rRepresentatives));
+ pTransferable->AddUserData(pData);
+ return pTransferable;
+}
+
+std::shared_ptr<TransferableData> TransferableData::GetFromTransferable (const SdTransferable* pTransferable)
+{
+ if (pTransferable)
+ {
+ for (sal_Int32 nIndex=0,nCount=pTransferable->GetUserDataCount(); nIndex<nCount; ++nIndex)
+ {
+ std::shared_ptr<TransferableData> xData =
+ std::dynamic_pointer_cast<TransferableData>(pTransferable->GetUserData(nIndex));
+ if (xData)
+ return xData;
+ }
+ }
+ return std::shared_ptr<TransferableData>();
+}
+
+TransferableData::TransferableData (
+ SlideSorterViewShell* pViewShell,
+ ::std::vector<Representative>&& rRepresentatives)
+ : mpViewShell(pViewShell),
+ maRepresentatives(std::move(rRepresentatives))
+{
+ if (mpViewShell != nullptr)
+ StartListening(*mpViewShell);
+}
+
+TransferableData::~TransferableData()
+{
+ if (mpViewShell != nullptr)
+ EndListening(*mpViewShell);
+}
+
+void TransferableData::Notify (SfxBroadcaster&, const SfxHint& rHint)
+{
+ if (mpViewShell)
+ {
+ if (rHint.GetId() == SfxHintId::Dying)
+ {
+ // This hint may come either from the ViewShell or from the
+ // document (registered by SdTransferable). We do not know
+ // which but both are sufficient to disconnect from the
+ // ViewShell.
+ EndListening(*mpViewShell);
+ mpViewShell = nullptr;
+ }
+ }
+}
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/controller/SlsVisibleAreaManager.cxx b/sd/source/ui/slidesorter/controller/SlsVisibleAreaManager.cxx
new file mode 100644
index 000000000..6f85f362d
--- /dev/null
+++ b/sd/source/ui/slidesorter/controller/SlsVisibleAreaManager.cxx
@@ -0,0 +1,234 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <controller/SlsVisibleAreaManager.hxx>
+#include <controller/SlideSorterController.hxx>
+#include <controller/SlsAnimationFunction.hxx>
+#include <controller/SlsScrollBarManager.hxx>
+#include <controller/SlsCurrentSlideManager.hxx>
+#include <Window.hxx>
+#include <SlideSorter.hxx>
+#include <view/SlideSorterView.hxx>
+
+namespace sd::slidesorter::controller {
+
+namespace {
+ class VisibleAreaScroller
+ {
+ public:
+ VisibleAreaScroller (
+ SlideSorter& rSlideSorter,
+ const Point& rStart,
+ const Point& rEnd);
+ void operator() (const double nValue);
+ private:
+ SlideSorter& mrSlideSorter;
+ Point maStart;
+ const Point maEnd;
+ const ::std::function<double (double)> maAccelerationFunction;
+ };
+
+} // end of anonymous namespace
+
+VisibleAreaManager::VisibleAreaManager (SlideSorter& rSlideSorter)
+ : mrSlideSorter(rSlideSorter),
+ mbIsCurrentSlideTrackingActive(true),
+ mnDisableCount(0)
+{
+}
+
+VisibleAreaManager::~VisibleAreaManager()
+{
+}
+
+void VisibleAreaManager::ActivateCurrentSlideTracking()
+{
+ mbIsCurrentSlideTrackingActive = true;
+}
+
+void VisibleAreaManager::DeactivateCurrentSlideTracking()
+{
+ mbIsCurrentSlideTrackingActive = false;
+}
+
+void VisibleAreaManager::RequestVisible (
+ const model::SharedPageDescriptor& rpDescriptor,
+ const bool bForce)
+{
+ if (!rpDescriptor)
+ return;
+
+ if (mnDisableCount == 0)
+ {
+ maVisibleRequests.push_back(
+ mrSlideSorter.GetView().GetLayouter().GetPageObjectBox(
+ rpDescriptor->GetPageIndex(),
+ true));
+ }
+ if (bForce && ! mbIsCurrentSlideTrackingActive)
+ ActivateCurrentSlideTracking();
+ MakeVisible();
+}
+
+void VisibleAreaManager::RequestCurrentSlideVisible()
+{
+ if (mbIsCurrentSlideTrackingActive && mnDisableCount==0)
+ RequestVisible(
+ mrSlideSorter.GetController().GetCurrentSlideManager()->GetCurrentSlide());
+}
+
+void VisibleAreaManager::MakeVisible()
+{
+ if (maVisibleRequests.empty())
+ return;
+
+ sd::Window *pWindow (mrSlideSorter.GetContentWindow().get());
+ if ( ! pWindow)
+ return;
+ const Point aCurrentTopLeft (pWindow->PixelToLogic(Point(0,0)));
+
+ const ::std::optional<Point> aNewVisibleTopLeft (GetRequestedTopLeft());
+ maVisibleRequests.clear();
+ if ( ! aNewVisibleTopLeft)
+ return;
+
+ maRequestedVisibleTopLeft = *aNewVisibleTopLeft;
+ VisibleAreaScroller aAnimation(
+ mrSlideSorter,
+ aCurrentTopLeft,
+ maRequestedVisibleTopLeft);
+ // Execute the animation at its final value.
+ aAnimation(1.0);
+}
+
+::std::optional<Point> VisibleAreaManager::GetRequestedTopLeft() const
+{
+ sd::Window *pWindow (mrSlideSorter.GetContentWindow().get());
+ if ( ! pWindow)
+ return ::std::optional<Point>();
+
+ // Get the currently visible area and the model area.
+ const ::tools::Rectangle aVisibleArea (pWindow->PixelToLogic(
+ ::tools::Rectangle(
+ Point(0,0),
+ pWindow->GetOutputSizePixel())));
+ const ::tools::Rectangle aModelArea (mrSlideSorter.GetView().GetModelArea());
+
+ sal_Int32 nVisibleTop (aVisibleArea.Top());
+ const sal_Int32 nVisibleWidth (aVisibleArea.GetWidth());
+ sal_Int32 nVisibleLeft (aVisibleArea.Left());
+ const sal_Int32 nVisibleHeight (aVisibleArea.GetHeight());
+
+ // Find the longest run of boxes whose union fits into the visible area.
+ for (const auto& rBox : maVisibleRequests)
+ {
+ if (nVisibleTop+nVisibleHeight <= rBox.Bottom())
+ nVisibleTop = rBox.Bottom()-nVisibleHeight;
+ if (nVisibleTop > rBox.Top())
+ nVisibleTop = rBox.Top();
+
+ if (nVisibleLeft+nVisibleWidth <= rBox.Right())
+ nVisibleLeft = rBox.Right()-nVisibleWidth;
+ if (nVisibleLeft > rBox.Left())
+ nVisibleLeft = rBox.Left();
+
+ // Make sure the visible area does not move outside the model area.
+ if (nVisibleTop + nVisibleHeight > aModelArea.Bottom())
+ nVisibleTop = aModelArea.Bottom() - nVisibleHeight;
+ if (nVisibleTop < aModelArea.Top())
+ nVisibleTop = aModelArea.Top();
+
+ if (nVisibleLeft + nVisibleWidth > aModelArea.Right())
+ nVisibleLeft = aModelArea.Right() - nVisibleWidth;
+ if (nVisibleLeft < aModelArea.Left())
+ nVisibleLeft = aModelArea.Left();
+ }
+
+ const Point aRequestedTopLeft (nVisibleLeft, nVisibleTop);
+ if (aRequestedTopLeft == aVisibleArea.TopLeft())
+ return ::std::optional<Point>();
+ else
+ return ::std::optional<Point>(aRequestedTopLeft);
+}
+
+//===== VisibleAreaManager::TemporaryDisabler =================================
+
+VisibleAreaManager::TemporaryDisabler::TemporaryDisabler (SlideSorter const & rSlideSorter)
+ : mrVisibleAreaManager(rSlideSorter.GetController().GetVisibleAreaManager())
+{
+ ++mrVisibleAreaManager.mnDisableCount;
+}
+
+VisibleAreaManager::TemporaryDisabler::~TemporaryDisabler()
+{
+ --mrVisibleAreaManager.mnDisableCount;
+}
+
+//===== VerticalVisibleAreaScroller ===========================================
+
+namespace {
+
+const sal_Int32 gnMaxScrollDistance = 300;
+
+VisibleAreaScroller::VisibleAreaScroller (
+ SlideSorter& rSlideSorter,
+ const Point& rStart,
+ const Point& rEnd)
+ : mrSlideSorter(rSlideSorter),
+ maStart(rStart),
+ maEnd(rEnd),
+ maAccelerationFunction(
+ controller::AnimationParametricFunction(
+ controller::AnimationBezierFunction (0.1,0.6)))
+{
+ // When the distance to scroll is larger than a threshold then first
+ // jump to within this distance of the final value and start the
+ // animation from there.
+ if (std::abs(rStart.X()-rEnd.X()) > gnMaxScrollDistance)
+ {
+ if (rStart.X() < rEnd.X())
+ maStart.setX( rEnd.X()-gnMaxScrollDistance );
+ else
+ maStart.setX( rEnd.X()+gnMaxScrollDistance );
+ }
+ if (std::abs(rStart.Y()-rEnd.Y()) > gnMaxScrollDistance)
+ {
+ if (rStart.Y() < rEnd.Y())
+ maStart.setY( rEnd.Y()-gnMaxScrollDistance );
+ else
+ maStart.setY( rEnd.Y()+gnMaxScrollDistance );
+ }
+}
+
+void VisibleAreaScroller::operator() (const double nTime)
+{
+ const double nLocalTime (maAccelerationFunction(nTime));
+ mrSlideSorter.GetController().GetScrollBarManager().SetTopLeft(
+ Point(
+ sal_Int32(0.5 + maStart.X() * (1.0 - nLocalTime) + maEnd.X() * nLocalTime),
+ sal_Int32 (0.5 + maStart.Y() * (1.0 - nLocalTime) + maEnd.Y() * nLocalTime)));
+}
+
+} // end of anonymous namespace
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/cache/SlsCacheContext.hxx b/sd/source/ui/slidesorter/inc/cache/SlsCacheContext.hxx
new file mode 100644
index 000000000..12993fdbb
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/cache/SlsCacheContext.hxx
@@ -0,0 +1,98 @@
+/* -*- 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 <sal/types.h>
+#include <com/sun/star/uno/Reference.hxx>
+#include <memory>
+#include <vector>
+
+namespace com::sun::star::uno
+{
+class XInterface;
+}
+
+class SdrPage;
+
+namespace sd::slidesorter::cache
+{
+typedef const SdrPage* CacheKey;
+
+/** This interface allows the individualisation of different instances of
+ the PreviewCache.
+*/
+class CacheContext
+{
+public:
+ virtual ~CacheContext() {}
+
+ /** This method is called when the asynchronous creation of a preview
+ has been finished.
+ @param aKey
+ The key of the page for which the preview has been created.
+ */
+ virtual void NotifyPreviewCreation(CacheKey aKey) = 0;
+
+ /** Called to determine whether the system is idle and a preview can be
+ created without annoying the user.
+ */
+ virtual bool IsIdle() = 0;
+
+ /** This method is used to determine whether a page is currently visible
+ or not. It is called when the cache becomes too large and some
+ previews have to be released or scaled down.
+ */
+ virtual bool IsVisible(CacheKey aKey) = 0;
+
+ /** Return the page associated with the given key. Note that different
+ keys may map to a single page (this may be the case with custom
+ slide shows.)
+ */
+ virtual const SdrPage* GetPage(CacheKey aKey) = 0;
+
+ /** This method is used when the request queue is filled. It asks for
+ the list of visible entries and maybe for the list of not visible
+ entries and creates preview creation requests for them.
+ @param bVisible
+ When this is <FALSE/> then the implementation can decide whether
+ to allow rendering of previews that are not visible (ahead of
+ time). When not then return an empty pointer or an empty vector.
+ */
+ virtual std::shared_ptr<std::vector<CacheKey>> GetEntryList(bool bVisible) = 0;
+
+ /** Return the priority that defines the order in which previews are
+ created for different keys/pages. Typically the visible pages come
+ first, then top-down, left-to-right.
+ */
+ virtual sal_Int32 GetPriority(CacheKey aKey) = 0;
+
+ /** Return the model to which the pages belong for which the called
+ cache manages the previews. Different caches that belong to the
+ same model but have different preview sizes may access previews of
+ each other in order to create fast previews of the previews.
+ */
+ virtual css::uno::Reference<css::uno::XInterface> GetModel() = 0;
+};
+
+typedef std::shared_ptr<CacheContext> SharedCacheContext;
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/cache/SlsPageCache.hxx b/sd/source/ui/slidesorter/inc/cache/SlsPageCache.hxx
new file mode 100644
index 000000000..4fb596195
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/cache/SlsPageCache.hxx
@@ -0,0 +1,141 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <cache/SlsCacheContext.hxx>
+#include <vcl/bitmapex.hxx>
+#include <memory>
+
+class Size;
+
+namespace sd::slidesorter::cache
+{
+class GenericPageCache;
+
+/** The page cache is responsible for the creation and storage of preview
+ bitmaps of pages that are shown by the slide sorter.
+
+ <p>Bitmaps for previews and a cache are used to speed up the display
+ (painting) of the slide sorter. But, of course, we have to limit this
+ time-space-tradeoff by limiting the amount of space that can be use to
+ store bitmaps.</p>
+
+ <p>There are several strategies employed by this class to shorten the
+ perceived time that is used to paint the slide sorter:
+ <ul>
+ <li>Rendering pages ahead of time. Additionally to rendering the
+ visible slides we try to render part or all of the slides that are not
+ (yet) visible. This, of course, makes sense only when the computer is
+ otherwise idle while doing that.</li>
+ <li>When the size of the slides on the screen changes we mark the
+ bitmaps as needing an update but use them while the new bitmap in the
+ correct size is not available.</li>
+ <li>Give the UI the chance to handle user events between the rendering
+ of differe slides.</li>
+ <li>Limit the amount of space that may be used for storing preview
+ bitmaps and throw.</li>
+ </p>
+
+ <p>There is another somewhat similar methods for requesting new previews:
+ GetPreviewBitmap() schedules a re-rendering (when necessary) and
+ returns the preview what is currently available, either as a preview of
+ the preview or, when nothing has changed since the last call, as the
+ final thing.
+ </p>
+*/
+class PageCache
+{
+public:
+ /** The page cache is created with a reference to the slide sorter so
+ that it has access to both the view and the model and so can fill
+ itself with requests for all or just the visible pages.
+
+ It is the task of the PageCacheManager to create new objects of this
+ class.
+ */
+ PageCache(const Size& rPreviewSize, const bool bDoSuperSampling,
+ const SharedCacheContext& rpCacheContext);
+
+ ~PageCache();
+
+ void ChangeSize(const Size& rPreviewSize, const bool bDoSuperSampling);
+
+ /** Request a preview bitmap for the specified page object in the
+ specified size. The returned bitmap may be a preview of the
+ preview, i.e. either a scaled (up or down) version of a previous
+ preview (of the wrong size) or an empty bitmap. In this case a
+ request for the generation of a new preview is created and inserted
+ into the request queue. When the preview is available in the right
+ size the page shape will be told to paint itself again. When it
+ then calls this method again if receives the correctly sized preview
+ bitmap.
+ @param rRequestData
+ This data is used to determine the preview.
+ @param bResize
+ When <TRUE/> then when the available bitmap has not the
+ requested size, it is scaled before it is returned. When
+ <FALSE/> then the bitmap is returned in the wrong size and it is
+ the task of the caller to scale it.
+ @return
+ Returns a bitmap that is either empty, contains a scaled (up or
+ down) version or is the requested bitmap.
+ */
+ BitmapEx GetPreviewBitmap(const CacheKey aKey, const bool bResize);
+
+ BitmapEx GetMarkedPreviewBitmap(const CacheKey aKey);
+ void SetMarkedPreviewBitmap(const CacheKey aKey, const BitmapEx& rBitmap);
+
+ /** When the requested preview bitmap does not yet exist or is not
+ up-to-date then the rendering of one is scheduled. Otherwise this
+ method does nothing.
+ */
+ void RequestPreviewBitmap(const CacheKey aKey);
+
+ /** Tell the cache that the bitmap associated with the given request
+ data is not up-to-date anymore. This will invalidate all previews
+ in other caches that represent the same page as well.
+ A new preview is requested and will lead
+ eventually to a repaint of the associated page object.
+ */
+ void InvalidatePreviewBitmap(const CacheKey aKey);
+
+ /** Call this method when all preview bitmaps have to be generated anew.
+ This is the case when the size of the page objects on the screen has
+ changed or when the model has changed.
+ */
+ void InvalidateCache();
+
+ /** With the precious flag you can control whether a bitmap can be
+ removed or reduced in size to make room for other bitmaps or is so
+ precious that it will not touched. A typical use is to set the
+ precious flag for exactly the visible pages.
+ */
+ void SetPreciousFlag(const CacheKey aKey, const bool bIsPrecious);
+
+ void Pause();
+ void Resume();
+
+private:
+ std::unique_ptr<GenericPageCache> mpImplementation;
+};
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/cache/SlsPageCacheManager.hxx b/sd/source/ui/slidesorter/inc/cache/SlsPageCacheManager.hxx
new file mode 100644
index 000000000..eaddea5b2
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/cache/SlsPageCacheManager.hxx
@@ -0,0 +1,155 @@
+/* -*- 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 <sal/types.h>
+#include <com/sun/star/uno/XInterface.hpp>
+#include <memory>
+#include <vector>
+
+class Size;
+class SdrPage;
+
+namespace sd::slidesorter::cache {
+
+class BitmapCache;
+
+/** Provide and manage the preview bitmap caches for all slide sorter
+ instances. There is one cache per active slide sorter plus a small
+ number of caches that are no longer in use. The later are kept to speed
+ up the switching between views.
+*/
+class PageCacheManager
+{
+public:
+ typedef std::vector< std::pair<Size, std::shared_ptr<BitmapCache> > > BestFittingPageCaches;
+ typedef css::uno::Reference<css::uno::XInterface> DocumentKey;
+
+ /** Return the one instance of the PageCacheManager class.
+ */
+ static std::shared_ptr<PageCacheManager> Instance();
+
+ /** Look up the cache for the given model in which the previews have the
+ specified size. If no such cache exists, then one is created. When
+ a new BitmapCache is created its Recycle() method is called with a
+ sorted list of existing caches from which the new one initialize its
+ previews.
+ @return
+ The returned cache lives as long as somebody keeps a shared
+ pointer and the ReleaseCache() method has not been called.
+ */
+ std::shared_ptr<BitmapCache> GetCache (
+ const DocumentKey& pDocument,
+ const Size& rPreviewSize);
+
+ /** Tell the cache manager to release its own reference to the specified
+ cache. After that the cache will live as long as the caller (and
+ maybe others) holds its reference.
+ */
+ void ReleaseCache (const std::shared_ptr<BitmapCache>& rpCache);
+
+ /** This is an information to the cache manager that the size of preview
+ bitmaps in the specified cache has changed.
+
+ */
+ std::shared_ptr<BitmapCache> ChangeSize (
+ const std::shared_ptr<BitmapCache>& rpCache,
+ const Size& rOldPreviewSize,
+ const Size& rNewPreviewSize);
+
+ /** Invalidate the preview bitmap for one slide that belongs to the
+ specified document. The bitmaps for this slide in all caches are
+ marked as out-of-date and will be re-created when they are requested
+ the next time.
+ */
+ bool InvalidatePreviewBitmap (
+ const DocumentKey& pDocument,
+ const SdrPage* pPage);
+
+ /** Invalidate the preview bitmaps for all slides that belong to the
+ specified document. This is necessary after model changes that
+ affect e.g. page number fields.
+ */
+ void InvalidateAllPreviewBitmaps (const DocumentKey& pDocument);
+
+ /** Invalidate all the caches that are currently in use and destroy
+ those that are not. This is used for example when the high contrast
+ mode is turned on or off.
+ */
+ void InvalidateAllCaches();
+
+ /** Call this method when a page has been deleted and its preview
+ is not needed anymore.
+ */
+ void ReleasePreviewBitmap (const SdrPage* pPage);
+
+private:
+ /** Singleton instance of the cache manager. Note that this is a weak
+ pointer. The (implementation class of) ViewShellBase holds a
+ shared_ptr so that the cache manager has the same life time as the
+ ViewShellBase.
+ */
+ static std::weak_ptr<PageCacheManager> mpInstance;
+
+ /// List of active caches.
+ class PageCacheContainer;
+ std::unique_ptr<PageCacheContainer> mpPageCaches;
+
+ /// List of inactive, recently used caches.
+ class RecentlyUsedPageCaches;
+ std::unique_ptr<RecentlyUsedPageCaches> mpRecentlyUsedPageCaches;
+
+ /** The maximal number of recently used caches that are kept alive after
+ they have become inactive, i.e. after they are not used anymore by a
+ slide sorter.
+ */
+ static const sal_uInt32 mnMaximalRecentlyCacheCount = 2;
+
+ PageCacheManager();
+ ~PageCacheManager();
+
+ class Deleter;
+ friend class Deleter;
+
+ std::shared_ptr<BitmapCache> GetRecentlyUsedCache(
+ const DocumentKey& pDocument,
+ const Size& rSize);
+
+ /** Add the given cache to the list of recently used caches for the
+ document. There is one such list per document. Each least has at
+ most mnMaximalRecentlyCacheCount members.
+ */
+ void PutRecentlyUsedCache(
+ DocumentKey const & pDocument,
+ const Size& rPreviewSize,
+ const std::shared_ptr<BitmapCache>& rpCache);
+
+ /** This method is used internally to initialize a newly created
+ BitmapCache with already existing previews.
+ */
+ void Recycle (
+ const std::shared_ptr<BitmapCache>& rpCache,
+ const DocumentKey& pDocument,
+ const Size& rPreviewSize);
+};
+
+} // end of namespace ::sd::slidesorter::cache
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/controller/SlideSorterController.hxx b/sd/source/ui/slidesorter/inc/controller/SlideSorterController.hxx
new file mode 100644
index 000000000..10c2aa13e
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/controller/SlideSorterController.hxx
@@ -0,0 +1,327 @@
+/* -*- 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 <model/SlsSharedPageDescriptor.hxx>
+#include <pres.hxx>
+
+#include <tools/link.hxx>
+#include <tools/gen.hxx>
+#include <rtl/ref.hxx>
+#include <rtl/ustring.hxx>
+
+#include <sddllapi.h>
+
+#include <memory>
+#include <vector>
+
+namespace com::sun::star::container
+{
+class XIndexAccess;
+}
+namespace com::sun::star::uno
+{
+template <typename> class Reference;
+}
+namespace sd
+{
+class FuPoor;
+}
+namespace sd
+{
+class Window;
+}
+namespace vcl
+{
+class Window;
+}
+
+namespace sd::slidesorter
+{
+class SlideSorter;
+}
+namespace sd::slidesorter::view
+{
+class SlideSorterView;
+}
+namespace sd::slidesorter::model
+{
+class SlideSorterModel;
+}
+
+class CommandEvent;
+class SdPage;
+class SfxItemSet;
+class SfxRequest;
+class VclSimpleEvent;
+class VclWindowEvent;
+
+namespace sd::slidesorter::controller
+{
+class Animator;
+class Clipboard;
+class CurrentSlideManager;
+class FocusManager;
+class InsertionIndicatorHandler;
+class Listener;
+class PageSelector;
+class ScrollBarManager;
+class SelectionFunction;
+class SelectionManager;
+class SlotManager;
+class VisibleAreaManager;
+
+class SlideSorterController final
+{
+public:
+ /** Create a new controller for the slide sorter.
+ @param pParentWindow
+ The window that contains the controls of the new
+ controller.
+ */
+ SlideSorterController(SlideSorter& rSlideSorter);
+
+ /** Late initialization. Call this method once a new object has been
+ created.
+ */
+ void Init();
+
+ ~SlideSorterController();
+
+ void Dispose();
+
+ /** Place and size the scroll bars and the browser window so that the
+ given rectangle is filled.
+ */
+ void Resize(const ::tools::Rectangle& rAvailableSpace);
+
+ /** Determine which of the UI elements--the scroll bars, the scroll bar
+ filler, the actual slide sorter view--are visible and place them in
+ the area last passed to Resize().
+ @param bForce
+ When <TRUE/> is given (<FALSE/> is the default) then the content
+ window and with it the SlideSorterView is resized event when its
+ size does not change (the size does change when the visibility
+ of scroll bars changes.)
+ */
+ void Rearrange(bool bForce);
+
+ /** Return the descriptor of the page that is rendered under the
+ given position. This takes the IsOnlyPreviewTriggersMouseOver
+ property into account.
+ @return
+ Returns a pointer to a page descriptor instead of a
+ reference because when no page is found at the position
+ then NULL is returned to indicate this.
+ */
+ model::SharedPageDescriptor GetPageAt(const Point& rPixelPosition);
+
+ // Exported for unit test
+ SD_DLLPUBLIC PageSelector& GetPageSelector();
+ FocusManager& GetFocusManager();
+ // Exported for unit test
+ SD_DLLPUBLIC controller::Clipboard& GetClipboard();
+
+ /** Return the object that manages the scroll bars.
+ */
+ ScrollBarManager& GetScrollBarManager();
+
+ std::shared_ptr<CurrentSlideManager> const& GetCurrentSlideManager() const;
+ std::shared_ptr<SlotManager> const& GetSlotManager() const;
+ std::shared_ptr<SelectionManager> const& GetSelectionManager() const;
+ std::shared_ptr<InsertionIndicatorHandler> const& GetInsertionIndicatorHandler() const;
+
+ /** This method forwards the call to the SlideSorterView and executes
+ pending operations like moving selected pages into the visible area.
+ */
+ void Paint(const ::tools::Rectangle& rRect, vcl::Window* pWin);
+
+ void FuTemporary(SfxRequest& rRequest);
+ void FuPermanent(SfxRequest& rRequest);
+ void FuSupport(SfxRequest& rRequest);
+ bool Command(const CommandEvent& rEvent, ::sd::Window* pWindow);
+
+ void GetCtrlState(SfxItemSet& rSet);
+ void GetStatusBarState(SfxItemSet& rSet);
+
+ void ExecCtrl(SfxRequest& rRequest);
+ void GetAttrState(SfxItemSet& rSet);
+
+ /** Create an object of this inner class to prevent updates due to model
+ changes.
+ */
+ class ModelChangeLock
+ {
+ public:
+ ModelChangeLock(SlideSorterController& rController);
+ ~ModelChangeLock() COVERITY_NOEXCEPT_FALSE;
+ void Release();
+
+ private:
+ SlideSorterController* mpController;
+ };
+ friend class ModelChangeLock;
+
+ /** Handle a change of the model, that is, handle the removal and
+ insertion of whole pages or a change of the edit mode.
+
+ This method is a convenience function that simply calls
+ PreModelChange() and then PostModelChange().
+ */
+ void HandleModelChange();
+
+ DECL_LINK(WindowEventHandler, VclWindowEvent&, void);
+ DECL_LINK(ApplicationEventHandler, VclSimpleEvent&, void);
+
+ /** Update the display of all pages. This involves a redraw and
+ releasing previews and caches.
+ */
+ void UpdateAllPages();
+
+ /** This factory method creates a selection function.
+ */
+ rtl::Reference<FuPoor> CreateSelectionFunction(SfxRequest& rRequest);
+
+ /** When the current function of the view shell is the slide sorter
+ selection function then return a reference to it. Otherwise return
+ an empty reference.
+ */
+ ::rtl::Reference<SelectionFunction> GetCurrentSelectionFunction() const;
+
+ /** Prepare for a change of the edit mode. Depending on the current
+ edit mode we may save the selection so that it can be restored when
+ later changing back to the current edit mode.
+ */
+ void PrepareEditModeChange();
+
+ /** Set a new edit mode and return whether the edit mode really
+ has been changed. For proper saving and restoring of the selection
+ this method should be called between calls to
+ PrepareEditModeChange() and FinishEditModeChange().
+ */
+ void ChangeEditMode(EditMode eEditMode);
+
+ /** Finish the change of the edit mode. Here we may select a page or
+ restore a previously saved selection.
+ */
+ void FinishEditModeChange();
+
+ /** Call this method when the name of one of the pages has changed.
+ This is then notified to the accessibility object, when that exists.
+ @param nPageIndex
+ The index of the page whose name has been changed.
+ @param rsOldName
+ The old name of the page. The new name can be taken from the
+ page object.
+ */
+ void PageNameHasChanged(int nPageIndex, const OUString& rsOldName);
+
+ /** Provide the set of pages to be displayed in the slide sorter. The
+ GetDocumentSlides() method can be found only in the SlideSorterModel.
+ */
+ void SetDocumentSlides(const css::uno::Reference<css::container::XIndexAccess>& rxSlides);
+
+ /** Return an Animator object.
+ */
+ const std::shared_ptr<Animator>& GetAnimator() const { return mpAnimator; }
+
+ VisibleAreaManager& GetVisibleAreaManager() const;
+
+ void CheckForMasterPageAssignment();
+ void CheckForSlideTransitionAssignment();
+
+private:
+ SlideSorter& mrSlideSorter;
+ model::SlideSorterModel& mrModel;
+ view::SlideSorterView& mrView;
+ std::unique_ptr<PageSelector> mpPageSelector;
+ std::unique_ptr<FocusManager> mpFocusManager;
+ std::shared_ptr<SlotManager> mpSlotManager;
+ std::unique_ptr<ScrollBarManager> mpScrollBarManager;
+ mutable std::shared_ptr<CurrentSlideManager> mpCurrentSlideManager;
+ std::shared_ptr<SelectionManager> mpSelectionManager;
+ std::unique_ptr<controller::Clipboard> mpClipboard;
+ std::shared_ptr<InsertionIndicatorHandler> mpInsertionIndicatorHandler;
+ std::shared_ptr<Animator> mpAnimator;
+ std::unique_ptr<VisibleAreaManager> mpVisibleAreaManager;
+
+ // The listener listens to UNO events and thus is a UNO object.
+ ::rtl::Reference<controller::Listener> mpListener;
+
+ int mnModelChangeLockCount;
+ bool mbIsForcedRearrangePending;
+ bool mbContextMenuOpen;
+
+ bool mbPostModelChangePending;
+
+ /** This array stores the indices of the selected page descriptors at
+ the time when the edit mode is switched to EditMode::MasterPage. With this
+ we can restore the selection when switching back to EditMode::Page mode.
+ */
+ ::std::vector<SdPage*> maSelectionBeforeSwitch;
+ /// The current page before the edit mode is switched to EditMode::MasterPage.
+ int mnCurrentPageBeforeSwitch;
+
+ /** The master page to select after the edit mode is changed. This
+ member is used to pass the pointer from PrepareEditModeChange() to
+ FinishEditModeChange().
+ */
+ SdPage* mpEditModeChangeMasterPage;
+
+ /** This rectangle in the parent window encloses scroll bars and slide
+ sorter window. It is set when Resize() is called.
+ */
+ ::tools::Rectangle maTotalWindowArea;
+
+ /** This counter is used to avoid processing of reentrant calls to
+ Paint().
+ */
+ sal_Int32 mnPaintEntranceCount;
+
+ /** Prepare for several model changes, i.e. prevent time-consuming and
+ non-critical operations like repaints until UnlockModelChange() is
+ called. Critical operations like releasing references to pages that
+ do not exist anymore are executed.
+ */
+ void LockModelChange();
+
+ /** Further calls to HandleModelChange() will result in a full featured
+ update of model, view, and controller. When HandleModelChange() has
+ been called since the last LockModelChange() then this is done right
+ away to bring the view up-to-date.
+ */
+ void UnlockModelChange();
+
+ /** Prepare for a model change. This method does all the things that
+ need to be done _before_ the model changes, e.g. because they need
+ access to the model data before the change.
+ */
+ void PreModelChange();
+
+ /** Complete a model change. This includes the recreation of data
+ structures that depend on the model and the request for a repaint to
+ show the changes.
+ */
+ void PostModelChange();
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/controller/SlsAnimationFunction.hxx b/sd/source/ui/slidesorter/inc/controller/SlsAnimationFunction.hxx
new file mode 100644
index 000000000..b4847de1a
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/controller/SlsAnimationFunction.hxx
@@ -0,0 +1,77 @@
+/* -*- 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 <basegfx/point/b2dpoint.hxx>
+
+#include <functional>
+#include <vector>
+
+
+namespace sd::slidesorter::controller {
+
+class AnimationBezierFunction
+{
+public:
+ /** Create a cubic bezier curve whose start and end points are given
+ implicitly as P0=(0,0) and P3=(1,1). The second control point is
+ implicitly given as P2=(1-nY1,1-nX1).
+ */
+ AnimationBezierFunction (
+ const double nX1,
+ const double nY1);
+
+ ::basegfx::B2DPoint operator() (const double nT);
+
+private:
+ const double mnX1;
+ const double mnY1;
+ const double mnX2;
+ const double mnY2;
+
+ static double EvaluateComponent (
+ const double nT,
+ const double nV1,
+ const double nV2);
+};
+
+/** Turn a parametric function into one whose y-Values depend on its
+ x-Values. Note a lot of interpolation takes place. The resulting
+ accuracy should be good enough for the purpose of acceleration
+ function for animations.
+*/
+class AnimationParametricFunction
+{
+public:
+ typedef ::std::function<basegfx::B2DPoint (double)> ParametricFunction;
+ AnimationParametricFunction (const ParametricFunction& rFunction);
+
+ double operator() (const double nX);
+
+private:
+ /** y-Values of the parametric function given to the constructor
+ evaluated (and interpolated) for evenly spaced x-Values.
+ */
+ ::std::vector<double> maY;
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/controller/SlsAnimator.hxx b/sd/source/ui/slidesorter/inc/controller/SlsAnimator.hxx
new file mode 100644
index 000000000..8c9ec9e81
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/controller/SlsAnimator.hxx
@@ -0,0 +1,122 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <view/SlideSorterView.hxx>
+#include <canvas/elapsedtime.hxx>
+#include <vcl/idle.hxx>
+#include <sal/types.h>
+#include <o3tl/deleter.hxx>
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+namespace sd::slidesorter { class SlideSorter; }
+
+namespace sd::slidesorter::controller {
+
+/** Experimental class for simple eye candy animations.
+*/
+class Animator
+{
+public:
+ /** In some circumstances we have to avoid animation and jump to the
+ final animation state immediately. Use this enum instead of a bool
+ to be more expressive.
+ */
+ enum AnimationMode { AM_Animated, AM_Immediate };
+
+ explicit Animator (SlideSorter& rSlideSorter);
+ ~Animator();
+ Animator(const Animator&) = delete;
+ Animator& operator=(const Animator&) = delete;
+
+ /** When disposed the animator will stop its work immediately and not
+ process any timer events anymore.
+ */
+ void Dispose();
+
+ /** An animation object is called with values between 0 and 1 as single
+ argument to its operator() method.
+ */
+ typedef ::std::function<void (double)> AnimationFunctor;
+ typedef ::std::function<void ()> FinishFunctor;
+
+ typedef sal_Int32 AnimationId;
+ static const AnimationId NotAnAnimationId = -1;
+
+ /** Schedule a new animation for execution. The () operator of that
+ animation will be called with increasing values between 0 and 1 for
+ the specified duration.
+ @param rAnimation
+ The animation operation.
+ */
+ AnimationId AddAnimation (
+ const AnimationFunctor& rAnimation,
+ const FinishFunctor& rFinishFunctor);
+
+ /** Abort and remove an animation. In order to reduce the bookkeeping
+ on the caller side, it is OK to call this method with an animation
+ function that is not currently being animated. Such a call is
+ silently ignored.
+ */
+ void RemoveAnimation (const AnimationId nAnimationId);
+
+ /** A typical use case for this method is the temporary shutdown of the
+ slidesorter when the slide sorter bar is put into a cache due to a
+ change of the edit mode.
+ */
+ void RemoveAllAnimations();
+
+private:
+ SlideSorter& mrSlideSorter;
+ Idle maIdle;
+ bool mbIsDisposed;
+ class Animation;
+ typedef ::std::vector<std::shared_ptr<Animation> > AnimationList;
+ AnimationList maAnimations;
+ ::canvas::tools::ElapsedTime maElapsedTime;
+
+ std::unique_ptr<view::SlideSorterView::DrawLock, o3tl::default_delete<view::SlideSorterView::DrawLock>> mpDrawLock;
+
+ AnimationId mnNextAnimationId;
+
+ DECL_LINK(TimeoutHandler, Timer *, void);
+
+ /** Execute one step of every active animation.
+ @param nTime
+ Time measured in milliseconds with some arbitrary reference point.
+ @return
+ When one or more animation has finished then <TRUE/> is
+ returned. Call CleanUpAnimationList() in this case.
+ */
+ bool ProcessAnimations (const double nTime);
+
+ /** Remove animations that have expired.
+ */
+ void CleanUpAnimationList();
+
+ void RequestNextFrame();
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/controller/SlsClipboard.hxx b/sd/source/ui/slidesorter/inc/controller/SlsClipboard.hxx
new file mode 100644
index 000000000..6ced17486
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/controller/SlsClipboard.hxx
@@ -0,0 +1,208 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <memory>
+#include <ViewClipboard.hxx>
+#include <controller/SlsSelectionObserver.hxx>
+#include <sdxfer.hxx>
+
+#include <sal/types.h>
+#include <o3tl/deleter.hxx>
+#include <svx/svdtypes.hxx>
+
+#include <sddllapi.h>
+
+class SfxRequest;
+struct AcceptDropEvent;
+class DropTargetHelper;
+struct ExecuteDropEvent;
+struct ImplSVEvent;
+class Point;
+class SdPage;
+namespace vcl { class Window; }
+
+namespace sd {
+class Window;
+}
+
+namespace sd::slidesorter { class SlideSorter; }
+
+namespace sd::slidesorter::controller {
+
+class SlideSorterController;
+
+class SAL_DLLPUBLIC_RTTI Clipboard final
+ : public ViewClipboard
+{
+public:
+ Clipboard (SlideSorter& rSlideSorter);
+ virtual ~Clipboard() override;
+
+ /** Create a slide sorter transferable from the given sd
+ transferable. The returned transferable is set up with all
+ information necessary so that it can be dropped on a slide sorter.
+ */
+ static std::shared_ptr<SdTransferable::UserData> CreateTransferableUserData (SdTransferable* pTransferable);
+
+ void HandleSlotCall (SfxRequest& rRequest);
+
+ void DoCut ();
+ // Exported for unit test
+ SD_DLLPUBLIC void DoCopy();
+ // Exported for unit test
+ SD_DLLPUBLIC void DoPaste();
+ void DoDelete ();
+
+ void StartDrag (
+ const Point& rDragPt,
+ vcl::Window* pWindow );
+
+ void DragFinished (
+ sal_Int8 nDropAction);
+
+ sal_Int8 AcceptDrop (
+ const AcceptDropEvent& rEvt,
+ DropTargetHelper& rTargetHelper,
+ ::sd::Window* pTargetWindow,
+ sal_uInt16 nPage,
+ SdrLayerID nLayer );
+
+ sal_Int8 ExecuteDrop (
+ const ExecuteDropEvent& rEvt,
+ DropTargetHelper& rTargetHelper,
+ ::sd::Window* pTargetWindow,
+ sal_uInt16 nPage,
+ SdrLayerID nLayer );
+
+ void Abort();
+
+private:
+ virtual sal_uInt16 DetermineInsertPosition () override;
+
+ SlideSorter& mrSlideSorter;
+ SlideSorterController& mrController;
+
+ typedef ::std::vector<SdPage*> PageList;
+ /** Remember the pages that are dragged to another document or to
+ another place in the same document so that they can be removed after
+ a move operation.
+ */
+ PageList maPagesToRemove;
+
+ /** Used when a drop is executed to combine all undo actions into one.
+ Typically created in ExecuteDrop() and released in DragFinish().
+ */
+ class UndoContext;
+ std::unique_ptr<UndoContext> mxUndoContext;
+
+ std::unique_ptr<SelectionObserver::Context, o3tl::default_delete<SelectionObserver::Context>> mxSelectionObserverContext;
+ ImplSVEvent * mnDragFinishedUserEventId;
+
+ void CreateSlideTransferable (
+ vcl::Window* pWindow,
+ bool bDrag);
+
+ /** Determine the position of where to insert the pages in the current
+ transferable of the sd module.
+ @return
+ The index in the range [0,n] (both inclusive) with n the number
+ of pages is returned.
+ */
+ sal_Int32 GetInsertionPosition ();
+
+ /** Paste the pages of the transferable of the sd module at the given
+ position.
+ @param nInsertPosition
+ The position at which to insert the pages. The valid range is
+ [0,n] (both inclusive) with n the number of pages in the
+ document.
+ @return
+ The number of inserted pages is returned.
+ */
+ sal_Int32 PasteTransferable (sal_Int32 nInsertPosition);
+
+ /** Select a range of pages of the model. Typically usage is the
+ selection of newly inserted pages.
+ @param nFirstIndex
+ The index of the first page to select.
+ @param nPageCount
+ The number of pages to select.
+ */
+ void SelectPageRange (sal_Int32 nFirstIndex, sal_Int32 nPageCount);
+
+ /** Return <TRUE/> when the current transferable in the current state of
+ the slidesorter is acceptable to be pasted. For this the
+ transferable has to
+ a) exist,
+ b) contain one or more regular draw pages, no master pages.
+ When master pages are involved, either in the transferable or in the
+ slide sorter (by it displaying master pages) the drop of the
+ transferable is not accepted. The reason is the missing
+ implementation of proper handling master pages copy-and-paste.
+ */
+ enum DropType { DT_PAGE, DT_PAGE_FROM_NAVIGATOR, DT_SHAPE, DT_NONE };
+ DropType IsDropAccepted() const;
+
+ /** This method contains the code for AcceptDrop() and ExecuteDrop() shapes.
+ There are only minor differences for the two cases at this level.
+ @param eCommand
+ This parameter specifies whether to do an AcceptDrop() or
+ ExecuteDrop().
+ @param rPosition
+ Since the event is given as void pointer we can not take the
+ mouse position from it. The caller has to supply it in this
+ parameter.
+ @param pDropEvent
+ Event though the AcceptDropEvent and ExecuteDropEvent are very
+ similar they do not have a common base class. Because of that
+ we have to use a void* to pass these structs.
+ @param nPage
+ When the page number is given as 0xffff then it is replaced by
+ the number of the page at the mouse position. If the mouse is
+ not over a page then neither AcceptDrop() nor ExecuteDrop() are
+ executed.
+ */
+ enum DropCommand { DC_ACCEPT, DC_EXECUTE };
+ sal_Int8 ExecuteOrAcceptShapeDrop (
+ DropCommand eCommand,
+ const Point& rPosition,
+ const void* pDropEvent ,
+ DropTargetHelper& rTargetHelper,
+ ::sd::Window* pTargetWindow,
+ sal_uInt16 nPage,
+ SdrLayerID nLayer);
+
+ /** Return whether the insertion defined by the transferable is
+ trivial, ie would not change either source nor target document.
+ */
+ bool IsInsertionTrivial (
+ SdTransferable const * pTransferable,
+ const sal_Int8 nDndAction) const;
+
+ /** Asynchronous part of DragFinished. The argument is the sal_Int8
+ nDropAction, disguised as void*.
+ */
+ DECL_DLLPRIVATE_LINK(ProcessDragFinished, void*, void);
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/controller/SlsCurrentSlideManager.hxx b/sd/source/ui/slidesorter/inc/controller/SlsCurrentSlideManager.hxx
new file mode 100644
index 000000000..0c5b3b243
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/controller/SlsCurrentSlideManager.hxx
@@ -0,0 +1,112 @@
+/* -*- 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 <model/SlsSharedPageDescriptor.hxx>
+#include <vcl/timer.hxx>
+#include <tools/link.hxx>
+
+class SdPage;
+
+namespace sd::slidesorter
+{
+class SlideSorter;
+}
+
+namespace sd::slidesorter::controller
+{
+/** Manage the current slide. This includes setting the according flags at
+ the PageDescriptor objects and setting the current slide at the main
+ view shell.
+
+ Switching pages is triggered only after a little delay. This allows
+ fast travelling through a larger set of slides without having to wait
+ for the edit view to update its content after every slide change.
+*/
+class CurrentSlideManager
+{
+public:
+ /** Create a new CurrentSlideManager object that manages the current
+ slide for the given SlideSorter.
+ */
+ CurrentSlideManager(SlideSorter& rSlideSorter);
+
+ ~CurrentSlideManager();
+
+ /** Call this when the current page of the main view shell has been
+ switched. Use SwitchCurrentSlide() to initiate such a switch.
+ */
+ void NotifyCurrentSlideChange(const sal_Int32 nSlideIndex);
+ void NotifyCurrentSlideChange(const SdPage* pPage);
+
+ /** Call this method to switch the current page of the main view shell
+ to the given slide. Use CurrentSlideHasChanged() when the current
+ slide change has been initiated by someone else.
+ @param nSlideIndex
+ Zero based index in the range [0,number-of-slides).
+ The page selection is cleared and only the new
+ current slide is selected.
+ */
+ void SwitchCurrentSlide(const sal_Int32 nSlideIndex);
+ void SwitchCurrentSlide(const model::SharedPageDescriptor& rpSlide,
+ const bool bUpdateSelection = false);
+
+ /** Return the page descriptor for the current slide. Note, that when
+ there is no current slide then the returned pointer is empty.
+ */
+ const model::SharedPageDescriptor& GetCurrentSlide() const { return mpCurrentSlide; }
+
+ /** Release all references to model data.
+ */
+ void PrepareModelChange();
+
+ /** Modify inner state in reaction to a change of the SlideSorterModel.
+ */
+ void HandleModelChange();
+
+private:
+ SlideSorter& mrSlideSorter;
+ sal_Int32 mnCurrentSlideIndex;
+ model::SharedPageDescriptor mpCurrentSlide;
+ /** Timer to control the delay after which to ask
+ XController/ViewShellBase to switch to another slide.
+ */
+ Timer maSwitchPageDelayTimer;
+
+ void SetCurrentSlideAtViewShellBase(const model::SharedPageDescriptor& rpSlide);
+ void SetCurrentSlideAtTabControl(const model::SharedPageDescriptor& rpSlide);
+ void SetCurrentSlideAtXController(const model::SharedPageDescriptor& rpSlide);
+
+ /** When switching from one slide to a new current slide then this
+ method releases all ties to the old slide.
+ */
+ void ReleaseCurrentSlide();
+
+ /** When switching from one slide to a new current slide then this
+ method connects to the new current slide.
+ */
+ void AcquireCurrentSlide(const sal_Int32 nSlideIndex);
+
+ DECL_LINK(SwitchPageCallback, Timer*, void);
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/controller/SlsFocusManager.hxx b/sd/source/ui/slidesorter/inc/controller/SlsFocusManager.hxx
new file mode 100644
index 000000000..180b7143f
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/controller/SlsFocusManager.hxx
@@ -0,0 +1,211 @@
+/* -*- 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 <model/SlsSharedPageDescriptor.hxx>
+
+#include <sal/types.h>
+#include <tools/link.hxx>
+#include <vector>
+
+namespace sd::slidesorter
+{
+class SlideSorter;
+}
+
+namespace sd::slidesorter::controller
+{
+/** This class manages the focus of the slide sorter. There is the focus
+ page which is or is not focused. Initialized to point to the first page
+ it can be set to other pages by using the MoveFocus() method. The
+ focused state of the focus page can be toggled with the ToggleFocus()
+ method.
+*/
+class FocusManager
+{
+public:
+ /** Create a new focus manager that operates on the pages of the model
+ associated with the given controller. The focus page is set to the
+ first page. Focused state is off.
+ */
+ FocusManager(SlideSorter& rSlideSorter);
+
+ ~FocusManager();
+
+ enum class FocusMoveDirection
+ {
+ Left,
+ Right,
+ Up,
+ Down
+ };
+
+ /** Move the focus from the currently focused page to one that is
+ displayed adjacent to it, either vertically or horizontally.
+ @param eDirection
+ Direction in which to move the focus. Wrap around is done
+ differently when moving vertically or horizontally. Vertical
+ wrap around takes place in the same column, i.e. when you are
+ in the top row and move up you come out in the bottom row in the
+ same column. Horizontal wrap around moves to the next
+ (FocusMoveDirection::Right) or previous (FocusMoveDirection::Left) page. Moving to the right
+ from the last page goes to the first page and vice versa.
+ The current page index is set to the nearest valid
+ page index.
+ */
+ void MoveFocus(FocusMoveDirection eDirection);
+
+ /** Show the focus indicator of the current slide.
+ @param bScrollToFocus
+ When <TRUE/> (the default) then the view is scrolled so that the
+ focus rectangle lies inside its visible area.
+ */
+ void ShowFocus(const bool bScrollToFocus = true);
+
+ /** Hide the focus indicator.
+ */
+ void HideFocus();
+
+ /** Toggle the focused state of the current slide.
+ @return
+ Returns the focused state of the focus page after the call.
+ */
+ bool ToggleFocus();
+
+ /** Return whether the window managed by the called focus manager has
+ the input focus of the application.
+ */
+ bool HasFocus() const;
+
+ /** Return the descriptor of the page that currently has the focus.
+ @return
+ When there is no page that currently has the focus then NULL is
+ returned.
+ */
+ model::SharedPageDescriptor GetFocusedPageDescriptor() const;
+
+ /** Return the index of the page that currently has the focus as it is
+ accepted by the slide sorter model.
+ @return
+ When there is no page that currently has the focus then -1 is
+ returned.
+ */
+ sal_Int32 GetFocusedPageIndex() const { return mnPageIndex; }
+
+ /** Set the focused page to the one described by the given page
+ descriptor. The visibility of the focus indicator is not modified.
+ @param rDescriptor
+ One of the page descriptors that are currently managed by the
+ SlideSorterModel.
+ */
+ bool SetFocusedPage(const model::SharedPageDescriptor& rDescriptor);
+
+ /** Set the focused page to the one described by the given page
+ index. The visibility of the focus indicator is not modified.
+ @param nPageIndex
+ A valid page index that is understood by the SlideSorterModel.
+ */
+ void SetFocusedPage(sal_Int32 nPageIndex);
+
+ bool SetFocusedPageToCurrentPage();
+
+ /** Return <TRUE/> when the focus indicator is currently shown. A
+ prerequisite is that the window managed by this focus manager has
+ the input focus as indicated by a <TRUE/> return value of
+ HasFocus(). It is not necessary that the focus indicator is
+ visible. It may have been scrolled outside the visible area.
+ */
+ bool IsFocusShowing() const;
+
+ /** Add a listener that is called when the focus is shown or hidden or
+ set to another page object.
+ @param rListener
+ When this method is called multiple times for the same listener
+ the second and all following calls are ignored. Each listener
+ is added only once.
+ */
+ void AddFocusChangeListener(const Link<LinkParamNone*, void>& rListener);
+
+ /** Remove a focus change listener.
+ @param rListener
+ It is safe to pass a listener that was not added are has been
+ removed previously. Such calls are ignored.
+ */
+ void RemoveFocusChangeListener(const Link<LinkParamNone*, void>& rListener);
+
+ /** Create an instance of this class to temporarily hide the focus
+ indicator. It is restored to its former visibility state when the
+ FocusHider is destroyed.
+ */
+ class FocusHider
+ {
+ public:
+ FocusHider(FocusManager&);
+ ~FocusHider() COVERITY_NOEXCEPT_FALSE;
+
+ private:
+ bool mbFocusVisible;
+ FocusManager& mrManager;
+ };
+
+private:
+ SlideSorter& mrSlideSorter;
+
+ /** Index of the page that may be focused. It is -1 when the model
+ contains no page.
+ */
+ sal_Int32 mnPageIndex;
+
+ /** This flag indicates whether the page pointed to by mpFocusDescriptor
+ has the focus.
+ */
+ bool mbPageIsFocused;
+
+ ::std::vector<Link<LinkParamNone*, void>> maFocusChangeListeners;
+
+ /** Reset the focus state of the given descriptor and request a repaint
+ so that the focus indicator is hidden.
+ @param pDescriptor
+ When NULL is given then the call is ignored.
+ */
+ void HideFocusIndicator(const model::SharedPageDescriptor& rpDescriptor);
+
+ /** Set the focus state of the given descriptor, scroll it into the
+ visible area and request a repaint so that the focus indicator is
+ made visible.
+ @param pDescriptor
+ When NULL is given then the call is ignored.
+ @param bScrollToFocus
+ When <TRUE/> (the default) then the view is scrolled so that the
+ focus rectangle lies inside its visible area.
+ */
+ void ShowFocusIndicator(const model::SharedPageDescriptor& rpDescriptor,
+ const bool bScrollToFocus);
+
+ /** Call all currently registered listeners that a focus change has
+ happened. The focus may be hidden or shown or moved from one page
+ object to another.
+ */
+ void NotifyFocusChangeListeners() const;
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/controller/SlsInsertionIndicatorHandler.hxx b/sd/source/ui/slidesorter/inc/controller/SlsInsertionIndicatorHandler.hxx
new file mode 100644
index 000000000..43f2d2f6a
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/controller/SlsInsertionIndicatorHandler.hxx
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <controller/SlsAnimator.hxx>
+
+#include <view/SlsLayouter.hxx>
+
+namespace sd::slidesorter { class SlideSorter; }
+namespace sd::slidesorter::view {
+class InsertAnimator;
+class InsertionIndicatorOverlay;
+}
+
+class SdTransferable;
+
+namespace sd::slidesorter::controller {
+
+/** Manage the visibility and location of the insertion indicator. Its
+ actual display is controlled by the InsertionIndicatorOverlay.
+*/
+class InsertionIndicatorHandler
+{
+public:
+ InsertionIndicatorHandler (SlideSorter& rSlideSorter);
+ ~InsertionIndicatorHandler() COVERITY_NOEXCEPT_FALSE;
+
+ enum Mode { CopyMode, MoveMode, UnknownMode };
+ static Mode GetModeFromDndAction (const sal_Int8 nDndAction);
+
+ /** Activate the insertion marker at the given coordinates.
+ */
+ void Start (const bool bIsOverSourceView);
+
+ /** Deactivate the insertion marker.
+ */
+ void End (const controller::Animator::AnimationMode eMode);
+
+ /** This context make sure that the insertion indicator is shown
+ (provided that the clipboard is not empty) while the context is
+ alive. Typically used while a context menu is displayed.
+ */
+ class ForceShowContext
+ {
+ public:
+ ForceShowContext (const std::shared_ptr<InsertionIndicatorHandler>& rpHandler);
+ ~ForceShowContext() COVERITY_NOEXCEPT_FALSE;
+ private:
+ const std::shared_ptr<InsertionIndicatorHandler> mpHandler;
+ };
+
+ /** Update the indicator icon from the current transferable (from the
+ clipboard or an active drag and drop operation.)
+ */
+ void UpdateIndicatorIcon (const SdTransferable* pTransferable);
+
+ /** Set the position of the insertion marker to the given coordinates.
+ */
+ void UpdatePosition (
+ const Point& rMouseModelPosition,
+ const Mode eMode);
+ void UpdatePosition (
+ const Point& rMouseModelPosition,
+ const sal_Int8 nDndAction);
+
+ /** Return whether the insertion marker is active.
+ */
+ bool IsActive() const { return mbIsActive;}
+
+ /** Return the insertion index that corresponds with the current
+ graphical location of the insertion indicator.
+ */
+ sal_Int32 GetInsertionPageIndex() const;
+
+ /** Determine whether moving the current selection to the current
+ position of the insertion marker would alter the document. This
+ would be the case when the selection is not consecutive or would be
+ moved to a position outside and not adjacent to the selection.
+ */
+ bool IsInsertionTrivial (
+ const sal_Int32 nInsertionIndex,
+ const Mode eMode) const;
+ /** This method is like the other variant. It operates implicitly
+ on the current insertion index as would be returned by
+ GetInsertionPageIndex().
+ */
+ bool IsInsertionTrivial (const sal_Int8 nDndAction);
+
+private:
+ SlideSorter& mrSlideSorter;
+ std::shared_ptr<view::InsertAnimator> mpInsertAnimator;
+ std::shared_ptr<view::InsertionIndicatorOverlay> mpInsertionIndicatorOverlay;
+ view::InsertPosition maInsertPosition;
+ Mode meMode;
+ bool mbIsInsertionTrivial;
+ bool mbIsActive;
+ bool mbIsReadOnly;
+ bool mbIsOverSourceView;
+ Size maIconSize;
+ bool mbIsForcedShow;
+
+ void SetPosition (
+ const Point& rPoint,
+ const Mode eMode);
+ std::shared_ptr<view::InsertAnimator> const & GetInsertAnimator();
+
+ /** Make the insertion indicator visible (that is the show part) and
+ keep it visible, even when the mouse leaves the window (that is the
+ force part). We need this when a context menu is displayed (mouse
+ over the popup menu triggers a mouse leave event) while the
+ insertion indicator remains visible in the background.
+
+ In effect all calls to End() are ignored until ForceEnd() is called.
+ */
+ void ForceShow();
+ void ForceEnd();
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/controller/SlsPageSelector.hxx b/sd/source/ui/slidesorter/inc/controller/SlsPageSelector.hxx
new file mode 100644
index 000000000..6a4b75004
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/controller/SlsPageSelector.hxx
@@ -0,0 +1,219 @@
+/* -*- 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 <model/SlsSharedPageDescriptor.hxx>
+
+#include <vector>
+#include <memory>
+
+#include <sddllapi.h>
+
+class SdPage;
+
+namespace sd::slidesorter
+{
+class SlideSorter;
+}
+namespace sd::slidesorter::model
+{
+class SlideSorterModel;
+}
+
+namespace sd::slidesorter::controller
+{
+class SlideSorterController;
+
+/** A sub-controller that handles page selection of the slide browser.
+ Selecting a page does not make it the current page (of the main view)
+ automatically as this would not be desired in a multi selection. This
+ has to be done explicitly by calling the
+ CurrentSlideManager::SetCurrentSlide() method.
+
+ Indices of pages relate always to the number of all pages in the model
+ (as returned by GetPageCount()) not just the selected pages.
+*/
+class PageSelector
+{
+public:
+ explicit PageSelector(SlideSorter& rSlideSorter);
+ PageSelector(const PageSelector&) = delete;
+ PageSelector& operator=(const PageSelector&) = delete;
+
+ // Exported for unit test
+ SD_DLLPUBLIC void SelectAllPages();
+ SD_DLLPUBLIC void DeselectAllPages();
+
+ /** Update the selection state of all page descriptors to be the same as
+ that of the corresponding pages of the SdPage objects and issue
+ redraw requests where necessary.
+ */
+ void GetCoreSelection();
+
+ /** Update the selection state of the SdPage objects to be the same as
+ that of the corresponding page descriptors.
+ */
+ void SetCoreSelection();
+
+ /** Select the specified descriptor. The selection state of the other
+ descriptors is not affected.
+ */
+ void SelectPage(int nPageIndex);
+ /** Select the descriptor that is associated with the given page. The
+ selection state of the other descriptors is not affected.
+ */
+ void SelectPage(const SdPage* pPage);
+ /** Select the specified descriptor. The selection state of the other
+ descriptors is not affected.
+ */
+ void SelectPage(const model::SharedPageDescriptor& rpDescriptor);
+
+ /** Return whether the specified page is selected. This convenience
+ method is a substitute for
+ SlideSorterModel::GetPageDescriptor(i)->HasState(ST_Selected) is
+ included here to make this class more self contained.
+ */
+ SD_DLLPUBLIC bool IsPageSelected(int nPageIndex);
+
+ /** Return whether the specified page is visible. This convenience
+ method is a substitute for
+ SlideSorterModel::GetPageDescriptor(i)->HasState(ST_Visible) is
+ included here to make this class more self contained.
+ */
+ bool IsPageVisible(int nPageIndex);
+
+ /** Deselect the descriptor that is associated with the given page.
+ The current page is updated to the first slide
+ of the remaining selection.
+ */
+ void DeselectPage(int nPageIndex);
+ void DeselectPage(const model::SharedPageDescriptor& rpDescriptor,
+ const bool bUpdateCurrentPage = true);
+
+ /** This convenience method returns the same number of pages that
+ SlideSorterModel.GetPageCount() returns. It is included here so
+ that it is self contained for iterating over all pages to select or
+ deselect them.
+ */
+ int GetPageCount() const;
+ int GetSelectedPageCount() const { return mnSelectedPageCount; }
+
+ /** Return the anchor for a range selection. This usually is the first
+ selected page after all pages have been deselected.
+ @return
+ The returned anchor may be NULL.
+ */
+ const model::SharedPageDescriptor& GetSelectionAnchor() const { return mpSelectionAnchor; }
+
+ typedef ::std::vector<SdPage*> PageSelection;
+
+ /** Return an object that describes the current selection. The caller
+ can use that object to later restore the selection.
+ @return
+ The object returned describes the selection via indices. So
+ even if pages are exchanged a later call to SetPageSelection()
+ is valid.
+ */
+ std::shared_ptr<PageSelection> GetPageSelection() const;
+
+ /** Restore a page selection according to the given selection object.
+ @param rSelection
+ Typically obtained by calling GetPageSelection() this object
+ is used to restore the selection. If pages were exchanged since
+ the last call to GetPageSelection() it is still valid to call
+ this method with the selection. When pages have been inserted
+ or removed the result may be unexpected.
+ @param bUpdateCurrentPage
+ When <TRUE/> (the default value) then after setting the
+ selection update the current page to the first page of the
+ selection.
+ When called from within UpdateCurrentPage() then this flag is
+ used to prevent a recursion loop.
+ */
+ void SetPageSelection(const std::shared_ptr<PageSelection>& rSelection,
+ const bool bUpdateCurrentPage);
+
+ /** Call this method after the model has changed to set the number
+ of selected pages.
+ */
+ void CountSelectedPages();
+
+ /** Use the UpdateLock whenever you do a complex selection, i.e. call
+ more than one method in a row. An active lock prevents intermediate
+ changes of the current slide.
+ */
+ class UpdateLock
+ {
+ public:
+ UpdateLock(SlideSorter const& rSlideSorter);
+ UpdateLock(PageSelector& rPageSelector);
+ ~UpdateLock();
+ void Release();
+
+ private:
+ PageSelector* mpSelector;
+ };
+
+ class BroadcastLock
+ {
+ public:
+ BroadcastLock(SlideSorter const& rSlideSorter);
+ BroadcastLock(PageSelector& rPageSelector);
+ ~BroadcastLock();
+
+ private:
+ PageSelector& mrSelector;
+ };
+
+private:
+ model::SlideSorterModel& mrModel;
+ SlideSorter& mrSlideSorter;
+ SlideSorterController& mrController;
+ int mnSelectedPageCount;
+ int mnBroadcastDisableLevel;
+ bool mbSelectionChangeBroadcastPending;
+ model::SharedPageDescriptor mpMostRecentlySelectedPage;
+ /// Anchor for a range selection.
+ model::SharedPageDescriptor mpSelectionAnchor;
+ sal_Int32 mnUpdateLockCount;
+ bool mbIsUpdateCurrentPagePending;
+
+ /** Enable the broadcasting of selection change events. This calls the
+ SlideSorterController::SelectionHasChanged() method to do the actual
+ work. When EnableBroadcasting has been called as many times as
+ DisableBroadcasting() was called before and the selection has been
+ changed in the meantime, this change will be broadcasted.
+ */
+ void EnableBroadcasting();
+
+ /** Disable the broadcasting of selection change events. Subsequent
+ changes of the selection will set a flag that triggers the sending
+ of events when EnableBroadcasting() is called.
+ */
+ void DisableBroadcasting();
+
+ void UpdateCurrentPage(const bool bUpdateOnlyWhenPending = false);
+
+ void CheckConsistency() const;
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/controller/SlsProperties.hxx b/sd/source/ui/slidesorter/inc/controller/SlsProperties.hxx
new file mode 100644
index 000000000..344ac67f3
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/controller/SlsProperties.hxx
@@ -0,0 +1,125 @@
+/* -*- 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 <tools/color.hxx>
+
+namespace sd::slidesorter::controller
+{
+/** An extensible set of properties used throughout the slide sorter.
+*/
+class Properties
+{
+public:
+ Properties();
+
+ /** Call this method after receiving a VclEventId::ApplicationDataChanged
+ event.
+ */
+ void HandleDataChangeEvent();
+
+ /** When this method returns <TRUE/> then the current slide is
+ highlighted in the view. The default value is <FALSE/>.
+ */
+ bool IsHighlightCurrentSlide() const { return mbIsHighlightCurrentSlide; }
+ void SetHighlightCurrentSlide(const bool bIsHighlightCurrentSlide);
+
+ /** When this method returns <TRUE/> then the selection is indicated in
+ the view (typically by drawing rectangles around the selected
+ slides.) The default value is <TRUE/>.
+ */
+ bool IsShowSelection() const { return mbIsShowSelection; }
+ void SetShowSelection(const bool bIsShowSelection);
+
+ /** When this method returns <TRUE/> then the focusdselection is indicated in
+ the view (typically by drawing dotted rectangles around the selected
+ slides.) The default value is <TRUE/>.
+ */
+ bool IsShowFocus() const { return mbIsShowFocus; }
+ void SetShowFocus(const bool bIsShowFocus);
+
+ /** When this method returns <TRUE/> then on a selection change the
+ visible area is adapted so that the selected slides are shown
+ centered in the view. This can be used to center the current slide
+ by selecting only the current slide. The default value is <FALSE/>.
+ */
+ bool IsCenterSelection() const { return mbIsCenterSelection; }
+ void SetCenterSelection(const bool bIsCenterSelection);
+
+ /** When this method returns <TRUE/> then the view may try to change the
+ visible area by scrolling it smoothly on the screen. Experimental.
+ Default value is <FALSE/>.
+ */
+ bool IsSmoothSelectionScrolling() const { return mbIsSmoothSelectionScrolling; }
+ void SetSmoothSelectionScrolling(const bool bIsSmoothSelectionScrolling);
+
+ /** When this method returns <TRUE/> then during a full screen
+ presentation the previews in a slide sorter are not updated.
+ Default value is <TRUE/>.
+ */
+ bool IsSuspendPreviewUpdatesDuringFullScreenPresentation() const
+ {
+ return mbIsSuspendPreviewUpdatesDuringFullScreenPresentation;
+ }
+ void SetSuspendPreviewUpdatesDuringFullScreenPresentation(const bool bFlag);
+
+ /** Return the background color.
+ */
+ const Color& GetBackgroundColor() const { return maBackgroundColor; }
+ void SetBackgroundColor(const Color& rColor);
+
+ /** Return the text color.
+ */
+ const Color& GetTextColor() const { return maTextColor; }
+ void SetTextColor(const Color& rColor);
+
+ /** Return the color in which selections are to be painted.
+ */
+ const Color& GetSelectionColor() const { return maSelectionColor; }
+ void SetSelectionColor(const Color& rColor);
+
+ /** Return the color used for highlighting e.g. the current slide.
+ */
+ const Color& GetHighlightColor() const { return maHighlightColor; }
+ void SetHighlightColor(const Color& rColor);
+
+ /** The UI can be set to be read only independently from the model status.
+ Used for instance in the presenter view.
+ */
+ bool IsUIReadOnly() const { return mbIsUIReadOnly; }
+ void SetUIReadOnly(const bool bIsUIReadOnly);
+
+private:
+ bool mbIsHighlightCurrentSlide;
+ bool mbIsShowSelection;
+ bool mbIsShowFocus;
+ bool mbIsCenterSelection;
+ bool mbIsSmoothSelectionScrolling;
+ bool mbIsSuspendPreviewUpdatesDuringFullScreenPresentation;
+ Color maBackgroundColor;
+ Color maTextColor;
+ Color maSelectionColor;
+ Color maHighlightColor;
+ bool mbIsUIReadOnly;
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/controller/SlsScrollBarManager.hxx b/sd/source/ui/slidesorter/inc/controller/SlsScrollBarManager.hxx
new file mode 100644
index 000000000..853d98f4a
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/controller/SlsScrollBarManager.hxx
@@ -0,0 +1,248 @@
+/* -*- 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 <tools/link.hxx>
+#include <tools/gen.hxx>
+#include <vcl/timer.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/vclptr.hxx>
+
+#include <functional>
+
+namespace sd { class Window; }
+
+namespace sd::slidesorter { class SlideSorter; }
+
+namespace sd::slidesorter::controller {
+
+/** Manage the horizontal and vertical scroll bars. Listen for events, set
+ their sizes, place them in the window, determine their visibilities.
+
+ <p>Handle auto scrolling, i.e. the scrolling of the window when the
+ mouse comes near the window border while dragging a selection.</p>
+
+ <p>In order to make the slide sorter be used in the task pane with its
+ own vertical scrollbars the vertical scrollbar of the use of the slide
+ sorter is optional. When using it the available area in a window is
+ used and the vertical scrollbar is displayed when that area is not large
+ enough. When the vertical scrollbar is not used then the available area
+ is assumed to be modifiable. In that case the PlaceScrollBars() method
+ may return an area larger than the one given.<p>
+*/
+class ScrollBarManager
+{
+public:
+ /** Create a new scroll bar manager that manages three controls: the
+ horizontal scroll bar, the vertical scroll bar, and the little
+ window that fills the gap at the bottom right corner that is left
+ between the two scroll bars. Call LateInitialization() after
+ constructing a new object.
+ */
+ ScrollBarManager (SlideSorter& rSlideSorter);
+
+ ~ScrollBarManager();
+
+ /** Register listeners at the scroll bars. This method is called after
+ startup of a new slide sorter object or after a reactivation of a
+ slide sorter that for example is taken from a cache.
+ */
+ void Connect();
+
+ /** Remove listeners from the scroll bars. This method is called when
+ the slide sorter is destroyed or when it is suspended, e.g. put
+ into a cache for later reuse.
+ */
+ void Disconnect();
+
+ /** Set up the scroll bar, i.e. thumb size and position. Call this
+ method when the content of the browser window changed, i.e. pages
+ were inserted or deleted, the layout or the zoom factor has
+ changed.
+ @param bScrollToCurrentPosition
+ When <TRUE/> then scroll the window to the new offset that is
+ defined by the scroll bars. Otherwise the new offset is simply
+ set and the whole window is repainted.
+ */
+ void UpdateScrollBars (
+ bool bScrollToCurrentPosition);
+
+ /** Place the scroll bars inside the given area. When the available
+ area is not large enough for the content to display the horizontal
+ and/or vertical scroll bar is enabled.
+ @param rAvailableArea
+ The scroll bars will be placed inside this rectangle. It is
+ expected to be given in pixel relative to its parent.
+ @param bIsHorizontalScrollBarAllowed
+ Only when this flag is <TRUE/> the horizontal scroll may be
+ displayed.
+ @param bIsVerticalScrollBarAllowed
+ Only when this flag is <TRUE/> the horizontal scroll may be
+ displayed.
+ @return
+ Returns the space that remains after the scroll bars are
+ placed.
+ */
+ ::tools::Rectangle PlaceScrollBars (
+ const ::tools::Rectangle& rAvailableArea,
+ const bool bIsHorizontalScrollBarAllowed,
+ const bool bIsVerticalScrollBarAllowed);
+
+ /** Update the vertical and horizontal scroll bars so that the visible
+ area has the given top and left values.
+ */
+ void SetTopLeft (const Point& rNewTopLeft);
+
+ /** Return the width of the vertical scroll bar, which--when
+ shown--should be fixed in contrast to its height.
+ @return
+ Returns 0 when the vertical scroll bar is not shown or does not
+ exist, otherwise its width in pixel is returned.
+ */
+ int GetVerticalScrollBarWidth() const;
+
+ /** Return the height of the horizontal scroll bar, which--when
+ shown--should be fixed in contrast to its width.
+ @return
+ Returns 0 when the vertical scroll bar is not shown or does not
+ exist, otherwise its height in pixel is returned.
+ */
+ int GetHorizontalScrollBarHeight() const;
+
+ /** Call this method to scroll a window while the mouse is in dragging a
+ selection. If the mouse is near the window border or is outside the
+ window then scroll the window accordingly.
+ @param rMouseWindowPosition
+ The mouse position for which the scroll amount is calculated.
+ @param rAutoScrollFunctor
+ Every time when the window is scrolled then this functor is executed.
+ @return
+ When the window is scrolled then this method returns <TRUE/>.
+ When the window is not changed then <FALSE/> is returned.
+ */
+ bool AutoScroll (
+ const Point& rMouseWindowPosition,
+ const ::std::function<void ()>& rAutoScrollFunctor);
+
+ void StopAutoScroll();
+
+ void clearAutoScrollFunctor();
+
+ enum Orientation { Orientation_Horizontal, Orientation_Vertical };
+ /** Scroll the slide sorter by setting the thumbs of the scroll bars and
+ by moving the content of the content window.
+ @param eOrientation
+ Defines whether to scroll horizontally or vertically.
+ @param nDistance
+ distance in slides.
+ */
+ void Scroll(
+ const Orientation eOrientation,
+ const sal_Int32 nDistance);
+
+private:
+ SlideSorter& mrSlideSorter;
+
+ /** The horizontal scroll bar. Note that is used but not owned by
+ objects of this class. It is given to the constructor.
+ */
+ VclPtr<ScrollBar> mpHorizontalScrollBar;
+
+ /** The vertical scroll bar. Note that is used but not owned by
+ objects of this class. It is given to the constructor.
+ */
+ VclPtr<ScrollBar> mpVerticalScrollBar;
+
+ /// Relative horizontal position of the visible area in the view.
+ double mnHorizontalPosition;
+ /// Relative vertical position of the visible area in the view.
+ double mnVerticalPosition;
+ /** The width and height of the border at the inside of the window which
+ when entered while in drag mode leads to a scrolling of the window.
+ */
+ Size maScrollBorder;
+ /** The only task of this little window is to paint the little square at
+ the bottom right corner left by the two scroll bars (when both are
+ visible).
+ */
+ VclPtr<ScrollBarBox> mpScrollBarFiller;
+
+ /** The auto scroll timer is used for keep scrolling the window when the
+ mouse reaches its border while dragging a selection. When the mouse
+ is not moved the timer issues events to keep scrolling.
+ */
+ Timer maAutoScrollTimer;
+ Size maAutoScrollOffset;
+ bool mbIsAutoScrollActive;
+
+ /** The content window is the one whose view port is controlled by the
+ scroll bars.
+ */
+ VclPtr<sd::Window> mpContentWindow;
+
+ ::std::function<void ()> maAutoScrollFunctor;
+
+ void SetWindowOrigin (
+ double nHorizontalPosition,
+ double nVerticalPosition);
+
+ /** Determine the visibility of the scroll bars so that the window
+ content is not clipped in any dimension without showing a scroll
+ bar.
+ @param rAvailableArea
+ The area in which the scroll bars, the scroll bar filler, and
+ the SlideSorterView will be placed.
+ @return
+ The area that is enclosed by the scroll bars is returned. It
+ will be filled with the SlideSorterView.
+ */
+ ::tools::Rectangle DetermineScrollBarVisibilities(
+ const ::tools::Rectangle& rAvailableArea,
+ const bool bIsHorizontalScrollBarAllowed,
+ const bool bIsVerticalScrollBarAllowed);
+
+ /** Typically called by DetermineScrollBarVisibilities() this method
+ tests a specific configuration of the two scroll bars being visible
+ or hidden.
+ @return
+ When the window content can be shown with only being clipped in
+ an orientation where the scroll bar would be shown then <TRUE/>
+ is returned.
+ */
+ bool TestScrollBarVisibilities (
+ bool bHorizontalScrollBarVisible,
+ bool bVerticalScrollBarVisible,
+ const ::tools::Rectangle& rAvailableArea);
+
+ void CalcAutoScrollOffset (const Point& rMouseWindowPosition);
+ bool RepeatAutoScroll();
+
+ DECL_LINK(HorizontalScrollBarHandler, ScrollBar*, void);
+ DECL_LINK(VerticalScrollBarHandler, ScrollBar*, void);
+ DECL_LINK(AutoScrollTimeoutHandler, Timer *, void);
+
+ void PlaceHorizontalScrollBar (const ::tools::Rectangle& aArea);
+ void PlaceVerticalScrollBar (const ::tools::Rectangle& aArea);
+ void PlaceFiller (const ::tools::Rectangle& aArea);
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/controller/SlsSelectionFunction.hxx b/sd/source/ui/slidesorter/inc/controller/SlsSelectionFunction.hxx
new file mode 100644
index 000000000..7d80fbd26
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/controller/SlsSelectionFunction.hxx
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <controller/SlsFocusManager.hxx>
+#include <fupoor.hxx>
+#include <memory>
+
+namespace sd::slidesorter
+{
+class SlideSorter;
+}
+
+struct AcceptDropEvent;
+
+namespace sd::slidesorter::controller
+{
+class SlideSorterController;
+
+class SelectionFunction final : public FuPoor
+{
+public:
+ SelectionFunction(const SelectionFunction&) = delete;
+ SelectionFunction& operator=(const SelectionFunction&) = delete;
+
+ static rtl::Reference<FuPoor> Create(SlideSorter& rSlideSorter, SfxRequest& rRequest);
+
+ // Mouse- & Key-Events
+ virtual bool KeyInput(const KeyEvent& rKEvt) override;
+ virtual bool MouseMove(const MouseEvent& rMEvt) override;
+ virtual bool MouseButtonUp(const MouseEvent& rMEvt) override;
+ virtual bool MouseButtonDown(const MouseEvent& rMEvt) override;
+
+ /// Forward to the clipboard manager.
+ virtual void DoCut() override;
+
+ /// Forward to the clipboard manager.
+ virtual void DoCopy() override;
+
+ /// Forward to the clipboard manager.
+ virtual void DoPaste() override;
+
+ /** is called when the current function should be aborted. <p>
+ This is used when a function gets a KEY_ESCAPE but can also
+ be called directly.
+
+ @returns
+ true if an active function was aborted
+ */
+ virtual bool cancel() override;
+
+ void MouseDragged(const AcceptDropEvent& rEvent, const sal_Int8 nDragAction);
+
+ /** Turn of substitution display and insertion indicator.
+ */
+ void NotifyDragFinished();
+
+ class EventDescriptor;
+ class ModeHandler;
+ friend class ModeHandler;
+ enum Mode
+ {
+ NormalMode,
+ MultiSelectionMode,
+ DragAndDropMode
+ };
+ void SwitchToNormalMode();
+ void SwitchToDragAndDropMode(const Point& rMousePosition);
+ void SwitchToMultiSelectionMode(const Point& rMousePosition, const sal_uInt32 nEventCode);
+
+ void ResetShiftKeySelectionAnchor();
+ /** Special case handling for when the context menu is hidden. This
+ method will reinitialize the current mouse position to prevent the
+ mouse motion during the time the context menu is displayed from
+ being interpreted as drag-and-drop start.
+ */
+ void ResetMouseAnchor();
+
+private:
+ SlideSorter& mrSlideSorter;
+ SlideSorterController& mrController;
+
+ SelectionFunction(SlideSorter& rSlideSorter, SfxRequest& rRequest);
+
+ virtual ~SelectionFunction() override;
+
+ /** Remember the slide where the shift key was pressed and started a
+ multiselection via keyboard.
+ */
+ sal_Int32 mnShiftKeySelectionAnchor;
+
+ /** The selection function can be in one of several mutually
+ exclusive modes.
+ */
+ std::shared_ptr<ModeHandler> mpModeHandler;
+
+ /** Make the slide nOffset slides away of the current one the new
+ current slide. When the new index is outside the range of valid
+ page numbers it is clipped to that range.
+ @param nOffset
+ When nOffset is negative then go back. When nOffset if positive go
+ forward. When it is zero then ignore the call.
+ */
+ void GotoNextPage(int nOffset);
+
+ /** Make the slide with the given index the new current slide.
+ @param nIndex
+ Index of the new current slide. When the new index is outside
+ the range of valid page numbers it is clipped to that range.
+ */
+ void GotoPage(int nIndex);
+
+ void ProcessMouseEvent(sal_uInt32 nEventType, const MouseEvent& rEvent);
+
+ // What follows are a couple of helper methods that are used by
+ // ProcessMouseEvent().
+
+ void ProcessEvent(EventDescriptor& rEvent);
+
+ void MoveFocus(const FocusManager::FocusMoveDirection eDirection, const bool bIsShiftDown,
+ const bool bIsControlDown);
+
+ void SwitchMode(const std::shared_ptr<ModeHandler>& rpHandler);
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/controller/SlsSelectionManager.hxx b/sd/source/ui/slidesorter/inc/controller/SlsSelectionManager.hxx
new file mode 100644
index 000000000..4f52d49e6
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/controller/SlsSelectionManager.hxx
@@ -0,0 +1,139 @@
+/* -*- 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 <sal/types.h>
+#include <tools/link.hxx>
+#include <vector>
+#include <memory>
+
+class SdPage;
+
+namespace sd::slidesorter
+{
+class SlideSorter;
+}
+
+namespace sd::slidesorter::controller
+{
+class SlideSorterController;
+class SelectionObserver;
+
+/** This class is a part of the controller and handles the selection of
+ slides.
+ <p>It has methods to modify the selected slides (delete them or
+ move them to other places in the document), change the visible area so
+ to make the selected slides visible, tell listeners when the selection
+ changes.</p>
+*/
+class SelectionManager
+{
+public:
+ /** Create a new SelectionManager for the given slide sorter.
+ */
+ SelectionManager(SlideSorter& rSlideSorter);
+
+ ~SelectionManager();
+
+ /** Delete the currently selected slides. When this method returns the
+ selection is empty.
+ @param bSelectFollowingPage
+ When <TRUE/> then after deleting the selected pages make the
+ slide after the last selected page the new current page.
+ When <FALSE/> then make the first slide before the selected
+ pages the new current slide.
+ */
+ void DeleteSelectedPages(const bool bSelectFollowingPage = true);
+
+ /** Call this method after the selection has changed (possible several
+ calls to the PageSelector) to invalidate the relevant slots and send
+ appropriate events.
+ */
+ void SelectionHasChanged();
+
+ /** Add a listener that is called when the selection of the slide sorter
+ changes.
+ @param rListener
+ When this method is called multiple times for the same listener
+ the second and all following calls are ignored. Each listener
+ is added only once.
+ */
+ void AddSelectionChangeListener(const Link<LinkParamNone*, void>& rListener);
+
+ /** Remove a listener that was called when the selection of the slide
+ sorter changes.
+ @param rListener
+ It is safe to pass a listener that was not added are has been
+ removed previously. Such calls are ignored.
+ */
+ void RemoveSelectionChangeListener(const Link<LinkParamNone*, void>& rListener);
+
+ /** Return the position where to insert pasted slides based on the
+ current selection. When there is a selection then the insert
+ position is behind the last slide. When the selection is empty then
+ most of the time the insert position is at the end of the document.
+ There is an exception right after the display of a popup-menu. The
+ position of the associated insertion marker is stored here and reset
+ the next time the selection changes.
+ */
+ sal_Int32 GetInsertionPosition() const;
+
+ /** Store an insertion position temporarily. It is reset when the
+ selection changes the next time.
+ */
+ void SetInsertionPosition(const sal_Int32 nInsertionPosition);
+
+ const std::shared_ptr<SelectionObserver>& GetSelectionObserver() const
+ {
+ return mpSelectionObserver;
+ }
+
+private:
+ SlideSorter& mrSlideSorter;
+ SlideSorterController& mrController;
+
+ ::std::vector<Link<LinkParamNone*, void>> maSelectionChangeListeners;
+
+ /** The insertion position is only temporarily valid. Negative values
+ indicate that the explicit insertion position is not valid. In this
+ case GetInsertionPosition() calculates it from the current selection.
+ */
+ sal_Int32 mnInsertionPosition;
+
+ std::shared_ptr<SelectionObserver> mpSelectionObserver;
+
+ /** Delete the given list of normal pages. This method is a helper
+ function for DeleteSelectedPages().
+ @param rSelectedNormalPages
+ A list of normal pages. Supplying master pages is an error.
+ */
+ void DeleteSelectedNormalPages(const ::std::vector<SdPage*>& rSelectedNormalPages);
+
+ /** Delete the given list of master pages. This method is a helper
+ function for DeleteSelectedPages().
+ @param rSelectedMasterPages
+ A list of master pages. Supplying normal pages is an error.
+ */
+ void DeleteSelectedMasterPages(const ::std::vector<SdPage*>& rSelectedMasterPages);
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/controller/SlsSelectionObserver.hxx b/sd/source/ui/slidesorter/inc/controller/SlsSelectionObserver.hxx
new file mode 100644
index 000000000..11742b890
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/controller/SlsSelectionObserver.hxx
@@ -0,0 +1,77 @@
+/* -*- 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 <sal/types.h>
+#include <memory>
+#include <vector>
+
+namespace sd::slidesorter
+{
+class SlideSorter;
+}
+
+class SdrPage;
+class SdPage;
+
+namespace sd::slidesorter::controller
+{
+/** Observe insertions and deletions of pages between calls to
+ StartObservation() and EndObservation(). When the later is called
+ the selection is set to just the newly inserted pages.
+*/
+class SelectionObserver final
+{
+public:
+ SelectionObserver(SlideSorter& rSlideSorter);
+ ~SelectionObserver();
+
+ void NotifyPageEvent(const SdrPage* pPage);
+ void StartObservation();
+ void AbortObservation();
+ void EndObservation();
+
+ /** Use this little class instead of calling StartObservation and
+ EndObservation directly so that EndObservation is not forgotten or
+ omitted due to an exception or some break or return in the middle of
+ code.
+ */
+ class Context
+ {
+ public:
+ Context(SlideSorter const& rSlideSorter);
+ ~Context() COVERITY_NOEXCEPT_FALSE;
+ void Abort();
+
+ private:
+ std::shared_ptr<SelectionObserver> mpSelectionObserver;
+ };
+
+private:
+ SlideSorter& mrSlideSorter;
+ bool mbIsObservationActive;
+ bool mbPageEventOccurred;
+
+ ::std::vector<const SdPage*> maInsertedPages;
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/controller/SlsSlotManager.hxx b/sd/source/ui/slidesorter/inc/controller/SlsSlotManager.hxx
new file mode 100644
index 000000000..57de8422a
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/controller/SlsSlotManager.hxx
@@ -0,0 +1,98 @@
+/* -*- 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 <model/SlsSharedPageDescriptor.hxx>
+#include <tools/link.hxx>
+#include <rtl/ustring.hxx>
+
+class AbstractSvxNameDialog;
+class SfxItemSet;
+class SfxRequest;
+
+namespace sd::slidesorter
+{
+class SlideSorter;
+}
+
+namespace sd::slidesorter::controller
+{
+/** This manager takes over the work of handling slot calls from the
+ controller of the slide sorter.
+*/
+class SlotManager
+{
+public:
+ /** Create a new slot manager that handles slot calls for the controller
+ of a slide sorter.
+ @param rController
+ The controller for which to handle the slot calls.
+ */
+ SlotManager(SlideSorter& rSlideSorter);
+
+ void FuTemporary(SfxRequest& rRequest);
+ void FuPermanent(SfxRequest& rRequest);
+ void FuSupport(SfxRequest& rRequest);
+ void GetMenuState(SfxItemSet& rSet);
+ void GetClipboardState(SfxItemSet& rSet);
+ void GetStatusBarState(SfxItemSet& rSet);
+ void ExecCtrl(SfxRequest& rRequest);
+ void GetAttrState(SfxItemSet& rSet);
+
+ /** Exclude or include one slide or all selected slides.
+ @param rpDescriptor
+ When the pointer is empty then apply the new state to all
+ selected pages. Otherwise apply the new state to just the
+ specified state.
+ */
+ void ChangeSlideExclusionState(const model::SharedPageDescriptor& rpDescriptor,
+ const bool bExcludeSlide);
+
+ /** Call this after a change from normal mode to master mode or back.
+ The affected slots are invalidated.
+ */
+ void NotifyEditModeChange();
+
+private:
+ /// The controller for which we manage the slot calls.
+ SlideSorter& mrSlideSorter;
+
+ /** The implementation is a copy of the code for SID_RENAMEPAGE in
+ drviews2.cxx.
+ */
+ void RenameSlide(const SfxRequest& rRequest);
+ DECL_LINK(RenameSlideHdl, AbstractSvxNameDialog&, bool);
+ DECL_STATIC_LINK(SlotManager, RenameSlideTooltipHdl, AbstractSvxNameDialog&, OUString);
+ bool RenameSlideFromDrawViewShell(sal_uInt16 nPageId, const OUString& rName);
+
+ /** Handle SID_INSERTPAGE slot calls.
+ */
+ void InsertSlide(SfxRequest& rRequest);
+
+ void DuplicateSelectedSlides(SfxRequest& rRequest);
+
+ /** Use one of several ways to determine where to insert a new page.
+ This can be the current selection or the insertion indicator.
+ */
+ sal_Int32 GetInsertionPosition() const;
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/controller/SlsTransferableData.hxx b/sd/source/ui/slidesorter/inc/controller/SlsTransferableData.hxx
new file mode 100644
index 000000000..863c2fe73
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/controller/SlsTransferableData.hxx
@@ -0,0 +1,78 @@
+/* -*- 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 <sdxfer.hxx>
+
+#include <vcl/bitmapex.hxx>
+
+#include <vector>
+
+class SdDrawDocument;
+namespace sd::slidesorter { class SlideSorterViewShell; }
+
+namespace sd::slidesorter::controller {
+
+/** Represent previews and other information so that they can be
+ attached to an existing transferable.
+*/
+class TransferableData final
+ : public SdTransferable::UserData,
+ public SfxListener
+{
+public:
+ class Representative
+ {
+ public:
+ Representative (const BitmapEx& rBitmap, const bool bIsExcluded)
+ : maBitmap(rBitmap), mbIsExcluded(bIsExcluded) {}
+
+ BitmapEx maBitmap;
+ bool mbIsExcluded;
+ };
+
+ static rtl::Reference<SdTransferable> CreateTransferable (
+ SdDrawDocument* pSrcDoc,
+ SlideSorterViewShell* pViewShell,
+ ::std::vector<TransferableData::Representative>&& rRepresentatives);
+
+ static std::shared_ptr<TransferableData> GetFromTransferable (const SdTransferable* pTransferable);
+
+ TransferableData (
+ SlideSorterViewShell* pViewShell,
+ ::std::vector<TransferableData::Representative>&& rRepresentatives);
+ virtual ~TransferableData() override;
+
+ const ::std::vector<Representative>& GetRepresentatives() const { return maRepresentatives;}
+
+ /** Return the view shell for which the transferable was created.
+ */
+ SlideSorterViewShell* GetSourceViewShell() const { return mpViewShell;}
+
+private:
+ SlideSorterViewShell* mpViewShell;
+ const ::std::vector<Representative> maRepresentatives;
+
+ virtual void Notify (SfxBroadcaster& rBroadcaster, const SfxHint& rHint) override;
+};
+
+} // end of namespace ::sd::slidesorter::controller
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/controller/SlsVisibleAreaManager.hxx b/sd/source/ui/slidesorter/inc/controller/SlsVisibleAreaManager.hxx
new file mode 100644
index 000000000..d9f5845af
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/controller/SlsVisibleAreaManager.hxx
@@ -0,0 +1,90 @@
+/* -*- 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 <model/SlsSharedPageDescriptor.hxx>
+#include <optional>
+#include <tools/gen.hxx>
+#include <vector>
+
+namespace sd::slidesorter
+{
+class SlideSorter;
+}
+
+namespace sd::slidesorter::controller
+{
+/** Manage requests for scrolling page objects into view.
+*/
+class VisibleAreaManager
+{
+public:
+ explicit VisibleAreaManager(SlideSorter& rSlideSorter);
+ ~VisibleAreaManager();
+ VisibleAreaManager(const VisibleAreaManager&) = delete;
+ VisibleAreaManager& operator=(const VisibleAreaManager&) = delete;
+
+ void ActivateCurrentSlideTracking();
+ void DeactivateCurrentSlideTracking();
+ bool IsCurrentSlideTrackingActive() const { return mbIsCurrentSlideTrackingActive; }
+
+ /** Request the current slide to be moved into the visible area.
+ This request is only obeyed when the current slide tracking is
+ active.
+ @see ActivateCurrentSlideTracking() and DeactivateCurrentSlideTracking()
+ */
+ void RequestCurrentSlideVisible();
+
+ /** Request to make the specified page object visible.
+ */
+ void RequestVisible(const model::SharedPageDescriptor& rpDescriptor, const bool bForce = false);
+
+ /** Temporarily disable the update of the visible area.
+ */
+ class TemporaryDisabler
+ {
+ public:
+ explicit TemporaryDisabler(SlideSorter const& rSlideSorter);
+ ~TemporaryDisabler();
+
+ private:
+ VisibleAreaManager& mrVisibleAreaManager;
+ };
+
+private:
+ SlideSorter& mrSlideSorter;
+
+ /** List of rectangle that someone wants to be moved into the visible
+ area.
+ Cleared on every call to ForgetVisibleRequests() and MakeVisible().
+ */
+ ::std::vector<::tools::Rectangle> maVisibleRequests;
+
+ Point maRequestedVisibleTopLeft;
+ bool mbIsCurrentSlideTrackingActive;
+ int mnDisableCount;
+
+ void MakeVisible();
+ ::std::optional<Point> GetRequestedTopLeft() const;
+};
+
+} // end of namespace ::sd::slidesorter::view
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/model/SlideSorterModel.hxx b/sd/source/ui/slidesorter/inc/model/SlideSorterModel.hxx
new file mode 100644
index 000000000..90223a1bc
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/model/SlideSorterModel.hxx
@@ -0,0 +1,227 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <model/SlsSharedPageDescriptor.hxx>
+#include <pres.hxx>
+#include <osl/mutex.hxx>
+#include <vcl/region.hxx>
+#include <com/sun/star/uno/Reference.hxx>
+#include <vector>
+
+class SdDrawDocument;
+class SdrPage;
+class SdPage;
+namespace sd::slidesorter
+{
+class SlideSorter;
+}
+namespace com::sun::star::container
+{
+class XIndexAccess;
+}
+namespace com::sun::star::drawing
+{
+class XDrawPage;
+}
+
+namespace sd::slidesorter::model
+{
+inline sal_Int32 FromCoreIndex(const sal_uInt16 nCoreIndex) { return (nCoreIndex - 1) / 2; }
+
+/** The model of the slide sorter gives access to the slides that are to be
+ displayed in the slide sorter view. Via the SetDocumentSlides() method
+ this set of slides can be modified (but do not call it directly, use
+ SlideSorterController::SetDocumentSlides() instead.)
+*/
+class SlideSorterModel final
+{
+public:
+ SlideSorterModel(SlideSorter& rSlideSorter);
+
+ ~SlideSorterModel();
+ void Dispose();
+
+ /** This method is present to let the view create a ShowView for
+ displaying slides.
+ */
+ SdDrawDocument* GetDocument();
+
+ /** Set a new edit mode and return whether the edit mode really
+ has been changed. When the edit mode is changed then the
+ previous page descriptor list is replaced by a new one which
+ has to be repainted.
+ @return
+ A return value of <TRUE/> indicates that the edit mode has
+ changed and thus the page descriptor list has been set up
+ to reflect that change. A repaint is necessary.
+ */
+ bool SetEditMode(EditMode eEditMode);
+
+ EditMode GetEditMode() const { return meEditMode; }
+
+ /** Return the number of slides in the document regardless of whether
+ they are visible or not or whether they are hidden or not.
+ The number of slides depends on the set of slides available through
+ the XIndexAccess given to SetDocumentSlides().
+ */
+ sal_Int32 GetPageCount() const;
+
+ /** Return a page descriptor for the page with the specified index.
+ Page descriptors are created on demand. The page descriptor is
+ found (or not found) in constant time.
+ @param nPageIndex
+ The index of the requested slide. The valid values
+ are 0 to GetPageCount()-1.
+ @param bCreate
+ When <TRUE/> and the requested page descriptor is missing then
+ it is created. When <FALSE/> then an empty reference is
+ returned for missing descriptors.
+ @return
+ When the given index is not valid, i.e. lower than zero or
+ larger than or equal to the number of pages then an empty
+ reference is returned. Note that the page count may change
+ between calls to GetPageCount() and GetPageDescriptor().
+ */
+ SharedPageDescriptor GetPageDescriptor(const sal_Int32 nPageIndex,
+ const bool bCreate = true) const;
+
+ /** Return a page descriptor for the given XDrawPage. Page descriptors
+ are created on demand. The page descriptor is found (or not found)
+ in (at most) linear time. Note that all page descriptors in front of
+ the one associated with the given XDrawPage are created when not yet
+ present. When the XDrawPage is not found then all descriptors are
+ created.
+ @return
+ Returns the index to the requested page descriptor or -1 when
+ there is no such page descriptor.
+ */
+ sal_Int32 GetIndex(const css::uno::Reference<css::drawing::XDrawPage>& rxSlide) const;
+
+ /** Return a page descriptor for the given SdrPage. Page descriptors
+ are created on demand. The page descriptor is found (or not found)
+ in (at most) linear time. Note that all page descriptors in front of
+ the one associated with the given XDrawPage are created when not yet
+ present. When the SdrPage is not found then all descriptors are
+ created.
+ @return
+ Returns the index to the requested page descriptor or -1 when
+ there is no such page descriptor.
+ */
+ sal_Int32 GetIndex(const SdrPage* pPage) const;
+
+ /** Return an index for accessing an SdrModel that corresponds to the
+ given SlideSorterModel index. In many cases we just have to apply
+ the n*2+1 magic. Only when a special model is set, like a custom
+ slide show, then the returned value is different.
+ */
+ sal_uInt16 GetCoreIndex(const sal_Int32 nIndex) const;
+
+ /** Call this method after the document has changed its structure. This
+ will get the model in sync with the SdDrawDocument. This method
+ tries not to throw away too much information already gathered. This
+ is especially important for previews of complex pages that take some
+ time to create.
+ */
+ void Resync();
+
+ /** Delete all descriptors that currently are in the container. The size
+ of the container, however, is not altered. Use the AdaptSize
+ method for that.
+ */
+ void ClearDescriptorList();
+
+ /** Set the selection of the document to exactly that of the called model.
+ */
+ void SynchronizeDocumentSelection();
+
+ /** Set the selection of the called model to exactly that of the document.
+ */
+ void SynchronizeModelSelection();
+
+ /** Return the mutex so that the caller can lock it and then safely
+ access the model.
+ */
+ ::osl::Mutex& GetMutex() { return maMutex; }
+
+ /** Set the XIndexAccess from which the called SlideSorterModel takes
+ its pages.
+ @param rxSlides
+ The set of slides accessible through this XIndexAccess are not
+ necessarily the same as the ones of the XModel of the
+ XController (although it typically is a subset).
+ */
+ void SetDocumentSlides(const css::uno::Reference<css::container::XIndexAccess>& rxSlides);
+
+ /** Return the set of pages that is currently displayed by the slide sorter.
+ */
+ css::uno::Reference<css::container::XIndexAccess> GetDocumentSlides() const;
+
+ /** This method is called when the edit mode has changed. It calls
+ SetDocumentSlides() with the set of slides or master pages obtained
+ from the model of the XController.
+ */
+ void UpdatePageList();
+
+ bool IsReadOnly() const;
+
+ /** The current selection is saved by copying the ST_Selected state into
+ ST_WasSelected for slides.
+ */
+ void SaveCurrentSelection();
+
+ /** The current selection is restored from the ST_WasSelected state from
+ the slides.
+ @returns
+ The returned region has to be repainted to reflect the updated
+ selection states.
+ */
+ vcl::Region RestoreSelection();
+
+ /** Typically called from controller::Listener this method handles the
+ insertion and deletion of single pages.
+ @return
+ Returns <TRUE/> when the given page is relevant for the current
+ page kind and edit mode.
+ */
+ bool NotifyPageEvent(const SdrPage* pPage);
+
+private:
+ mutable ::osl::Mutex maMutex;
+ SlideSorter& mrSlideSorter;
+ css::uno::Reference<css::container::XIndexAccess> mxSlides;
+ EditMode meEditMode;
+ mutable ::std::vector<SharedPageDescriptor> maPageDescriptors;
+
+ /** Resize the descriptor container according to current values of
+ page kind and edit mode.
+ */
+ void AdaptSize();
+
+ SdPage* GetPage(const sal_Int32 nCoreIndex) const;
+ void InsertSlide(SdPage* pPage, bool bMarkSelected);
+ // return if this page was marked as selected before being removed
+ bool DeleteSlide(const SdPage* pPage);
+ void UpdateIndices(const sal_Int32 nFirstIndex);
+};
+
+} // end of namespace ::sd::slidesorter::model
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/model/SlsEnumeration.hxx b/sd/source/ui/slidesorter/inc/model/SlsEnumeration.hxx
new file mode 100644
index 000000000..289f85911
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/model/SlsEnumeration.hxx
@@ -0,0 +1,44 @@
+/* -*- 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 <memory>
+
+namespace sd::slidesorter::model
+{
+/** Interface to generic enumerations. Designed to operate on shared
+ pointers. Therefore GetNextElement() returns T and not T&.
+*/
+template <class T> class Enumeration
+{
+public:
+ virtual ~Enumeration() {}
+
+ virtual bool HasMoreElements() const = 0;
+ /** Returns T instead of T& so that it can handle shared pointers.
+ */
+ virtual T GetNextElement() = 0;
+ virtual void Rewind() = 0;
+ virtual ::std::unique_ptr<Enumeration<T>> Clone() = 0;
+};
+
+} // end of namespace ::sd::slidesorter::model
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/model/SlsPageDescriptor.hxx b/sd/source/ui/slidesorter/inc/model/SlsPageDescriptor.hxx
new file mode 100644
index 000000000..4f3be3b42
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/model/SlsPageDescriptor.hxx
@@ -0,0 +1,144 @@
+/* -*- 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 <model/SlsVisualState.hxx>
+#include <tools/gen.hxx>
+#include <com/sun/star/uno/Reference.hxx>
+
+#include <memory>
+
+namespace com::sun::star::drawing { class XDrawPage; }
+
+class SdPage;
+class SdrPage;
+
+namespace sd::slidesorter::model {
+
+/** Each PageDescriptor object represents the preview of one draw page,
+ slide, or master page of a Draw or Impress document as they are displayed
+ in the slide sorter. This class gives access to some associated
+ information like prerendered preview or position on the screen.
+
+ <p>Bounding boxes of page objects come in four varieties:
+ Model and screen/pixel coordinates and the bounding boxes of the actual
+ page objects and the larger bounding boxes that include page names and
+ fade symbol.</p>
+*/
+class PageDescriptor
+ : public ::std::enable_shared_from_this<PageDescriptor>
+{
+public:
+ /** Create a PageDescriptor for the given SdPage object.
+ @param rxPage
+ The page that is represented by the new PageDescriptor object.
+ @param pPage
+ The page pointer can in some situations not be detected from
+ rxPage, e.g. after undo of page deletion. Therefore supply it
+ separately.
+ @param nIndex
+ This index is displayed in the view as page number. It is not
+ necessarily the page index (not even when you add or subtract 1
+ or use (x-1)/2 magic).
+ */
+ PageDescriptor (
+ const css::uno::Reference<css::drawing::XDrawPage>& rxPage,
+ SdPage* pPage,
+ const sal_Int32 nIndex);
+
+ ~PageDescriptor();
+
+ /** Return the page that is represented by the descriptor as SdPage pointer .
+ */
+ SdPage* GetPage() const { return mpPage;}
+
+ /** Return the page that is represented by the descriptor as XDrawPage reference.
+ */
+ const css::uno::Reference<css::drawing::XDrawPage>& GetXDrawPage() const { return mxPage;}
+
+ /** Returns the index of the page as it is displayed in the view as page
+ number. The value may differ from the index returned by the
+ XDrawPage when there are hidden slides and the XIndexAccess used to
+ access the model filters them out.
+ */
+ sal_Int32 GetPageIndex() const { return mnIndex;}
+ void SetPageIndex (const sal_Int32 nIndex);
+
+ bool UpdateMasterPage();
+ bool UpdateTransitionFlag();
+
+ enum State { ST_Visible, ST_Selected, ST_WasSelected,
+ ST_Focused, ST_MouseOver, ST_Current, ST_Excluded };
+
+ bool HasState (const State eState) const;
+
+ bool SetState (const State eState, const bool bStateValue);
+
+ /** Set the internal mbIsSelected flag to the selection state of the
+ page. Use this method to synchronize a page descriptor with the
+ page it describes and determine whether a redraw to update the
+ selection indicator is necessary.
+ @return
+ When the two selection states were different <TRUE/> is
+ returned. When they were the same this method returns
+ <FALSE/>.
+ */
+ bool GetCoreSelection();
+
+ /** Set the selection flags of the SdPage objects to the corresponding
+ selection states of the page descriptors.
+ */
+ void SetCoreSelection();
+
+ VisualState& GetVisualState() { return maVisualState;}
+
+ ::tools::Rectangle GetBoundingBox() const;
+ Point GetLocation (const bool bIgnoreLocation) const;
+ void SetBoundingBox (const ::tools::Rectangle& rBoundingBox);
+
+private:
+ SdPage* mpPage;
+ css::uno::Reference<css::drawing::XDrawPage> mxPage;
+ SdrPage const* mpMasterPage;
+
+ /** This index is displayed as page number in the view. It may or may
+ not be the actual page index.
+ */
+ sal_Int32 mnIndex;
+
+ ::tools::Rectangle maBoundingBox;
+ VisualState maVisualState;
+
+ bool mbIsSelected : 1;
+ bool mbWasSelected : 1;
+ bool mbIsVisible : 1;
+ bool mbIsFocused : 1;
+ bool mbIsCurrent : 1;
+ bool mbIsMouseOver : 1;
+ bool mbHasTransition : 1;
+
+ PageDescriptor (const PageDescriptor& rDescriptor) = delete;
+
+ PageDescriptor& operator= (const PageDescriptor& rDescriptor) = delete;
+};
+
+} // end of namespace ::sd::slidesorter::model
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/model/SlsPageEnumeration.hxx b/sd/source/ui/slidesorter/inc/model/SlsPageEnumeration.hxx
new file mode 100644
index 000000000..6901a9ff1
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/model/SlsPageEnumeration.hxx
@@ -0,0 +1,95 @@
+/* -*- 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 <model/SlsEnumeration.hxx>
+#include <model/SlsSharedPageDescriptor.hxx>
+
+#include <functional>
+#include <memory>
+
+namespace sd::slidesorter::model
+{
+class SlideSorterModel;
+
+/** Public class of page enumerations that delegates its calls to an
+ implementation object that can filter pages by using a given predicate.
+
+ @see PageEnumerationProvider
+ The PageEnumerationProvider has methods for creating different types
+ of page enumerations.
+*/
+class PageEnumeration final : public Enumeration<SharedPageDescriptor>
+{
+public:
+ /** Create a new page enumeration that enumerates a subset of the pages
+ of the given model.
+ @param rModel
+ The new page enumeration enumerates the pages of this model.
+ @param rPredicate
+ This predicate determines which pages to include in the
+ enumeration. Pages for which rPredicate returns <FALSE/> are
+ exclude.
+ */
+ typedef ::std::function<bool(const SharedPageDescriptor&)> PagePredicate;
+ static PageEnumeration Create(const SlideSorterModel& rModel, const PagePredicate& rPredicate);
+
+ /** This copy constructor creates a copy of the given enumeration.
+ */
+ PageEnumeration(const PageEnumeration& rEnumeration);
+
+ virtual ~PageEnumeration() override;
+
+ /** Create and return an exact copy of the called object.
+ */
+ virtual ::std::unique_ptr<Enumeration<SharedPageDescriptor>> Clone() override;
+
+ PageEnumeration& operator=(const PageEnumeration& rEnumeration);
+
+ /** Return <TRUE/> when the enumeration has more elements, i.e. it is
+ save to call GetNextElement() at least one more time.
+ */
+ virtual bool HasMoreElements() const override;
+
+ /** Return the next element of the enumeration. Call the
+ HasMoreElements() before to make sure that there exists at least one
+ more element. Calling this method with HasMoreElements() returning
+ <FALSE/> is an error.
+ */
+ virtual SharedPageDescriptor GetNextElement() override;
+
+ /** Rewind the enumeration so that the next call to GetNextElement()
+ will return its first element.
+ */
+ virtual void Rewind() override;
+
+private:
+ /// Implementation object.
+ ::std::unique_ptr<Enumeration<SharedPageDescriptor>> mpImpl;
+
+ /** This constructor expects an implementation object that holds
+ the predicate that filters the pages.
+ */
+ PageEnumeration(::std::unique_ptr<Enumeration<SharedPageDescriptor>>&& pImpl);
+};
+
+} // end of namespace ::sd::slidesorter::model
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/model/SlsPageEnumerationProvider.hxx b/sd/source/ui/slidesorter/inc/model/SlsPageEnumerationProvider.hxx
new file mode 100644
index 000000000..b6de98d13
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/model/SlsPageEnumerationProvider.hxx
@@ -0,0 +1,51 @@
+/* -*- 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 <model/SlsPageEnumeration.hxx>
+
+namespace sd::slidesorter::model
+{
+class SlideSorterModel;
+
+/** Collection of methods that create enumeration of slides.
+*/
+class PageEnumerationProvider
+{
+public:
+ /** The returned enumeration of slides iterates over all slides of the
+ given model.
+ */
+ static PageEnumeration CreateAllPagesEnumeration(const SlideSorterModel& rModel);
+
+ /** The returned enumeration of slides iterates over the currently
+ selected slides of the given model.
+ */
+ static PageEnumeration CreateSelectedPagesEnumeration(const SlideSorterModel& rModel);
+
+ /** The returned enumeration of slides iterates over the slides
+ (partially) inside the visible area.
+ */
+ static PageEnumeration CreateVisiblePagesEnumeration(const SlideSorterModel& rModel);
+};
+
+} // end of namespace ::sd::slidesorter::model
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/model/SlsSharedPageDescriptor.hxx b/sd/source/ui/slidesorter/inc/model/SlsSharedPageDescriptor.hxx
new file mode 100644
index 000000000..6ee1e2b22
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/model/SlsSharedPageDescriptor.hxx
@@ -0,0 +1,32 @@
+/* -*- 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 <memory>
+
+namespace sd::slidesorter::model
+{
+class PageDescriptor;
+
+typedef std::shared_ptr<PageDescriptor> SharedPageDescriptor;
+
+} // end of namespace ::sd::slidesorter::model
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/model/SlsVisualState.hxx b/sd/source/ui/slidesorter/inc/model/SlsVisualState.hxx
new file mode 100644
index 000000000..89eae16ca
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/model/SlsVisualState.hxx
@@ -0,0 +1,47 @@
+/* -*- 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 <sal/types.h>
+#include <tools/gen.hxx>
+
+namespace sd::slidesorter::model
+{
+/** This class gives access to values related to the visualization of page
+ objects. This includes animation state when blending from one state to
+ another.
+*/
+class VisualState
+{
+public:
+ VisualState(const sal_Int32 nPageId);
+
+ const Point& GetLocationOffset() const { return maLocationOffset; }
+ void SetLocationOffset(const Point& rPoint);
+
+ sal_Int32 mnPageId; // For debugging
+
+private:
+ Point maLocationOffset;
+};
+
+} // end of namespace ::sd::slidesorter::model
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/view/SlideSorterView.hxx b/sd/source/ui/slidesorter/inc/view/SlideSorterView.hxx
new file mode 100644
index 000000000..0f3493ab3
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/view/SlideSorterView.hxx
@@ -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 .
+ */
+
+#pragma once
+
+#include <model/SlsPageDescriptor.hxx>
+#include <model/SlsSharedPageDescriptor.hxx>
+#include <view/SlsLayouter.hxx>
+#include <view/SlsILayerPainter.hxx>
+#include <o3tl/deleter.hxx>
+
+#include <View.hxx>
+#include <tools/gen.hxx>
+#include <vcl/region.hxx>
+#include <memory>
+
+namespace sd::slidesorter::cache { class PageCache; }
+namespace sd::slidesorter::model { class SlideSorterModel; }
+namespace sd { class Window; }
+namespace sd::slidesorter { class SlideSorter; }
+namespace sd::slidesorter::view {
+
+class LayeredDevice;
+class PageObjectPainter;
+class ToolTip;
+
+class SlideSorterView final
+ : public sd::View
+{
+public:
+
+ /** Create a new view for the slide sorter.
+ @param rViewShell
+ This reference is simply passed to the base class and not used
+ by this class.
+
+ */
+ explicit SlideSorterView (SlideSorter& rSlideSorter);
+ void Init();
+
+ virtual ~SlideSorterView() override;
+ void Dispose();
+
+ SlideSorterView(const SlideSorterView&) = delete;
+ SlideSorterView& operator=(const SlideSorterView&) = delete;
+
+ /** Set the general way of layouting the page objects. Note that this
+ method does not trigger any repaints or layouts.
+ */
+ bool SetOrientation (const Layouter::Orientation eOrientation);
+ Layouter::Orientation GetOrientation() const { return meOrientation;}
+
+ void RequestRepaint();
+ void RequestRepaint (const model::SharedPageDescriptor& rDescriptor);
+ void RequestRepaint (const ::tools::Rectangle& rRepaintBox);
+ void RequestRepaint (const vcl::Region& rRepaintRegion);
+
+ ::tools::Rectangle GetModelArea() const;
+
+ /** Return the index of the page that is rendered at the given position.
+ @param rPosition
+ The position is expected to be in pixel coordinates.
+ @return
+ The returned index is -1 when there is no page object at the
+ given position.
+ */
+ sal_Int32 GetPageIndexAtPoint (const Point& rPosition) const;
+
+ view::Layouter& GetLayouter();
+
+ virtual void ModelHasChanged() override;
+
+ /** This method is typically called before a model change takes place.
+ All references to model data are released. PostModelChange() has to
+ be called to complete the handling of the model change. When the
+ calls to Pre- and PostModelChange() are very close to each other you
+ may call HandleModelChange() instead.
+ */
+ void PreModelChange();
+
+ /** This method is typically called after a model change took place.
+ References to model data are re-allocated. Call this method only
+ after PreModelChange() has been called.
+ */
+ void PostModelChange();
+
+ /** This method is a convenience function that simply calls
+ PreModelChange() and then PostModelChange().
+ */
+ void HandleModelChange();
+
+ void HandleDrawModeChange();
+
+ void Resize();
+ virtual void CompleteRedraw (
+ OutputDevice* pDevice,
+ const vcl::Region& rPaintArea,
+ sdr::contact::ViewObjectContactRedirector* pRedirector = nullptr) override;
+ void Paint (OutputDevice& rDevice, const ::tools::Rectangle& rRepaintArea);
+
+ virtual void ConfigurationChanged (
+ utl::ConfigurationBroadcaster* pBroadcaster,
+ ConfigurationHints nHint) override;
+
+ void HandleDataChangeEvent();
+
+ void Layout();
+ /** This tells the view that it has to re-determine the visibility of
+ the page objects before painting them the next time.
+ */
+ void InvalidatePageObjectVisibilities();
+
+ std::shared_ptr<cache::PageCache> const & GetPreviewCache();
+
+ /** Return the range of currently visible page objects including the
+ first and last one in that range.
+ @return
+ The returned pair of page object indices is empty when the
+ second index is lower than the first.
+ */
+ Range const & GetVisiblePageRange();
+
+ /** Add a shape to the page. Typically used from inside
+ PostModelChange().
+ */
+ // void AddSdrObject (SdrObject& rObject);
+
+ /** Add a listener that is called when the set of visible slides.
+ @param rListener
+ When this method is called multiple times for the same listener
+ the second and all following calls are ignored. Each listener
+ is added only once.
+ */
+ void AddVisibilityChangeListener (const Link<LinkParamNone*,void>& rListener);
+
+ /** Remove a listener that is called when the set of visible slides changes.
+ @param rListener
+ It is safe to pass a listener that was not added or has been
+ removed previously. Such calls are ignored.
+ */
+ void RemoveVisibilityChangeListener (const Link<LinkParamNone*,void>& rListener);
+
+ /** The page under the mouse is not highlighted in some contexts. Call
+ this method on context changes.
+ */
+ void UpdatePageUnderMouse ();
+ void UpdatePageUnderMouse (const Point& rMousePosition);
+ void SetPageUnderMouse (const model::SharedPageDescriptor& rpDescriptor);
+
+ bool SetState (
+ const model::SharedPageDescriptor& rpDescriptor,
+ const model::PageDescriptor::State eState,
+ const bool bStateValue);
+
+ void UpdateOrientation();
+
+ std::shared_ptr<PageObjectPainter> const & GetPageObjectPainter();
+ const std::shared_ptr<LayeredDevice>& GetLayeredDevice() const { return mpLayeredDevice;}
+
+ class DrawLock
+ {
+ public:
+ DrawLock (SlideSorter const & rSlideSorter);
+ ~DrawLock();
+ /** When the DrawLock is disposed then it will not request a repaint
+ on destruction.
+ */
+ void Dispose();
+ private:
+ view::SlideSorterView& mrView;
+ VclPtr<sd::Window> mpWindow;
+ };
+
+ ToolTip& GetToolTip() const;
+
+ virtual void DragFinished (sal_Int8 nDropAction) override;
+
+private:
+ SlideSorter& mrSlideSorter;
+ model::SlideSorterModel& mrModel;
+ bool mbIsDisposed;
+ ::std::unique_ptr<Layouter> mpLayouter;
+ bool mbPageObjectVisibilitiesValid;
+ std::shared_ptr<cache::PageCache> mpPreviewCache;
+ std::shared_ptr<LayeredDevice> mpLayeredDevice;
+ Range maVisiblePageRange;
+ Size maPreviewSize;
+ bool mbPreciousFlagUpdatePending;
+ Layouter::Orientation meOrientation;
+ model::SharedPageDescriptor mpPageUnderMouse;
+ std::shared_ptr<PageObjectPainter> mpPageObjectPainter;
+ vcl::Region maRedrawRegion;
+ SharedILayerPainter mpBackgroundPainter;
+ std::unique_ptr<ToolTip, o3tl::default_delete<ToolTip>> mpToolTip;
+ bool mbIsRearrangePending;
+ ::std::vector<Link<LinkParamNone*,void>> maVisibilityChangeListeners;
+
+ /** Determine the visibility of all page objects.
+ */
+ void DeterminePageObjectVisibilities();
+
+ void UpdatePreciousFlags();
+ void RequestRearrange();
+ void Rearrange();
+};
+
+} // end of namespace ::sd::slidesorter::view
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/view/SlsILayerPainter.hxx b/sd/source/ui/slidesorter/inc/view/SlsILayerPainter.hxx
new file mode 100644
index 000000000..57b90af0a
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/view/SlsILayerPainter.hxx
@@ -0,0 +1,53 @@
+/* -*- 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 <memory>
+
+class OutputDevice;
+namespace tools { class Rectangle; }
+
+namespace sd::slidesorter::view {
+
+class ILayerInvalidator
+{
+public:
+ virtual ~ILayerInvalidator() {}
+
+ virtual void Invalidate (const ::tools::Rectangle& rInvalidationBox) = 0;
+};
+typedef std::shared_ptr<ILayerInvalidator> SharedILayerInvalidator;
+
+class ILayerPainter
+{
+public:
+ virtual ~ILayerPainter() {}
+
+ virtual void SetLayerInvalidator (
+ const SharedILayerInvalidator& rpInvalidator) = 0;
+ virtual void Paint (
+ OutputDevice& rDevice,
+ const ::tools::Rectangle& rRepaintArea) = 0;
+};
+typedef std::shared_ptr<ILayerPainter> SharedILayerPainter;
+
+} // end of namespace ::sd::slidesorter::view
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/view/SlsInsertAnimator.hxx b/sd/source/ui/slidesorter/inc/view/SlsInsertAnimator.hxx
new file mode 100644
index 000000000..c74d06cb9
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/view/SlsInsertAnimator.hxx
@@ -0,0 +1,59 @@
+/* -*- 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 <controller/SlsAnimator.hxx>
+#include <memory>
+
+namespace sd::slidesorter::view
+{
+class InsertPosition;
+
+/** Animate the positions of page objects to make room at the insert
+ position while a move or copy operation takes place.
+*/
+class InsertAnimator
+{
+public:
+ explicit InsertAnimator(SlideSorter& rSlideSorter);
+ InsertAnimator(const InsertAnimator&) = delete;
+ InsertAnimator& operator=(const InsertAnimator&) = delete;
+
+ /** Set the position at which we have to make room for the display of an
+ icon.
+ */
+ void SetInsertPosition(const InsertPosition& rInsertPosition);
+
+ /** Restore the normal position of all page objects.
+ @param eMode
+ This flag controls whether to start an animation that ends in the
+ normal positions of all slides (AM_Animated) or to restore the
+ normal positions immediately (AM_Immediate).
+ */
+ void Reset(const controller::Animator::AnimationMode eMode);
+
+private:
+ class Implementation;
+ std::shared_ptr<Implementation> mpImplementation;
+};
+
+} // end of namespace ::sd::slidesorter::view
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/view/SlsInsertionIndicatorOverlay.hxx b/sd/source/ui/slidesorter/inc/view/SlsInsertionIndicatorOverlay.hxx
new file mode 100644
index 000000000..3f4cc2218
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/view/SlsInsertionIndicatorOverlay.hxx
@@ -0,0 +1,101 @@
+/* -*- 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 <controller/SlsTransferableData.hxx>
+
+#include <tools/gen.hxx>
+#include <vcl/bitmapex.hxx>
+#include <memory>
+#include <vector>
+
+class OutputDevice;
+class SdTransferable;
+
+namespace sd::slidesorter { class SlideSorter; }
+
+namespace sd::slidesorter::view {
+
+class FramePainter;
+
+/** The insertion indicator is painted as a vertical or horizontal bar
+ in the space between slides.
+*/
+class InsertionIndicatorOverlay final
+ : public ILayerPainter,
+ public std::enable_shared_from_this<InsertionIndicatorOverlay>
+{
+public:
+ InsertionIndicatorOverlay (SlideSorter& rSlideSorter);
+ virtual ~InsertionIndicatorOverlay() override;
+
+ virtual void SetLayerInvalidator (const SharedILayerInvalidator& rpInvalidator) override;
+
+ void Create (const SdTransferable* pTransferable);
+
+ /** Given a position in model coordinates this method calculates the
+ insertion marker both as an index in the document and as a location
+ used for drawing the insertion indicator.
+ */
+ void SetLocation (const Point& rPosition);
+
+ Size GetSize() const;
+
+ virtual void Paint (
+ OutputDevice& rDevice,
+ const ::tools::Rectangle& rRepaintArea) override;
+
+ bool IsVisible() const { return mbIsVisible;}
+ void Hide();
+ void Show();
+
+ ::tools::Rectangle GetBoundingBox() const;
+
+private:
+ SlideSorter& mrSlideSorter;
+ bool mbIsVisible;
+ SharedILayerInvalidator mpLayerInvalidator;
+ // Center of the insertion indicator.
+ Point maLocation;
+ BitmapEx maIcon;
+ std::unique_ptr<FramePainter> mpShadowPainter;
+
+ Point PaintRepresentatives (
+ OutputDevice& rContent,
+ const Size& rPreviewSize,
+ const sal_Int32 nOffset,
+ const ::std::vector<controller::TransferableData::Representative>& rPages) const;
+ void PaintPageCount (
+ OutputDevice& rDevice,
+ const sal_Int32 nSelectionCount,
+ const Size& rPreviewSize,
+ const Point& rFirstPageOffset) const;
+ /** Setup the insertion indicator by creating the icon. It consists of
+ scaled down previews of some of the selected pages.
+ */
+ void Create (
+ const ::std::vector<controller::TransferableData::Representative>& rPages,
+ const sal_Int32 nSelectionCount);
+};
+
+} // end of namespace ::sd::slidesorter::view
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/view/SlsLayouter.hxx b/sd/source/ui/slidesorter/inc/view/SlsLayouter.hxx
new file mode 100644
index 000000000..b91ae83c5
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/view/SlsLayouter.hxx
@@ -0,0 +1,237 @@
+/* -*- 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/vclptr.hxx>
+#include <tools/gen.hxx>
+#include <sal/types.h>
+#include <memory>
+
+namespace sd { class Window; }
+namespace sd::slidesorter::model { class SlideSorterModel; }
+namespace sd::slidesorter::view { class PageObjectLayouter; }
+namespace sd::slidesorter::view { class Theme; }
+
+namespace sd::slidesorter::view {
+
+class InsertPosition;
+
+/** Calculate the size and position of page objects displayed by a slide
+ sorter. The layouter takes into account various input values:
+ 1.) Size of the window in which the slide sorter is displayed.
+ 2.) Desired and minimal and maximal widths of page objects.
+ 3.) Minimal and maximal number of columns.
+ 4.) Vertical and horizontal gaps between objects in adjacent columns.
+ 5.) Borders around every page object.
+ 6.) Vertical and horizontal borders between enclosing page and outer
+ page objects.
+ From these, it calculates various output values:
+ 1.) The width of page objects.
+ 2.) The number of columns.
+ 3.) The size of the enclosing page.
+
+ <p>Sizes and lengths are all in pixel except where explicitly stated
+ otherwise.</p>
+
+ <p>The GetIndex... methods may return indices that are larger than or
+ equal to (zero based) the number of pages. This is so because the
+ number of pages is not known to the class instances. Indices are
+ calculated with reference to the general grid layout of page
+ objects.</p>
+*/
+class Layouter
+{
+public:
+ enum Orientation { HORIZONTAL, VERTICAL, GRID };
+
+ Layouter (
+ sd::Window *rpWindow,
+ const std::shared_ptr<Theme>& rpTheme);
+ ~Layouter();
+
+ std::shared_ptr<PageObjectLayouter> const & GetPageObjectLayouter() const;
+ /** Set the interval of valid column counts. When nMinimalColumnCount
+ <= nMaximalColumnCount is not fulfilled then the call is ignored.
+ @param nMinimalColumnCount
+ The default value is 1. The question whether higher values make
+ any sense is left to the caller.
+ @param nMaximalColumnCount
+ The default value is 5.
+ */
+ void SetColumnCount (sal_Int32 nMinimalColumnCount,
+ sal_Int32 nMaximalColumnCount);
+
+ /** Central method of this class. It takes the input values and
+ calculates the output values. Both given sizes must not be 0 in any
+ dimension or the call is ignored.
+ @param eOrientation
+ This defines the generally layout and specifies whether there may
+ be more than one row or more than one column.
+ @param rWindowSize
+ The size of the window in pixels that the slide sorter is
+ displayed in. This can differ from the size of mpWindow during
+ detection of whether or not the scroll bars should be visible.
+ @param rPreviewModelSize
+ Size of each page in model coordinates.
+ @param rpWindow
+ The map mode of this window is adapted to the new layout of the
+ page objects.
+ @return
+ The return value indicates whether the Get... methods can be
+ used to obtain valid values (<TRUE/>).
+ */
+ bool Rearrange (
+ const Orientation eOrientation,
+ const Size& rWindowSize,
+ const Size& rPreviewModelSize,
+ const sal_uInt32 nPageCount);
+
+ /** Return the number of columns.
+ */
+ sal_Int32 GetColumnCount() const;
+
+ sal_Int32 GetIndex (const sal_Int32 nRow, const sal_Int32 nColumn) const;
+
+ Size const & GetPageObjectSize() const;
+
+ /** Return the bounding box in window coordinates of the nIndex-th page
+ object.
+ */
+ ::tools::Rectangle GetPageObjectBox (
+ const sal_Int32 nIndex,
+ const bool bIncludeBorderAndGap) const;
+
+ /** Return the bounding box in model coordinates of the page that
+ contains the given amount of page objects.
+ */
+ ::tools::Rectangle GetTotalBoundingBox() const;
+
+ /** Return the index of the first fully or partially visible page
+ object. This takes into account only the vertical dimension.
+ @return
+ The second index may be larger than the number of existing
+ page objects.
+ */
+ Range GetRangeOfVisiblePageObjects (const ::tools::Rectangle& rVisibleArea) const;
+
+ /** Return the index of the page object that is rendered at the given
+ point.
+ @param rPosition
+ The position is expected to be in model coordinates relative to
+ the page origin.
+ @param bIncludePageBorders
+ When <TRUE/> then include the page borders into the calculation,
+ i.e. when a point lies in the border of a page object but not on
+ the actual page area the index of that page is returned;
+ otherwise -1 would be returned to indicate that no page object
+ has been hit.
+ @param bClampToValidRange
+ When <TRUE/> then values outside the valid range [0,mnPageCount)
+ are mapped to 0 (when smaller than 0) or mnPageCount-1 when
+ equal to or larger than mnPageCount.
+ When <FALSE/> then -1 is returned for values outside the valid range.
+ @return
+ The returned index may be larger than the number of existing
+ page objects.
+ */
+ sal_Int32 GetIndexAtPoint (
+ const Point& rModelPosition,
+ const bool bIncludePageBorders,
+ const bool bClampToValidRange = true) const;
+
+ /** Return an object that describes the logical and visual properties of
+ where to do an insert operation when the user would release the
+ mouse button at the given position after a drag operation and of
+ where and how to display an insertion indicator.
+ @param rModelPosition
+ The position in the model coordinate system for which to
+ determine the insertion page index. The position does not have
+ to be over a page object to return a valid value.
+ @param rIndicatorSize
+ The size of the insertion indicator. This size is used to adapt
+ the location when at the left or right of a row or at the top or
+ bottom of a column.
+ @param rModel
+ The model is used to get access to the selection states of the
+ pages. This in turn is used to determine the visual bounding
+ boxes.
+ */
+ InsertPosition GetInsertPosition (
+ const Point& rModelPosition,
+ const Size& rIndicatorSize,
+ model::SlideSorterModel const & rModel) const;
+
+ Range GetValidHorizontalSizeRange() const;
+ Range GetValidVerticalSizeRange() const;
+
+ class Implementation;
+
+private:
+ std::unique_ptr<Implementation> mpImplementation;
+ VclPtr<sd::Window> mpWindow;
+};
+
+/** Collect all values concerning the logical and visual properties of the
+ insertion position that is used for drag-and-drop and copy-and-paste.
+*/
+class InsertPosition
+{
+public:
+ InsertPosition();
+ bool operator== (const InsertPosition& rInsertPosition) const;
+ bool operator!= (const InsertPosition& rInsertPosition) const;
+
+ void SetLogicalPosition (
+ const sal_Int32 nRow,
+ const sal_Int32 nColumn,
+ const sal_Int32 nIndex,
+ const bool bIsAtRunStart,
+ const bool bIsAtRunEnd,
+ const bool bIsExtraSpaceNeeded);
+ void SetGeometricalPosition(
+ const Point& rLocation,
+ const Point& rLeadingOffset,
+ const Point& rTrailingOffset);
+
+ sal_Int32 GetRow() const { return mnRow; }
+ sal_Int32 GetColumn() const { return mnColumn; }
+ sal_Int32 GetIndex() const { return mnIndex; }
+ const Point& GetLocation() const { return maLocation; }
+ const Point& GetLeadingOffset() const { return maLeadingOffset; }
+ const Point& GetTrailingOffset() const { return maTrailingOffset; }
+ bool IsAtRunStart() const { return mbIsAtRunStart; }
+ bool IsAtRunEnd() const { return mbIsAtRunEnd; }
+ bool IsExtraSpaceNeeded() const { return mbIsExtraSpaceNeeded; }
+
+private:
+ sal_Int32 mnRow;
+ sal_Int32 mnColumn;
+ sal_Int32 mnIndex;
+ bool mbIsAtRunStart : 1;
+ bool mbIsAtRunEnd : 1;
+ bool mbIsExtraSpaceNeeded : 1;
+ Point maLocation;
+ Point maLeadingOffset;
+ Point maTrailingOffset;
+};
+
+} // end of namespace ::sd::slidesorter::view
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/view/SlsPageObjectLayouter.hxx b/sd/source/ui/slidesorter/inc/view/SlsPageObjectLayouter.hxx
new file mode 100644
index 000000000..8bb77a988
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/view/SlsPageObjectLayouter.hxx
@@ -0,0 +1,144 @@
+/* -*- 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 <model/SlsSharedPageDescriptor.hxx>
+#include <tools/gen.hxx>
+#include <vcl/image.hxx>
+
+namespace vcl { class Font; }
+namespace sd { class Window; }
+
+namespace sd::slidesorter::view {
+
+/** In contrast to the Layouter that places page objects in the view, the
+ PageObjectLayouter places the parts of individual page objects like page
+ number area, borders, preview.
+*/
+class PageObjectLayouter
+{
+public:
+ /** Create a new PageObjectLayouter object.
+ @param rPageObjectSize
+ In general either the width or the height will be 0 in order to
+ signal that this size component has to be calculated from the other.
+ This calculation will make the preview as large as possible.
+ @param nPageCount
+ The page count is used to determine how wide the page number
+ area has to be, how many digits to except for the largest page number.
+ */
+ PageObjectLayouter(
+ const Size& rPageObjectWindowSize,
+ const Size& rPreviewModelSize,
+ sd::Window *pWindow,
+ const sal_Int32 nPageCount);
+ ~PageObjectLayouter();
+
+ enum class Part {
+ // The focus indicator is painted outside the actual page object.
+ FocusIndicator,
+ // This is the outer bounding box that includes the preview, page
+ // number, title.
+ PageObject,
+ // Bounding box of the actual preview.
+ Preview,
+ // Bounding box of the page number.
+ PageNumber,
+ // Indicator whether or not there is a slide transition associated
+ // with this slide.
+ TransitionEffectIndicator,
+ // Indicator whether or not there is a custom animation associated
+ // with this slide.
+ CustomAnimationEffectIndicator
+ };
+ /** Two coordinate systems are supported. They differ only in
+ translation not in scale. Both relate to pixel values in the window.
+ A position in the model coordinate system does not change when the window content is
+ scrolled up or down. In the window coordinate system (relative
+ to the top left point of the window)scrolling leads to different values.
+ */
+ enum CoordinateSystem {
+ WindowCoordinateSystem,
+ ModelCoordinateSystem
+ };
+
+ /** Return the bounding box of the page object or one of its graphical
+ parts.
+ @param rWindow
+ This device is used to translate between model and window
+ coordinates.
+ @param rpPageDescriptor
+ The page for which to calculate the bounding box. This may be
+ NULL. When it is NULL then a generic bounding box is calculated
+ for the location (0,0).
+ @param ePart
+ The part of the page object for which to return the bounding
+ box.
+ @param eCoordinateSystem
+ The bounding box can be returned in model and in pixel
+ (window) coordinates.
+ @param bIgnoreLocation
+ Return a position ignoring the slides' location, ie. as if
+ we were the first slide.
+ */
+ ::tools::Rectangle GetBoundingBox (
+ const model::SharedPageDescriptor& rpPageDescriptor,
+ const Part ePart,
+ const CoordinateSystem eCoordinateSystem,
+ bool bIgnoreLocation = false);
+
+ /// the size of the embedded preview: position independent, in window coordinate system
+ Size GetPreviewSize();
+
+ /// the maximum size of each tile, also position independent, in window coordinate system
+ Size GetGridMaxSize();
+
+ const Image& GetTransitionEffectIcon() const { return maTransitionEffectIcon;}
+ const Image& GetCustomAnimationEffectIcon() const { return maCustomAnimationEffectIcon;}
+
+private:
+ ::tools::Rectangle GetBoundingBox (
+ const Point& rPageObjectLocation,
+ const Part ePart,
+ const CoordinateSystem eCoordinateSystem);
+
+private:
+ VclPtr<sd::Window> mpWindow;
+ ::tools::Rectangle maFocusIndicatorBoundingBox;
+ ::tools::Rectangle maPageObjectBoundingBox;
+ ::tools::Rectangle maPageNumberAreaBoundingBox;
+ ::tools::Rectangle maPreviewBoundingBox;
+ ::tools::Rectangle maTransitionEffectBoundingBox;
+ ::tools::Rectangle maCustomAnimationEffectBoundingBox;
+ const Image maTransitionEffectIcon;
+ const Image maCustomAnimationEffectIcon;
+ const std::shared_ptr<vcl::Font> mpPageNumberFont;
+
+ Size GetPageNumberAreaSize (const int nPageCount);
+ ::tools::Rectangle CalculatePreviewBoundingBox (
+ Size& rPageObjectSize,
+ const Size& rPreviewModelSize,
+ const sal_Int32 nPageNumberAreaWidth,
+ const sal_Int32 nFocusIndicatorWidth);
+};
+
+} // end of namespace ::sd::slidesorter::view
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/view/SlsPageObjectPainter.hxx b/sd/source/ui/slidesorter/inc/view/SlsPageObjectPainter.hxx
new file mode 100644
index 000000000..747c09500
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/view/SlsPageObjectPainter.hxx
@@ -0,0 +1,119 @@
+/* -*- 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 <model/SlsSharedPageDescriptor.hxx>
+#include <view/SlsTheme.hxx>
+#include <memory>
+
+namespace sd::slidesorter::cache { class PageCache; }
+namespace sd::slidesorter { class SlideSorter; }
+
+namespace sd::slidesorter::view {
+
+class Layouter;
+class PageObjectLayouter;
+class FramePainter;
+
+class PageObjectPainter
+{
+public:
+ PageObjectPainter (const SlideSorter& rSlideSorter);
+ ~PageObjectPainter();
+
+ void PaintPageObject (
+ OutputDevice& rDevice,
+ const model::SharedPageDescriptor& rpDescriptor);
+
+ /** Called when the theme changes, either because it is replaced with
+ another or because the system colors have changed. So, even when
+ the given theme is the same object as the one already in use by this
+ painter everything that depends on the theme is updated.
+ */
+ void SetTheme (const std::shared_ptr<view::Theme>& rpTheme);
+
+ /** Return a preview bitmap for the given page descriptor. When the
+ page is excluded from the show then the preview is marked
+ accordingly.
+ @rpDescriptor
+ Defines the page for which to return the preview.
+ @pReferenceDevice
+ When not <NULL/> then this reference device is used to created a
+ compatible bitmap.
+ @return
+ The returned bitmap may have a different size then the preview area.
+ */
+ BitmapEx GetPreviewBitmap (
+ const model::SharedPageDescriptor& rpDescriptor,
+ const OutputDevice* pReferenceDevice) const;
+
+private:
+ const Layouter& mrLayouter;
+ std::shared_ptr<cache::PageCache> mpCache;
+ std::shared_ptr<view::Theme> mpTheme;
+ std::shared_ptr<vcl::Font> mpPageNumberFont;
+ std::unique_ptr<FramePainter> mpShadowPainter;
+ std::unique_ptr<FramePainter> mpFocusBorderPainter;
+
+ void PaintBackground (
+ PageObjectLayouter *pPageObjectLayouter,
+ OutputDevice& rDevice,
+ const model::SharedPageDescriptor& rpDescriptor) const;
+ void PaintPreview (
+ PageObjectLayouter *pPageObjectLayouter,
+ OutputDevice& rDevice,
+ const model::SharedPageDescriptor& rpDescriptor) const;
+ void PaintPageNumber (
+ PageObjectLayouter *pPageObjectLayouter,
+ OutputDevice& rDevice,
+ const model::SharedPageDescriptor& rpDescriptor) const;
+ static void PaintTransitionEffect (
+ PageObjectLayouter *pPageObjectLayouter,
+ OutputDevice& rDevice,
+ const model::SharedPageDescriptor& rpDescriptor);
+ static void PaintCustomAnimationEffect (
+ PageObjectLayouter *pPageObjectLayouter,
+ OutputDevice& rDevice,
+ const model::SharedPageDescriptor& rpDescriptor);
+ void PaintBorder (
+ OutputDevice& rDevice,
+ const Theme::GradientColorType eColorType,
+ const ::tools::Rectangle& rBox) const;
+ void PaintBackgroundDetail(
+ PageObjectLayouter *pPageObjectLayouter,
+ OutputDevice& rDevice,
+ const model::SharedPageDescriptor& rpDescriptor) const;
+
+ static BitmapEx CreateMarkedPreview(
+ const Size& rSize,
+ const BitmapEx& rPreview,
+ const BitmapEx& rOverlay,
+ const OutputDevice* pReferenceDevice);
+
+ /** Update the local pointer to the page object layouter to the
+ one owned by the general layouter.
+ Return <TRUE/> when after the call we have a valid page object layouter.
+ */
+ bool UpdatePageObjectLayouter();
+};
+
+} // end of namespace sd::slidesorter::view
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/view/SlsTheme.hxx b/sd/source/ui/slidesorter/inc/view/SlsTheme.hxx
new file mode 100644
index 000000000..efb7b2a3e
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/view/SlsTheme.hxx
@@ -0,0 +1,135 @@
+/* -*- 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>
+#include <tools/color.hxx>
+
+#include <memory>
+
+namespace vcl { class Font; }
+
+namespace sd::slidesorter::controller { class Properties; }
+
+namespace sd::slidesorter::view {
+
+const int Theme_FocusIndicatorWidth = 3;
+
+/** Collection of colors and styles that are used to paint the slide sorter
+ view.
+*/
+class Theme
+{
+public:
+ Theme (const std::shared_ptr<controller::Properties>& rpProperties);
+
+ /** Call this method to update some colors as response to a change of
+ a system color change.
+ */
+ void Update (
+ const std::shared_ptr<controller::Properties>& rpProperties);
+
+ // BitmapEx GetInsertIndicatorIcon() const;
+
+ enum FontType {
+ Font_PageNumber,
+ Font_PageCount
+ };
+ static std::shared_ptr<vcl::Font> GetFont (
+ const FontType eType,
+ const OutputDevice& rDevice);
+
+ enum ColorType {
+ Color_Background,
+ Color_PageNumberDefault,
+ Color_PageNumberHover,
+ Color_PageNumberHighContrast,
+ Color_PageNumberBrightBackground,
+ Color_PageNumberDarkBackground,
+ Color_Selection,
+ Color_PreviewBorder,
+ Color_PageCountFontColor,
+ ColorType_Size_
+ };
+ Color GetColor (const ColorType eType);
+
+ enum GradientColorType {
+ Gradient_NormalPage,
+ Gradient_SelectedPage,
+ Gradient_SelectedAndFocusedPage,
+ Gradient_MouseOverPage,
+ Gradient_MouseOverSelected,
+ Gradient_MouseOverSelectedAndFocusedPage,
+ Gradient_FocusedPage,
+ GradientColorType_Size_
+ };
+ enum class GradientColorClass {
+ Border1,
+ Border2,
+ Fill1,
+ Fill2
+ };
+ Color GetGradientColor (
+ const GradientColorType eType,
+ const GradientColorClass eClass);
+ void 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);
+
+ enum IconType
+ {
+ Icon_RawShadow,
+ Icon_RawInsertShadow,
+ Icon_HideSlideOverlay,
+ Icon_FocusBorder,
+ IconType_Size_
+ };
+ const BitmapEx& GetIcon (const IconType eType);
+
+private:
+ class GradientDescriptor
+ {
+ public:
+ Color maFillColor1;
+ Color maFillColor2;
+ Color maBorderColor1;
+ Color maBorderColor2;
+ };
+ Color maBackgroundColor;
+ ::std::vector<GradientDescriptor> maGradients;
+ ::std::vector<BitmapEx> maIcons;
+ ::std::vector<Color> maColor;
+
+ GradientDescriptor& GetGradient (const GradientColorType eType);
+ /** Guarded initialization of the specified icon in the maIcons
+ container.
+ */
+ void InitializeIcon(const IconType eType, const OUString& rResourceId);
+};
+
+} // end of namespace ::sd::slidesorter::view
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/inc/view/SlsToolTip.hxx b/sd/source/ui/slidesorter/inc/view/SlsToolTip.hxx
new file mode 100644
index 000000000..6c3557e64
--- /dev/null
+++ b/sd/source/ui/slidesorter/inc/view/SlsToolTip.hxx
@@ -0,0 +1,75 @@
+/* -*- 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 <model/SlsSharedPageDescriptor.hxx>
+#include <rtl/ustring.hxx>
+#include <vcl/timer.hxx>
+
+namespace sd::slidesorter
+{
+class SlideSorter;
+}
+
+namespace sd::slidesorter::view
+{
+/** Manage the display of tool tips. The tool tip text changes when the
+ mouse is moved from slide to slide or from button to button.
+ After the mouse enters a slide the first display of the tool tip is
+ delayed for a short time in order to not draw attention from the slide
+ or its button bar.
+*/
+class ToolTip
+{
+public:
+ ToolTip(SlideSorter& rSlideSorter);
+ ~ToolTip();
+
+ /** Set a new page. This modifies the default help text. After a page
+ change a timer is started to delay the display of the tool tip for
+ the new page.
+ @param rpPage
+ When this is empty then the tool tip is hidden.
+ */
+ void SetPage(const model::SharedPageDescriptor& rpPage);
+
+ /** Hide the tool tip.
+ @return
+ Returns whether the tool tip was visible at the time this method
+ was called.
+ */
+ bool Hide();
+
+private:
+ SlideSorter& mrSlideSorter;
+ model::SharedPageDescriptor mpDescriptor;
+ OUString msCurrentHelpText;
+ void* mnHelpWindowHandle;
+ Timer maShowTimer;
+ Timer maHiddenTimer;
+
+ void DoShow();
+
+ DECL_LINK(DelayTrigger, Timer*, void);
+};
+
+} // end of namespace ::sd::slidesorter::view
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/model/SlideSorterModel.cxx b/sd/source/ui/slidesorter/model/SlideSorterModel.cxx
new file mode 100644
index 000000000..4d3e79656
--- /dev/null
+++ b/sd/source/ui/slidesorter/model/SlideSorterModel.cxx
@@ -0,0 +1,676 @@
+/* -*- 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 <model/SlideSorterModel.hxx>
+
+#include <SlideSorter.hxx>
+#include <sal/log.hxx>
+#include <model/SlsPageDescriptor.hxx>
+#include <model/SlsPageEnumerationProvider.hxx>
+#include <controller/SlideSorterController.hxx>
+#include <controller/SlsPageSelector.hxx>
+#include <controller/SlsCurrentSlideManager.hxx>
+#include <controller/SlsSlotManager.hxx>
+#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
+#include <com/sun/star/drawing/XMasterPagesSupplier.hpp>
+#include <com/sun/star/beans/XPropertySet.hpp>
+#include <com/sun/star/frame/XController.hpp>
+
+#include <vcl/uitest/logger.hxx>
+#include <vcl/uitest/eventdescription.hxx>
+
+#include <ViewShellBase.hxx>
+#include <DrawDocShell.hxx>
+#include <drawdoc.hxx>
+#include <sdpage.hxx>
+#include <FrameView.hxx>
+
+#include <o3tl/safeint.hxx>
+#include <tools/diagnose_ex.h>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+
+namespace sd::slidesorter::model {
+
+namespace {
+ bool PrintModel (const SlideSorterModel& rModel)
+ {
+ for (sal_Int32 nIndex=0,nCount=rModel.GetPageCount(); nIndex<nCount; ++nIndex)
+ {
+ SharedPageDescriptor pDescriptor (rModel.GetPageDescriptor(nIndex));
+ if (pDescriptor)
+ {
+ SAL_INFO(
+ "sd.sls",
+ nIndex << " " << pDescriptor->GetPageIndex() << " "
+ << pDescriptor->GetVisualState().mnPageId << " "
+ << FromCoreIndex(pDescriptor->GetPage()->GetPageNum())
+ << " " << pDescriptor->GetPage());
+ }
+ else
+ {
+ SAL_INFO("sd.sls", nIndex);
+ }
+ }
+
+ return true;
+ }
+ bool CheckModel (const SlideSorterModel& rModel)
+ {
+ for (sal_Int32 nIndex=0,nCount=rModel.GetPageCount(); nIndex<nCount; ++nIndex)
+ {
+ SharedPageDescriptor pDescriptor (rModel.GetPageDescriptor(nIndex));
+ if ( ! pDescriptor)
+ {
+ PrintModel(rModel);
+ assert(pDescriptor);
+ return false;
+ }
+ if (nIndex != pDescriptor->GetPageIndex())
+ {
+ PrintModel(rModel);
+ assert(nIndex == pDescriptor->GetPageIndex());
+ return false;
+ }
+ if (nIndex != pDescriptor->GetVisualState().mnPageId)
+ {
+ PrintModel(rModel);
+ assert(nIndex == pDescriptor->GetVisualState().mnPageId);
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
+
+namespace {
+
+void collectUIInformation(const OUString& num, const OUString& rAction)
+{
+ EventDescription aDescription;
+ aDescription.aID = "impress_win_or_draw_win";
+ aDescription.aParameters = {{"POS", num}};
+ aDescription.aAction = rAction;
+ aDescription.aKeyWord = "ImpressWindowUIObject";
+ aDescription.aParent = "MainWindow";
+
+ UITestLogger::getInstance().logEvent(aDescription);
+}
+
+}
+
+SlideSorterModel::SlideSorterModel (SlideSorter& rSlideSorter)
+ : mrSlideSorter(rSlideSorter),
+ meEditMode(EditMode::Page),
+ maPageDescriptors(0)
+{
+}
+
+SlideSorterModel::~SlideSorterModel()
+{
+ ClearDescriptorList ();
+}
+
+void SlideSorterModel::Dispose()
+{
+ ClearDescriptorList ();
+}
+
+SdDrawDocument* SlideSorterModel::GetDocument()
+{
+ if (mrSlideSorter.GetViewShellBase() != nullptr)
+ return mrSlideSorter.GetViewShellBase()->GetDocument();
+ else
+ return nullptr;
+}
+
+bool SlideSorterModel::SetEditMode (EditMode eEditMode)
+{
+ bool bEditModeChanged = false;
+ if (meEditMode != eEditMode)
+ {
+ meEditMode = eEditMode;
+ UpdatePageList();
+ bEditModeChanged = true;
+ }
+ return bEditModeChanged;
+}
+
+sal_Int32 SlideSorterModel::GetPageCount() const
+{
+ return maPageDescriptors.size();
+}
+
+SharedPageDescriptor SlideSorterModel::GetPageDescriptor (
+ const sal_Int32 nPageIndex,
+ const bool bCreate) const
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ SharedPageDescriptor pDescriptor;
+
+ if (nPageIndex>=0 && nPageIndex<GetPageCount())
+ {
+ pDescriptor = maPageDescriptors[nPageIndex];
+ if (pDescriptor == nullptr && bCreate && mxSlides.is())
+ {
+ SdPage* pPage = GetPage(nPageIndex);
+ pDescriptor = std::make_shared<PageDescriptor>(
+ Reference<drawing::XDrawPage>(mxSlides->getByIndex(nPageIndex),UNO_QUERY),
+ pPage,
+ nPageIndex);
+ maPageDescriptors[nPageIndex] = pDescriptor;
+ }
+ }
+
+ return pDescriptor;
+}
+
+sal_Int32 SlideSorterModel::GetIndex (const Reference<drawing::XDrawPage>& rxSlide) const
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ // First try to guess the right index.
+ Reference<beans::XPropertySet> xSet (rxSlide, UNO_QUERY);
+ if (xSet.is())
+ {
+ try
+ {
+ const Any aNumber (xSet->getPropertyValue("Number"));
+ sal_Int16 nNumber (-1);
+ aNumber >>= nNumber;
+ nNumber -= 1;
+ SharedPageDescriptor pDescriptor (GetPageDescriptor(nNumber, false));
+ if (pDescriptor
+ && pDescriptor->GetXDrawPage() == rxSlide)
+ {
+ return nNumber;
+ }
+ }
+ catch (uno::Exception&)
+ {
+ DBG_UNHANDLED_EXCEPTION("sd");
+ }
+ }
+
+ // Guess was wrong, iterate over all slides and search for the right
+ // one.
+ const sal_Int32 nCount (maPageDescriptors.size());
+ for (sal_Int32 nIndex=0; nIndex<nCount; ++nIndex)
+ {
+ SharedPageDescriptor pDescriptor (maPageDescriptors[nIndex]);
+
+ // Make sure that the descriptor exists. Without it the given slide
+ // can not be found.
+ if (!pDescriptor)
+ {
+ // Call GetPageDescriptor() to create the missing descriptor.
+ pDescriptor = GetPageDescriptor(nIndex);
+ }
+
+ if (pDescriptor->GetXDrawPage() == rxSlide)
+ return nIndex;
+ }
+
+ return -1;
+}
+
+sal_Int32 SlideSorterModel::GetIndex (const SdrPage* pPage) const
+{
+ if (pPage == nullptr)
+ return -1;
+
+ ::osl::MutexGuard aGuard (maMutex);
+
+ // First try to guess the right index.
+ sal_Int16 nNumber ((pPage->GetPageNum()-1)/2);
+ SharedPageDescriptor pDescriptor (GetPageDescriptor(nNumber, false));
+ if (pDescriptor
+ && pDescriptor->GetPage() == pPage)
+ {
+ return nNumber;
+ }
+
+ // Guess was wrong, iterate over all slides and search for the right
+ // one.
+ const sal_Int32 nCount (maPageDescriptors.size());
+ for (sal_Int32 nIndex=0; nIndex<nCount; ++nIndex)
+ {
+ pDescriptor = maPageDescriptors[nIndex];
+
+ // Make sure that the descriptor exists. Without it the given slide
+ // can not be found.
+ if (!pDescriptor)
+ {
+ // Call GetPageDescriptor() to create the missing descriptor.
+ pDescriptor = GetPageDescriptor(nIndex);
+ }
+
+ if (pDescriptor->GetPage() == pPage)
+ return nIndex;
+ }
+
+ return -1;
+}
+
+sal_uInt16 SlideSorterModel::GetCoreIndex (const sal_Int32 nIndex) const
+{
+ SharedPageDescriptor pDescriptor (GetPageDescriptor(nIndex));
+ if (pDescriptor)
+ return pDescriptor->GetPage()->GetPageNum();
+ else
+ return mxSlides->getCount()*2+1;
+}
+
+/** For now this method uses a trivial algorithm: throw away all descriptors
+ and create them anew (on demand). The main problem that we are facing
+ when designing a better algorithm is that we can not compare pointers to
+ pages stored in the PageDescriptor objects and those obtained from the
+ document: pages may have been deleted and others may have been created
+ at the exact same memory locations.
+*/
+void SlideSorterModel::Resync()
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ // Check if document and this model really differ.
+ bool bIsUpToDate (true);
+ SdDrawDocument* pDocument = GetDocument();
+ if (pDocument!=nullptr && maPageDescriptors.size()==pDocument->GetSdPageCount(PageKind::Standard))
+ {
+ for (sal_Int32 nIndex=0,nCount=maPageDescriptors.size(); nIndex<nCount; ++nIndex)
+ {
+ if (maPageDescriptors[nIndex]
+ && maPageDescriptors[nIndex]->GetPage()
+ != GetPage(nIndex))
+ {
+ SAL_INFO("sd.sls", "page " << nIndex << " differs");
+ bIsUpToDate = false;
+ break;
+ }
+ }
+ }
+ else
+ {
+ bIsUpToDate = false;
+ }
+
+ if ( ! bIsUpToDate)
+ {
+ SynchronizeDocumentSelection(); // Try to make the current selection persistent.
+ ClearDescriptorList ();
+ AdaptSize();
+ SynchronizeModelSelection();
+ mrSlideSorter.GetController().GetPageSelector().CountSelectedPages();
+ }
+ CheckModel(*this);
+}
+
+void SlideSorterModel::ClearDescriptorList()
+{
+ ::std::vector<SharedPageDescriptor> aDescriptors;
+
+ {
+ ::osl::MutexGuard aGuard (maMutex);
+ aDescriptors.swap(maPageDescriptors);
+ }
+
+ for (auto& rxDescriptor : aDescriptors)
+ {
+ if (rxDescriptor != nullptr)
+ {
+ if (rxDescriptor.use_count() > 1)
+ {
+ SAL_INFO(
+ "sd.sls",
+ "trying to delete page descriptor that is still used with"
+ " count " << rxDescriptor.use_count());
+ // No assertion here because that can hang the office when
+ // opening a dialog from here.
+ }
+ rxDescriptor.reset();
+ }
+ }
+}
+
+void SlideSorterModel::SynchronizeDocumentSelection()
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ PageEnumeration aAllPages (PageEnumerationProvider::CreateAllPagesEnumeration(*this));
+ while (aAllPages.HasMoreElements())
+ {
+ SharedPageDescriptor pDescriptor (aAllPages.GetNextElement());
+ const bool bIsSelected (pDescriptor->HasState(PageDescriptor::ST_Selected));
+ pDescriptor->GetPage()->SetSelected(bIsSelected);
+ }
+}
+
+void SlideSorterModel::SynchronizeModelSelection()
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ PageEnumeration aAllPages (PageEnumerationProvider::CreateAllPagesEnumeration(*this));
+ while (aAllPages.HasMoreElements())
+ {
+ SharedPageDescriptor pDescriptor (aAllPages.GetNextElement());
+ const bool bIsSelected (pDescriptor->GetPage()->IsSelected());
+ pDescriptor->SetState(PageDescriptor::ST_Selected, bIsSelected);
+ }
+}
+
+void SlideSorterModel::SetDocumentSlides (
+ const Reference<container::XIndexAccess>& rxSlides)
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ // Make the current selection persistent and then release the
+ // current set of pages.
+ SynchronizeDocumentSelection();
+ mxSlides = nullptr;
+ ClearDescriptorList ();
+
+ // Reset the current page to cause everybody to release references to it.
+ mrSlideSorter.GetController().GetCurrentSlideManager()->NotifyCurrentSlideChange(-1);
+
+ // Set the new set of pages.
+ mxSlides = rxSlides;
+ AdaptSize();
+ SynchronizeModelSelection();
+ mrSlideSorter.GetController().GetPageSelector().CountSelectedPages();
+
+ model::PageEnumeration aSelectedPages (
+ model::PageEnumerationProvider::CreateSelectedPagesEnumeration(*this));
+ if (aSelectedPages.HasMoreElements())
+ {
+ SharedPageDescriptor pDescriptor (aSelectedPages.GetNextElement());
+ mrSlideSorter.GetController().GetCurrentSlideManager()->NotifyCurrentSlideChange(
+ pDescriptor->GetPage());
+ }
+
+ ViewShell* pViewShell = mrSlideSorter.GetViewShell();
+ if (pViewShell != nullptr)
+ {
+ SdPage* pPage = pViewShell->getCurrentPage();
+ if (pPage != nullptr)
+ mrSlideSorter.GetController().GetCurrentSlideManager()->NotifyCurrentSlideChange(
+ pPage);
+ else
+ {
+ // No current page. This can only be when the slide sorter is
+ // the main view shell. Get current slide form frame view.
+ const FrameView* pFrameView = pViewShell->GetFrameView();
+ if (pFrameView != nullptr)
+ mrSlideSorter.GetController().GetCurrentSlideManager()->NotifyCurrentSlideChange(
+ pFrameView->GetSelectedPage());
+ else
+ {
+ // No frame view. As a last resort use the first slide as
+ // current slide.
+ mrSlideSorter.GetController().GetCurrentSlideManager()->NotifyCurrentSlideChange(
+ sal_Int32(0));
+ }
+ }
+ }
+
+ mrSlideSorter.GetController().GetSlotManager()->NotifyEditModeChange();
+}
+
+Reference<container::XIndexAccess> SlideSorterModel::GetDocumentSlides() const
+{
+ ::osl::MutexGuard aGuard (maMutex);
+ return mxSlides;
+}
+
+void SlideSorterModel::UpdatePageList()
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ Reference<container::XIndexAccess> xPages;
+
+ // Get the list of pages according to the edit mode.
+ Reference<frame::XController> xController (mrSlideSorter.GetXController());
+ if (xController.is())
+ {
+ switch (meEditMode)
+ {
+ case EditMode::MasterPage:
+ {
+ Reference<drawing::XMasterPagesSupplier> xSupplier (
+ xController->getModel(), UNO_QUERY);
+ if (xSupplier.is())
+ {
+ xPages = xSupplier->getMasterPages();
+ }
+ }
+ break;
+
+ case EditMode::Page:
+ {
+ Reference<drawing::XDrawPagesSupplier> xSupplier (
+ xController->getModel(), UNO_QUERY);
+ if (xSupplier.is())
+ {
+ xPages = xSupplier->getDrawPages();
+ }
+ }
+ break;
+
+ default:
+ // We should never get here.
+ assert(false);
+ break;
+ }
+ }
+
+ mrSlideSorter.GetController().SetDocumentSlides(xPages);
+}
+
+void SlideSorterModel::AdaptSize()
+{
+ if (mxSlides.is())
+ maPageDescriptors.resize(mxSlides->getCount());
+ else
+ maPageDescriptors.resize(0);
+}
+
+bool SlideSorterModel::IsReadOnly() const
+{
+ if (mrSlideSorter.GetViewShellBase() != nullptr
+ && mrSlideSorter.GetViewShellBase()->GetDocShell())
+ return mrSlideSorter.GetViewShellBase()->GetDocShell()->IsReadOnly();
+ else
+ return true;
+}
+
+void SlideSorterModel::SaveCurrentSelection()
+{
+ PageEnumeration aPages (PageEnumerationProvider::CreateAllPagesEnumeration(*this));
+ while (aPages.HasMoreElements())
+ {
+ SharedPageDescriptor pDescriptor (aPages.GetNextElement());
+ pDescriptor->SetState(
+ PageDescriptor::ST_WasSelected,
+ pDescriptor->HasState(PageDescriptor::ST_Selected));
+ }
+}
+
+vcl::Region SlideSorterModel::RestoreSelection()
+{
+ vcl::Region aRepaintRegion;
+ PageEnumeration aPages (PageEnumerationProvider::CreateAllPagesEnumeration(*this));
+ while (aPages.HasMoreElements())
+ {
+ SharedPageDescriptor pDescriptor (aPages.GetNextElement());
+ if (pDescriptor->SetState(
+ PageDescriptor::ST_Selected,
+ pDescriptor->HasState(PageDescriptor::ST_WasSelected)))
+ {
+ aRepaintRegion.Union(pDescriptor->GetBoundingBox());
+ }
+ }
+ return aRepaintRegion;
+}
+
+bool SlideSorterModel::NotifyPageEvent (const SdrPage* pSdrPage)
+{
+ ::osl::MutexGuard aGuard (maMutex);
+
+ SdPage* pPage = const_cast<SdPage*>(dynamic_cast<const SdPage*>(pSdrPage));
+ if (pPage == nullptr)
+ return false;
+
+ // We are only interested in pages that are currently served by this
+ // model.
+ if (pPage->GetPageKind() != PageKind::Standard)
+ return false;
+ if (pPage->IsMasterPage() != (meEditMode==EditMode::MasterPage))
+ return false;
+
+ //NotifyPageEvent is called for add, remove, *and* change position so for
+ //the change position case we must ensure we don't end up with the slide
+ //duplicated in our list
+ bool bSelected = DeleteSlide(pPage);
+ if (pPage->IsInserted())
+ {
+ InsertSlide(pPage, bSelected);
+ }
+ CheckModel(*this);
+
+ return true;
+}
+
+void SlideSorterModel::InsertSlide(SdPage* pPage, bool bMarkSelected)
+{
+ // Find the index at which to insert the given page.
+ sal_uInt16 nCoreIndex (pPage->GetPageNum());
+ sal_Int32 nIndex (FromCoreIndex(nCoreIndex));
+ if (pPage != GetPage(nIndex))
+ return;
+
+ // Check that the pages in the document before and after the given page
+ // are present in this model.
+ if (nIndex>0)
+ if (GetPage(nIndex-1) != GetPageDescriptor(nIndex-1)->GetPage())
+ return;
+ if (nIndex < static_cast<sal_Int32>(maPageDescriptors.size()) -1)
+ if (GetPage(nIndex+1) != GetPageDescriptor(nIndex)->GetPage())
+ return;
+
+ auto iter = maPageDescriptors.begin() + nIndex;
+
+ // Insert the given page at index nIndex
+ iter = maPageDescriptors.insert(
+ iter,
+ std::make_shared<PageDescriptor>(
+ Reference<drawing::XDrawPage>(mxSlides->getByIndex(nIndex),UNO_QUERY),
+ pPage,
+ nIndex));
+
+ if (bMarkSelected)
+ (*iter)->SetState(PageDescriptor::ST_Selected, true);
+
+ // Update page indices.
+ UpdateIndices(nIndex+1);
+}
+
+bool SlideSorterModel::DeleteSlide (const SdPage* pPage)
+{
+ sal_Int32 nIndex(0);
+
+ // Caution, GetIndex() may be negative since it uses GetPageNumber()-1
+ // for calculation, so do this only when page is inserted, else the
+ // GetPageNumber() will be zero and thus GetIndex() == -1
+ if(pPage->IsInserted())
+ {
+ nIndex = GetIndex(pPage);
+ }
+ else
+ {
+ // if not inserted, search for page
+ for(; nIndex < static_cast<sal_Int32>(maPageDescriptors.size()); nIndex++)
+ {
+ if(maPageDescriptors[nIndex]->GetPage() == pPage)
+ {
+ break;
+ }
+ }
+ }
+
+ bool bMarkedSelected(false);
+
+ if(nIndex >= 0 && o3tl::make_unsigned(nIndex) < maPageDescriptors.size())
+ {
+ if (maPageDescriptors[nIndex])
+ if (maPageDescriptors[nIndex]->GetPage() != pPage)
+ return false;
+
+ auto iter = maPageDescriptors.begin() + nIndex;
+ bMarkedSelected = (*iter)->HasState(PageDescriptor::ST_Selected);
+ maPageDescriptors.erase(iter);
+ UpdateIndices(nIndex);
+
+ collectUIInformation(OUString::number(nIndex + 1), "Delete_Slide_or_Page");
+ }
+ return bMarkedSelected;
+}
+
+void SlideSorterModel::UpdateIndices (const sal_Int32 nFirstIndex)
+{
+ for (sal_Int32 nDescriptorIndex=0,nCount=maPageDescriptors.size();
+ nDescriptorIndex<nCount;
+ ++nDescriptorIndex)
+ {
+ SharedPageDescriptor& rpDescriptor (maPageDescriptors[nDescriptorIndex]);
+ if (rpDescriptor)
+ {
+ if (nDescriptorIndex < nFirstIndex)
+ {
+ if (rpDescriptor->GetPageIndex()!=nDescriptorIndex)
+ {
+ assert(rpDescriptor->GetPageIndex()==nDescriptorIndex);
+ }
+ }
+ else
+ {
+ rpDescriptor->SetPageIndex(nDescriptorIndex);
+ }
+ }
+ }
+}
+
+SdPage* SlideSorterModel::GetPage (const sal_Int32 nSdIndex) const
+{
+ SdDrawDocument* pModel = const_cast<SlideSorterModel*>(this)->GetDocument();
+ if (pModel != nullptr)
+ {
+ if (meEditMode == EditMode::Page)
+ return pModel->GetSdPage (static_cast<sal_uInt16>(nSdIndex), PageKind::Standard);
+ else
+ return pModel->GetMasterSdPage (static_cast<sal_uInt16>(nSdIndex), PageKind::Standard);
+ }
+ else
+ return nullptr;
+}
+
+} // end of namespace ::sd::slidesorter::model
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/model/SlsPageDescriptor.cxx b/sd/source/ui/slidesorter/model/SlsPageDescriptor.cxx
new file mode 100644
index 000000000..5118cf58e
--- /dev/null
+++ b/sd/source/ui/slidesorter/model/SlsPageDescriptor.cxx
@@ -0,0 +1,226 @@
+/* -*- 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 <model/SlsPageDescriptor.hxx>
+
+#include <sdpage.hxx>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star;
+
+namespace sd::slidesorter::model {
+
+PageDescriptor::PageDescriptor (
+ const Reference<drawing::XDrawPage>& rxPage,
+ SdPage* pPage,
+ const sal_Int32 nIndex)
+ : mpPage(pPage),
+ mxPage(rxPage),
+ mpMasterPage(nullptr),
+ mnIndex(nIndex),
+ maVisualState(nIndex),
+ mbIsSelected(false),
+ mbWasSelected(false),
+ mbIsVisible(false),
+ mbIsFocused(false),
+ mbIsCurrent(false),
+ mbIsMouseOver(false),
+ mbHasTransition(false)
+{
+ assert(mpPage);
+ assert(mpPage == SdPage::getImplementation(rxPage));
+ if (mpPage != nullptr)
+ {
+ if (mpPage->TRG_HasMasterPage())
+ mpMasterPage = &mpPage->TRG_GetMasterPage();
+ if (mpPage->getTransitionType() > 0)
+ mbHasTransition = true;
+ }
+}
+
+PageDescriptor::~PageDescriptor()
+{
+}
+
+void PageDescriptor::SetPageIndex (const sal_Int32 nNewIndex)
+{
+ mnIndex = nNewIndex;
+ maVisualState.mnPageId = nNewIndex;
+}
+
+bool PageDescriptor::UpdateMasterPage()
+{
+ const SdrPage* pMaster = nullptr;
+ if (mpPage!=nullptr && mpPage->TRG_HasMasterPage())
+ pMaster = &mpPage->TRG_GetMasterPage();
+ if (mpMasterPage != pMaster)
+ {
+ mpMasterPage = pMaster;
+ return true;
+ }
+ else
+ return false;
+}
+
+bool PageDescriptor::UpdateTransitionFlag()
+{
+ bool bHasSlideTransition (false);
+ if (mpPage != nullptr)
+ bHasSlideTransition = mpPage->getTransitionType() > 0;
+ if (bHasSlideTransition != mbHasTransition)
+ {
+ mbHasTransition = bHasSlideTransition;
+ return true;
+ }
+ else
+ return false;
+}
+
+bool PageDescriptor::HasState (const State eState) const
+{
+ switch (eState)
+ {
+ case ST_Visible:
+ return mbIsVisible;
+
+ case ST_Selected:
+ return mbIsSelected;
+
+ case ST_WasSelected:
+ return mbWasSelected;
+
+ case ST_Focused:
+ return mbIsFocused;
+
+ case ST_MouseOver:
+ return mbIsMouseOver;
+
+ case ST_Current:
+ return mbIsCurrent;
+
+ case ST_Excluded:
+ return mpPage!=nullptr && mpPage->IsExcluded();
+
+ default:
+ assert(false);
+ return false;
+ }
+}
+
+bool PageDescriptor::SetState (const State eState, const bool bNewStateValue)
+{
+ bool bModified (false);
+ switch (eState)
+ {
+ case ST_Visible:
+ bModified = (bNewStateValue!=mbIsVisible);
+ if (bModified)
+ mbIsVisible = bNewStateValue;
+ break;
+
+ case ST_Selected:
+ bModified = (bNewStateValue!=mbIsSelected);
+ if (bModified)
+ mbIsSelected = bNewStateValue;
+ break;
+
+ case ST_WasSelected:
+ bModified = (bNewStateValue!=mbWasSelected);
+ if (bModified)
+ mbWasSelected = bNewStateValue;
+ break;
+
+ case ST_Focused:
+ bModified = (bNewStateValue!=mbIsFocused);
+ if (bModified)
+ mbIsFocused = bNewStateValue;
+ break;
+
+ case ST_MouseOver:
+ bModified = (bNewStateValue!=mbIsMouseOver);
+ if (bModified)
+ mbIsMouseOver = bNewStateValue;
+ break;
+
+ case ST_Current:
+ bModified = (bNewStateValue!=mbIsCurrent);
+ if (bModified)
+ mbIsCurrent = bNewStateValue;
+ break;
+
+ case ST_Excluded:
+ // This is a state of the page and has to be handled differently
+ // from the view-only states.
+ if (mpPage != nullptr)
+ if (bNewStateValue != mpPage->IsExcluded())
+ {
+ mpPage->SetExcluded(bNewStateValue);
+ bModified = true;
+ }
+ break;
+ }
+
+ return bModified;
+}
+
+bool PageDescriptor::GetCoreSelection()
+{
+ if (mpPage!=nullptr && mpPage->IsSelected() != mbIsSelected)
+ return SetState(ST_Selected, !mbIsSelected);
+ else
+ return false;
+}
+
+void PageDescriptor::SetCoreSelection()
+{
+ if (mpPage != nullptr)
+ if (HasState(ST_Selected))
+ mpPage->SetSelected(true);
+ else
+ mpPage->SetSelected(false);
+ else
+ {
+ assert(mpPage!=nullptr);
+ }
+}
+
+::tools::Rectangle PageDescriptor::GetBoundingBox() const
+{
+ ::tools::Rectangle aBox (maBoundingBox);
+ const Point aOffset (maVisualState.GetLocationOffset());
+ aBox.Move(aOffset.X(), aOffset.Y());
+ return aBox;
+}
+
+Point PageDescriptor::GetLocation (const bool bIgnoreOffset) const
+{
+ if (bIgnoreOffset)
+ return maBoundingBox.TopLeft();
+ else
+ return maBoundingBox.TopLeft() + maVisualState.GetLocationOffset();
+}
+
+void PageDescriptor::SetBoundingBox (const ::tools::Rectangle& rBoundingBox)
+{
+ maBoundingBox = rBoundingBox;
+}
+
+} // end of namespace ::sd::slidesorter::model
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/model/SlsPageEnumeration.cxx b/sd/source/ui/slidesorter/model/SlsPageEnumeration.cxx
new file mode 100644
index 000000000..a67f057e7
--- /dev/null
+++ b/sd/source/ui/slidesorter/model/SlsPageEnumeration.cxx
@@ -0,0 +1,202 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <sal/config.h>
+
+#include <utility>
+
+#include <model/SlsPageEnumeration.hxx>
+#include <model/SlideSorterModel.hxx>
+
+using namespace ::sd::slidesorter;
+using namespace ::sd::slidesorter::model;
+
+namespace {
+
+class PageEnumerationImpl
+ : public Enumeration<SharedPageDescriptor>
+{
+public:
+ PageEnumerationImpl (
+ const SlideSorterModel& rModel,
+ const PageEnumeration::PagePredicate& rPredicate);
+ PageEnumerationImpl(const PageEnumerationImpl&) = delete;
+ PageEnumerationImpl& operator=(const PageEnumerationImpl&) = delete;
+ /** Create a copy of the called enumeration object.
+ */
+ virtual ::std::unique_ptr<Enumeration<SharedPageDescriptor> > Clone() override;
+
+ virtual bool HasMoreElements() const override;
+ virtual SharedPageDescriptor GetNextElement() override;
+ virtual void Rewind() override;
+
+private:
+ const SlideSorterModel& mrModel;
+ const PageEnumeration::PagePredicate maPredicate;
+ int mnIndex;
+
+ /** This constructor sets the internal page index to the given value.
+ It does not call AdvanceToNextValidElement() to skip elements that
+ do not fulfill Predicate.
+ */
+ PageEnumerationImpl (
+ const SlideSorterModel& rModel,
+ const PageEnumeration::PagePredicate& rPredicate,
+ int nIndex);
+
+ /** Skip all elements that do not fulfill Predicate starting with the
+ one pointed to by mnIndex.
+ */
+ void AdvanceToNextValidElement();
+};
+
+} // end of anonymous namespace
+
+namespace sd::slidesorter::model {
+
+PageEnumeration PageEnumeration::Create (
+ const SlideSorterModel& rModel,
+ const PagePredicate& rPredicate)
+{
+ return PageEnumeration(::std::unique_ptr<Enumeration<SharedPageDescriptor> >(
+ new PageEnumerationImpl(rModel, rPredicate)));
+}
+
+PageEnumeration::PageEnumeration (
+ ::std::unique_ptr<Enumeration<SharedPageDescriptor> > && pImpl)
+ : mpImpl(std::move(pImpl))
+{
+}
+
+PageEnumeration::PageEnumeration (const PageEnumeration& rEnumeration )
+: sd::slidesorter::model::Enumeration<sd::slidesorter::model::SharedPageDescriptor>()
+{
+ mpImpl = rEnumeration.mpImpl->Clone();
+}
+
+PageEnumeration::~PageEnumeration()
+{
+}
+
+PageEnumeration& PageEnumeration::operator= (
+ const PageEnumeration& rEnumeration)
+{
+ mpImpl = rEnumeration.mpImpl->Clone();
+ return *this;
+}
+
+::std::unique_ptr<Enumeration<SharedPageDescriptor> > PageEnumeration::Clone()
+{
+ return ::std::unique_ptr<Enumeration<SharedPageDescriptor> >(
+ new PageEnumeration (*this));
+}
+
+bool PageEnumeration::HasMoreElements() const
+{
+ return mpImpl->HasMoreElements();
+}
+
+SharedPageDescriptor PageEnumeration::GetNextElement()
+{
+ return mpImpl->GetNextElement();
+}
+
+void PageEnumeration::Rewind()
+{
+ return mpImpl->Rewind();
+}
+
+} // end of namespace ::sd::slidesorter::model
+
+namespace {
+
+PageEnumerationImpl::PageEnumerationImpl (
+ const SlideSorterModel& rModel,
+ const PageEnumeration::PagePredicate& rPredicate)
+ : mrModel(rModel),
+ maPredicate(rPredicate),
+ mnIndex(0)
+{
+ Rewind();
+}
+
+PageEnumerationImpl::PageEnumerationImpl (
+ const SlideSorterModel& rModel,
+ const PageEnumeration::PagePredicate& rPredicate,
+ int nIndex)
+ : mrModel(rModel),
+ maPredicate(rPredicate),
+ mnIndex(nIndex)
+{
+}
+
+::std::unique_ptr<Enumeration<SharedPageDescriptor> >
+ PageEnumerationImpl::Clone()
+{
+ return ::std::unique_ptr<Enumeration<SharedPageDescriptor> >(
+ new PageEnumerationImpl(mrModel,maPredicate,mnIndex));
+}
+
+bool PageEnumerationImpl::HasMoreElements() const
+{
+ return (mnIndex < mrModel.GetPageCount());
+}
+
+SharedPageDescriptor PageEnumerationImpl::GetNextElement()
+{
+ SharedPageDescriptor pDescriptor (mrModel.GetPageDescriptor(mnIndex));
+
+ // Go to the following valid element.
+ mnIndex += 1;
+ AdvanceToNextValidElement();
+
+ return pDescriptor;
+}
+
+void PageEnumerationImpl::Rewind()
+{
+ // Go to first valid element.
+ mnIndex = 0;
+ AdvanceToNextValidElement();
+}
+
+void PageEnumerationImpl::AdvanceToNextValidElement()
+{
+ while (mnIndex < mrModel.GetPageCount())
+ {
+ SharedPageDescriptor pDescriptor (mrModel.GetPageDescriptor(mnIndex));
+
+ // Test for the predicate being fulfilled.
+ if (pDescriptor && maPredicate(pDescriptor))
+ {
+ // This predicate is valid.
+ break;
+ }
+ else
+ {
+ // Advance to next predicate.
+ mnIndex += 1;
+ }
+ }
+}
+
+} // end of anonymous namespace
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/model/SlsPageEnumerationProvider.cxx b/sd/source/ui/slidesorter/model/SlsPageEnumerationProvider.cxx
new file mode 100644
index 000000000..800fa12db
--- /dev/null
+++ b/sd/source/ui/slidesorter/model/SlsPageEnumerationProvider.cxx
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <model/SlsPageEnumerationProvider.hxx>
+#include <model/SlsPageEnumeration.hxx>
+#include <model/SlsPageDescriptor.hxx>
+
+namespace sd::slidesorter::model {
+
+namespace {
+
+class AllPagesPredicate
+{
+public:
+ bool operator() (const SharedPageDescriptor&) const
+ {
+ return true;
+ }
+};
+
+class SelectedPagesPredicate
+{
+public:
+ bool operator() (const SharedPageDescriptor& rpDescriptor)
+ {
+ return rpDescriptor->HasState(PageDescriptor::ST_Selected);
+ }
+};
+
+class VisiblePagesPredicate
+{
+public:
+ bool operator() (const SharedPageDescriptor& rpDescriptor)
+ {
+ return rpDescriptor->HasState(PageDescriptor::ST_Visible);
+ }
+};
+
+}
+
+PageEnumeration PageEnumerationProvider::CreateAllPagesEnumeration (
+ const SlideSorterModel& rModel)
+{
+ return PageEnumeration::Create(rModel, AllPagesPredicate());
+}
+
+PageEnumeration PageEnumerationProvider::CreateSelectedPagesEnumeration (
+ const SlideSorterModel& rModel)
+{
+ return PageEnumeration::Create(
+ rModel,
+ SelectedPagesPredicate());
+}
+
+PageEnumeration PageEnumerationProvider::CreateVisiblePagesEnumeration (
+ const SlideSorterModel& rModel)
+{
+ return PageEnumeration::Create(
+ rModel,
+ VisiblePagesPredicate());
+}
+
+} // end of namespace ::sd::slidesorter::model
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/model/SlsVisualState.cxx b/sd/source/ui/slidesorter/model/SlsVisualState.cxx
new file mode 100644
index 000000000..3e16823ff
--- /dev/null
+++ b/sd/source/ui/slidesorter/model/SlsVisualState.cxx
@@ -0,0 +1,40 @@
+/* -*- 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 <model/SlsVisualState.hxx>
+
+namespace sd::slidesorter::model
+{
+VisualState::VisualState(const sal_Int32 nPageId)
+ : mnPageId(nPageId)
+ , maLocationOffset(0, 0)
+{
+}
+
+void VisualState::SetLocationOffset(const Point& rOffset)
+{
+ if (maLocationOffset != rOffset)
+ {
+ maLocationOffset = rOffset;
+ }
+}
+
+} // end of namespace ::sd::slidesorter::model
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/shell/SlideSorter.cxx b/sd/source/ui/slidesorter/shell/SlideSorter.cxx
new file mode 100644
index 000000000..550a40469
--- /dev/null
+++ b/sd/source/ui/slidesorter/shell/SlideSorter.cxx
@@ -0,0 +1,456 @@
+/* -*- 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 <SlideSorter.hxx>
+
+#include <com/sun/star/frame/XController.hpp>
+
+#include <controller/SlideSorterController.hxx>
+#include <controller/SlsScrollBarManager.hxx>
+#include <controller/SlsProperties.hxx>
+#include <controller/SlsAnimator.hxx>
+#include <o3tl/deleter.hxx>
+#include <view/SlideSorterView.hxx>
+#include <view/SlsTheme.hxx>
+#include <model/SlideSorterModel.hxx>
+
+#include <ViewShell.hxx>
+#include <ViewShellBase.hxx>
+#include <Window.hxx>
+
+#include <tools/debug.hxx>
+#include <vcl/scrbar.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star;
+
+namespace sd::slidesorter {
+
+namespace {
+class ContentWindow : public ::sd::Window
+{
+public:
+ ContentWindow(vcl::Window& rParent, SlideSorter& rSlideSorter);
+
+ void SetCurrentFunction (const rtl::Reference<FuPoor>& rpFunction);
+ virtual void Paint(vcl::RenderContext& /*rRenderContext*/, const ::tools::Rectangle& rRect) override;
+ virtual void KeyInput (const KeyEvent& rEvent) override;
+ virtual void MouseMove (const MouseEvent& rEvent) override;
+ virtual void MouseButtonUp (const MouseEvent& rEvent) override;
+ virtual void MouseButtonDown (const MouseEvent& rEvent) override;
+ virtual void Command (const CommandEvent& rEvent) override;
+ virtual bool EventNotify (NotifyEvent& rEvent) override;
+
+private:
+ SlideSorter& mrSlideSorter;
+ rtl::Reference<FuPoor> mpCurrentFunction;
+};
+}
+
+//===== SlideSorter ===========================================================
+
+std::shared_ptr<SlideSorter> SlideSorter::CreateSlideSorter(
+ ViewShell& rViewShell,
+ sd::Window* pContentWindow,
+ ScrollBar* pHorizontalScrollBar,
+ ScrollBar* pVerticalScrollBar,
+ ScrollBarBox* pScrollBarBox)
+{
+ std::shared_ptr<SlideSorter> pSlideSorter(
+ new SlideSorter(
+ rViewShell,
+ pContentWindow,
+ pHorizontalScrollBar,
+ pVerticalScrollBar,
+ pScrollBarBox),
+ o3tl::default_delete<SlideSorter>());
+ pSlideSorter->Init();
+ return pSlideSorter;
+}
+
+std::shared_ptr<SlideSorter> SlideSorter::CreateSlideSorter (
+ ViewShellBase& rBase,
+ vcl::Window& rParentWindow)
+{
+ std::shared_ptr<SlideSorter> pSlideSorter(
+ new SlideSorter(
+ rBase,
+ rParentWindow),
+ o3tl::default_delete<SlideSorter>());
+ pSlideSorter->Init();
+ return pSlideSorter;
+}
+
+SlideSorter::SlideSorter (
+ ViewShell& rViewShell,
+ sd::Window* pContentWindow,
+ ScrollBar* pHorizontalScrollBar,
+ ScrollBar* pVerticalScrollBar,
+ ScrollBarBox* pScrollBarBox)
+ : mbIsValid(false),
+ mpViewShell(&rViewShell),
+ mpViewShellBase(&rViewShell.GetViewShellBase()),
+ mpContentWindow(pContentWindow),
+ mpHorizontalScrollBar(pHorizontalScrollBar),
+ mpVerticalScrollBar(pVerticalScrollBar),
+ mpScrollBarBox(pScrollBarBox),
+ mpProperties(std::make_shared<controller::Properties>()),
+ mpTheme(std::make_shared<view::Theme>(mpProperties))
+{
+}
+
+SlideSorter::SlideSorter (
+ ViewShellBase& rBase,
+ vcl::Window& rParentWindow)
+ : mbIsValid(false),
+ mpViewShell(nullptr),
+ mpViewShellBase(&rBase),
+ mpContentWindow(VclPtr<ContentWindow>::Create(rParentWindow,*this )),
+ mpHorizontalScrollBar(VclPtr<ScrollBar>::Create(&rParentWindow,WinBits(WB_HSCROLL | WB_DRAG))),
+ mpVerticalScrollBar(VclPtr<ScrollBar>::Create(&rParentWindow,WinBits(WB_VSCROLL | WB_DRAG))),
+ mpScrollBarBox(VclPtr<ScrollBarBox>::Create(&rParentWindow)),
+ mpProperties(std::make_shared<controller::Properties>()),
+ mpTheme(std::make_shared<view::Theme>(mpProperties))
+{
+}
+
+void SlideSorter::Init()
+{
+ if (mpViewShellBase != nullptr)
+ mxControllerWeak = mpViewShellBase->GetController();
+
+ // Reinitialize colors in Properties with window specific values.
+ if (mpContentWindow)
+ {
+ mpProperties->SetBackgroundColor(
+ mpContentWindow->GetSettings().GetStyleSettings().GetWindowColor());
+ mpProperties->SetTextColor(
+ mpContentWindow->GetSettings().GetStyleSettings().GetWindowTextColor());
+ mpProperties->SetSelectionColor(
+ mpContentWindow->GetSettings().GetStyleSettings().GetMenuHighlightColor());
+ mpProperties->SetHighlightColor(
+ mpContentWindow->GetSettings().GetStyleSettings().GetMenuHighlightColor());
+ }
+
+ CreateModelViewController ();
+
+ SetupListeners ();
+
+ // Initialize the window.
+ sd::Window *pContentWindow = GetContentWindow().get();
+ if (!pContentWindow)
+ return;
+
+ vcl::Window* pParentWindow = pContentWindow->GetParent();
+ if (pParentWindow != nullptr)
+ pParentWindow->SetBackground(Wallpaper());
+ pContentWindow->SetBackground(Wallpaper());
+ pContentWindow->SetViewOrigin (Point(0,0));
+ // We do our own scrolling while dragging a page selection.
+ pContentWindow->SetUseDropScroll (false);
+ // Change the winbits so that the active window accepts the focus.
+ pContentWindow->SetStyle ((pContentWindow->GetStyle() & ~WB_DIALOGCONTROL) | WB_TABSTOP);
+ pContentWindow->Hide();
+
+ // Set view pointer of base class.
+ SetupControls();
+
+ mbIsValid = true;
+}
+
+SlideSorter::~SlideSorter()
+{
+ mbIsValid = false;
+
+ ReleaseListeners();
+
+ // Dispose model, view and controller to avoid calls between them when
+ // they are being destructed and one or two of them are already gone.
+ mpSlideSorterController->Dispose();
+ mpSlideSorterView->Dispose();
+ mpSlideSorterModel->Dispose();
+
+ // Reset the auto pointers explicitly to control the order of destruction.
+ mpSlideSorterController.reset();
+ mpSlideSorterView.reset();
+ mpSlideSorterModel.reset();
+
+ mpHorizontalScrollBar.reset();
+ mpVerticalScrollBar.reset();
+ mpScrollBarBox.reset();
+}
+
+model::SlideSorterModel& SlideSorter::GetModel() const
+{
+ assert(mpSlideSorterModel);
+ return *mpSlideSorterModel;
+}
+
+view::SlideSorterView& SlideSorter::GetView() const
+{
+ assert(mpSlideSorterView);
+ return *mpSlideSorterView;
+}
+
+controller::SlideSorterController& SlideSorter::GetController() const
+{
+ assert(mpSlideSorterController);
+ return *mpSlideSorterController;
+}
+
+Reference<frame::XController> SlideSorter::GetXController() const
+{
+ Reference<frame::XController> xController(mxControllerWeak);
+ return xController;
+}
+
+void SlideSorter::Paint (const ::tools::Rectangle& rRepaintArea)
+{
+ GetController().Paint(
+ rRepaintArea,
+ GetContentWindow());
+}
+
+void SlideSorter::SetupControls()
+{
+ GetVerticalScrollBar()->Show();
+}
+
+void SlideSorter::SetupListeners()
+{
+ sd::Window *pWindow = GetContentWindow().get();
+ if (pWindow)
+ {
+ vcl::Window* pParentWindow = pWindow->GetParent();
+ if (pParentWindow != nullptr)
+ pParentWindow->AddEventListener(
+ LINK(
+ mpSlideSorterController.get(),
+ controller::SlideSorterController,
+ WindowEventHandler));
+ pWindow->AddEventListener(
+ LINK(
+ mpSlideSorterController.get(),
+ controller::SlideSorterController,
+ WindowEventHandler));
+ }
+ Application::AddEventListener(
+ LINK(
+ mpSlideSorterController.get(),
+ controller::SlideSorterController,
+ ApplicationEventHandler));
+
+ mpSlideSorterController->GetScrollBarManager().Connect();
+}
+
+void SlideSorter::ReleaseListeners()
+{
+ mpSlideSorterController->GetScrollBarManager().Disconnect();
+
+ sd::Window *pWindow (GetContentWindow().get());
+ if (pWindow)
+ {
+ pWindow->RemoveEventListener(
+ LINK(mpSlideSorterController.get(),
+ controller::SlideSorterController,
+ WindowEventHandler));
+
+ vcl::Window* pParentWindow = pWindow->GetParent();
+ if (pParentWindow != nullptr)
+ pParentWindow->RemoveEventListener(
+ LINK(mpSlideSorterController.get(),
+ controller::SlideSorterController,
+ WindowEventHandler));
+ }
+ Application::RemoveEventListener(
+ LINK(mpSlideSorterController.get(),
+ controller::SlideSorterController,
+ ApplicationEventHandler));
+}
+
+void SlideSorter::CreateModelViewController()
+{
+ mpSlideSorterModel.reset(CreateModel());
+ DBG_ASSERT(mpSlideSorterModel != nullptr, "Can not create model for slide browser");
+
+ mpSlideSorterView.reset(new view::SlideSorterView (*this));
+ mpSlideSorterController.reset(new controller::SlideSorterController(*this));
+
+ // Now that model, view, and controller are constructed, do the
+ // initialization that relies on all three being in place.
+ mpSlideSorterController->Init();
+ mpSlideSorterView->Init();
+}
+
+model::SlideSorterModel* SlideSorter::CreateModel()
+{
+ // Get pointers to the document.
+ ViewShellBase* pViewShellBase = GetViewShellBase();
+ if (pViewShellBase != nullptr)
+ {
+ assert (pViewShellBase->GetDocument() != nullptr);
+
+ return new model::SlideSorterModel(*this);
+ }
+ else
+ return nullptr;
+}
+
+void SlideSorter::ArrangeGUIElements (
+ const Point& rOffset,
+ const Size& rSize)
+{
+ Point aOrigin (rOffset);
+
+ if (rSize.Width()>0
+ && rSize.Height()>0
+ && GetContentWindow()
+ && GetContentWindow()->IsVisible())
+ {
+ // Prevent untimely redraws while the view is not yet correctly
+ // resized.
+ view::SlideSorterView::DrawLock aLock (*this);
+ GetContentWindow()->EnablePaint (false);
+
+ mpSlideSorterController->Resize (::tools::Rectangle(aOrigin, rSize));
+
+ GetContentWindow()->EnablePaint (true);
+ }
+}
+
+void SlideSorter::RelocateToWindow (vcl::Window* pParentWindow)
+{
+ // Stop all animations for they have been started for the old window.
+ mpSlideSorterController->GetAnimator()->RemoveAllAnimations();
+
+ ReleaseListeners();
+
+ if (mpViewShell)
+ {
+ mpViewShell->ViewShell::RelocateToParentWindow(pParentWindow);
+ }
+
+ SetupControls();
+ SetupListeners();
+
+ // For accessibility we have to shortly hide the content window. This
+ // triggers the construction of a new accessibility object for the new
+ // view shell. (One is created earlier while the constructor of the base
+ // class is executed. But because at that time the correct
+ // accessibility object can not be constructed we do that now.)
+ if (mpContentWindow)
+ {
+ mpContentWindow->Hide();
+ mpContentWindow->Show();
+ }
+}
+
+void SlideSorter::SetCurrentFunction (const rtl::Reference<FuPoor>& rpFunction)
+{
+ if (GetViewShell() != nullptr)
+ {
+ GetViewShell()->SetCurrentFunction(rpFunction);
+ GetViewShell()->SetOldFunction(rpFunction);
+ }
+ else
+ {
+ ContentWindow* pWindow = dynamic_cast<ContentWindow*>(GetContentWindow().get());
+ if (pWindow != nullptr)
+ pWindow->SetCurrentFunction(rpFunction);
+ }
+}
+
+std::shared_ptr<controller::Properties> const & SlideSorter::GetProperties() const
+{
+ assert(mpProperties);
+ return mpProperties;
+}
+
+std::shared_ptr<view::Theme> const & SlideSorter::GetTheme() const
+{
+ assert(mpTheme);
+ return mpTheme;
+}
+
+//===== ContentWindow =========================================================
+
+namespace {
+
+ContentWindow::ContentWindow(
+ vcl::Window& rParent,
+ SlideSorter& rSlideSorter)
+ : ::sd::Window(&rParent),
+ mrSlideSorter(rSlideSorter)
+{
+ SetDialogControlFlags(GetDialogControlFlags() & ~DialogControlFlags::WantFocus);
+ SetStyle(GetStyle() | WB_NOPOINTERFOCUS);
+}
+
+void ContentWindow::SetCurrentFunction (const rtl::Reference<FuPoor>& rpFunction)
+{
+ mpCurrentFunction = rpFunction;
+}
+
+void ContentWindow::Paint (vcl::RenderContext& /*rRenderContext*/, const ::tools::Rectangle& rRect)
+{
+ mrSlideSorter.Paint(rRect);
+}
+
+void ContentWindow::KeyInput (const KeyEvent& rEvent)
+{
+ if (mpCurrentFunction.is())
+ mpCurrentFunction->KeyInput(rEvent);
+}
+
+void ContentWindow::MouseMove (const MouseEvent& rEvent)
+{
+ if (mpCurrentFunction.is())
+ mpCurrentFunction->MouseMove(rEvent);
+}
+
+void ContentWindow::MouseButtonUp(const MouseEvent& rEvent)
+{
+ if (mpCurrentFunction.is())
+ mpCurrentFunction->MouseButtonUp(rEvent);
+}
+
+void ContentWindow::MouseButtonDown(const MouseEvent& rEvent)
+{
+ if (mpCurrentFunction.is())
+ mpCurrentFunction->MouseButtonDown(rEvent);
+}
+
+void ContentWindow::Command(const CommandEvent& rEvent)
+{
+ if (mpCurrentFunction.is())
+ mpCurrentFunction->Command(rEvent);
+}
+
+bool ContentWindow::EventNotify(NotifyEvent&)
+{
+ return false;
+}
+
+} // end of anonymous namespace
+
+} // end of namespace ::sd::slidesorter
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/shell/SlideSorterService.cxx b/sd/source/ui/slidesorter/shell/SlideSorterService.cxx
new file mode 100644
index 000000000..a086f3b9e
--- /dev/null
+++ b/sd/source/ui/slidesorter/shell/SlideSorterService.cxx
@@ -0,0 +1,412 @@
+/* -*- 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 "SlideSorterService.hxx"
+#include <SlideSorter.hxx>
+#include <controller/SlideSorterController.hxx>
+#include <controller/SlsProperties.hxx>
+#include <controller/SlsCurrentSlideManager.hxx>
+#include <model/SlideSorterModel.hxx>
+#include <model/SlsPageDescriptor.hxx>
+#include <view/SlideSorterView.hxx>
+#include <view/SlsLayouter.hxx>
+#include <DrawController.hxx>
+
+#include <comphelper/servicehelper.hxx>
+#include <toolkit/helper/vclunohelper.hxx>
+#include <com/sun/star/lang/XUnoTunnel.hpp>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::drawing::framework;
+using ::sd::slidesorter::view::Layouter;
+
+namespace sd::slidesorter {
+
+//===== SlideSorterService ==========================================================
+
+SlideSorterService::SlideSorterService()
+{
+}
+
+SlideSorterService::~SlideSorterService()
+{
+}
+
+void SlideSorterService::disposing(std::unique_lock<std::mutex>&)
+{
+ mpSlideSorter.reset();
+
+ if (mxParentWindow.is())
+ {
+ mxParentWindow->removeWindowListener(this);
+ }
+}
+
+//----- XInitialization -------------------------------------------------------
+
+void SAL_CALL SlideSorterService::initialize (const Sequence<Any>& rArguments)
+{
+ ThrowIfDisposed();
+
+ if (rArguments.getLength() != 3)
+ {
+ throw RuntimeException("SlideSorterService: invalid number of arguments",
+ static_cast<drawing::XDrawView*>(this));
+ }
+
+ mxViewId.set(rArguments[0], UNO_QUERY_THROW);
+
+ // Get the XController.
+ Reference<frame::XController> xController (rArguments[1], UNO_QUERY_THROW);
+
+ // Tunnel through the controller to obtain a ViewShellBase.
+ ViewShellBase* pBase = nullptr;
+ Reference<lang::XUnoTunnel> xTunnel (xController, UNO_QUERY_THROW);
+ ::sd::DrawController* pController = comphelper::getFromUnoTunnel<sd::DrawController>(xTunnel);
+ if (pController != nullptr)
+ pBase = pController->GetViewShellBase();
+
+ // Get the parent window.
+ mxParentWindow.set(rArguments[2], UNO_QUERY_THROW);
+ VclPtr<vcl::Window> pParentWindow = VCLUnoHelper::GetWindow(mxParentWindow);
+
+ mxParentWindow->addWindowListener(this);
+
+ if (pBase != nullptr && pParentWindow)
+ mpSlideSorter = SlideSorter::CreateSlideSorter(
+ *pBase,
+ *pParentWindow);
+
+ Resize();
+}
+
+//----- XView -----------------------------------------------------------------
+
+Reference<XResourceId> SAL_CALL SlideSorterService::getResourceId()
+{
+ return mxViewId;
+}
+
+sal_Bool SAL_CALL SlideSorterService::isAnchorOnly()
+{
+ return false;
+}
+
+//----- XWindowListener -------------------------------------------------------
+
+void SAL_CALL SlideSorterService::windowResized (const awt::WindowEvent&)
+{
+ ThrowIfDisposed();
+
+ Resize();
+}
+
+void SAL_CALL SlideSorterService::windowMoved (const awt::WindowEvent&) {}
+
+void SAL_CALL SlideSorterService::windowShown (const lang::EventObject&)
+{
+ ThrowIfDisposed();
+ Resize();
+}
+
+void SAL_CALL SlideSorterService::windowHidden (const lang::EventObject&)
+{
+ ThrowIfDisposed();
+}
+
+//----- lang::XEventListener --------------------------------------------------
+
+void SAL_CALL SlideSorterService::disposing (const lang::EventObject& rEvent)
+{
+ if (rEvent.Source == mxParentWindow)
+ mxParentWindow = nullptr;
+}
+
+//----- XDrawView -------------------------------------------------------------
+
+void SAL_CALL SlideSorterService::setCurrentPage(const Reference<drawing::XDrawPage>& rxSlide)
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter != nullptr)
+ mpSlideSorter->GetController().GetCurrentSlideManager()->NotifyCurrentSlideChange(
+ mpSlideSorter->GetModel().GetIndex(rxSlide));
+}
+
+Reference<drawing::XDrawPage> SAL_CALL SlideSorterService::getCurrentPage()
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter != nullptr)
+ return mpSlideSorter->GetController().GetCurrentSlideManager()->GetCurrentSlide()->GetXDrawPage();
+ else
+ return nullptr;
+}
+
+//----- attributes ------------------------------------------------------------
+
+Reference<container::XIndexAccess> SAL_CALL SlideSorterService::getDocumentSlides()
+{
+ return mpSlideSorter->GetModel().GetDocumentSlides();
+}
+
+void SAL_CALL SlideSorterService::setDocumentSlides (
+ const Reference<container::XIndexAccess >& rxSlides)
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter != nullptr && mpSlideSorter->IsValid())
+ mpSlideSorter->GetController().SetDocumentSlides(rxSlides);
+}
+
+sal_Bool SAL_CALL SlideSorterService::getIsHighlightCurrentSlide()
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter == nullptr || !mpSlideSorter->IsValid())
+ return false;
+ else
+ return mpSlideSorter->GetProperties()->IsHighlightCurrentSlide();
+}
+
+void SAL_CALL SlideSorterService::setIsHighlightCurrentSlide (sal_Bool bValue)
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter != nullptr && mpSlideSorter->IsValid())
+ {
+ mpSlideSorter->GetProperties()->SetHighlightCurrentSlide(bValue);
+ controller::SlideSorterController::ModelChangeLock aLock (mpSlideSorter->GetController());
+ mpSlideSorter->GetController().HandleModelChange();
+ }
+}
+
+sal_Bool SAL_CALL SlideSorterService::getIsShowSelection()
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter == nullptr || !mpSlideSorter->IsValid())
+ return false;
+ else
+ return mpSlideSorter->GetProperties()->IsShowSelection();
+}
+
+void SAL_CALL SlideSorterService::setIsShowSelection (sal_Bool bValue)
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter != nullptr && mpSlideSorter->IsValid())
+ mpSlideSorter->GetProperties()->SetShowSelection(bValue);
+}
+
+sal_Bool SAL_CALL SlideSorterService::getIsShowFocus()
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter == nullptr || !mpSlideSorter->IsValid())
+ return false;
+ else
+ return mpSlideSorter->GetProperties()->IsShowFocus();
+}
+
+void SAL_CALL SlideSorterService::setIsShowFocus (sal_Bool bValue)
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter != nullptr && mpSlideSorter->IsValid())
+ mpSlideSorter->GetProperties()->SetShowFocus(bValue);
+}
+
+sal_Bool SAL_CALL SlideSorterService::getIsCenterSelection()
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter == nullptr || !mpSlideSorter->IsValid())
+ return false;
+ else
+ return mpSlideSorter->GetProperties()->IsCenterSelection();
+}
+
+void SAL_CALL SlideSorterService::setIsCenterSelection (sal_Bool bValue)
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter != nullptr && mpSlideSorter->IsValid())
+ mpSlideSorter->GetProperties()->SetCenterSelection(bValue);
+}
+
+sal_Bool SAL_CALL SlideSorterService::getIsSuspendPreviewUpdatesDuringFullScreenPresentation()
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter == nullptr || !mpSlideSorter->IsValid())
+ return true;
+ else
+ return mpSlideSorter->GetProperties()
+ ->IsSuspendPreviewUpdatesDuringFullScreenPresentation();
+}
+
+void SAL_CALL SlideSorterService::setIsSuspendPreviewUpdatesDuringFullScreenPresentation (
+ sal_Bool bValue)
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter != nullptr && mpSlideSorter->IsValid())
+ mpSlideSorter->GetProperties()
+ ->SetSuspendPreviewUpdatesDuringFullScreenPresentation(bValue);
+}
+
+sal_Bool SAL_CALL SlideSorterService::getIsOrientationVertical()
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter == nullptr || !mpSlideSorter->IsValid())
+ return true;
+ else
+ return mpSlideSorter->GetView().GetOrientation() != Layouter::HORIZONTAL;
+}
+
+void SAL_CALL SlideSorterService::setIsOrientationVertical (sal_Bool bValue)
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter != nullptr && mpSlideSorter->IsValid())
+ mpSlideSorter->GetView().SetOrientation(bValue
+ ? Layouter::GRID
+ : Layouter::HORIZONTAL);
+}
+
+sal_Bool SAL_CALL SlideSorterService::getIsSmoothScrolling()
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter == nullptr || !mpSlideSorter->IsValid())
+ return false;
+ else
+ return mpSlideSorter->GetProperties()->IsSmoothSelectionScrolling();
+}
+
+void SAL_CALL SlideSorterService::setIsSmoothScrolling (sal_Bool bValue)
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter != nullptr && mpSlideSorter->IsValid())
+ mpSlideSorter->GetProperties()->SetSmoothSelectionScrolling(bValue);
+}
+
+sal_Int32 SAL_CALL SlideSorterService::getBackgroundColor()
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter == nullptr || !mpSlideSorter->IsValid())
+ return util::Color();
+ else
+ return util::Color(
+ mpSlideSorter->GetProperties()->GetBackgroundColor());
+}
+
+void SAL_CALL SlideSorterService::setBackgroundColor (sal_Int32 aBackgroundColor)
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter != nullptr && mpSlideSorter->IsValid())
+ mpSlideSorter->GetProperties()->SetBackgroundColor(Color(ColorTransparency, aBackgroundColor));
+}
+
+sal_Int32 SAL_CALL SlideSorterService::getTextColor()
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter == nullptr || !mpSlideSorter->IsValid())
+ return util::Color();
+ else
+ return util::Color(
+ mpSlideSorter->GetProperties()->GetTextColor());
+}
+
+void SAL_CALL SlideSorterService::setTextColor (sal_Int32 aTextColor)
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter != nullptr && mpSlideSorter->IsValid())
+ mpSlideSorter->GetProperties()->SetTextColor(Color(ColorTransparency, aTextColor));
+}
+
+sal_Int32 SAL_CALL SlideSorterService::getSelectionColor()
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter == nullptr || !mpSlideSorter->IsValid())
+ return util::Color();
+ else
+ return util::Color(
+ mpSlideSorter->GetProperties()->GetSelectionColor());
+}
+
+void SAL_CALL SlideSorterService::setSelectionColor (sal_Int32 aSelectionColor)
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter != nullptr && mpSlideSorter->IsValid())
+ mpSlideSorter->GetProperties()->SetSelectionColor(Color(ColorTransparency, aSelectionColor));
+}
+
+sal_Int32 SAL_CALL SlideSorterService::getHighlightColor()
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter == nullptr || !mpSlideSorter->IsValid())
+ return util::Color();
+ else
+ return util::Color(
+ mpSlideSorter->GetProperties()->GetHighlightColor());
+}
+
+void SAL_CALL SlideSorterService::setHighlightColor (sal_Int32 aHighlightColor)
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter != nullptr && mpSlideSorter->IsValid())
+ mpSlideSorter->GetProperties()->SetHighlightColor(Color(ColorTransparency, aHighlightColor));
+}
+
+sal_Bool SAL_CALL SlideSorterService::getIsUIReadOnly()
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter == nullptr || !mpSlideSorter->IsValid())
+ return true;
+ else
+ return mpSlideSorter->GetProperties()->IsUIReadOnly();
+}
+
+void SAL_CALL SlideSorterService::setIsUIReadOnly (sal_Bool bIsUIReadOnly)
+{
+ ThrowIfDisposed();
+ if (mpSlideSorter != nullptr && mpSlideSorter->IsValid())
+ mpSlideSorter->GetProperties()->SetUIReadOnly(bIsUIReadOnly);
+}
+
+void SlideSorterService::Resize()
+{
+ if (mxParentWindow.is())
+ {
+ awt::Rectangle aWindowBox = mxParentWindow->getPosSize();
+ mpSlideSorter->ArrangeGUIElements(
+ Point(0,0),
+ Size(aWindowBox.Width, aWindowBox.Height));
+ }
+}
+
+void SlideSorterService::ThrowIfDisposed()
+{
+ if (SlideSorterServiceInterfaceBase::m_bDisposed)
+ {
+ throw lang::DisposedException ("SlideSorterService object has already been disposed",
+ static_cast<drawing::XDrawView*>(this));
+ }
+}
+
+} // end of namespace ::sd::slidesorter
+
+
+extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
+com_sun_star_comp_Draw_SlideSorter_get_implementation(css::uno::XComponentContext* /*context*/,
+ css::uno::Sequence<css::uno::Any> const &)
+{
+ return cppu::acquire(new sd::slidesorter::SlideSorterService);
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/shell/SlideSorterService.hxx b/sd/source/ui/slidesorter/shell/SlideSorterService.hxx
new file mode 100644
index 000000000..579a5bae5
--- /dev/null
+++ b/sd/source/ui/slidesorter/shell/SlideSorterService.hxx
@@ -0,0 +1,153 @@
+/* -*- 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 <com/sun/star/awt/XWindowListener.hpp>
+#include <com/sun/star/drawing/XSlideSorterBase.hpp>
+#include <com/sun/star/lang/XInitialization.hpp>
+#include <comphelper/compbase.hxx>
+#include <memory>
+
+namespace com::sun::star::awt { class XWindow; }
+
+namespace sd::slidesorter {
+
+typedef comphelper::WeakComponentImplHelper <
+ css::drawing::XSlideSorterBase,
+ css::lang::XInitialization,
+ css::awt::XWindowListener
+> SlideSorterServiceInterfaceBase;
+
+class SlideSorter;
+
+/** Implementation of the com.sun.star.drawing.SlideSorter service.
+*/
+class SlideSorterService
+ : public SlideSorterServiceInterfaceBase
+{
+public:
+ explicit SlideSorterService();
+ virtual ~SlideSorterService() override;
+ SlideSorterService(const SlideSorterService&) = delete;
+ SlideSorterService& operator=(const SlideSorterService&) = delete;
+ virtual void disposing(std::unique_lock<std::mutex>&) override;
+
+ // XInitialization
+
+ virtual void SAL_CALL initialize (const css::uno::Sequence<css::uno::Any>& rArguments) override;
+
+ // XResourceId
+
+ css::uno::Reference<css::drawing::framework::XResourceId> SAL_CALL getResourceId() override;
+
+ sal_Bool SAL_CALL isAnchorOnly() override;
+
+ // XWindowListener
+
+ virtual void SAL_CALL windowResized (const css::awt::WindowEvent& rEvent) override;
+
+ virtual void SAL_CALL windowMoved (const css::awt::WindowEvent& rEvent) override;
+
+ virtual void SAL_CALL windowShown (const css::lang::EventObject& rEvent) override;
+
+ virtual void SAL_CALL windowHidden (const css::lang::EventObject& rEvent) override;
+
+ // lang::XEventListener
+ virtual void SAL_CALL disposing (const css::lang::EventObject& rEvent) override;
+
+ // XDrawView
+
+ virtual void SAL_CALL setCurrentPage(
+ const css::uno::Reference<css::drawing::XDrawPage>& rxSlide) override;
+
+ virtual css::uno::Reference<css::drawing::XDrawPage> SAL_CALL getCurrentPage() override;
+
+ // Attributes
+
+ virtual css::uno::Reference<css::container::XIndexAccess> SAL_CALL getDocumentSlides() override;
+
+ virtual void SAL_CALL setDocumentSlides (
+ const css::uno::Reference<css::container::XIndexAccess >& rxSlides) override;
+
+ virtual sal_Bool SAL_CALL getIsHighlightCurrentSlide() override;
+
+ virtual void SAL_CALL setIsHighlightCurrentSlide (sal_Bool bIsHighlightCurrentSlide) override;
+
+ virtual sal_Bool SAL_CALL getIsShowSelection() override;
+
+ virtual void SAL_CALL setIsShowSelection (sal_Bool bIsShowSelection) override;
+
+ virtual sal_Bool SAL_CALL getIsCenterSelection() override;
+
+ virtual void SAL_CALL setIsCenterSelection (sal_Bool bIsCenterSelection) override;
+
+ virtual sal_Bool SAL_CALL getIsSuspendPreviewUpdatesDuringFullScreenPresentation() override;
+
+ virtual void SAL_CALL setIsSuspendPreviewUpdatesDuringFullScreenPresentation (
+ sal_Bool bIsSuspendPreviewUpdatesDuringFullScreenPresentation) override;
+
+ virtual sal_Bool SAL_CALL getIsOrientationVertical() override;
+
+ virtual void SAL_CALL setIsOrientationVertical (sal_Bool bIsOrientationVertical) override;
+
+ virtual sal_Bool SAL_CALL getIsSmoothScrolling() override;
+
+ virtual void SAL_CALL setIsSmoothScrolling (sal_Bool bIsOrientationVertical) override;
+
+ virtual sal_Int32 SAL_CALL getBackgroundColor() override;
+
+ virtual void SAL_CALL setBackgroundColor (sal_Int32 aBackgroundColor) override;
+
+ virtual sal_Int32 SAL_CALL getTextColor() override;
+
+ virtual void SAL_CALL setTextColor (sal_Int32 aTextColor) override;
+
+ virtual sal_Int32 SAL_CALL getSelectionColor() override;
+
+ virtual void SAL_CALL setSelectionColor (sal_Int32 aSelectionColor) override;
+
+ virtual sal_Int32 SAL_CALL getHighlightColor() override;
+
+ virtual void SAL_CALL setHighlightColor (sal_Int32 aHighlightColor) override;
+
+ virtual sal_Bool SAL_CALL getIsUIReadOnly() override;
+
+ virtual void SAL_CALL setIsUIReadOnly (sal_Bool bIsUIReadOnly) override;
+
+ virtual sal_Bool SAL_CALL getIsShowFocus() override;
+
+ virtual void SAL_CALL setIsShowFocus (sal_Bool bIsShowFocus) override;
+
+private:
+ std::shared_ptr<SlideSorter> mpSlideSorter;
+ css::uno::Reference<css::drawing::framework::XResourceId> mxViewId;
+ css::uno::Reference<css::awt::XWindow> mxParentWindow;
+
+ void Resize();
+
+ /** @throws css::lang::DisposedException when the object has already been
+ disposed.
+ */
+ void ThrowIfDisposed();
+};
+
+} // end of namespace ::sd::slidesorter
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/slidesorter/shell/SlideSorterViewShell.cxx b/sd/source/ui/slidesorter/shell/SlideSorterViewShell.cxx
new file mode 100644
index 000000000..af5bd5791
--- /dev/null
+++ b/sd/source/ui/slidesorter/shell/SlideSorterViewShell.cxx
@@ -0,0 +1,924 @@
+#/* -*- 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 <SlideSorterViewShell.hxx>
+#include <ViewShellImplementation.hxx>
+
+#include <SlideSorter.hxx>
+#include <controller/SlideSorterController.hxx>
+#include <controller/SlsClipboard.hxx>
+#include <controller/SlsScrollBarManager.hxx>
+#include <controller/SlsPageSelector.hxx>
+#include <controller/SlsSlotManager.hxx>
+#include <controller/SlsCurrentSlideManager.hxx>
+#include <controller/SlsSelectionManager.hxx>
+#include <view/SlideSorterView.hxx>
+#include <view/SlsLayouter.hxx>
+#include <model/SlideSorterModel.hxx>
+#include <model/SlsPageDescriptor.hxx>
+#include <framework/FrameworkHelper.hxx>
+#include <ViewShellBase.hxx>
+#include <drawdoc.hxx>
+#include <sdpage.hxx>
+#include <app.hrc>
+#include <AccessibleSlideSorterView.hxx>
+#include <DrawDocShell.hxx>
+#include <DrawViewShell.hxx>
+#include <FrameView.hxx>
+#include <SdUnoSlideView.hxx>
+#include <ViewShellManager.hxx>
+#include <Window.hxx>
+#include <drawview.hxx>
+#include <sfx2/msg.hxx>
+#include <sfx2/objface.hxx>
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/request.hxx>
+#include <sfx2/sidebar/SidebarChildWindow.hxx>
+#include <sfx2/devtools/DevelopmentToolChildWindow.hxx>
+#include <svx/svxids.hrc>
+#include <vcl/EnumContext.hxx>
+#include <svx/sidebar/ContextChangeEventMultiplexer.hxx>
+#include <tools/diagnose_ex.h>
+#include <sfx2/sidebar/SidebarController.hxx>
+
+using namespace ::sd::slidesorter;
+#define ShellClass_SlideSorterViewShell
+#include <sdslots.hxx>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::drawing::framework;
+
+using ::sd::framework::FrameworkHelper;
+using ::vcl::EnumContext;
+using namespace sfx2::sidebar;
+
+namespace sd::slidesorter {
+
+namespace {
+
+bool inChartContext(const sd::View* pView)
+{
+ if (!pView)
+ return false;
+
+ SfxViewShell* pViewShell = pView->GetSfxViewShell();
+ SidebarController* pSidebar = SidebarController::GetSidebarControllerForView(pViewShell);
+ if (pSidebar)
+ return pSidebar->hasChartContextCurrently();
+
+ return false;
+}
+
+} // anonymous namespace
+
+
+SFX_IMPL_INTERFACE(SlideSorterViewShell, SfxShell)
+
+void SlideSorterViewShell::InitInterface_Impl()
+{
+ GetStaticInterface()->RegisterChildWindow(::sfx2::sidebar::SidebarChildWindow::GetChildWindowId());
+ GetStaticInterface()->RegisterChildWindow(DevelopmentToolChildWindow::GetChildWindowId());
+}
+
+
+std::shared_ptr<SlideSorterViewShell> SlideSorterViewShell::Create (
+ SfxViewFrame* pFrame,
+ ViewShellBase& rViewShellBase,
+ vcl::Window* pParentWindow,
+ FrameView* pFrameViewArgument)
+{
+ std::shared_ptr<SlideSorterViewShell> pViewShell;
+ try
+ {
+ pViewShell.reset(
+ new SlideSorterViewShell(pFrame,rViewShellBase,pParentWindow,pFrameViewArgument));
+ pViewShell->Initialize();
+ if (pViewShell->mpSlideSorter == nullptr)
+ pViewShell.reset();
+ }
+ catch(Exception&)
+ {
+ pViewShell.reset();
+ }
+ return pViewShell;
+}
+
+SlideSorterViewShell::SlideSorterViewShell (
+ SfxViewFrame* /*pFrame*/,
+ ViewShellBase& rViewShellBase,
+ vcl::Window* pParentWindow,
+ FrameView* pFrameViewArgument)
+ : ViewShell (pParentWindow, rViewShellBase),
+ mbIsArrangeGUIElementsPending(true)
+{
+ GetContentWindow()->set_id("slidesorter");
+ meShellType = ST_SLIDE_SORTER;
+
+ if (pFrameViewArgument != nullptr)
+ mpFrameView = pFrameViewArgument;
+ else
+ mpFrameView = new FrameView(GetDoc());
+ GetFrameView()->Connect();
+
+ SetName ("SlideSorterViewShell");
+
+ pParentWindow->SetStyle(pParentWindow->GetStyle() | WB_DIALOGCONTROL);
+}
+
+SlideSorterViewShell::~SlideSorterViewShell()
+{
+ DisposeFunctions();
+
+ try
+ {
+ ::sd::Window* pWindow = GetActiveWindow();
+ if (pWindow!=nullptr)
+ {
+ css::uno::Reference<css::lang::XComponent> xComponent (
+ pWindow->GetAccessible(false),
+ css::uno::UNO_QUERY);
+ if (xComponent.is())
+ xComponent->dispose();
+ }
+ }
+ catch( css::uno::Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sd", "sd::SlideSorterViewShell::~SlideSorterViewShell()" );
+ }
+ GetFrameView()->Disconnect();
+}
+
+void SlideSorterViewShell::Initialize()
+{
+ mpSlideSorter = SlideSorter::CreateSlideSorter(
+ *this,
+ mpContentWindow,
+ mpHorizontalScrollBar,
+ mpVerticalScrollBar,
+ mpScrollBarBox);
+ mpView = &mpSlideSorter->GetView();
+
+ doShow();
+
+ SetPool( &GetDoc()->GetPool() );
+ SetUndoManager( GetDoc()->GetDocSh()->GetUndoManager() );
+
+ // For accessibility we have to shortly hide the content window.
+ // This triggers the construction of a new accessibility object for
+ // the new view shell. (One is created earlier while the constructor
+ // of the base class is executed. At that time the correct
+ // accessibility object can not be constructed.)
+ sd::Window *pWindow (mpSlideSorter->GetContentWindow().get());
+ if (pWindow)
+ {
+ pWindow->Hide();
+ pWindow->Show();
+ }
+}
+
+void SlideSorterViewShell::Init (bool bIsMainViewShell)
+{
+ ViewShell::Init(bIsMainViewShell);
+
+ // since the updatePageList will show focus, the window.show() must be called ahead. This show is deferred from Init()
+ ::sd::Window* pActiveWindow = GetActiveWindow();
+ if (pActiveWindow)
+ pActiveWindow->Show();
+ mpSlideSorter->GetModel().UpdatePageList();
+
+ if (mpContentWindow)
+ mpContentWindow->SetViewShell(this);
+}
+
+SlideSorterViewShell* SlideSorterViewShell::GetSlideSorter (ViewShellBase& rBase)
+{
+ SlideSorterViewShell* pViewShell = nullptr;
+
+ // Test the center and left pane for showing a slide sorter.
+ OUString aPaneURLs[] = {
+ FrameworkHelper::msCenterPaneURL,
+ FrameworkHelper::msFullScreenPaneURL,
+ FrameworkHelper::msLeftImpressPaneURL,
+ FrameworkHelper::msLeftDrawPaneURL,
+ OUString()};
+
+ try
+ {
+ std::shared_ptr<FrameworkHelper> pFrameworkHelper (FrameworkHelper::Instance(rBase));
+ if (pFrameworkHelper->IsValid())
+ for (int i=0; pViewShell==nullptr && !aPaneURLs[i].isEmpty(); ++i)
+ {
+ pViewShell = dynamic_cast<SlideSorterViewShell*>(
+ pFrameworkHelper->GetViewShell(aPaneURLs[i]).get());
+ }
+ }
+ catch (RuntimeException&)
+ {}
+
+ return pViewShell;
+}
+
+Reference<drawing::XDrawSubController> SlideSorterViewShell::CreateSubController()
+{
+ Reference<drawing::XDrawSubController> xSubController;
+
+ if (IsMainViewShell())
+ {
+ // Create uno controller for the main view shell.
+ xSubController.set( new SdUnoSlideView( *mpSlideSorter));
+ }
+
+ return xSubController;
+}
+
+/** If there is a valid controller then create a new instance of
+ <type>AccessibleSlideSorterView</type>. Otherwise delegate this call
+ to the base class to return a default object (probably an empty
+ reference).
+*/
+css::uno::Reference<css::accessibility::XAccessible>
+ SlideSorterViewShell::CreateAccessibleDocumentView (::sd::Window* pWindow)
+{
+ // When the view is not set then the initialization is not yet complete
+ // and we can not yet provide an accessibility object.
+ if (mpView == nullptr || mpSlideSorter == nullptr)
+ return nullptr;
+
+ assert(mpSlideSorter);
+
+ rtl::Reference<::accessibility::AccessibleSlideSorterView> pAccessibleView =
+ new ::accessibility::AccessibleSlideSorterView(
+ *mpSlideSorter,
+ pWindow);
+
+ pAccessibleView->Init();
+
+ return pAccessibleView;
+}
+
+void SlideSorterViewShell::SwitchViewFireFocus(const css::uno::Reference< css::accessibility::XAccessible >& xAcc )
+{
+ if (xAcc)
+ {
+ ::accessibility::AccessibleSlideSorterView* pBase = static_cast< ::accessibility::AccessibleSlideSorterView* >(xAcc.get());
+ if (pBase)
+ {
+ pBase->SwitchViewActivated();
+ }
+ }
+}
+
+SlideSorter& SlideSorterViewShell::GetSlideSorter() const
+{
+ assert(mpSlideSorter);
+ return *mpSlideSorter;
+}
+
+bool SlideSorterViewShell::RelocateToParentWindow (vcl::Window* pParentWindow)
+{
+ assert(mpSlideSorter);
+ if ( ! mpSlideSorter)
+ return false;
+
+ mpSlideSorter->RelocateToWindow(pParentWindow);
+ ReadFrameViewData(mpFrameView);
+
+ return true;
+}
+
+SfxUndoManager* SlideSorterViewShell::ImpGetUndoManager() const
+{
+ SfxShell* pObjectBar = GetViewShellBase().GetViewShellManager()->GetTopShell();
+ if (pObjectBar != nullptr)
+ {
+ // When it exists then return the undo manager of the currently
+ // active object bar. The object bar is missing when the
+ // SlideSorterViewShell is not the main view shell.
+ return pObjectBar->GetUndoManager();
+ }
+ else
+ {
+ // Return the undo manager of this shell when there is no object or
+ // tool bar.
+ return const_cast<SlideSorterViewShell*>(this)->GetUndoManager();
+ }
+}
+
+SdPage* SlideSorterViewShell::getCurrentPage() const
+{
+ // since SlideSorterViewShell::GetActualPage() currently also
+ // returns master pages, which is a wrong behaviour for GetActualPage(),
+ // we can just use that for now
+ return const_cast<SlideSorterViewShell*>(this)->GetActualPage();
+}
+
+SdPage* SlideSorterViewShell::GetActualPage()
+{
+ SdPage* pCurrentPage = nullptr;
+
+ // 1. Try to get the current page from the view shell in the center pane
+ // (if we are that not ourself).
+ if ( ! IsMainViewShell())
+ {
+ std::shared_ptr<ViewShell> pMainViewShell = GetViewShellBase().GetMainViewShell();
+ if (pMainViewShell != nullptr)
+ pCurrentPage = pMainViewShell->GetActualPage();
+ }
+
+ if (pCurrentPage == nullptr)
+ {
+ model::SharedPageDescriptor pDescriptor (
+ mpSlideSorter->GetController().GetCurrentSlideManager()->GetCurrentSlide());
+ if (pDescriptor)
+ pCurrentPage = pDescriptor->GetPage();
+ }
+
+ return pCurrentPage;
+}
+
+void SlideSorterViewShell::GetMenuState ( SfxItemSet& rSet)
+{
+ ViewShell::GetMenuState(rSet);
+ assert(mpSlideSorter);
+ mpSlideSorter->GetController().GetSlotManager()->GetMenuState(rSet);
+}
+
+void SlideSorterViewShell::GetClipboardState ( SfxItemSet& rSet)
+{
+ ViewShell::GetMenuState(rSet);
+ assert(mpSlideSorter);
+ mpSlideSorter->GetController().GetSlotManager()->GetClipboardState(rSet);
+}
+
+void SlideSorterViewShell::ExecCtrl (SfxRequest& rRequest)
+{
+ assert(mpSlideSorter);
+ mpSlideSorter->GetController().ExecCtrl(rRequest);
+}
+
+void SlideSorterViewShell::GetCtrlState (SfxItemSet& rSet)
+{
+ assert(mpSlideSorter);
+ mpSlideSorter->GetController().GetCtrlState(rSet);
+}
+
+void SlideSorterViewShell::FuSupport (SfxRequest& rRequest)
+{
+ assert(mpSlideSorter);
+ mpSlideSorter->GetController().FuSupport(rRequest);
+}
+
+/** We have to handle those slot calls here that need to have access to
+ private or protected members and methods of this class.
+*/
+void SlideSorterViewShell::FuTemporary (SfxRequest& rRequest)
+{
+ assert(mpSlideSorter);
+ switch (rRequest.GetSlot())
+ {
+ case SID_MODIFYPAGE:
+ {
+ SdPage* pCurrentPage = GetActualPage();
+ if (pCurrentPage != nullptr)
+ mpImpl->ProcessModifyPageSlot (
+ rRequest,
+ pCurrentPage,
+ PageKind::Standard);
+ Cancel();
+ rRequest.Done ();
+ }
+ break;
+
+ default:
+ mpSlideSorter->GetController().FuTemporary(rRequest);
+ break;
+ }
+}
+
+void SlideSorterViewShell::GetStatusBarState (SfxItemSet& rSet)
+{
+ assert(mpSlideSorter);
+ mpSlideSorter->GetController().GetStatusBarState(rSet);
+}
+
+void SlideSorterViewShell::FuPermanent (SfxRequest& rRequest)
+{
+ assert(mpSlideSorter);
+ mpSlideSorter->GetController().FuPermanent(rRequest);
+}
+
+void SlideSorterViewShell::GetAttrState (SfxItemSet& rSet)
+{
+ assert(mpSlideSorter);
+ mpSlideSorter->GetController().GetAttrState(rSet);
+}
+
+void SlideSorterViewShell::ExecStatusBar (SfxRequest& )
+{
+}
+
+void SlideSorterViewShell::Paint (
+ const ::tools::Rectangle& rBBox,
+ ::sd::Window* pWindow)
+{
+ SetActiveWindow (pWindow);
+ assert(mpSlideSorter);
+ if (mpSlideSorter)
+ mpSlideSorter->GetController().Paint(rBBox,pWindow);
+}
+
+void SlideSorterViewShell::ArrangeGUIElements()
+{
+ if (IsActive())
+ {
+ assert(mpSlideSorter);
+ mpSlideSorter->ArrangeGUIElements(maViewPos, maViewSize);
+ mbIsArrangeGUIElementsPending = false;
+ }
+ else
+ mbIsArrangeGUIElementsPending = true;
+}
+
+void SlideSorterViewShell::Activate (bool bIsMDIActivate)
+{
+ if(inChartContext(GetView()))
+ {
+ // Avoid context changes for chart during activation / deactivation.
+ const bool bIsContextBroadcasterEnabled (SfxShell::SetContextBroadcasterEnabled(false));
+
+ ViewShell::Activate(bIsMDIActivate);
+
+ SfxShell::SetContextBroadcasterEnabled(bIsContextBroadcasterEnabled);
+ return;
+ }
+
+ ViewShell::Activate(bIsMDIActivate);
+ if (mbIsArrangeGUIElementsPending)
+ ArrangeGUIElements();
+
+ // Determine and broadcast the context that belongs to the main view shell.
+ EnumContext::Context eContext = EnumContext::Context::Unknown;
+ std::shared_ptr<ViewShell> pMainViewShell (GetViewShellBase().GetMainViewShell());
+ ViewShell::ShellType eMainViewShellType (
+ pMainViewShell
+ ? pMainViewShell->GetShellType()
+ : ViewShell::ST_NONE);
+ switch (eMainViewShellType)
+ {
+ case ViewShell::ST_IMPRESS:
+ case ViewShell::ST_SLIDE_SORTER:
+ case ViewShell::ST_NOTES:
+ case ViewShell::ST_DRAW:
+ eContext = EnumContext::Context::DrawPage;
+ if( nullptr != dynamic_cast< const DrawViewShell *>( pMainViewShell.get() ))
+ {
+ DrawViewShell* pDrawViewShell = dynamic_cast<DrawViewShell*>(pMainViewShell.get());
+ if (pDrawViewShell != nullptr)
+ eContext = EnumContext::GetContextEnum(pDrawViewShell->GetSidebarContextName());
+ }
+ break;
+
+ default:
+ break;
+ }
+ ContextChangeEventMultiplexer::NotifyContextChange(
+ &GetViewShellBase(),
+ eContext);
+}
+
+void SlideSorterViewShell::Deactivate (bool /*bIsMDIActivate*/)
+{
+ // Save Settings - Specifically SlidesPerRow to retrieve it later
+ WriteFrameViewData();
+}
+
+void SlideSorterViewShell::Command (
+ const CommandEvent& rEvent,
+ ::sd::Window* pWindow)
+{
+ assert(mpSlideSorter);
+ if ( ! mpSlideSorter->GetController().Command (rEvent, pWindow))
+ ViewShell::Command (rEvent, pWindow);
+}
+
+void SlideSorterViewShell::ReadFrameViewData (FrameView* pFrameView)
+{
+ assert(mpSlideSorter);
+ if (pFrameView != nullptr)
+ {
+ view::SlideSorterView& rView (mpSlideSorter->GetView());
+
+ sal_uInt16 nSlidesPerRow (pFrameView->GetSlidesPerRow());
+ if (nSlidesPerRow > 0
+ && rView.GetOrientation() == view::Layouter::GRID
+ && IsMainViewShell())
+ {
+ rView.GetLayouter().SetColumnCount(nSlidesPerRow,nSlidesPerRow);
+ }
+ if (IsMainViewShell())
+ mpSlideSorter->GetController().GetCurrentSlideManager()->NotifyCurrentSlideChange(
+ mpFrameView->GetSelectedPage());
+ mpSlideSorter->GetController().Rearrange(true);
+
+ // DrawMode for 'main' window
+ if (GetActiveWindow()->GetOutDev()->GetDrawMode() != pFrameView->GetDrawMode() )
+ GetActiveWindow()->GetOutDev()->SetDrawMode( pFrameView->GetDrawMode() );
+ }
+
+ // When this slide sorter is not displayed in the main window then we do
+ // not share the same frame view and have to find other ways to acquire
+ // certain values.
+ if ( ! IsMainViewShell())
+ {
+ std::shared_ptr<ViewShell> pMainViewShell = GetViewShellBase().GetMainViewShell();
+ if (pMainViewShell != nullptr)
+ mpSlideSorter->GetController().GetCurrentSlideManager()->NotifyCurrentSlideChange(
+ pMainViewShell->getCurrentPage());
+ }
+}
+
+void SlideSorterViewShell::WriteFrameViewData()
+{
+ assert(mpSlideSorter);
+ if (mpFrameView == nullptr)
+ return;
+
+ view::SlideSorterView& rView (mpSlideSorter->GetView());
+ mpFrameView->SetSlidesPerRow(static_cast<sal_uInt16>(rView.GetLayouter().GetColumnCount()));
+
+ // DrawMode for 'main' window
+ if( mpFrameView->GetDrawMode() != GetActiveWindow()->GetOutDev()->GetDrawMode() )
+ mpFrameView->SetDrawMode( GetActiveWindow()->GetOutDev()->GetDrawMode() );
+
+ SdPage* pActualPage = GetActualPage();
+ if (pActualPage != nullptr)
+ {
+ if (IsMainViewShell())
+ mpFrameView->SetSelectedPage((pActualPage->GetPageNum()- 1) / 2);
+ // else
+ // The slide sorter is not expected to switch the current page
+ // other than by double clicks. That is handled separately.
+ }
+ else
+ {
+ // We have no current page to set but at least we can make sure
+ // that the index of the frame view has a legal value.
+ if (mpFrameView->GetSelectedPage() >= mpSlideSorter->GetModel().GetPageCount())
+ mpFrameView->SetSelectedPage(static_cast<sal_uInt16>(mpSlideSorter->GetModel().GetPageCount())-1);
+ }
+}
+
+void SlideSorterViewShell::SetZoom (::tools::Long )
+{
+ // Ignored.
+ // The zoom scale is adapted internally to fit a number of columns in
+ // the window.
+}
+
+void SlideSorterViewShell::SetZoomRect (const ::tools::Rectangle& rZoomRect)
+{
+ assert(mpSlideSorter);
+ Size aPageSize (mpSlideSorter->GetView().GetLayouter().GetPageObjectSize());
+
+ ::tools::Rectangle aRect(rZoomRect);
+
+ if (aRect.GetWidth() < aPageSize.Width())
+ {
+ ::tools::Long nWidthDiff = (aPageSize.Width() - aRect.GetWidth()) / 2;
+
+ aRect.AdjustLeft( -nWidthDiff );
+ aRect.AdjustRight(nWidthDiff );
+
+ if (aRect.Left() < 0)
+ {
+ aRect.SetPos(Point(0, aRect.Top()));
+ }
+ }
+
+ if (aRect.GetHeight() < aPageSize.Height())
+ {
+ ::tools::Long nHeightDiff = (aPageSize.Height() - aRect.GetHeight()) / 2;
+
+ aRect.AdjustTop( -nHeightDiff );
+ aRect.AdjustBottom(nHeightDiff );
+
+ if (aRect.Top() < 0)
+ {
+ aRect.SetPos(Point(aRect.Left(), 0));
+ }
+ }
+
+ ViewShell::SetZoomRect(aRect);
+
+ GetViewFrame()->GetBindings().Invalidate( SID_ATTR_ZOOM );
+ GetViewFrame()->GetBindings().Invalidate( SID_ATTR_ZOOMSLIDER );
+}
+
+void SlideSorterViewShell::UpdateScrollBars()
+{
+ // Do not call the overwritten method of the base class: We do all the
+ // scroll bar setup by ourselves.
+ mpSlideSorter->GetController().GetScrollBarManager().UpdateScrollBars(true);
+}
+
+void SlideSorterViewShell::StartDrag (
+ const Point& rDragPt,
+ vcl::Window* pWindow )
+{
+ assert(mpSlideSorter);
+ mpSlideSorter->GetController().GetClipboard().StartDrag (
+ rDragPt,
+ pWindow);
+}
+
+sal_Int8 SlideSorterViewShell::AcceptDrop (
+ const AcceptDropEvent& rEvt,
+ DropTargetHelper& rTargetHelper,
+ ::sd::Window* pTargetWindow,
+ sal_uInt16 nPage,
+ SdrLayerID nLayer)
+{
+ assert(mpSlideSorter);
+ return mpSlideSorter->GetController().GetClipboard().AcceptDrop (
+ rEvt,
+ rTargetHelper,
+ pTargetWindow,
+ nPage,
+ nLayer);
+}
+
+sal_Int8 SlideSorterViewShell::ExecuteDrop (
+ const ExecuteDropEvent& rEvt,
+ DropTargetHelper& rTargetHelper,
+ ::sd::Window* pTargetWindow,
+ sal_uInt16 nPage,
+ SdrLayerID nLayer)
+{
+ assert(mpSlideSorter);
+ return mpSlideSorter->GetController().GetClipboard().ExecuteDrop (
+ rEvt,
+ rTargetHelper,
+ pTargetWindow,
+ nPage,
+ nLayer);
+}
+
+std::shared_ptr<SlideSorterViewShell::PageSelection>
+ SlideSorterViewShell::GetPageSelection() const
+{
+ assert(mpSlideSorter);
+ return mpSlideSorter->GetController().GetPageSelector().GetPageSelection();
+}
+
+void SlideSorterViewShell::SetPageSelection (
+ const std::shared_ptr<PageSelection>& rSelection)
+{
+ assert(mpSlideSorter);
+ mpSlideSorter->GetController().GetPageSelector().SetPageSelection(rSelection, true);
+}
+
+void SlideSorterViewShell::AddSelectionChangeListener (
+ const Link<LinkParamNone*,void>& rCallback)
+{
+ assert(mpSlideSorter);
+ mpSlideSorter->GetController().GetSelectionManager()->AddSelectionChangeListener(rCallback);
+}
+
+void SlideSorterViewShell::RemoveSelectionChangeListener (
+ const Link<LinkParamNone*,void>& rCallback)
+{
+ assert(mpSlideSorter);
+ mpSlideSorter->GetController().GetSelectionManager()->RemoveSelectionChangeListener(rCallback);
+}
+
+void SlideSorterViewShell::MainViewEndEditAndUnmarkAll()
+{
+ std::shared_ptr<ViewShell> pMainViewShell = GetViewShellBase().GetMainViewShell();
+ DrawViewShell* pDrawViewShell = dynamic_cast<DrawViewShell*>(pMainViewShell.get());
+ SdrView* pView = pDrawViewShell ? pDrawViewShell->GetDrawView() : nullptr;
+ if (pView)
+ {
+ pView->SdrEndTextEdit();
+ pView->UnmarkAll();
+ }
+}
+
+std::pair<sal_uInt16, sal_uInt16> SlideSorterViewShell::SyncPageSelectionToDocument(const std::shared_ptr<SlideSorterViewShell::PageSelection> &rpSelection)
+{
+ sal_uInt16 firstSelectedPageNo = SAL_MAX_UINT16;
+ sal_uInt16 lastSelectedPageNo = 0;
+
+ GetDoc()->UnselectAllPages();
+ for (auto& rpPage : *rpSelection)
+ {
+ // Check page number
+ sal_uInt16 pageNo = rpPage->GetPageNum();
+ if (pageNo > lastSelectedPageNo)
+ lastSelectedPageNo = pageNo;
+ if (pageNo < firstSelectedPageNo)
+ firstSelectedPageNo = pageNo;
+ GetDoc()->SetSelected(rpPage, true);
+ }
+
+ return std::make_pair(firstSelectedPageNo, lastSelectedPageNo);
+}
+
+void SlideSorterViewShell::ExecMovePageFirst (SfxRequest& /*rReq*/)
+{
+ MainViewEndEditAndUnmarkAll();
+
+ std::shared_ptr<SlideSorterViewShell::PageSelection> xSelection(GetPageSelection());
+
+ // SdDrawDocument MovePages is based on SdPage IsSelected, so
+ // transfer the SlideSorter selection to SdPages
+ SyncPageSelectionToDocument(xSelection);
+
+ // Moves selected pages after page -1
+ GetDoc()->MovePages( sal_uInt16(-1) );
+
+ PostMoveSlidesActions(xSelection);
+}
+
+void SlideSorterViewShell::GetStateMovePageFirst (SfxItemSet& rSet)
+{
+ if ( ! IsMainViewShell())
+ {
+ std::shared_ptr<ViewShell> pMainViewShell = GetViewShellBase().GetMainViewShell();
+ DrawViewShell* pDrawViewShell = dynamic_cast<DrawViewShell*>(pMainViewShell.get());
+ if (pDrawViewShell != nullptr && pDrawViewShell->GetPageKind() == PageKind::Handout)
+ {
+ rSet.DisableItem( SID_MOVE_PAGE_FIRST );
+ rSet.DisableItem( SID_MOVE_PAGE_UP );
+ return;
+ }
+ }
+
+ std::shared_ptr<SlideSorterViewShell::PageSelection> xSelection(GetPageSelection());
+
+ // SdDrawDocument MovePages is based on SdPage IsSelected, so
+ // transfer the SlideSorter selection to SdPages
+ sal_uInt16 firstSelectedPageNo = SyncPageSelectionToDocument(xSelection).first;
+ // Now compute human page number from internal page number
+ firstSelectedPageNo = (firstSelectedPageNo - 1) / 2;
+
+ if (firstSelectedPageNo == 0)
+ {
+ rSet.DisableItem( SID_MOVE_PAGE_FIRST );
+ rSet.DisableItem( SID_MOVE_PAGE_UP );
+ }
+}
+
+void SlideSorterViewShell::ExecMovePageUp (SfxRequest& /*rReq*/)
+{
+ MainViewEndEditAndUnmarkAll();
+
+ std::shared_ptr<SlideSorterViewShell::PageSelection> xSelection(GetPageSelection());
+
+ // SdDrawDocument MovePages is based on SdPage IsSelected, so
+ // transfer the SlideSorter selection to SdPages
+ sal_uInt16 firstSelectedPageNo = SyncPageSelectionToDocument(xSelection).first;
+
+ // In case no slide is selected
+ if (firstSelectedPageNo == SAL_MAX_UINT16)
+ return;
+
+ // Now compute human page number from internal page number
+ firstSelectedPageNo = (firstSelectedPageNo - 1) / 2;
+
+ if (firstSelectedPageNo == 0)
+ return;
+
+ // Move pages before firstSelectedPageNo - 1 (so after firstSelectedPageNo - 2),
+ // remembering that -1 means at first, which is good.
+ GetDoc()->MovePages( firstSelectedPageNo - 2 );
+
+ PostMoveSlidesActions(xSelection);
+}
+
+void SlideSorterViewShell::GetStateMovePageUp (SfxItemSet& rSet)
+{
+ GetStateMovePageFirst(rSet);
+}
+
+void SlideSorterViewShell::ExecMovePageDown (SfxRequest& /*rReq*/)
+{
+ MainViewEndEditAndUnmarkAll();
+
+ std::shared_ptr<SlideSorterViewShell::PageSelection> xSelection(GetPageSelection());
+
+ // SdDrawDocument MovePages is based on SdPage IsSelected, so
+ // transfer the SlideSorter selection to SdPages
+ sal_uInt16 lastSelectedPageNo = SyncPageSelectionToDocument(xSelection).second;
+
+ // Get page number of the last page
+ sal_uInt16 nNoOfPages = GetDoc()->GetSdPageCount(PageKind::Standard);
+
+ // Now compute human page number from internal page number
+ lastSelectedPageNo = (lastSelectedPageNo - 1) / 2;
+ if (lastSelectedPageNo == nNoOfPages - 1)
+ return;
+
+ // Move to position after lastSelectedPageNo
+ GetDoc()->MovePages( lastSelectedPageNo + 1 );
+
+ PostMoveSlidesActions(xSelection);
+}
+
+void SlideSorterViewShell::GetStateMovePageDown (SfxItemSet& rSet)
+{
+ GetStateMovePageLast( rSet );
+}
+
+void SlideSorterViewShell::ExecMovePageLast (SfxRequest& /*rReq*/)
+{
+ MainViewEndEditAndUnmarkAll();
+
+ std::shared_ptr<SlideSorterViewShell::PageSelection> xSelection(GetPageSelection());
+
+ // SdDrawDocument MovePages is based on SdPage IsSelected, so
+ // transfer the SlideSorter selection to SdPages
+ SyncPageSelectionToDocument(xSelection);
+
+ // Get page number of the last page
+ sal_uInt16 nNoOfPages = GetDoc()->GetSdPageCount(PageKind::Standard);
+
+ // Move to position after last page No (=Number of pages - 1)
+ GetDoc()->MovePages( nNoOfPages - 1 );
+
+ PostMoveSlidesActions(xSelection);
+}
+
+void SlideSorterViewShell::GetStateMovePageLast (SfxItemSet& rSet)
+{
+ std::shared_ptr<ViewShell> pMainViewShell = GetViewShellBase().GetMainViewShell();
+ DrawViewShell* pDrawViewShell = dynamic_cast<DrawViewShell*>(pMainViewShell.get());
+ if (pDrawViewShell != nullptr && pDrawViewShell->GetPageKind() == PageKind::Handout)
+ {
+ rSet.DisableItem( SID_MOVE_PAGE_LAST );
+ rSet.DisableItem( SID_MOVE_PAGE_DOWN );
+ return;
+ }
+
+ std::shared_ptr<SlideSorterViewShell::PageSelection> xSelection(GetPageSelection());
+
+ // SdDrawDocument MovePages is based on SdPage IsSelected, so
+ // transfer the SlideSorter selection to SdPages
+ sal_uInt16 lastSelectedPageNo = SyncPageSelectionToDocument(xSelection).second;
+
+ // Get page number of the last page
+ sal_uInt16 nNoOfPages = GetDoc()->GetSdPageCount(PageKind::Standard);
+
+ // Now compute human page number from internal page number
+ lastSelectedPageNo = (lastSelectedPageNo - 1) / 2;
+ if (lastSelectedPageNo == nNoOfPages - 1)
+ {
+ rSet.DisableItem( SID_MOVE_PAGE_LAST );
+ rSet.DisableItem( SID_MOVE_PAGE_DOWN );
+ }
+}
+
+void SlideSorterViewShell::PostMoveSlidesActions(const std::shared_ptr<SlideSorterViewShell::PageSelection> &rpSelection)
+{
+ sal_uInt16 nNoOfPages = GetDoc()->GetSdPageCount(PageKind::Standard);
+ for (sal_uInt16 nPage = 0; nPage < nNoOfPages; nPage++)
+ {
+ SdPage* pPage = GetDoc()->GetSdPage(nPage, PageKind::Standard);
+ GetDoc()->SetSelected(pPage, false);
+ }
+
+ mpSlideSorter->GetController().GetPageSelector().DeselectAllPages();
+ for (const auto& rpPage : *rpSelection)
+ {
+ mpSlideSorter->GetController().GetPageSelector().SelectPage(rpPage);
+ }
+
+ // Refresh toolbar icons
+ SfxBindings& rBindings = GetViewFrame()->GetBindings();
+ rBindings.Invalidate(SID_MOVE_PAGE_FIRST);
+ rBindings.Invalidate(SID_MOVE_PAGE_UP);
+ rBindings.Invalidate(SID_MOVE_PAGE_DOWN);
+ rBindings.Invalidate(SID_MOVE_PAGE_LAST);
+
+}
+
+} // end of namespace ::sd::slidesorter
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
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: */