/* -*- 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 <comphelper/diagnose_ex.hxx>
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(u"Number"_ustr));
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& rViewShell = mrSlideSorter.GetViewShell();
SdPage* pPage = rViewShell.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 = rViewShell.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), u"Delete_Slide_or_Page"_ustr);
}
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: */