diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sd/source/ui/slidesorter | |
parent | Initial commit. (diff) | |
download | libreoffice-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')
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: */ |