/* -*- 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 <com/sun/star/drawing/XDrawView.hpp>
#include <com/sun/star/frame/XController.hpp>
#include <com/sun/star/geometry/RealPoint2D.hpp>
#include <com/sun/star/text/XText.hpp>
#include <com/sun/star/document/XEventBroadcaster.hpp>
#include <com/sun/star/office/XAnnotationAccess.hpp>
#include <comphelper/lok.hxx>
#include <svx/svxids.hrc>
#include <vcl/settings.hxx>
#include <vcl/svapp.hxx>
#include <vcl/weld.hxx>
#include <tools/gen.hxx>
#include <sal/macros.h>
#include <svl/itempool.hxx>
#include <svl/intitem.hxx>
#include <unotools/localedatawrapper.hxx>
#include <unotools/useroptions.hxx>
#include <unotools/syslocale.hxx>
#include <unotools/saveopt.hxx>
#include <tools/datetime.hxx>
#include <tools/UnitConversion.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <sfx2/viewfrm.hxx>
#include <sfx2/bindings.hxx>
#include <sfx2/request.hxx>
#include <sfx2/dispatch.hxx>
#include <editeng/editeng.hxx>
#include <editeng/eeitem.hxx>
#include <editeng/fontitem.hxx>
#include <editeng/fhgtitem.hxx>
#include <editeng/outlobj.hxx>
#include <editeng/postitem.hxx>
#include <svx/postattr.hxx>
#include <annotationmanager.hxx>
#include "annotationmanagerimpl.hxx"
#include "annotationwindow.hxx"
#include <strings.hrc>
#include <Annotation.hxx>
#include "AnnotationPopup.hxx"
#include <DrawDocShell.hxx>
#include <DrawViewShell.hxx>
#include <DrawController.hxx>
#include <sdresid.hxx>
#include <EventMultiplexer.hxx>
#include <ViewShellBase.hxx>
#include <sdpage.hxx>
#include <drawdoc.hxx>
#include <svx/annotation/TextAPI.hxx>
#include <svx/annotation/AnnotationObject.hxx>
#include <svx/annotation/Annotation.hxx>
#include <svx/annotation/ObjectAnnotationData.hxx>
#include <optsitem.hxx>
#include <sdmod.hxx>
#include <svx/svdobj.hxx>
#include <svx/svdocirc.hxx>
#include <svx/svdorect.hxx>
#include <svx/svdopath.hxx>
#include <svx/svdotext.hxx>
#include <svx/svdograf.hxx>
#include <svx/xfillit0.hxx>
#include <svx/xflclit.hxx>
#include <svx/xlineit0.hxx>
#include <svx/xlnclit.hxx>
#include <svx/xlnstwit.hxx>
#include <svx/xlnwtit.hxx>
#include <svx/xfltrit.hxx>
#include <svx/xlntrit.hxx>
#include <cmath>
#include <memory>
using namespace css;
namespace sd
{
SfxItemPool* GetAnnotationPool()
{
static rtl::Reference<SfxItemPool> s_pAnnotationPool;
if( !s_pAnnotationPool )
{
s_pAnnotationPool = EditEngine::CreatePool();
s_pAnnotationPool->SetUserDefaultItem(SvxFontHeightItem(423,100,EE_CHAR_FONTHEIGHT));
vcl::Font aAppFont( Application::GetSettings().GetStyleSettings().GetAppFont() );
s_pAnnotationPool->SetUserDefaultItem(SvxFontItem(aAppFont.GetFamilyType(),aAppFont.GetFamilyName(),u""_ustr,PITCH_DONTKNOW,RTL_TEXTENCODING_DONTKNOW,EE_CHAR_FONTINFO));
}
return s_pAnnotationPool.get();
}
static SfxBindings* getBindings( ViewShellBase const & rBase )
{
auto pMainViewShell = rBase.GetMainViewShell().get();
if( pMainViewShell && pMainViewShell->GetViewFrame() )
return &pMainViewShell->GetViewFrame()->GetBindings();
return nullptr;
}
static SfxDispatcher* getDispatcher( ViewShellBase const & rBase )
{
auto pMainViewShell = rBase.GetMainViewShell().get();
if( pMainViewShell && pMainViewShell->GetViewFrame() )
return pMainViewShell->GetViewFrame()->GetDispatcher();
return nullptr;
}
css::util::DateTime getCurrentDateTime()
{
DateTime aCurrentDate( DateTime::SYSTEM );
return css::util::DateTime( 0, aCurrentDate.GetSec(),
aCurrentDate.GetMin(), aCurrentDate.GetHour(),
aCurrentDate.GetDay(), aCurrentDate.GetMonth(),
aCurrentDate.GetYear(), false );
}
OUString getAnnotationDateTimeString(const uno::Reference<office::XAnnotation>& xAnnotation)
{
OUString sRet;
if( xAnnotation.is() )
{
const SvtSysLocale aSysLocale;
const LocaleDataWrapper& rLocalData = aSysLocale.GetLocaleData();
css::util::DateTime aDateTime( xAnnotation->getDateTime() );
Date aSysDate( Date::SYSTEM );
Date aDate( aDateTime.Day, aDateTime.Month, aDateTime.Year );
if (aDate==aSysDate)
sRet = SdResId(STR_ANNOTATION_TODAY);
else if (aDate == (aSysDate-1))
sRet = SdResId(STR_ANNOTATION_YESTERDAY);
else if (aDate.IsValidAndGregorian() )
sRet = rLocalData.getDate(aDate);
::tools::Time aTime( aDateTime );
if(aTime.GetTime() != 0)
sRet += " " + rLocalData.getTime( aTime,false );
}
return sRet;
}
AnnotationManagerImpl::AnnotationManagerImpl( ViewShellBase& rViewShellBase )
: mrBase( rViewShellBase )
, mpDoc( rViewShellBase.GetDocument() )
, mbShowAnnotations( true )
, mnUpdateTagsEvent( nullptr )
{
SdOptions* pOptions = SD_MOD()->GetSdOptions(mpDoc->GetDocumentType());
if( pOptions )
mbShowAnnotations = pOptions->IsShowComments();
}
void AnnotationManagerImpl::init()
{
// get current controller and initialize listeners
try
{
addListener();
mxView = mrBase.GetDrawController();
}
catch (uno::Exception&)
{
TOOLS_WARN_EXCEPTION( "sd", "sd::AnnotationManagerImpl::AnnotationManagerImpl()" );
}
try
{
uno::Reference<document::XEventBroadcaster> xModel (mrBase.GetDocShell()->GetModel(), uno::UNO_QUERY_THROW);
uno::Reference<document::XEventListener> xListener( this );
xModel->addEventListener( xListener );
}
catch (uno::Exception&)
{
}
}
// WeakComponentImplHelper
void AnnotationManagerImpl::disposing (std::unique_lock<std::mutex>&)
{
try
{
uno::Reference<document::XEventBroadcaster> xModel (mrBase.GetDocShell()->GetModel(), uno::UNO_QUERY_THROW);
uno::Reference<document::XEventListener> xListener( this );
xModel->removeEventListener( xListener );
}
catch (uno::Exception&)
{
}
removeListener();
if( mnUpdateTagsEvent )
{
Application::RemoveUserEvent( mnUpdateTagsEvent );
mnUpdateTagsEvent = nullptr;
}
mxView.clear();
mxCurrentPage.clear();
}
// XEventListener
void SAL_CALL AnnotationManagerImpl::notifyEvent( const css::document::EventObject& aEvent )
{
if( !(aEvent.EventName == "OnAnnotationInserted" || aEvent.EventName == "OnAnnotationRemoved" || aEvent.EventName == "OnAnnotationChanged") )
return;
// AnnotationInsertion and modification is not handled here because when
// a new annotation is inserted, it consists of OnAnnotationInserted
// followed by a chain of OnAnnotationChanged (called for setting each
// of the annotation attributes - author, text etc.). This is not what a
// LOK client wants. So only handle removal here as annotation removal
// consists of only one event - 'OnAnnotationRemoved'
if ( aEvent.EventName == "OnAnnotationRemoved" )
{
uno::Reference<office::XAnnotation> xAnnotation( aEvent.Source, uno::UNO_QUERY );
if ( auto pAnnotation = dynamic_cast<sd::Annotation*>(xAnnotation.get()) )
{
LOKCommentNotify(sdr::annotation::CommentNotificationType::Remove, &mrBase, *pAnnotation);
}
}
UpdateTags();
}
void SAL_CALL AnnotationManagerImpl::disposing( const css::lang::EventObject& /*Source*/ )
{
}
rtl::Reference<sdr::annotation::Annotation> AnnotationManagerImpl::GetAnnotationById(sal_uInt32 nAnnotationId)
{
SdPage* pPage = nullptr;
do
{
pPage = GetNextPage(pPage, true);
if( pPage && !pPage->getAnnotations().empty() )
{
sdr::annotation::AnnotationVector aAnnotations(pPage->getAnnotations());
auto iterator = std::find_if(aAnnotations.begin(), aAnnotations.end(),
[nAnnotationId](rtl::Reference<sdr::annotation::Annotation> const& xAnnotation)
{
return xAnnotation->GetId() == nAnnotationId;
});
if (iterator != aAnnotations.end())
return *iterator;
}
} while(pPage);
rtl::Reference<sdr::annotation::Annotation> xAnnotationEmpty;
return xAnnotationEmpty;
}
void AnnotationManagerImpl::ShowAnnotations( bool bShow )
{
// enforce show annotations if a new annotation is inserted
if( mbShowAnnotations != bShow )
{
mbShowAnnotations = bShow;
SdOptions* pOptions = SD_MOD()->GetSdOptions(mpDoc->GetDocumentType());
if( pOptions )
pOptions->SetShowComments( mbShowAnnotations );
UpdateTags();
}
}
void AnnotationManagerImpl::ExecuteAnnotation(SfxRequest const & rReq )
{
switch( rReq.GetSlot() )
{
case SID_INSERT_POSTIT:
ExecuteInsertAnnotation( rReq );
break;
case SID_DELETE_POSTIT:
case SID_DELETEALL_POSTIT:
case SID_DELETEALLBYAUTHOR_POSTIT:
ExecuteDeleteAnnotation( rReq );
break;
case SID_EDIT_POSTIT:
ExecuteEditAnnotation( rReq );
break;
case SID_PREVIOUS_POSTIT:
case SID_NEXT_POSTIT:
SelectNextAnnotation( rReq.GetSlot() == SID_NEXT_POSTIT );
break;
case SID_REPLYTO_POSTIT:
ExecuteReplyToAnnotation( rReq );
break;
case SID_TOGGLE_NOTES:
ShowAnnotations( !mbShowAnnotations );
break;
}
}
void AnnotationManagerImpl::ExecuteInsertAnnotation(SfxRequest const & rReq)
{
if (!comphelper::LibreOfficeKit::isActive() || comphelper::LibreOfficeKit::isTiledAnnotations())
ShowAnnotations(true);
const SfxItemSet* pArgs = rReq.GetArgs();
OUString sText;
if (pArgs)
{
if (const SfxStringItem* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_TEXT))
{
sText = pPoolItem->GetValue();
}
}
InsertAnnotation(sText);
}
void AnnotationManagerImpl::ExecuteDeleteAnnotation(SfxRequest const & rReq)
{
const SfxItemSet* pArgs = rReq.GetArgs();
switch( rReq.GetSlot() )
{
case SID_DELETEALL_POSTIT:
DeleteAllAnnotations();
break;
case SID_DELETEALLBYAUTHOR_POSTIT:
if( pArgs )
{
const SfxPoolItem* pPoolItem = nullptr;
if( SfxItemState::SET == pArgs->GetItemState( SID_DELETEALLBYAUTHOR_POSTIT, true, &pPoolItem ) )
{
OUString sAuthor( static_cast<const SfxStringItem*>( pPoolItem )->GetValue() );
DeleteAnnotationsByAuthor( sAuthor );
}
}
break;
case SID_DELETE_POSTIT:
{
rtl::Reference<sdr::annotation::Annotation> xAnnotation;
sal_uInt32 nId = 0;
if( pArgs )
{
const SfxPoolItem* pPoolItem = nullptr;
if( SfxItemState::SET == pArgs->GetItemState( SID_DELETE_POSTIT, true, &pPoolItem ) )
{
uno::Reference<office::XAnnotation> xTmpAnnotation;
if (static_cast<const SfxUnoAnyItem*>(pPoolItem)->GetValue() >>= xTmpAnnotation)
{
xAnnotation = dynamic_cast<sdr::annotation::Annotation*>(xTmpAnnotation.get());
assert(bool(xAnnotation) == bool(xTmpAnnotation) && "must be of concrete type sd::Annotation");
}
}
if( SfxItemState::SET == pArgs->GetItemState( SID_ATTR_POSTIT_ID, true, &pPoolItem ) )
nId = static_cast<const SvxPostItIdItem*>(pPoolItem)->GetValue().toUInt32();
}
if (nId != 0)
xAnnotation = GetAnnotationById(nId);
else if( !xAnnotation.is() )
GetSelectedAnnotation(xAnnotation);
DeleteAnnotation(xAnnotation);
}
break;
}
UpdateTags();
}
void AnnotationManagerImpl::ExecuteEditAnnotation(SfxRequest const & rReq)
{
const SfxItemSet* pArgs = rReq.GetArgs();
rtl::Reference<sdr::annotation::Annotation> xAnnotation;
OUString sText;
sal_Int32 nPositionX = -1;
sal_Int32 nPositionY = -1;
if (!pArgs)
return;
if (mpDoc->IsUndoEnabled())
mpDoc->BegUndo(SdResId(STR_ANNOTATION_UNDO_EDIT));
if (const SvxPostItIdItem* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_ID))
{
sal_uInt32 nId = pPoolItem->GetValue().toUInt32();
xAnnotation = GetAnnotationById(nId);
}
if (const SfxStringItem* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_TEXT))
sText = pPoolItem->GetValue();
if (const SfxInt32Item* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_POSITION_X))
nPositionX = pPoolItem->GetValue();
if (const SfxInt32Item* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_POSITION_Y))
nPositionY = pPoolItem->GetValue();
if (xAnnotation.is())
{
auto pSdAnnotation = static_cast<sd::Annotation*>(xAnnotation.get());
pSdAnnotation->createChangeUndo();
SdrObject* pObject = xAnnotation->findAnnotationObject();
if (pObject && nPositionX >= 0 && nPositionY >= 0)
{
double fX = convertTwipToMm100<double>(nPositionX);
double fY = convertTwipToMm100<double>(nPositionY);
::tools::Long deltaX = std::round(fX - (pSdAnnotation->getPosition().X * 100.0));
::tools::Long deltaY = std::round(fY - (pSdAnnotation->getPosition().Y * 100.0));
pObject->Move({ deltaX, deltaY });
}
if (!sText.isEmpty())
{
// TODO: Not allow other authors to change others' comments ?
uno::Reference<text::XText> xText(xAnnotation->getTextRange());
xText->setString(sText);
}
LOKCommentNotifyAll(sdr::annotation::CommentNotificationType::Modify, *xAnnotation);
}
if (mpDoc->IsUndoEnabled())
mpDoc->EndUndo();
UpdateTags(true);
}
void AnnotationManagerImpl::InsertAnnotation(const OUString& rText)
{
SdPage* pPage = GetCurrentPage();
if (!pPage)
return;
if (mpDoc->IsUndoEnabled())
mpDoc->BegUndo(SdResId(STR_ANNOTATION_UNDO_INSERT));
// find free space for new annotation
int y = 0;
int x = 0;
sdr::annotation::AnnotationVector aAnnotations(pPage->getAnnotations());
if (!aAnnotations.empty())
{
const int fPageWidth = pPage->GetSize().Width();
const int fWidth = 1000;
const int fHeight = 800;
while (true)
{
::tools::Rectangle aNewRect(Point(x, y), Size(fWidth, fHeight));
bool bFree = true;
for (const auto& rxAnnotation : aAnnotations)
{
geometry::RealPoint2D aRealPoint2D(rxAnnotation->getPosition());
Point aPoint(::tools::Long(aRealPoint2D.X * 100.0), ::tools::Long(aRealPoint2D.Y * 100.0));
Size aSize(fWidth, fHeight);
if (aNewRect.Overlaps(::tools::Rectangle(aPoint, aSize)))
{
bFree = false;
break;
}
}
if (!bFree)
{
x += fWidth;
if (x > fPageWidth)
{
x = 0;
y += fHeight;
}
}
else
{
break;
}
}
}
rtl::Reference<sdr::annotation::Annotation> xAnnotation = pPage->createAnnotation();
OUString sAuthor;
if (comphelper::LibreOfficeKit::isActive())
sAuthor = mrBase.GetMainViewShell()->GetView()->GetAuthor();
else
{
SvtUserOptions aUserOptions;
sAuthor = aUserOptions.GetFullName();
xAnnotation->setInitials( aUserOptions.GetID() );
}
if (!rText.isEmpty())
{
uno::Reference<text::XText> xText(xAnnotation->getTextRange());
xText->setString(rText);
}
// set current author to new annotation
xAnnotation->setAuthor( sAuthor );
// set current time to new annotation
xAnnotation->setDateTime( getCurrentDateTime() );
// set position
geometry::RealPoint2D aPosition(x / 100.0, y / 100.0);
xAnnotation->setPosition(aPosition);
xAnnotation->setSize({5.0, 5.0});
pPage->addAnnotation(xAnnotation, -1);
if (mpDoc->IsUndoEnabled())
mpDoc->EndUndo();
// Tell our LOK clients about new comment added
LOKCommentNotifyAll(sdr::annotation::CommentNotificationType::Add, *xAnnotation);
UpdateTags(true);
SelectAnnotation(xAnnotation, true);
}
void AnnotationManagerImpl::ExecuteReplyToAnnotation( SfxRequest const & rReq )
{
rtl::Reference< sdr::annotation::Annotation> xAnnotation;
const SfxItemSet* pArgs = rReq.GetArgs();
OUString sReplyText;
if( pArgs )
{
const SfxPoolItem* pPoolItem = nullptr;
if( SfxItemState::SET == pArgs->GetItemState( SID_ATTR_POSTIT_ID, true, &pPoolItem ) )
{
sal_uInt32 nReplyId = 0; // Id of the comment to reply to
nReplyId = static_cast<const SvxPostItIdItem*>(pPoolItem)->GetValue().toUInt32();
xAnnotation = GetAnnotationById(nReplyId);
}
else if( SfxItemState::SET == pArgs->GetItemState( rReq.GetSlot(), true, &pPoolItem ) )
{
uno::Reference<office::XAnnotation> xTmpAnnotation;
if (static_cast<const SfxUnoAnyItem*>(pPoolItem)->GetValue() >>= xTmpAnnotation)
{
xAnnotation = dynamic_cast<Annotation*>(xTmpAnnotation.get());
assert(bool(xAnnotation) == bool(xTmpAnnotation) && "must be of concrete type sd::Annotation");
}
}
if( SfxItemState::SET == pArgs->GetItemState( SID_ATTR_POSTIT_TEXT, true, &pPoolItem ) )
sReplyText = static_cast<const SvxPostItTextItem*>( pPoolItem )->GetValue();
}
auto* pTextApi = getTextApiObject( xAnnotation );
if( !pTextApi )
return;
if (mpDoc->IsUndoEnabled())
mpDoc->BegUndo(SdResId(STR_ANNOTATION_REPLY));
if (xAnnotation)
{
auto pSdAnnotation = static_cast<sd::Annotation*>(xAnnotation.get());
pSdAnnotation->createChangeUndo();
}
::Outliner aOutliner( GetAnnotationPool(),OutlinerMode::TextObject );
SdDrawDocument::SetCalcFieldValueHdl( &aOutliner );
aOutliner.SetUpdateLayout( true );
OUString aStr(SdResId(STR_ANNOTATION_REPLY));
OUString sAuthor( xAnnotation->getAuthor() );
if( sAuthor.isEmpty() )
sAuthor = SdResId( STR_ANNOTATION_NOAUTHOR );
aStr = aStr.replaceFirst("%1", sAuthor) +
" (" + getAnnotationDateTimeString( xAnnotation ) + "): \"";
OUString sQuote( pTextApi->GetText() );
if( sQuote.isEmpty() )
sQuote = "...";
aStr += sQuote + "\"\n";
for( sal_Int32 nIdx = 0; nIdx >= 0; )
aOutliner.Insert( aStr.getToken( 0, '\n', nIdx ), EE_PARA_APPEND, -1 );
if( aOutliner.GetParagraphCount() > 1 )
{
SfxItemSet aAnswerSet( aOutliner.GetEmptyItemSet() );
aAnswerSet.Put(SvxPostureItem(ITALIC_NORMAL,EE_CHAR_ITALIC));
ESelection aSel;
aSel.nEndPara = aOutliner.GetParagraphCount()-2;
aSel.nEndPos = aOutliner.GetText( aOutliner.GetParagraph( aSel.nEndPara ) ).getLength();
aOutliner.QuickSetAttribs( aAnswerSet, aSel );
}
if (!sReplyText.isEmpty())
aOutliner.Insert(sReplyText);
std::optional< OutlinerParaObject > pOPO( aOutliner.CreateParaObject() );
pTextApi->SetText(*pOPO);
OUString sReplyAuthor;
if (comphelper::LibreOfficeKit::isActive())
sReplyAuthor = mrBase.GetMainViewShell()->GetView()->GetAuthor();
else
{
SvtUserOptions aUserOptions;
sReplyAuthor = aUserOptions.GetFullName();
xAnnotation->setInitials( aUserOptions.GetID() );
}
xAnnotation->setAuthor( sReplyAuthor );
// set current time to reply
xAnnotation->setDateTime( getCurrentDateTime() );
// Tell our LOK clients about this (comment modification)
LOKCommentNotifyAll(sdr::annotation::CommentNotificationType::Modify, *xAnnotation);
if( mpDoc->IsUndoEnabled() )
mpDoc->EndUndo();
UpdateTags(true);
SelectAnnotation( xAnnotation, true );
}
void AnnotationManagerImpl::DeleteAnnotation(rtl::Reference<sdr::annotation::Annotation> const& xAnnotation )
{
SdPage* pPage = GetCurrentPage();
if( xAnnotation.is() && pPage )
{
if( mpDoc->IsUndoEnabled() )
mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) );
pPage->removeAnnotation( xAnnotation );
if( mpDoc->IsUndoEnabled() )
mpDoc->EndUndo();
}
}
void AnnotationManagerImpl::DeleteAnnotationsByAuthor( std::u16string_view sAuthor )
{
if( mpDoc->IsUndoEnabled() )
mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) );
SdPage* pPage = nullptr;
do
{
pPage = GetNextPage( pPage, true );
if( pPage )
{
std::vector<rtl::Reference<sdr::annotation::Annotation>> aAnnotations(pPage->getAnnotations()); // intentionally copy
for (auto const& xAnnotation : aAnnotations)
{
if( xAnnotation->getAuthor() == sAuthor )
{
if( mxSelectedAnnotation == xAnnotation )
mxSelectedAnnotation.clear();
pPage->removeAnnotation( xAnnotation );
}
}
}
} while( pPage );
if( mpDoc->IsUndoEnabled() )
mpDoc->EndUndo();
}
void AnnotationManagerImpl::DeleteAllAnnotations()
{
if( mpDoc->IsUndoEnabled() )
mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) );
SdPage* pPage = nullptr;
do
{
pPage = GetNextPage( pPage, true );
if( pPage && !pPage->getAnnotations().empty() )
{
std::vector<rtl::Reference<sdr::annotation::Annotation>> aAnnotations(pPage->getAnnotations()); // intentionally copy
for( const auto& rxAnnotation : aAnnotations)
{
pPage->removeAnnotation( rxAnnotation );
}
}
}
while( pPage );
mxSelectedAnnotation.clear();
if( mpDoc->IsUndoEnabled() )
mpDoc->EndUndo();
}
void AnnotationManagerImpl::GetAnnotationState(SfxItemSet& rSet)
{
SdPage* pCurrentPage = GetCurrentPage();
const bool bReadOnly = mrBase.GetDocShell()->IsReadOnly();
const bool bWrongPageKind = (pCurrentPage == nullptr) || (pCurrentPage->GetPageKind() != PageKind::Standard);
const SvtSaveOptions::ODFSaneDefaultVersion nCurrentODFVersion( GetODFSaneDefaultVersion() );
if (bReadOnly || bWrongPageKind || (nCurrentODFVersion <= SvtSaveOptions::ODFSVER_012))
rSet.DisableItem( SID_INSERT_POSTIT );
rSet.Put(SfxBoolItem(SID_TOGGLE_NOTES, mbShowAnnotations));
rtl::Reference<sdr::annotation::Annotation> xAnnotation;
GetSelectedAnnotation(xAnnotation);
// Don't disable these slot in case of LOK, as postit doesn't need to
// selected before doing an operation on it in LOK
if( (!xAnnotation.is() && !comphelper::LibreOfficeKit::isActive()) || bReadOnly )
{
rSet.DisableItem( SID_DELETE_POSTIT );
rSet.DisableItem( SID_EDIT_POSTIT );
}
SdPage* pPage = nullptr;
bool bHasAnnotations = false;
do
{
pPage = GetNextPage( pPage, true );
if( pPage && !pPage->getAnnotations().empty() )
bHasAnnotations = true;
}
while( pPage && !bHasAnnotations );
if( !bHasAnnotations || bReadOnly )
{
rSet.DisableItem( SID_DELETEALL_POSTIT );
}
if( bWrongPageKind || !bHasAnnotations )
{
rSet.DisableItem( SID_PREVIOUS_POSTIT );
rSet.DisableItem( SID_NEXT_POSTIT );
}
}
void AnnotationManagerImpl::SelectNextAnnotation(bool bForward)
{
ShowAnnotations( true );
rtl::Reference<sdr::annotation::Annotation> xCurrent;
GetSelectedAnnotation(xCurrent);
SdPage* pPage = GetCurrentPage();
if( !pPage )
return;
sdr::annotation::AnnotationVector const& aAnnotations = pPage->getAnnotations();
if( bForward )
{
if( xCurrent.is() )
{
auto iter = std::find(aAnnotations.begin(), aAnnotations.end(), xCurrent);
if (iter != aAnnotations.end())
{
++iter;
if( iter != aAnnotations.end() )
{
SelectAnnotation( *iter );
return;
}
}
}
else if( !aAnnotations.empty() )
{
SelectAnnotation( *(aAnnotations.begin()) );
return;
}
}
else
{
if( xCurrent.is() )
{
auto iter = std::find(aAnnotations.begin(), aAnnotations.end(), xCurrent);
if (iter != aAnnotations.end() && iter != aAnnotations.begin())
{
--iter;
SelectAnnotation( *iter );
return;
}
}
else if( !aAnnotations.empty() )
{
auto iterator = aAnnotations.end();
iterator--;
SelectAnnotation(*iterator);
return;
}
}
mxSelectedAnnotation.clear();
do
{
do
{
pPage = GetNextPage( pPage, bForward );
if( pPage && !pPage->getAnnotations().empty() )
{
// switch to next/previous slide with annotations
std::shared_ptr<DrawViewShell> pDrawViewShell(std::dynamic_pointer_cast<DrawViewShell>(mrBase.GetMainViewShell()));
if (pDrawViewShell != nullptr)
{
pDrawViewShell->ChangeEditMode(pPage->IsMasterPage() ? EditMode::MasterPage : EditMode::Page, false);
pDrawViewShell->SwitchPage((pPage->GetPageNum() - 1) >> 1);
SfxDispatcher* pDispatcher = getDispatcher( mrBase );
if( pDispatcher )
pDispatcher->Execute( bForward ? SID_NEXT_POSTIT : SID_PREVIOUS_POSTIT );
return;
}
}
}
while( pPage );
// The question text depends on the search direction.
bool bImpress = mpDoc->GetDocumentType() == DocumentType::Impress;
TranslateId pStringId;
if(bForward)
pStringId = bImpress ? STR_ANNOTATION_WRAP_FORWARD : STR_ANNOTATION_WRAP_FORWARD_DRAW;
else
pStringId = bImpress ? STR_ANNOTATION_WRAP_BACKWARD : STR_ANNOTATION_WRAP_BACKWARD_DRAW;
// Pop up question box that asks the user whether to wrap around.
// The dialog is made modal with respect to the whole application.
std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr,
VclMessageType::Question, VclButtonsType::YesNo,
SdResId(pStringId)));
xQueryBox->set_default_response(RET_YES);
if (xQueryBox->run() != RET_YES)
break;
}
while( true );
}
void AnnotationManagerImpl::SelectAnnotation(rtl::Reference<sdr::annotation::Annotation> const& xAnnotation, bool /*bEdit*/)
{
mxSelectedAnnotation = xAnnotation;
}
void AnnotationManagerImpl::GetSelectedAnnotation( rtl::Reference<sdr::annotation::Annotation>& xAnnotation )
{
xAnnotation = mxSelectedAnnotation;
}
void AnnotationManagerImpl::invalidateSlots()
{
SfxBindings* pBindings = getBindings( mrBase );
if( pBindings )
{
pBindings->Invalidate( SID_INSERT_POSTIT );
pBindings->Invalidate( SID_DELETE_POSTIT );
pBindings->Invalidate( SID_DELETEALL_POSTIT );
pBindings->Invalidate( SID_PREVIOUS_POSTIT );
pBindings->Invalidate( SID_NEXT_POSTIT );
pBindings->Invalidate( SID_UNDO );
pBindings->Invalidate( SID_REDO );
}
}
void AnnotationManagerImpl::onSelectionChanged()
{
if (!mxView.is() || !mrBase.GetDrawView())
return;
rtl::Reference<SdPage> xPage = mrBase.GetMainViewShell()->getCurrentPage();
if (xPage != mxCurrentPage)
{
mxCurrentPage = std::move(xPage);
UpdateTags(true);
}
}
void AnnotationManagerImpl::UpdateTags(bool bSynchron)
{
SyncAnnotationObjects();
invalidateSlots();
if (bSynchron)
{
if (mnUpdateTagsEvent)
Application::RemoveUserEvent(mnUpdateTagsEvent);
UpdateTagsHdl(nullptr);
}
else
{
if (!mnUpdateTagsEvent && mxView.is())
mnUpdateTagsEvent = Application::PostUserEvent(LINK(this, AnnotationManagerImpl, UpdateTagsHdl));
}
}
IMPL_LINK_NOARG(AnnotationManagerImpl, UpdateTagsHdl, void*, void)
{
mnUpdateTagsEvent = nullptr;
SyncAnnotationObjects();
if (mrBase.GetDrawView())
static_cast<::sd::View*>(mrBase.GetDrawView())->updateHandles();
invalidateSlots();
}
namespace
{
void applyAnnotationCommon(SdrObject& rObject, rtl::Reference<sdr::annotation::Annotation> const& xAnnotation)
{
rObject.setAsAnnotationObject(true);
auto& xAnnotationData = rObject.getAnnotationData();
xAnnotationData->mpAnnotationPopup.reset(new AnnotationPopup(xAnnotation));
xAnnotationData->mxAnnotation = xAnnotation;
rObject.SetPrintable(false);
}
void applyAnnotationProperties(SdrObject& rObject, sdr::annotation::CreationInfo const& rInfo)
{
if (rInfo.mbColor)
{
rObject.SetMergedItem(XLineStyleItem(drawing::LineStyle_SOLID));
rObject.SetMergedItem(XLineColorItem(OUString(), rInfo.maColor));
sal_uInt16 nTransparence = 100.0 - (rInfo.maColor.GetAlpha() / 255.0) * 100.0;
rObject.SetMergedItem(XLineTransparenceItem(nTransparence));
}
rObject.SetMergedItem(XLineWidthItem(rInfo.mnWidth));
if (rInfo.mbFillColor)
{
rObject.SetMergedItem(XFillStyleItem(drawing::FillStyle_SOLID));
rObject.SetMergedItem(XFillColorItem(OUString(), rInfo.maFillColor));
sal_uInt16 nTransparence = 100.0 - (rInfo.maFillColor.GetAlpha() / 255.0) * 100.0;
rObject.SetMergedItem(XFillTransparenceItem(nTransparence));
}
}
}
void AnnotationManagerImpl::SyncAnnotationObjects()
{
if (!mxCurrentPage.is() || !mpDoc)
return;
sd::DrawDocShell* pDocShell = dynamic_cast<sd::DrawDocShell*>(SfxObjectShell::Current());
sd::ViewShell* pViewShell = pDocShell ? pDocShell->GetViewShell() : nullptr;
if (!pViewShell)
{
pViewShell = mrBase.GetMainViewShell().get();
if (!pViewShell)
return;
}
auto* pView = pViewShell->GetView();
if (!pView)
return;
if (!pView->GetSdrPageView())
return;
auto& rModel = pView->getSdrModelFromSdrView();
sal_Int32 nIndex = 1;
bool bAnnotatonInserted = false;
for (auto const& xAnnotation : mxCurrentPage->getAnnotations())
{
SdrObject* pObject = xAnnotation->findAnnotationObject();
if (pObject)
{
nIndex++;
continue;
}
if (!bAnnotatonInserted && mpDoc->IsUndoEnabled())
mpDoc->BegUndo(SdResId(STR_ANNOTATION_UNDO_INSERT));
bAnnotatonInserted = true;
auto const& rInfo = xAnnotation->getCreationInfo();
auto aRealPoint2D = xAnnotation->getPosition();
Point aPosition(::tools::Long(aRealPoint2D.X * 100.0), ::tools::Long(aRealPoint2D.Y * 100.0));
auto aRealSize2D = xAnnotation->getSize();
Size aSize(::tools::Long(aRealSize2D.Width * 100.0), ::tools::Long(aRealSize2D.Height * 100.0));
// If the size is not set, set it to a default value so it is non-zero
if (aSize.getWidth() == 0 || aSize.getHeight() == 0)
aSize = Size(500, 500);
::tools::Rectangle aRectangle(aPosition, aSize);
rtl::Reference<SdrObject> pNewObject;
if (rInfo.meType == sdr::annotation::AnnotationType::None)
{
sal_uInt16 nAuthorIndex = mpDoc->GetAnnotationAuthorIndex(xAnnotation->getAuthor());
sdr::annotation::AnnotationViewData aAnnotationViewData
{
.nIndex = nIndex,
.nAuthorIndex = nAuthorIndex
};
rtl::Reference<sdr::annotation::AnnotationObject> pAnnotationObject = new sdr::annotation::AnnotationObject(rModel, aRectangle, aAnnotationViewData);
pNewObject = pAnnotationObject;
applyAnnotationCommon(*pNewObject, xAnnotation);
pAnnotationObject->ApplyAnnotationName();
}
else if (rInfo.meType == sdr::annotation::AnnotationType::FreeText)
{
rtl::Reference<SdrRectObj> pRectangleObject = new SdrRectObj(rModel, SdrObjKind::Text, aRectangle);
pNewObject = pRectangleObject;
applyAnnotationCommon(*pNewObject, xAnnotation);
applyAnnotationProperties(*pNewObject, rInfo);
OUString aString = xAnnotation->getTextRange()->getString();
pRectangleObject->SetText(aString);
}
else if (rInfo.meType == sdr::annotation::AnnotationType::Square)
{
pNewObject = new SdrRectObj(rModel, aRectangle);
applyAnnotationCommon(*pNewObject, xAnnotation);
applyAnnotationProperties(*pNewObject, rInfo);
}
else if (rInfo.meType == sdr::annotation::AnnotationType::Circle)
{
pNewObject = new SdrCircObj(rModel, SdrCircKind::Full, aRectangle);
applyAnnotationCommon(*pNewObject, xAnnotation);
applyAnnotationProperties(*pNewObject, rInfo);
}
else if (rInfo.meType == sdr::annotation::AnnotationType::Stamp)
{
rtl::Reference<SdrGrafObj> pGrafObject = new SdrGrafObj(rModel, Graphic(rInfo.maBitmapEx), aRectangle);
pNewObject = pGrafObject;
applyAnnotationCommon(*pNewObject, xAnnotation);
pGrafObject->SetMergedItem(XLineStyleItem(drawing::LineStyle_NONE));
pGrafObject->SetMergedItem(XFillStyleItem(drawing::FillStyle_NONE));
}
else
{
SdrObjKind ekind = SdrObjKind::Polygon;
switch (rInfo.meType)
{
case sdr::annotation::AnnotationType::Polygon:
ekind = SdrObjKind::Polygon;
break;
case sdr::annotation::AnnotationType::Line:
ekind = SdrObjKind::PolyLine;
break;
case sdr::annotation::AnnotationType::Ink:
ekind = SdrObjKind::FreehandLine;
break;
default:
break;
}
basegfx::B2DPolyPolygon aPolyPolygon;
for (auto const& rPolygon : rInfo.maPolygons)
aPolyPolygon.append(rPolygon);
pNewObject = new SdrPathObj(rModel, ekind, std::move(aPolyPolygon));
applyAnnotationCommon(*pNewObject, xAnnotation);
applyAnnotationProperties(*pNewObject, rInfo);
}
pNewObject->SetRelativePos(aRectangle.TopLeft());
pView->InsertObjectAtView(pNewObject.get(), *pView->GetSdrPageView());
nIndex++;
}
if (bAnnotatonInserted && mpDoc->IsUndoEnabled())
mpDoc->EndUndo();
}
void AnnotationManagerImpl::addListener()
{
Link<tools::EventMultiplexerEvent&,void> aLink( LINK(this,AnnotationManagerImpl,EventMultiplexerListener) );
mrBase.GetEventMultiplexer()->AddEventListener(aLink);
}
void AnnotationManagerImpl::removeListener()
{
Link<tools::EventMultiplexerEvent&,void> aLink( LINK(this,AnnotationManagerImpl,EventMultiplexerListener) );
mrBase.GetEventMultiplexer()->RemoveEventListener( aLink );
}
IMPL_LINK(AnnotationManagerImpl,EventMultiplexerListener,
tools::EventMultiplexerEvent&, rEvent, void)
{
switch (rEvent.meEventId)
{
case EventMultiplexerEventId::CurrentPageChanged:
case EventMultiplexerEventId::EditViewSelection:
onSelectionChanged();
break;
case EventMultiplexerEventId::MainViewRemoved:
mxView.clear();
onSelectionChanged();
break;
case EventMultiplexerEventId::MainViewAdded:
mxView = mrBase.GetDrawController();
onSelectionChanged();
break;
default: break;
}
}
// TODO: Update colors on notification via DrawViewShell::ConfigurationChanged()
Color AnnotationManagerImpl::GetColor(sal_uInt16 aAuthorIndex)
{
if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode())
{
svtools::ColorConfig aColorConfig;
switch (aAuthorIndex % 9)
{
case 0: return aColorConfig.GetColorValue(svtools::AUTHOR1).nColor;
case 1: return aColorConfig.GetColorValue(svtools::AUTHOR2).nColor;
case 2: return aColorConfig.GetColorValue(svtools::AUTHOR3).nColor;
case 3: return aColorConfig.GetColorValue(svtools::AUTHOR4).nColor;
case 4: return aColorConfig.GetColorValue(svtools::AUTHOR5).nColor;
case 5: return aColorConfig.GetColorValue(svtools::AUTHOR6).nColor;
case 6: return aColorConfig.GetColorValue(svtools::AUTHOR7).nColor;
case 7: return aColorConfig.GetColorValue(svtools::AUTHOR8).nColor;
case 8: return aColorConfig.GetColorValue(svtools::AUTHOR9).nColor;
}
}
return COL_WHITE;
}
Color AnnotationManagerImpl::GetColorLight(sal_uInt16 aAuthorIndex)
{
Color aColor = GetColor(aAuthorIndex);
if (MiscSettings::GetUseDarkMode())
aColor.IncreaseLuminance(30);
else
aColor.DecreaseLuminance(30);
return aColor;
}
Color AnnotationManagerImpl::GetColorDark(sal_uInt16 aAuthorIndex)
{
Color aColor = GetColor(aAuthorIndex);;
if (MiscSettings::GetUseDarkMode())
aColor.DecreaseLuminance(80);
else
aColor.IncreaseLuminance(80);
return aColor;
}
SdPage* AnnotationManagerImpl::GetNextPage( SdPage const * pPage, bool bForward )
{
if( pPage == nullptr )
{
if (bForward)
return mpDoc->GetSdPage(0, PageKind::Standard ); // first page
else
return mpDoc->GetMasterSdPage( mpDoc->GetMasterSdPageCount(PageKind::Standard) - 1, PageKind::Standard ); // last page
}
sal_uInt16 nPageNum = static_cast<sal_uInt16>((pPage->GetPageNum() - 1) >> 1);
// first all non master pages
if( !pPage->IsMasterPage() )
{
if( bForward )
{
if( nPageNum >= mpDoc->GetSdPageCount(PageKind::Standard)-1 )
{
// we reached end of draw pages, start with master pages (skip handout master for draw)
return mpDoc->GetMasterSdPage( (mpDoc->GetDocumentType() == DocumentType::Impress) ? 0 : 1, PageKind::Standard );
}
nPageNum++;
}
else
{
if( nPageNum == 0 )
return nullptr; // we are already on the first draw page, finished
nPageNum--;
}
return mpDoc->GetSdPage(nPageNum, PageKind::Standard);
}
else
{
if( bForward )
{
if( nPageNum >= mpDoc->GetMasterSdPageCount(PageKind::Standard)-1 )
{
return nullptr; // we reached the end, there is nothing more to see here
}
nPageNum++;
}
else
{
if( nPageNum == (mpDoc->GetDocumentType() == DocumentType::Impress ? 0 : 1) )
{
// we reached beginning of master pages, start with end if pages
return mpDoc->GetSdPage( mpDoc->GetSdPageCount(PageKind::Standard)-1, PageKind::Standard );
}
nPageNum--;
}
return mpDoc->GetMasterSdPage(nPageNum,PageKind::Standard);
}
}
SdPage* AnnotationManagerImpl::GetCurrentPage()
{
if (auto pMainViewShell = mrBase.GetMainViewShell().get())
return pMainViewShell->getCurrentPage();
return nullptr;
}
AnnotationManager::AnnotationManager( ViewShellBase& rViewShellBase )
: mxImpl( new AnnotationManagerImpl( rViewShellBase ) )
{
mxImpl->init();
}
AnnotationManager::~AnnotationManager()
{
mxImpl->dispose();
}
void AnnotationManager::ExecuteAnnotation(SfxRequest const & rRequest)
{
mxImpl->ExecuteAnnotation( rRequest );
}
void AnnotationManager::GetAnnotationState(SfxItemSet& rItemSet)
{
mxImpl->GetAnnotationState(rItemSet);
}
void AnnotationManager::SelectAnnotation(rtl::Reference<sdr::annotation::Annotation> const& xAnnotation)
{
mxImpl->SelectAnnotation(xAnnotation);
}
} // end sd
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
↑ V530 The return value of function 'Execute' is required to be utilized.
↑ V1048 The 'ekind' variable was assigned the same value.