summaryrefslogtreecommitdiffstats
path: root/sd/source/ui/annotations
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 05:54:39 +0000
commit267c6f2ac71f92999e969232431ba04678e7437e (patch)
tree358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sd/source/ui/annotations
parentInitial commit. (diff)
downloadlibreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz
libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--sd/source/ui/annotations/annotationmanager.cxx1231
-rw-r--r--sd/source/ui/annotations/annotationmanagerimpl.hxx141
-rw-r--r--sd/source/ui/annotations/annotationtag.cxx671
-rw-r--r--sd/source/ui/annotations/annotationtag.hxx89
-rw-r--r--sd/source/ui/annotations/annotationwindow.cxx800
-rw-r--r--sd/source/ui/annotations/annotationwindow.hxx143
6 files changed, 3075 insertions, 0 deletions
diff --git a/sd/source/ui/annotations/annotationmanager.cxx b/sd/source/ui/annotations/annotationmanager.cxx
new file mode 100644
index 0000000000..dbd9768b55
--- /dev/null
+++ b/sd/source/ui/annotations/annotationmanager.cxx
@@ -0,0 +1,1231 @@
+/* -*- 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 <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 <DrawDocShell.hxx>
+#include <DrawViewShell.hxx>
+#include <sdresid.hxx>
+#include <EventMultiplexer.hxx>
+#include <ViewShellBase.hxx>
+#include <sdpage.hxx>
+#include <drawdoc.hxx>
+#include <textapi.hxx>
+#include <optsitem.hxx>
+#include <sdmod.hxx>
+
+#include <memory>
+
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::drawing;
+using namespace ::com::sun::star::document;
+using namespace ::com::sun::star::geometry;
+using namespace ::com::sun::star::container;
+using namespace ::com::sun::star::beans;
+using namespace ::com::sun::star::text;
+using namespace ::com::sun::star::view;
+using namespace ::com::sun::star::style;
+using namespace ::com::sun::star::frame;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::ui;
+using namespace ::com::sun::star::task;
+using namespace ::com::sun::star::office;
+
+namespace sd {
+
+SfxItemPool* GetAnnotationPool()
+{
+ static rtl::Reference<SfxItemPool> s_pAnnotationPool;
+ if( !s_pAnnotationPool )
+ {
+ s_pAnnotationPool = EditEngine::CreatePool();
+ s_pAnnotationPool->SetPoolDefaultItem(SvxFontHeightItem(423,100,EE_CHAR_FONTHEIGHT));
+
+ vcl::Font aAppFont( Application::GetSettings().GetStyleSettings().GetAppFont() );
+ s_pAnnotationPool->SetPoolDefaultItem(SvxFontItem(aAppFont.GetFamilyType(),aAppFont.GetFamilyName(),"",PITCH_DONTKNOW,RTL_TEXTENCODING_DONTKNOW,EE_CHAR_FONTINFO));
+ }
+
+ return s_pAnnotationPool.get();
+}
+
+static SfxBindings* getBindings( ViewShellBase const & rBase )
+{
+ if( rBase.GetMainViewShell() && rBase.GetMainViewShell()->GetViewFrame() )
+ return &rBase.GetMainViewShell()->GetViewFrame()->GetBindings();
+
+ return nullptr;
+}
+
+static SfxDispatcher* getDispatcher( ViewShellBase const & rBase )
+{
+ if( rBase.GetMainViewShell() && rBase.GetMainViewShell()->GetViewFrame() )
+ return rBase.GetMainViewShell()->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 Reference< 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.set(mrBase.GetController(), UNO_QUERY);
+ }
+ catch( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sd", "sd::AnnotationManagerImpl::AnnotationManagerImpl()" );
+ }
+
+ try
+ {
+ Reference<XEventBroadcaster> xModel (mrBase.GetDocShell()->GetModel(), UNO_QUERY_THROW );
+ Reference<XEventListener> xListener( this );
+ xModel->addEventListener( xListener );
+ }
+ catch( Exception& )
+ {
+ }
+}
+
+// WeakComponentImplHelper
+void AnnotationManagerImpl::disposing (std::unique_lock<std::mutex>&)
+{
+ try
+ {
+ Reference<XEventBroadcaster> xModel (mrBase.GetDocShell()->GetModel(), UNO_QUERY_THROW );
+ Reference<XEventListener> xListener( this );
+ xModel->removeEventListener( xListener );
+ }
+ catch( Exception& )
+ {
+ }
+
+ removeListener();
+ DisposeTags();
+
+ 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" )
+ {
+ Reference< XAnnotation > xAnnotation( aEvent.Source, uno::UNO_QUERY );
+ if ( xAnnotation.is() )
+ {
+ LOKCommentNotify(CommentNotificationType::Remove, &mrBase, xAnnotation);
+ }
+ }
+
+ UpdateTags();
+}
+
+void SAL_CALL AnnotationManagerImpl::disposing( const css::lang::EventObject& /*Source*/ )
+{
+}
+
+rtl::Reference<Annotation> AnnotationManagerImpl::GetAnnotationById(sal_uInt32 nAnnotationId)
+{
+ SdPage* pPage = nullptr;
+ do
+ {
+ pPage = GetNextPage(pPage, true);
+ if( pPage && !pPage->getAnnotations().empty() )
+ {
+ AnnotationVector aAnnotations(pPage->getAnnotations());
+ auto iter = std::find_if(aAnnotations.begin(), aAnnotations.end(),
+ [nAnnotationId](const Reference<XAnnotation>& xAnnotation) {
+ return sd::getAnnotationId(xAnnotation) == nAnnotationId;
+ });
+ if (iter != aAnnotations.end())
+ return *iter;
+ }
+ } while( pPage );
+
+ rtl::Reference<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)
+{
+ ShowAnnotations( true );
+
+ 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< Annotation > xAnnotation;
+ sal_uInt32 nId = 0;
+ if( pArgs )
+ {
+ const SfxPoolItem* pPoolItem = nullptr;
+ if( SfxItemState::SET == pArgs->GetItemState( SID_DELETE_POSTIT, true, &pPoolItem ) )
+ {
+ uno::Reference<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_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();
+ Reference< XAnnotation > 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())
+ {
+ CreateChangeUndo(xAnnotation);
+
+ if (nPositionX >= 0 && nPositionY >= 0)
+ {
+ double fX = convertTwipToMm100(nPositionX) / 100.0;
+ double fY = convertTwipToMm100(nPositionY) / 100.0;
+ xAnnotation->setPosition({fX, fY});
+ }
+
+ if (!sText.isEmpty())
+ {
+ // TODO: Not allow other authors to change others' comments ?
+ Reference<XText> xText(xAnnotation->getTextRange());
+ xText->setString(sText);
+ }
+
+ LOKCommentNotifyAll(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, x = 0;
+
+ AnnotationVector aAnnotations( pPage->getAnnotations() );
+ if( !aAnnotations.empty() )
+ {
+ const int page_width = pPage->GetSize().Width();
+ const int width = 1000;
+ const int height = 800;
+ ::tools::Rectangle aTagRect;
+
+ while( true )
+ {
+ ::tools::Rectangle aNewRect( x, y, x + width - 1, y + height - 1 );
+ bool bFree = true;
+
+ for( const auto& rxAnnotation : aAnnotations )
+ {
+ RealPoint2D aPoint( rxAnnotation->getPosition() );
+ aTagRect.SetLeft( sal::static_int_cast< ::tools::Long >( aPoint.X * 100.0 ) );
+ aTagRect.SetTop( sal::static_int_cast< ::tools::Long >( aPoint.Y * 100.0 ) );
+ aTagRect.SetRight( aTagRect.Left() + width - 1 );
+ aTagRect.SetBottom( aTagRect.Top() + height - 1 );
+
+ if( aNewRect.Overlaps( aTagRect ) )
+ {
+ bFree = false;
+ break;
+ }
+ }
+
+ if( !bFree )
+ {
+ x += width;
+ if( x > page_width )
+ {
+ x = 0;
+ y += height;
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+
+ rtl::Reference< Annotation > xAnnotation;
+ pPage->createAnnotation( xAnnotation );
+
+ OUString sAuthor;
+ if (comphelper::LibreOfficeKit::isActive())
+ sAuthor = mrBase.GetMainViewShell()->GetView()->GetAuthor();
+ else
+ {
+ SvtUserOptions aUserOptions;
+ sAuthor = aUserOptions.GetFullName();
+ xAnnotation->setInitials( aUserOptions.GetID() );
+ }
+
+ if (!rText.isEmpty())
+ {
+ Reference<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
+ RealPoint2D aPos( static_cast<double>(x) / 100.0, static_cast<double>(y) / 100.0 );
+ xAnnotation->setPosition( aPos );
+
+ if( mpDoc->IsUndoEnabled() )
+ mpDoc->EndUndo();
+
+ // Tell our LOK clients about new comment added
+ LOKCommentNotifyAll(CommentNotificationType::Add, xAnnotation);
+
+ UpdateTags(true);
+ SelectAnnotation( xAnnotation, true );
+}
+
+void AnnotationManagerImpl::ExecuteReplyToAnnotation( SfxRequest const & rReq )
+{
+ rtl::Reference< 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<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();
+ }
+
+ TextApiObject* pTextApi = getTextApiObject( xAnnotation );
+ if( !pTextApi )
+ return;
+
+ ::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(CommentNotificationType::Modify, xAnnotation);
+
+ UpdateTags(true);
+ SelectAnnotation( xAnnotation, true );
+}
+
+void AnnotationManagerImpl::DeleteAnnotation( const rtl::Reference< Annotation >& 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();
+
+ UpdateTags();
+ }
+}
+
+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 )
+ {
+ for( const rtl::Reference< Annotation >& xAnnotation : pPage->getAnnotations() )
+ {
+ 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() )
+ {
+
+ AnnotationVector aAnnotations( pPage->getAnnotations() );
+ 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< 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< Annotation > xCurrent;
+ GetSelectedAnnotation( xCurrent );
+ SdPage* pPage = GetCurrentPage();
+ if( !pPage )
+ return;
+
+ AnnotationVector 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() )
+ {
+ AnnotationVector::iterator iter( aAnnotations.end() );
+ SelectAnnotation( *(--iter) );
+ 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::onTagSelected( AnnotationTag const & rTag )
+{
+ mxSelectedAnnotation = rTag.GetAnnotation();
+ invalidateSlots();
+}
+
+void AnnotationManagerImpl::onTagDeselected( AnnotationTag const & rTag )
+{
+ if( rTag.GetAnnotation() == mxSelectedAnnotation )
+ {
+ mxSelectedAnnotation.clear();
+ invalidateSlots();
+ }
+}
+
+void AnnotationManagerImpl::SelectAnnotation( const rtl::Reference< Annotation >& xAnnotation, bool bEdit /* = sal_False */ )
+{
+ mxSelectedAnnotation = xAnnotation;
+
+ auto iter = std::find_if(maTagVector.begin(), maTagVector.end(),
+ [&xAnnotation](const rtl::Reference<AnnotationTag>& rxTag) { return rxTag->GetAnnotation() == xAnnotation; });
+ if (iter != maTagVector.end())
+ {
+ SmartTagReference xTag( *iter );
+ mrBase.GetMainViewShell()->GetView()->getSmartTags().select( xTag );
+ (*iter)->OpenPopup( bEdit );
+ }
+}
+
+void AnnotationManagerImpl::GetSelectedAnnotation( rtl::Reference< 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;
+
+ try
+ {
+ rtl::Reference< SdPage > xPage = mrBase.GetMainViewShell()->getCurrentPage();
+
+ if( xPage != mxCurrentPage )
+ {
+ mxCurrentPage = xPage;
+
+ UpdateTags(true);
+ }
+ }
+ catch( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sd", "sd::AnnotationManagerImpl::onSelectionChanged()" );
+ }
+}
+
+void AnnotationManagerImpl::UpdateTags( bool bSynchron )
+{
+ 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;
+ DisposeTags();
+
+ if( mbShowAnnotations )
+ CreateTags();
+
+ if( mrBase.GetDrawView() )
+ static_cast< ::sd::View* >( mrBase.GetDrawView() )->updateHandles();
+
+ invalidateSlots();
+}
+
+void AnnotationManagerImpl::CreateTags()
+{
+ if( !(mxCurrentPage.is() && mpDoc) )
+ return;
+
+ auto xViewShell = mrBase.GetMainViewShell();
+ if (!xViewShell)
+ return;
+
+ try
+ {
+ int nIndex = 1;
+ maFont = Application::GetSettings().GetStyleSettings().GetAppFont();
+
+ rtl::Reference< AnnotationTag > xSelectedTag;
+
+ for (const rtl::Reference< Annotation > & xAnnotation : mxCurrentPage->getAnnotations() )
+ {
+ Color aColor( GetColorLight( mpDoc->GetAnnotationAuthorIndex( xAnnotation->getAuthor() ) ) );
+ rtl::Reference< AnnotationTag > xTag( new AnnotationTag( *this, *xViewShell->GetView(), xAnnotation, aColor, nIndex++, maFont ) );
+ maTagVector.push_back(xTag);
+
+ if( xAnnotation == mxSelectedAnnotation )
+ {
+ xSelectedTag = xTag;
+ }
+ }
+
+ if( xSelectedTag.is() )
+ {
+ SmartTagReference xTag( xSelectedTag );
+ mrBase.GetMainViewShell()->GetView()->getSmartTags().select( xTag );
+ }
+ else
+ {
+ // no tag, no selection!
+ mxSelectedAnnotation.clear();
+ }
+ }
+ catch( Exception& )
+ {
+ TOOLS_WARN_EXCEPTION( "sd", "sd::AnnotationManagerImpl::onSelectionChanged()" );
+ }
+}
+
+void AnnotationManagerImpl::DisposeTags()
+{
+ for (auto& rxTag : maTagVector)
+ {
+ rxTag->Dispose();
+ }
+
+ maTagVector.clear();
+}
+
+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.set( mrBase.GetController(), UNO_QUERY );
+ onSelectionChanged();
+ break;
+
+ default: break;
+ }
+}
+
+void AnnotationManagerImpl::ExecuteAnnotationTagContextMenu(const rtl::Reference<Annotation>& xAnnotation, weld::Widget* pParent, const ::tools::Rectangle& rContextRect)
+{
+ SfxDispatcher* pDispatcher( getDispatcher( mrBase ) );
+ if( !pDispatcher )
+ return;
+
+ const bool bReadOnly = mrBase.GetDocShell()->IsReadOnly();
+
+ if (bReadOnly)
+ return;
+
+ std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pParent, "modules/simpress/ui/annotationtagmenu.ui"));
+ std::unique_ptr<weld::Menu> xMenu(xBuilder->weld_menu("menu"));
+
+ SvtUserOptions aUserOptions;
+ OUString sCurrentAuthor( aUserOptions.GetFullName() );
+ OUString sAuthor( xAnnotation->getAuthor() );
+
+ OUString aStr(xMenu->get_label(".uno:DeleteAllAnnotationByAuthor"));
+ OUString aReplace( sAuthor );
+ if( aReplace.isEmpty() )
+ aReplace = SdResId( STR_ANNOTATION_NOAUTHOR );
+ aStr = aStr.replaceFirst("%1", aReplace);
+ xMenu->set_label(".uno:DeleteAllAnnotationByAuthor", aStr);
+
+ bool bShowReply = sAuthor != sCurrentAuthor;
+ xMenu->set_visible(".uno:ReplyToAnnotation", bShowReply);
+ xMenu->set_visible("separator", bShowReply);
+ xMenu->set_visible(".uno:DeleteAnnotation", xAnnotation.is());
+
+ auto sId = xMenu->popup_at_rect(pParent, rContextRect);
+
+ if (sId == ".uno:ReplyToAnnotation")
+ {
+ const SfxUnoAnyItem aItem( SID_REPLYTO_POSTIT, Any( css::uno::Reference<XInterface>(static_cast<cppu::OWeakObject*>(xAnnotation.get())) ) );
+ pDispatcher->ExecuteList(SID_REPLYTO_POSTIT,
+ SfxCallMode::ASYNCHRON, { &aItem });
+ }
+ else if (sId == ".uno:DeleteAnnotation")
+ {
+ const SfxUnoAnyItem aItem( SID_REPLYTO_POSTIT, Any( css::uno::Reference<XInterface>(static_cast<cppu::OWeakObject*>(xAnnotation.get())) ) );
+ pDispatcher->ExecuteList(SID_DELETE_POSTIT, SfxCallMode::ASYNCHRON,
+ { &aItem });
+ }
+ else if (sId == ".uno:DeleteAllAnnotationByAuthor")
+ {
+ const SfxStringItem aItem( SID_DELETEALLBYAUTHOR_POSTIT, sAuthor );
+ pDispatcher->ExecuteList( SID_DELETEALLBYAUTHOR_POSTIT,
+ SfxCallMode::ASYNCHRON, { &aItem });
+ }
+ else if (sId == ".uno:DeleteAllAnnotation")
+ pDispatcher->Execute( SID_DELETEALL_POSTIT );
+}
+
+Color AnnotationManagerImpl::GetColor(sal_uInt16 aAuthorIndex)
+{
+ if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode())
+ {
+ static const Color aArrayNormal[] = {
+ COL_AUTHOR1_NORMAL, COL_AUTHOR2_NORMAL, COL_AUTHOR3_NORMAL,
+ COL_AUTHOR4_NORMAL, COL_AUTHOR5_NORMAL, COL_AUTHOR6_NORMAL,
+ COL_AUTHOR7_NORMAL, COL_AUTHOR8_NORMAL, COL_AUTHOR9_NORMAL };
+
+ return aArrayNormal[ aAuthorIndex % SAL_N_ELEMENTS( aArrayNormal ) ];
+ }
+
+ return COL_WHITE;
+}
+
+Color AnnotationManagerImpl::GetColorLight(sal_uInt16 aAuthorIndex)
+{
+ if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode())
+ {
+ static const Color aArrayLight[] = {
+ COL_AUTHOR1_LIGHT, COL_AUTHOR2_LIGHT, COL_AUTHOR3_LIGHT,
+ COL_AUTHOR4_LIGHT, COL_AUTHOR5_LIGHT, COL_AUTHOR6_LIGHT,
+ COL_AUTHOR7_LIGHT, COL_AUTHOR8_LIGHT, COL_AUTHOR9_LIGHT };
+
+ return aArrayLight[ aAuthorIndex % SAL_N_ELEMENTS( aArrayLight ) ];
+ }
+
+ return COL_WHITE;
+}
+
+Color AnnotationManagerImpl::GetColorDark(sal_uInt16 aAuthorIndex)
+{
+ if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode())
+ {
+ static const Color aArrayAnkor[] = {
+ COL_AUTHOR1_DARK, COL_AUTHOR2_DARK, COL_AUTHOR3_DARK,
+ COL_AUTHOR4_DARK, COL_AUTHOR5_DARK, COL_AUTHOR6_DARK,
+ COL_AUTHOR7_DARK, COL_AUTHOR8_DARK, COL_AUTHOR9_DARK };
+
+ return aArrayAnkor[ aAuthorIndex % SAL_N_ELEMENTS( aArrayAnkor ) ];
+ }
+
+ return COL_WHITE;
+}
+
+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 = (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 (mrBase.GetMainViewShell())
+ return mrBase.GetMainViewShell()->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);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/annotations/annotationmanagerimpl.hxx b/sd/source/ui/annotations/annotationmanagerimpl.hxx
new file mode 100644
index 0000000000..bbcd5a8521
--- /dev/null
+++ b/sd/source/ui/annotations/annotationmanagerimpl.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 <com/sun/star/document/XEventListener.hpp>
+
+#include <rtl/ustring.hxx>
+
+#include <comphelper/compbase.hxx>
+
+#include "annotationtag.hxx"
+
+namespace com::sun::star::drawing { class XDrawView; }
+namespace com::sun::star::office { class XAnnotationAccess; }
+namespace com::sun::star::office { class XAnnotation; }
+
+class SfxRequest;
+class SdPage;
+class SdDrawDocument;
+struct ImplSVEvent;
+
+namespace sd
+{
+class Annotation;
+class ViewShellBase;
+
+namespace tools {
+class EventMultiplexerEvent;
+}
+
+typedef comphelper::WeakComponentImplHelper <
+ css::document::XEventListener
+ > AnnotationManagerImplBase;
+
+class AnnotationManagerImpl : public AnnotationManagerImplBase
+{
+public:
+ explicit AnnotationManagerImpl( ViewShellBase& rViewShellBase );
+
+ void init();
+
+ // WeakComponentImplHelper
+ virtual void disposing (std::unique_lock<std::mutex>&) override;
+
+ // XEventListener
+ virtual void SAL_CALL notifyEvent( const css::document::EventObject& Event ) override;
+ virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override;
+
+ void ExecuteAnnotation (SfxRequest const & rRequest);
+ void GetAnnotationState (SfxItemSet& rItemSet);
+
+ void ExecuteInsertAnnotation(SfxRequest const & rReq);
+ void ExecuteDeleteAnnotation(SfxRequest const & rReq);
+ void ExecuteEditAnnotation(SfxRequest const & rReq);
+ void ExecuteReplyToAnnotation(SfxRequest const & rReq);
+
+ void SelectNextAnnotation(bool bForward);
+
+ void SelectAnnotation( const rtl::Reference< Annotation >& xAnnotation, bool bEdit = false );
+ void GetSelectedAnnotation( rtl::Reference< Annotation >& xAnnotation );
+
+ void InsertAnnotation(const OUString& rText);
+ void DeleteAnnotation( const rtl::Reference< Annotation >& xAnnotation );
+ void DeleteAnnotationsByAuthor( std::u16string_view sAuthor );
+ void DeleteAllAnnotations();
+
+ void ExecuteAnnotationTagContextMenu(const rtl::Reference<Annotation>& xAnnotation, weld::Widget* pParent, const ::tools::Rectangle& rContextRect);
+
+ static Color GetColorDark(sal_uInt16 aAuthorIndex);
+ static Color GetColorLight(sal_uInt16 aAuthorIndex);
+ static Color GetColor(sal_uInt16 aAuthorIndex);
+
+ // callbacks
+ void onTagSelected( AnnotationTag const & rTag );
+ void onTagDeselected( AnnotationTag const & rTag );
+
+ void onSelectionChanged();
+
+ void addListener();
+ void removeListener();
+
+ void invalidateSlots();
+
+ DECL_LINK(EventMultiplexerListener, tools::EventMultiplexerEvent&, void);
+ DECL_LINK(UpdateTagsHdl, void *, void);
+
+ void UpdateTags(bool bSynchron = false);
+ void CreateTags();
+ void DisposeTags();
+
+ SdPage* GetNextPage( SdPage const * pPage, bool bForward );
+
+ SdPage* GetCurrentPage();
+
+ SdDrawDocument* GetDoc() { return mpDoc; }
+
+ void ShowAnnotations(bool bShow);
+
+private:
+ ViewShellBase& mrBase;
+ SdDrawDocument* mpDoc;
+
+ std::vector< rtl::Reference< AnnotationTag > > maTagVector;
+
+ css::uno::Reference< css::drawing::XDrawView > mxView;
+ rtl::Reference< SdPage > mxCurrentPage;
+ rtl::Reference< Annotation > mxSelectedAnnotation;
+
+ bool mbShowAnnotations;
+ ImplSVEvent * mnUpdateTagsEvent;
+ vcl::Font maFont;
+
+ rtl::Reference<Annotation> GetAnnotationById(sal_uInt32 nAnnotationId);
+};
+
+OUString getAnnotationDateTimeString( const css::uno::Reference< css::office::XAnnotation >& xAnnotation );
+
+SfxItemPool* GetAnnotationPool();
+
+css::util::DateTime getCurrentDateTime();
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/annotations/annotationtag.cxx b/sd/source/ui/annotations/annotationtag.cxx
new file mode 100644
index 0000000000..6bea249272
--- /dev/null
+++ b/sd/source/ui/annotations/annotationtag.cxx
@@ -0,0 +1,671 @@
+/* -*- 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/geometry/RealPoint2D.hpp>
+#include <com/sun/star/office/XAnnotation.hpp>
+
+#include <rtl/ustrbuf.hxx>
+
+#include <utility>
+#include <vcl/commandevent.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/weldutils.hxx>
+
+#include <svx/sdr/overlay/overlayanimatedbitmapex.hxx>
+#include <svx/sdr/overlay/overlaybitmapex.hxx>
+#include <svx/sdr/overlay/overlaypolypolygon.hxx>
+#include <svx/svdpagv.hxx>
+#include <svx/sdrpagewindow.hxx>
+#include <svx/sdrpaintwindow.hxx>
+#include <svx/svddrgmt.hxx>
+#include <tools/debug.hxx>
+
+#include <View.hxx>
+#include <sdresid.hxx>
+#include <strings.hrc>
+#include "annotationmanagerimpl.hxx"
+#include "annotationwindow.hxx"
+#include "annotationtag.hxx"
+#include <Annotation.hxx>
+#include <ViewShell.hxx>
+#include <Window.hxx>
+#include <drawdoc.hxx>
+
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::lang;
+using namespace ::com::sun::star::drawing;
+using namespace ::com::sun::star::office;
+using namespace ::com::sun::star::geometry;
+
+namespace sd
+{
+
+const sal_uInt32 SMART_TAG_HDL_NUM = SAL_MAX_UINT32;
+const int DRGPIX = 2; // Drag MinMove in Pixel
+
+static OUString getInitials( const OUString& rName )
+{
+ OUStringBuffer sInitials;
+
+ const sal_Unicode * pStr = rName.getStr();
+ sal_Int32 nLength = rName.getLength();
+
+ while( nLength )
+ {
+ // skip whitespace
+ while( nLength && (*pStr <= ' ') )
+ {
+ nLength--; pStr++;
+ }
+
+ // take letter
+ if( nLength )
+ {
+ sInitials.append(*pStr);
+ nLength--; pStr++;
+ }
+
+ // skip letters until whitespace
+ while( nLength && (*pStr > ' ') )
+ {
+ nLength--; pStr++;
+ }
+ }
+
+ return sInitials.makeStringAndClear();
+}
+
+namespace {
+
+class AnnotationDragMove : public SdrDragMove
+{
+public:
+ AnnotationDragMove(SdrDragView& rNewView, rtl::Reference <AnnotationTag > xTag);
+ virtual bool BeginSdrDrag() override;
+ virtual bool EndSdrDrag(bool bCopy) override;
+ virtual void MoveSdrDrag(const Point& rNoSnapPnt) override;
+ virtual void CancelSdrDrag() override;
+
+private:
+ rtl::Reference <AnnotationTag > mxTag;
+ Point maOrigin;
+};
+
+}
+
+AnnotationDragMove::AnnotationDragMove(SdrDragView& rNewView, rtl::Reference <AnnotationTag > xTag)
+: SdrDragMove(rNewView)
+, mxTag(std::move( xTag ))
+{
+}
+
+bool AnnotationDragMove::BeginSdrDrag()
+{
+ DragStat().SetRef1(GetDragHdl()->GetPos());
+ DragStat().SetShown(!DragStat().IsShown());
+
+ maOrigin = GetDragHdl()->GetPos();
+ DragStat().SetActionRect(::tools::Rectangle(maOrigin,maOrigin));
+
+ return true;
+}
+
+void AnnotationDragMove::MoveSdrDrag(const Point& rNoSnapPnt)
+{
+ Point aPnt(rNoSnapPnt);
+
+ if (DragStat().CheckMinMoved(rNoSnapPnt))
+ {
+ if (aPnt!=DragStat().GetNow())
+ {
+ Hide();
+ DragStat().NextMove(aPnt);
+ GetDragHdl()->SetPos( maOrigin + Point( DragStat().GetDX(), DragStat().GetDY() ) );
+ Show();
+ DragStat().SetActionRect(::tools::Rectangle(aPnt,aPnt));
+ }
+ }
+}
+
+bool AnnotationDragMove::EndSdrDrag(bool /*bCopy*/)
+{
+ Hide();
+ if( mxTag.is() )
+ mxTag->Move( DragStat().GetDX(), DragStat().GetDY() );
+ return true;
+}
+
+void AnnotationDragMove::CancelSdrDrag()
+{
+ Hide();
+}
+
+namespace {
+
+class AnnotationHdl : public SmartHdl
+{
+public:
+ AnnotationHdl( const SmartTagReference& xTag, const rtl::Reference< Annotation >& xAnnotation, const Point& rPnt );
+
+ virtual void CreateB2dIAObject() override;
+ virtual bool IsFocusHdl() const override;
+
+private:
+ rtl::Reference< sd::Annotation > mxAnnotation;
+ rtl::Reference< AnnotationTag > mxTag;
+};
+
+}
+
+AnnotationHdl::AnnotationHdl( const SmartTagReference& xTag, const rtl::Reference< Annotation >& xAnnotation, const Point& rPnt )
+: SmartHdl( xTag, rPnt, SdrHdlKind::SmartTag )
+, mxAnnotation( xAnnotation )
+, mxTag( dynamic_cast< AnnotationTag* >( xTag.get() ) )
+{
+}
+
+void AnnotationHdl::CreateB2dIAObject()
+{
+ // first throw away old one
+ GetRidOfIAObject();
+
+ if (!mxAnnotation.is())
+ return;
+
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+
+ const Point aTagPos( GetPos() );
+ basegfx::B2DPoint aPosition( aTagPos.X(), aTagPos.Y() );
+
+ const bool bFocused = IsFocusHdl() && m_pHdlList && (m_pHdlList->GetFocusHdl() == this);
+
+ BitmapEx aBitmapEx( mxTag->CreateAnnotationBitmap(mxTag->isSelected()) );
+ BitmapEx aBitmapEx2;
+ if( bFocused )
+ aBitmapEx2 = mxTag->CreateAnnotationBitmap(!mxTag->isSelected() );
+
+ if(!m_pHdlList)
+ return;
+
+ SdrMarkView* pView = m_pHdlList->GetView();
+
+ if(!pView || pView->areMarkHandlesHidden())
+ return;
+
+ SdrPageView* pPageView = pView->GetSdrPageView();
+
+ if(!pPageView)
+ return;
+
+ for(sal_uInt32 b = 0; b < pPageView->PageWindowCount(); b++)
+ {
+ // const SdrPageViewWinRec& rPageViewWinRec = rPageViewWinList[b];
+ const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(b);
+
+ SdrPaintWindow& rPaintWindow = rPageWindow.GetPaintWindow();
+ const rtl::Reference< sdr::overlay::OverlayManager >& xManager = rPageWindow.GetOverlayManager();
+ if(rPaintWindow.OutputToWindow() && xManager.is() )
+ {
+ std::unique_ptr<sdr::overlay::OverlayObject> pOverlayObject;
+
+ if (mxAnnotation && mxAnnotation->hasCustomAnnotationMarker())
+ {
+ CustomAnnotationMarker& rCustomAnnotationMarker = mxAnnotation->getCustomAnnotationMarker();
+
+ auto& rPolygons = rCustomAnnotationMarker.maPolygons;
+ if (!rPolygons.empty())
+ {
+ basegfx::B2DPolyPolygon aPolyPolygon;
+ for (auto const & rPolygon : rPolygons)
+ aPolyPolygon.append(rPolygon);
+
+ pOverlayObject.reset(new sdr::overlay::OverlayPolyPolygon(
+ std::move(aPolyPolygon),
+ rCustomAnnotationMarker.maLineColor,
+ rCustomAnnotationMarker.mnLineWidth,
+ rCustomAnnotationMarker.maFillColor));
+ }
+ }
+ else
+ {
+ // animate focused handles
+ if(bFocused)
+ {
+ const sal_uInt64 nBlinkTime = rStyleSettings.GetCursorBlinkTime();
+
+ pOverlayObject.reset(new sdr::overlay::OverlayAnimatedBitmapEx(aPosition, aBitmapEx, aBitmapEx2, nBlinkTime, 0, 0, 0, 0 ));
+ }
+ else
+ {
+ pOverlayObject.reset(new sdr::overlay::OverlayBitmapEx( aPosition, aBitmapEx, 0, 0 ));
+ }
+ }
+
+ // OVERLAYMANAGER
+ insertNewlyCreatedOverlayObjectForSdrHdl(
+ std::move(pOverlayObject),
+ rPageWindow.GetObjectContact(),
+ *xManager);
+ }
+ }
+}
+
+bool AnnotationHdl::IsFocusHdl() const
+{
+ return true;
+}
+
+AnnotationTag::AnnotationTag( AnnotationManagerImpl& rManager, ::sd::View& rView, const rtl::Reference< Annotation >& xAnnotation, Color const & rColor, int nIndex, const vcl::Font& rFont )
+: SmartTag( rView )
+, mrManager( rManager )
+, mxAnnotation( xAnnotation )
+, maColor( rColor )
+, mnIndex( nIndex )
+, mrFont( rFont )
+, mpListenWindow( nullptr )
+{
+}
+
+AnnotationTag::~AnnotationTag()
+{
+ DBG_ASSERT( !mxAnnotation.is(), "sd::AnnotationTag::~AnnotationTag(), dispose me first!" );
+ Dispose();
+}
+
+/** returns true if the AnnotationTag handled the event. */
+bool AnnotationTag::MouseButtonDown( const MouseEvent& rMEvt, SmartHdl& /*rHdl*/ )
+{
+ if( !mxAnnotation.is() )
+ return false;
+
+ bool bRet = false;
+ if( !isSelected() )
+ {
+ SmartTagReference xTag( this );
+ mrView.getSmartTags().select( xTag );
+ bRet = true;
+ }
+
+ if( rMEvt.IsLeft() && !rMEvt.IsRight() )
+ {
+ vcl::Window* pWindow = mrView.GetViewShell()->GetActiveWindow();
+ if( pWindow )
+ {
+ maMouseDownPos = pWindow->PixelToLogic( rMEvt.GetPosPixel() );
+
+ if( mpListenWindow )
+ mpListenWindow->RemoveEventListener( LINK(this, AnnotationTag, WindowEventHandler));
+
+ mpListenWindow = pWindow;
+ mpListenWindow->AddEventListener( LINK(this, AnnotationTag, WindowEventHandler));
+ }
+
+ bRet = true;
+ }
+
+ return bRet;
+}
+
+/** returns true if the SmartTag consumes this event. */
+bool AnnotationTag::KeyInput( const KeyEvent& rKEvt )
+{
+ if( !mxAnnotation.is() )
+ return false;
+
+ sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
+ switch( nCode )
+ {
+ case KEY_DELETE:
+ mrManager.DeleteAnnotation( mxAnnotation );
+ return true;
+
+ case KEY_DOWN:
+ case KEY_UP:
+ case KEY_LEFT:
+ case KEY_RIGHT:
+ return OnMove( rKEvt );
+
+ case KEY_ESCAPE:
+ {
+ SmartTagReference xThis( this );
+ mrView.getSmartTags().deselect();
+ return true;
+ }
+
+ case KEY_TAB:
+ mrManager.SelectNextAnnotation(!rKEvt.GetKeyCode().IsShift());
+ return true;
+
+ case KEY_RETURN:
+ case KEY_SPACE:
+ OpenPopup( true );
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+/** returns true if the SmartTag consumes this event. */
+bool AnnotationTag::Command( const CommandEvent& rCEvt )
+{
+ if (rCEvt.GetCommand() != CommandEventId::ContextMenu)
+ return false;
+ if (vcl::Window* pWindow = mrView.GetViewShell()->GetActiveWindow())
+ {
+ ::tools::Rectangle aContextRect(rCEvt.GetMousePosPixel(),Size(1,1));
+ weld::Window* pParent = weld::GetPopupParent(*pWindow, aContextRect);
+ mrManager.ExecuteAnnotationTagContextMenu(mxAnnotation, pParent, aContextRect);
+ return true;
+ }
+ return false;
+}
+
+void AnnotationTag::Move( int nDX, int nDY )
+{
+ if( !mxAnnotation.is() )
+ return;
+
+ if( mrManager.GetDoc()->IsUndoEnabled() )
+ mrManager.GetDoc()->BegUndo( SdResId( STR_ANNOTATION_UNDO_MOVE ) );
+
+ RealPoint2D aPosition( mxAnnotation->getPosition() );
+ aPosition.X += static_cast<double>(nDX) / 100.0;
+ aPosition.Y += static_cast<double>(nDY) / 100.0;
+ mxAnnotation->setPosition( aPosition );
+
+ if( mrManager.GetDoc()->IsUndoEnabled() )
+ mrManager.GetDoc()->EndUndo();
+
+ mrView.updateHandles();
+}
+
+bool AnnotationTag::OnMove( const KeyEvent& rKEvt )
+{
+ ::tools::Long nX = 0;
+ ::tools::Long nY = 0;
+
+ switch( rKEvt.GetKeyCode().GetCode() )
+ {
+ case KEY_UP: nY = -1; break;
+ case KEY_DOWN: nY = 1; break;
+ case KEY_LEFT: nX = -1; break;
+ case KEY_RIGHT: nX = 1; break;
+ default: break;
+ }
+
+ if(rKEvt.GetKeyCode().IsMod2())
+ {
+ OutputDevice* pOut = mrView.GetViewShell()->GetActiveWindow()->GetOutDev();
+ Size aLogicSizeOnePixel = pOut ? pOut->PixelToLogic(Size(1,1)) : Size(100, 100);
+ nX *= aLogicSizeOnePixel.Width();
+ nY *= aLogicSizeOnePixel.Height();
+ }
+ else
+ {
+ // old, fixed move distance
+ nX *= 100;
+ nY *= 100;
+ }
+
+ if( nX || nY )
+ {
+ // move the annotation
+ Move( nX, nY );
+ }
+
+ return true;
+}
+
+void AnnotationTag::CheckPossibilities()
+{
+}
+
+sal_Int32 AnnotationTag::GetMarkablePointCount() const
+{
+ return 0;
+}
+
+sal_Int32 AnnotationTag::GetMarkedPointCount() const
+{
+ return 0;
+}
+
+bool AnnotationTag::MarkPoint(SdrHdl& /*rHdl*/, bool /*bUnmark*/ )
+{
+ return false;
+}
+
+bool AnnotationTag::MarkPoints(const ::tools::Rectangle* /*pRect*/, bool /*bUnmark*/ )
+{
+ return false;
+}
+
+bool AnnotationTag::getContext( SdrViewContext& /*rContext*/ )
+{
+ return false;
+}
+
+void AnnotationTag::addCustomHandles( SdrHdlList& rHandlerList )
+{
+ if( !mxAnnotation.is() )
+ return;
+
+ SmartTagReference xThis( this );
+ std::unique_ptr<AnnotationHdl> pHdl(new AnnotationHdl( xThis, mxAnnotation, Point() ));
+ pHdl->SetObjHdlNum( SMART_TAG_HDL_NUM );
+ pHdl->SetPageView( mrView.GetSdrPageView() );
+
+ RealPoint2D aPosition( mxAnnotation->getPosition() );
+ Point aBasePos( static_cast<::tools::Long>(aPosition.X * 100.0), static_cast<::tools::Long>(aPosition.Y * 100.0) );
+ pHdl->SetPos( aBasePos );
+
+ rHandlerList.AddHdl( std::move(pHdl) );
+}
+
+void AnnotationTag::disposing()
+{
+ if( mpListenWindow )
+ {
+ mpListenWindow->RemoveEventListener( LINK(this, AnnotationTag, WindowEventHandler));
+ }
+
+ mxAnnotation.clear();
+ ClosePopup();
+ SmartTag::disposing();
+}
+
+void AnnotationTag::select()
+{
+ SmartTag::select();
+
+ mrManager.onTagSelected( *this );
+
+ vcl::Window* pWindow = mrView.GetViewShell()->GetActiveWindow();
+ if( pWindow )
+ {
+ RealPoint2D aPosition( mxAnnotation->getPosition() );
+ Point aPos( static_cast<::tools::Long>(aPosition.X * 100.0), static_cast<::tools::Long>(aPosition.Y * 100.0) );
+
+ ::tools::Rectangle aVisRect( aPos, pWindow->PixelToLogic(maSize) );
+ mrView.MakeVisible(aVisRect, *pWindow);
+ }
+}
+
+void AnnotationTag::deselect()
+{
+ SmartTag::deselect();
+
+ ClosePopup();
+
+ mrManager.onTagDeselected( *this );
+}
+
+BitmapEx AnnotationTag::CreateAnnotationBitmap( bool bSelected )
+{
+ ScopedVclPtrInstance< VirtualDevice > pVDev;
+
+ OUString sText;
+ if (mxAnnotation && mxAnnotation->isFreeText())
+ {
+ sText = mxAnnotation->getTextRange()->getString();
+ }
+ else
+ {
+ OUString sInitials(mxAnnotation->getInitials());
+ if (sInitials.isEmpty())
+ {
+ sInitials = getInitials(mxAnnotation->getAuthor());
+ }
+
+ sText = sInitials + " " + OUString::number(mnIndex);
+ }
+
+ pVDev->SetFont( mrFont );
+
+ const int BORDER_X = 4; // pixels
+ const int BORDER_Y = 4; // pixels
+
+ maSize = Size(pVDev->GetTextWidth(sText) + 2 * BORDER_X, pVDev->GetTextHeight() + 2 * BORDER_Y);
+ pVDev->SetOutputSizePixel( maSize, false );
+
+ Color aBorderColor( maColor );
+
+ if( bSelected )
+ {
+ aBorderColor.Invert();
+ }
+ else
+ {
+ if( maColor.IsDark() )
+ {
+ aBorderColor.IncreaseLuminance( 32 );
+ }
+ else
+ {
+ aBorderColor.DecreaseLuminance( 32 );
+ }
+ }
+
+ Point aPos;
+ ::tools::Rectangle aBorderRect( aPos, maSize );
+ pVDev->SetLineColor(aBorderColor);
+ pVDev->SetFillColor(maColor);
+ pVDev->DrawRect( aBorderRect );
+
+ pVDev->SetTextColor( maColor.IsDark() ? COL_WHITE : COL_BLACK );
+ pVDev->DrawText(Point(BORDER_X, BORDER_Y), sText);
+
+ return pVDev->GetBitmapEx( aPos, maSize );
+}
+
+void AnnotationTag::OpenPopup( bool bEdit )
+{
+ if( !mxAnnotation.is() )
+ return;
+
+ if( !mpAnnotationWindow )
+ {
+ OutputDevice* pOut = getView().GetFirstOutputDevice();
+ vcl::Window* pWindow = pOut ? pOut->GetOwnerWindow() : nullptr;
+ if( pWindow )
+ {
+ RealPoint2D aPosition( mxAnnotation->getPosition() );
+ Point aPos(pWindow->LogicToPixel( Point( static_cast<::tools::Long>(aPosition.X * 100.0), static_cast<::tools::Long>(aPosition.Y * 100.0) ) ) );
+
+ aPos.AdjustX(4 ); // magic!
+ aPos.AdjustY(1 );
+
+ ::tools::Rectangle aRect( aPos, maSize );
+
+ weld::Window* pParent = weld::GetPopupParent(*pWindow, aRect);
+ mpAnnotationWindow.reset(new AnnotationWindow(pParent, aRect, mrView.GetDocSh(), mxAnnotation));
+ mpAnnotationWindow->connect_closed(LINK(this, AnnotationTag, PopupModeEndHdl));
+ }
+ }
+
+ if (bEdit && mpAnnotationWindow)
+ mpAnnotationWindow->StartEdit();
+}
+
+IMPL_LINK_NOARG(AnnotationTag, PopupModeEndHdl, weld::Popover&, void)
+{
+ ClosePopup();
+}
+
+void AnnotationTag::ClosePopup()
+{
+ if (mpAnnotationWindow)
+ {
+ mpAnnotationWindow->SaveToDocument();
+ mpAnnotationWindow.reset();
+ }
+}
+
+IMPL_LINK(AnnotationTag, WindowEventHandler, VclWindowEvent&, rEvent, void)
+{
+ vcl::Window* pWindow = rEvent.GetWindow();
+
+ if( !pWindow )
+ return;
+
+ if( pWindow != mpListenWindow )
+ return;
+
+ switch( rEvent.GetId() )
+ {
+ case VclEventId::WindowMouseButtonUp:
+ {
+ // if we stop pressing the button without a mouse move we open the popup
+ mpListenWindow->RemoveEventListener( LINK(this, AnnotationTag, WindowEventHandler));
+ mpListenWindow = nullptr;
+ if( !mpAnnotationWindow )
+ OpenPopup(false);
+ }
+ break;
+ case VclEventId::WindowMouseMove:
+ {
+ // if we move the mouse after a button down we want to start dragging
+ mpListenWindow->RemoveEventListener( LINK(this, AnnotationTag, WindowEventHandler));
+ mpListenWindow = nullptr;
+
+ SdrHdl* pHdl = mrView.PickHandle(maMouseDownPos);
+ if( pHdl )
+ {
+ mrView.BrkAction();
+ const sal_uInt16 nDrgLog = static_cast<sal_uInt16>(pWindow->PixelToLogic(Size(DRGPIX,0)).Width());
+
+ rtl::Reference< AnnotationTag > xTag( this );
+
+ SdrDragMethod* pDragMethod = new AnnotationDragMove( mrView, xTag );
+ mrView.BegDragObj(maMouseDownPos, nullptr, pHdl, nDrgLog, pDragMethod );
+ }
+ }
+ break;
+ case VclEventId::ObjectDying:
+ mpListenWindow = nullptr;
+ break;
+ default: break;
+ }
+}
+
+} // end of namespace sd
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/annotations/annotationtag.hxx b/sd/source/ui/annotations/annotationtag.hxx
new file mode 100644
index 0000000000..b5807a4b08
--- /dev/null
+++ b/sd/source/ui/annotations/annotationtag.hxx
@@ -0,0 +1,89 @@
+/* -*- 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/vclevent.hxx>
+
+#include <smarttag.hxx>
+#include "annotationwindow.hxx"
+
+namespace com::sun::star::office { class XAnnotation; }
+
+namespace sd {
+class Annotation;
+class View;
+class AnnotationManagerImpl;
+
+class AnnotationTag final : public SmartTag
+{
+public:
+ AnnotationTag( AnnotationManagerImpl& rManager, ::sd::View& rView, const rtl::Reference< Annotation >& xAnnotation, Color const & rColor, int nIndex, const vcl::Font& rFont );
+ virtual ~AnnotationTag() override;
+
+ /// @return true if the SmartTag handled the event.
+ virtual bool MouseButtonDown( const MouseEvent&, SmartHdl& ) override;
+
+ /// @return true if the SmartTag consumes this event.
+ virtual bool KeyInput( const KeyEvent& rKEvt ) override;
+
+ /// @return true if the SmartTag consumes this event.
+ virtual bool Command( const CommandEvent& rCEvt ) override;
+
+ // callbacks from sdr view
+ virtual sal_Int32 GetMarkablePointCount() const override;
+ virtual sal_Int32 GetMarkedPointCount() const override;
+ virtual bool MarkPoint(SdrHdl& rHdl, bool bUnmark) override;
+ virtual void CheckPossibilities() override;
+ virtual bool MarkPoints(const ::tools::Rectangle* pRect, bool bUnmark) override;
+
+ void Move( int nDX, int nDY );
+ bool OnMove( const KeyEvent& rKEvt );
+
+ BitmapEx CreateAnnotationBitmap(bool);
+
+ const rtl::Reference< Annotation >& GetAnnotation() const { return mxAnnotation; }
+
+ void OpenPopup( bool bEdit );
+ void ClosePopup();
+
+private:
+ virtual void addCustomHandles( SdrHdlList& rHandlerList ) override;
+ virtual bool getContext( SdrViewContext& rContext ) override;
+ virtual void disposing() override;
+ virtual void select() override;
+ virtual void deselect() override;
+
+ DECL_LINK( WindowEventHandler, VclWindowEvent&, void );
+ DECL_LINK(PopupModeEndHdl, weld::Popover&, void);
+
+ AnnotationManagerImpl& mrManager;
+ rtl::Reference< Annotation > mxAnnotation;
+ std::unique_ptr<AnnotationWindow> mpAnnotationWindow;
+ Color maColor;
+ int mnIndex;
+ const vcl::Font& mrFont;
+ Size maSize;
+ VclPtr<vcl::Window> mpListenWindow;
+ Point maMouseDownPos;
+};
+
+} // end of namespace sd
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/annotations/annotationwindow.cxx b/sd/source/ui/annotations/annotationwindow.cxx
new file mode 100644
index 0000000000..02495794ae
--- /dev/null
+++ b/sd/source/ui/annotations/annotationwindow.cxx
@@ -0,0 +1,800 @@
+/* -*- 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 <editeng/eeitem.hxx>
+#include <editeng/udlnitem.hxx>
+#include <editeng/langitem.hxx>
+#include <editeng/editview.hxx>
+#include <editeng/editstat.hxx>
+#include <editeng/outliner.hxx>
+#include <editeng/editeng.hxx>
+#include <editeng/outlobj.hxx>
+#include <editeng/postitem.hxx>
+#include <editeng/wghtitem.hxx>
+#include <editeng/crossedoutitem.hxx>
+#include <editeng/editund2.hxx>
+#include <svx/svxids.hrc>
+#include <unotools/useroptions.hxx>
+
+#include <sfx2/viewfrm.hxx>
+#include <sfx2/bindings.hxx>
+#include <sfx2/dispatch.hxx>
+#include <svl/stritem.hxx>
+
+#include <vcl/commandevent.hxx>
+#include <vcl/commandinfoprovider.hxx>
+#include <vcl/vclenum.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/gradient.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/ptrstyle.hxx>
+
+#include <strings.hrc>
+#include "annotationwindow.hxx"
+#include "annotationmanagerimpl.hxx"
+
+#include <com/sun/star/office/XAnnotation.hpp>
+#include <DrawDocShell.hxx>
+#include <ViewShell.hxx>
+#include <drawdoc.hxx>
+#include <textapi.hxx>
+#include <sdresid.hxx>
+
+#include <memory>
+
+using namespace ::sd;
+using namespace ::com::sun::star;
+using namespace ::com::sun::star::uno;
+using namespace ::com::sun::star::office;
+using namespace ::com::sun::star::text;
+
+#define METABUTTON_WIDTH 16
+#define METABUTTON_HEIGHT 18
+#define POSTIT_META_HEIGHT sal_Int32(30)
+
+namespace sd {
+
+void AnnotationTextWindow::Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect)
+{
+ Size aSize = GetOutputSizePixel();
+
+ const bool bHighContrast = Application::GetSettings().GetStyleSettings().GetHighContrastMode();
+ if (!bHighContrast)
+ {
+ rRenderContext.DrawGradient(::tools::Rectangle(Point(0,0), rRenderContext.PixelToLogic(aSize)),
+ Gradient(css::awt::GradientStyle_LINEAR, mrContents.maColorLight, mrContents.maColor));
+ }
+
+ DoPaint(rRenderContext, rRect);
+}
+
+void AnnotationTextWindow::EditViewScrollStateChange()
+{
+ mrContents.SetScrollbar();
+}
+
+bool AnnotationTextWindow::KeyInput(const KeyEvent& rKeyEvt)
+{
+ const vcl::KeyCode& rKeyCode = rKeyEvt.GetKeyCode();
+ sal_uInt16 nKey = rKeyCode.GetCode();
+
+ bool bDone = false;
+
+ if ((rKeyCode.IsMod1() && rKeyCode.IsMod2()) && ((nKey == KEY_PAGEUP) || (nKey == KEY_PAGEDOWN)))
+ {
+ SfxDispatcher* pDispatcher = mrContents.DocShell()->GetViewShell()->GetViewFrame()->GetDispatcher();
+ if( pDispatcher )
+ pDispatcher->Execute( nKey == KEY_PAGEDOWN ? SID_NEXT_POSTIT : SID_PREVIOUS_POSTIT );
+ bDone = true;
+ }
+ else if (nKey == KEY_INSERT)
+ {
+ if (!rKeyCode.IsMod1() && !rKeyCode.IsMod2())
+ mrContents.ToggleInsMode();
+ bDone = true;
+ }
+ else
+ {
+ ::tools::Long aOldHeight = mrContents.GetPostItTextHeight();
+
+ /// HACK: need to switch off processing of Undo/Redo in Outliner
+ if ( !( (nKey == KEY_Z || nKey == KEY_Y) && rKeyCode.IsMod1()) )
+ {
+ bool bIsProtected = mrContents.IsProtected();
+ if (!bIsProtected || !EditEngine::DoesKeyChangeText(rKeyEvt) )
+ {
+ if (EditView* pEditView = GetEditView())
+ {
+ bDone = pEditView->PostKeyEvent(rKeyEvt);
+ if (!bDone && rKeyEvt.GetKeyCode().IsMod1() && !rKeyEvt.GetKeyCode().IsMod2())
+ {
+ if (nKey == KEY_A)
+ {
+ EditEngine* pEditEngine = GetEditEngine();
+ sal_Int32 nPar = pEditEngine->GetParagraphCount();
+ if (nPar)
+ {
+ sal_Int32 nLen = pEditEngine->GetTextLen(nPar - 1);
+ pEditView->SetSelection(ESelection(0, 0, nPar - 1, nLen));
+ }
+ bDone = true;
+ }
+ }
+ }
+ }
+ }
+ if (bDone)
+ {
+ mrContents.ResizeIfNecessary(aOldHeight, mrContents.GetPostItTextHeight());
+ }
+ }
+
+ return bDone;
+}
+
+AnnotationTextWindow::AnnotationTextWindow(AnnotationWindow& rContents)
+ : mrContents(rContents)
+{
+}
+
+EditView* AnnotationTextWindow::GetEditView() const
+{
+ OutlinerView* pOutlinerView = mrContents.GetOutlinerView();
+ if (!pOutlinerView)
+ return nullptr;
+ return &pOutlinerView->GetEditView();
+}
+
+EditEngine* AnnotationTextWindow::GetEditEngine() const
+{
+ OutlinerView* pOutlinerView = mrContents.GetOutlinerView();
+ if (!pOutlinerView)
+ return nullptr;
+ return pOutlinerView->GetEditView().GetEditEngine();
+}
+
+void AnnotationTextWindow::SetDrawingArea(weld::DrawingArea* pDrawingArea)
+{
+ Size aSize(0, 0);
+ pDrawingArea->set_size_request(aSize.Width(), aSize.Height());
+
+ SetOutputSizePixel(aSize);
+
+ weld::CustomWidgetController::SetDrawingArea(pDrawingArea);
+
+ EnableRTL(false);
+
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ Color aBgColor = rStyleSettings.GetWindowColor();
+
+ OutputDevice& rDevice = pDrawingArea->get_ref_device();
+
+ rDevice.SetMapMode(MapMode(MapUnit::Map100thMM));
+ rDevice.SetBackground(aBgColor);
+
+ Size aOutputSize(rDevice.PixelToLogic(aSize));
+
+ EditView* pEditView = GetEditView();
+ pEditView->setEditViewCallbacks(this);
+
+ EditEngine* pEditEngine = GetEditEngine();
+ pEditEngine->SetPaperSize(aOutputSize);
+ pEditEngine->SetRefDevice(&rDevice);
+
+ pEditView->SetOutputArea(::tools::Rectangle(Point(0, 0), aOutputSize));
+ pEditView->SetBackgroundColor(aBgColor);
+
+ pDrawingArea->set_cursor(PointerStyle::Text);
+
+ InitAccessible();
+}
+
+// see SwAnnotationWin in sw for something similar
+AnnotationWindow::AnnotationWindow(weld::Window* pParent, const ::tools::Rectangle& rRect,
+ DrawDocShell* pDocShell,
+ const Reference<XAnnotation>& xAnnotation)
+ : mxBuilder(Application::CreateBuilder(pParent, "modules/simpress/ui/annotation.ui"))
+ , mxPopover(mxBuilder->weld_popover("Annotation"))
+ , mxContainer(mxBuilder->weld_widget("container"))
+ , mpDocShell(pDocShell)
+ , mpDoc(pDocShell->GetDoc())
+ , mbReadonly(pDocShell->IsReadOnly())
+ , mbProtected(false)
+{
+ mxContainer->set_size_request(320, 240);
+ mxPopover->popup_at_rect(pParent, rRect);
+
+ InitControls();
+ setAnnotation(xAnnotation);
+ FillMenuButton();
+
+ DoResize();
+
+ mxTextControl->GrabFocus();
+}
+
+AnnotationWindow::~AnnotationWindow()
+{
+}
+
+void AnnotationWindow::InitControls()
+{
+ // window control for author and date
+ mxMeta = mxBuilder->weld_label("meta");
+ mxMeta->set_direction(AllSettings::GetLayoutRTL());
+
+ maLabelFont = Application::GetSettings().GetStyleSettings().GetLabelFont();
+ maLabelFont.SetFontHeight(8);
+
+ // we should leave this setting alone, but for this we need a better layout algo
+ // with variable meta size height
+ mxMeta->set_font(maLabelFont);
+
+ mpOutliner.reset( new ::Outliner(GetAnnotationPool(),OutlinerMode::TextObject) );
+ SdDrawDocument::SetCalcFieldValueHdl( mpOutliner.get() );
+ mpOutliner->SetUpdateLayout( true );
+
+ if (OutputDevice* pDev = mpDoc->GetRefDevice())
+ mpOutliner->SetRefDevice( pDev );
+
+ mpOutlinerView.reset( new OutlinerView ( mpOutliner.get(), nullptr) );
+ mpOutliner->InsertView(mpOutlinerView.get() );
+
+ //create Scrollbars
+ mxVScrollbar = mxBuilder->weld_scrolled_window("scrolledwindow", true);
+
+ // actual window which holds the user text
+ mxTextControl.reset(new AnnotationTextWindow(*this));
+ mxTextControlWin.reset(new weld::CustomWeld(*mxBuilder, "editview", *mxTextControl));
+ mxTextControl->SetPointer(PointerStyle::Text);
+
+ Rescale();
+ OutputDevice& rDevice = mxTextControl->GetDrawingArea()->get_ref_device();
+
+ mxVScrollbar->set_direction(false);
+ mxVScrollbar->connect_vadjustment_changed(LINK(this, AnnotationWindow, ScrollHdl));
+
+ mpOutlinerView->SetBackgroundColor(COL_TRANSPARENT);
+ mpOutlinerView->SetOutputArea(rDevice.PixelToLogic(::tools::Rectangle(0, 0, 1, 1)));
+
+ mxMenuButton = mxBuilder->weld_menu_button("menubutton");
+ if (mbReadonly)
+ mxMenuButton->hide();
+ else
+ {
+ mxMenuButton->set_size_request(METABUTTON_WIDTH, METABUTTON_HEIGHT);
+ mxMenuButton->connect_selected(LINK(this, AnnotationWindow, MenuItemSelectedHdl));
+ }
+
+ EEControlBits nCntrl = mpOutliner->GetControlWord();
+ nCntrl |= EEControlBits::PASTESPECIAL | EEControlBits::AUTOCORRECT | EEControlBits::USECHARATTRIBS | EEControlBits::NOCOLORS;
+ mpOutliner->SetControlWord(nCntrl);
+
+ mpOutliner->SetModifyHdl( Link<LinkParamNone*,void>() );
+ mpOutliner->EnableUndo( false );
+
+ mpOutliner->ClearModifyFlag();
+ mpOutliner->GetUndoManager().Clear();
+ mpOutliner->EnableUndo( true );
+
+ SetLanguage(SvxLanguageItem(mpDoc->GetLanguage(EE_CHAR_LANGUAGE), SID_ATTR_LANGUAGE));
+
+ mxTextControl->GrabFocus();
+}
+
+IMPL_LINK(AnnotationWindow, MenuItemSelectedHdl, const OUString&, rIdent, void)
+{
+ SfxDispatcher* pDispatcher = mpDocShell->GetViewShell()->GetViewFrame()->GetDispatcher();
+ if (!pDispatcher)
+ return;
+
+ if (rIdent == ".uno:ReplyToAnnotation")
+ {
+ const SfxUnoAnyItem aItem( SID_REPLYTO_POSTIT, Any( mxAnnotation ) );
+ pDispatcher->ExecuteList(SID_REPLYTO_POSTIT,
+ SfxCallMode::ASYNCHRON, { &aItem });
+ }
+ else if (rIdent == ".uno:DeleteAnnotation")
+ {
+ const SfxUnoAnyItem aItem( SID_DELETE_POSTIT, Any( mxAnnotation ) );
+ pDispatcher->ExecuteList(SID_DELETE_POSTIT, SfxCallMode::ASYNCHRON,
+ { &aItem });
+ }
+ else if (rIdent == ".uno:DeleteAllAnnotationByAuthor")
+ {
+ const SfxStringItem aItem( SID_DELETEALLBYAUTHOR_POSTIT, mxAnnotation->getAuthor() );
+ pDispatcher->ExecuteList( SID_DELETEALLBYAUTHOR_POSTIT,
+ SfxCallMode::ASYNCHRON, { &aItem });
+ }
+ else if (rIdent == ".uno:DeleteAllAnnotation")
+ pDispatcher->Execute( SID_DELETEALL_POSTIT );
+}
+
+void AnnotationWindow::FillMenuButton()
+{
+ SvtUserOptions aUserOptions;
+ OUString sCurrentAuthor( aUserOptions.GetFullName() );
+ OUString sAuthor( mxAnnotation->getAuthor() );
+
+ OUString aStr(mxMenuButton->get_item_label(".uno:DeleteAllAnnotationByAuthor"));
+ OUString aReplace( sAuthor );
+ if( aReplace.isEmpty() )
+ aReplace = SdResId( STR_ANNOTATION_NOAUTHOR );
+ aStr = aStr.replaceFirst("%1", aReplace);
+ mxMenuButton->set_item_label(".uno:DeleteAllAnnotationByAuthor", aStr);
+
+ bool bShowReply = sAuthor != sCurrentAuthor && !mbReadonly;
+ mxMenuButton->set_item_visible(".uno:ReplyToAnnotation", bShowReply);
+ mxMenuButton->set_item_visible("separator", bShowReply);
+ mxMenuButton->set_item_visible(".uno:DeleteAnnotation", mxAnnotation.is() && !mbReadonly);
+ mxMenuButton->set_item_visible(".uno:DeleteAllAnnotationByAuthor", !mbReadonly);
+ mxMenuButton->set_item_visible(".uno:DeleteAllAnnotation", !mbReadonly);
+}
+
+void AnnotationWindow::StartEdit()
+{
+ GetOutlinerView()->SetSelection(ESelection(EE_PARA_MAX_COUNT,EE_TEXTPOS_MAX_COUNT,EE_PARA_MAX_COUNT,EE_TEXTPOS_MAX_COUNT));
+ GetOutlinerView()->ShowCursor();
+}
+
+void AnnotationWindow::SetMapMode(const MapMode& rNewMapMode)
+{
+ OutputDevice& rDevice = mxTextControl->GetDrawingArea()->get_ref_device();
+ rDevice.SetMapMode(rNewMapMode);
+}
+
+void AnnotationWindow::Rescale()
+{
+ MapMode aMode(MapUnit::Map100thMM);
+ aMode.SetOrigin( Point() );
+ mpOutliner->SetRefMapMode( aMode );
+ SetMapMode( aMode );
+
+ if (mxMeta)
+ {
+ vcl::Font aFont = maLabelFont;
+ sal_Int32 nHeight = ::tools::Long(aFont.GetFontHeight() * aMode.GetScaleY());
+ aFont.SetFontHeight( nHeight );
+ mxMeta->set_font(aFont);
+ }
+}
+
+void AnnotationWindow::DoResize()
+{
+ OutputDevice& rDevice = mxTextControl->GetDrawingArea()->get_ref_device();
+
+ ::tools::Long aHeight = mxContainer->get_preferred_size().Height();
+ ::tools::ULong aWidth = mxContainer->get_preferred_size().Width();
+
+ aHeight -= POSTIT_META_HEIGHT;
+
+ mpOutliner->SetPaperSize( rDevice.PixelToLogic( Size(aWidth, aHeight) ) ) ;
+ ::tools::Long aTextHeight = rDevice.LogicToPixel(mpOutliner->CalcTextSize()).Height();
+
+ if( aTextHeight > aHeight )
+ {
+ const int nThickness = mxVScrollbar->get_scroll_thickness();
+ if (nThickness)
+ {
+ // we need vertical scrollbars and have to reduce the width
+ aWidth -= nThickness;
+ mpOutliner->SetPaperSize(rDevice.PixelToLogic(Size(aWidth, aHeight)));
+ }
+ mxVScrollbar->set_vpolicy(VclPolicyType::ALWAYS);
+ }
+ else
+ {
+ mxVScrollbar->set_vpolicy(VclPolicyType::NEVER);
+ }
+
+ ::tools::Rectangle aOutputArea = rDevice.PixelToLogic(::tools::Rectangle(0, 0, aWidth, aHeight));
+ if (mxVScrollbar->get_vpolicy() == VclPolicyType::NEVER)
+ {
+ // if we do not have a scrollbar anymore, we want to see the complete text
+ mpOutlinerView->SetVisArea(aOutputArea);
+ }
+ mpOutlinerView->SetOutputArea(aOutputArea);
+ mpOutlinerView->ShowCursor(true, true);
+
+ int nUpper = mpOutliner->GetTextHeight();
+ int nCurrentDocPos = mpOutlinerView->GetVisArea().Top();
+ int nStepIncrement = mpOutliner->GetTextHeight() / 10;
+ int nPageIncrement = rDevice.PixelToLogic(Size(0,aHeight)).Height() * 8 / 10;
+ int nPageSize = rDevice.PixelToLogic(Size(0,aHeight)).Height();
+
+ /* limit the page size to below nUpper because gtk's gtk_scrolled_window_start_deceleration has
+ effectively...
+
+ lower = gtk_adjustment_get_lower
+ upper = gtk_adjustment_get_upper - gtk_adjustment_get_page_size
+
+ and requires that upper > lower or the deceleration animation never ends
+ */
+ nPageSize = std::min(nPageSize, nUpper);
+
+ mxVScrollbar->vadjustment_configure(nCurrentDocPos, 0, nUpper,
+ nStepIncrement, nPageIncrement, nPageSize);
+}
+
+void AnnotationWindow::SetScrollbar()
+{
+ mxVScrollbar->vadjustment_set_value(mpOutlinerView->GetVisArea().Top());
+}
+
+void AnnotationWindow::ResizeIfNecessary(::tools::Long aOldHeight, ::tools::Long aNewHeight)
+{
+ if (aOldHeight != aNewHeight)
+ DoResize();
+ else
+ SetScrollbar();
+}
+
+void AnnotationWindow::SetLanguage(const SvxLanguageItem &aNewItem)
+{
+ mpOutliner->SetModifyHdl( Link<LinkParamNone*,void>() );
+ ESelection aOld = GetOutlinerView()->GetSelection();
+
+ ESelection aNewSelection( 0, 0, mpOutliner->GetParagraphCount()-1, EE_TEXTPOS_ALL );
+ GetOutlinerView()->SetSelection( aNewSelection );
+ SfxItemSet aEditAttr(GetOutlinerView()->GetAttribs());
+ aEditAttr.Put(aNewItem);
+ GetOutlinerView()->SetAttribs( aEditAttr );
+
+ GetOutlinerView()->SetSelection(aOld);
+
+ mxTextControl->Invalidate();
+}
+
+void AnnotationWindow::ToggleInsMode()
+{
+ if( mpOutlinerView )
+ {
+ SfxBindings &rBnd = mpDocShell->GetViewShell()->GetViewFrame()->GetBindings();
+ rBnd.Invalidate(SID_ATTR_INSERT);
+ rBnd.Update(SID_ATTR_INSERT);
+ }
+}
+
+::tools::Long AnnotationWindow::GetPostItTextHeight()
+{
+ OutputDevice& rDevice = mxTextControl->GetDrawingArea()->get_ref_device();
+ return mpOutliner ? rDevice.LogicToPixel(mpOutliner->CalcTextSize()).Height() : 0;
+}
+
+IMPL_LINK(AnnotationWindow, ScrollHdl, weld::ScrolledWindow&, rScrolledWindow, void)
+{
+ ::tools::Long nDiff = GetOutlinerView()->GetEditView().GetVisArea().Top() - rScrolledWindow.vadjustment_get_value();
+ GetOutlinerView()->Scroll( 0, nDiff );
+}
+
+TextApiObject* getTextApiObject( const Reference< XAnnotation >& xAnnotation )
+{
+ if( xAnnotation.is() )
+ {
+ Reference< XText > xText( xAnnotation->getTextRange() );
+ return TextApiObject::getImplementation( xText );
+ }
+ return nullptr;
+}
+
+void AnnotationWindow::setAnnotation( const Reference< XAnnotation >& xAnnotation )
+{
+ if( (xAnnotation == mxAnnotation) || !xAnnotation.is() )
+ return;
+
+ mxAnnotation = xAnnotation;
+
+ SetColor();
+
+ SvtUserOptions aUserOptions;
+ mbProtected = aUserOptions.GetFullName() != xAnnotation->getAuthor();
+
+ mpOutliner->Clear();
+ TextApiObject* pTextApi = getTextApiObject( mxAnnotation );
+
+ if( pTextApi )
+ {
+ std::optional< OutlinerParaObject > pOPO( pTextApi->CreateText() );
+ mpOutliner->SetText(*pOPO);
+ }
+
+ mpOutliner->ClearModifyFlag();
+ mpOutliner->GetUndoManager().Clear();
+
+//TODO Invalidate();
+
+ OUString sMeta( xAnnotation->getAuthor() );
+ OUString sDateTime( getAnnotationDateTimeString(xAnnotation) );
+
+ if( !sDateTime.isEmpty() )
+ {
+ if( !sMeta.isEmpty() )
+ sMeta += "\n";
+
+ sMeta += sDateTime;
+ }
+ mxMeta->set_label(sMeta);
+}
+
+void AnnotationWindow::SetColor()
+{
+ sal_uInt16 nAuthorIdx = mpDoc->GetAnnotationAuthorIndex( mxAnnotation->getAuthor() );
+
+ const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
+ const bool bHighContrast = rStyleSettings.GetHighContrastMode();
+ if( bHighContrast )
+ {
+ maColor = rStyleSettings.GetWindowColor();
+ maColorDark = maColor;
+ maColorLight = rStyleSettings.GetWindowTextColor();
+ }
+ else
+ {
+ maColor = AnnotationManagerImpl::GetColor( nAuthorIdx );
+ maColorDark = AnnotationManagerImpl::GetColorDark( nAuthorIdx );
+ maColorLight = AnnotationManagerImpl::GetColorLight( nAuthorIdx );
+ }
+
+ mpOutliner->ForceAutoColor( bHighContrast || SvtAccessibilityOptions::GetIsAutomaticFontColor() );
+
+ mxPopover->set_background(maColor);
+ mxMenuButton->set_background(maColor);
+
+ mxMeta->set_font_color(bHighContrast ? maColorLight : maColorDark);
+
+ mxVScrollbar->customize_scrollbars(maColorLight,
+ maColorDark,
+ maColor);
+ mxVScrollbar->set_scroll_thickness(GetPrefScrollbarWidth());
+}
+
+void AnnotationWindow::SaveToDocument()
+{
+ Reference< XAnnotation > xAnnotation( mxAnnotation );
+
+ // write changed text back to annotation
+ if (mpOutliner->IsModified())
+ {
+ TextApiObject* pTextApi = getTextApiObject( xAnnotation );
+
+ if( pTextApi )
+ {
+ std::optional<OutlinerParaObject> pOPO = mpOutliner->CreateParaObject();
+ if( pOPO )
+ {
+ if( mpDoc->IsUndoEnabled() )
+ mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_EDIT ) );
+
+ pTextApi->SetText( *pOPO );
+ pOPO.reset();
+
+ // set current time to changed annotation
+ xAnnotation->setDateTime( getCurrentDateTime() );
+
+ if( mpDoc->IsUndoEnabled() )
+ mpDoc->EndUndo();
+
+ mpDocShell->SetModified();
+ }
+
+ }
+ }
+ mpOutliner->ClearModifyFlag();
+
+ mpOutliner->GetUndoManager().Clear();
+}
+
+bool AnnotationTextWindow::Command(const CommandEvent& rCEvt)
+{
+ if (rCEvt.GetCommand() == CommandEventId::ContextMenu)
+ {
+ const bool bReadOnly = mrContents.DocShell()->IsReadOnly();
+ if (bReadOnly)
+ return true;
+
+ SfxDispatcher* pDispatcher = mrContents.DocShell()->GetViewShell()->GetViewFrame()->GetDispatcher();
+ if( !pDispatcher )
+ return true;
+
+ if (IsMouseCaptured())
+ {
+ // so the menu can capture it and the EditView doesn't get the button release and change its
+ // selection on a successful button click
+ ReleaseMouse();
+ }
+
+ ::tools::Rectangle aRect(rCEvt.GetMousePosPixel(), Size(1, 1));
+ weld::Widget* pPopupParent = GetDrawingArea();
+ std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pPopupParent, "modules/simpress/ui/annotationtagmenu.ui"));
+ std::unique_ptr<weld::Menu> xMenu(xBuilder->weld_menu("menu"));
+
+ auto xAnnotation = mrContents.getAnnotation();
+
+ SvtUserOptions aUserOptions;
+ OUString sCurrentAuthor( aUserOptions.GetFullName() );
+ OUString sAuthor( xAnnotation->getAuthor() );
+
+ OUString aStr(xMenu->get_label(".uno:DeleteAllAnnotationByAuthor"));
+ OUString aReplace( sAuthor );
+ if( aReplace.isEmpty() )
+ aReplace = SdResId( STR_ANNOTATION_NOAUTHOR );
+ aStr = aStr.replaceFirst("%1", aReplace);
+ xMenu->set_label(".uno:DeleteAllAnnotationByAuthor", aStr);
+
+ bool bShowReply = sAuthor != sCurrentAuthor && !bReadOnly;
+ xMenu->set_visible(".uno:ReplyToAnnotation", bShowReply);
+ xMenu->set_visible("separator", bShowReply);
+ xMenu->set_visible(".uno:DeleteAnnotation", xAnnotation.is() && !bReadOnly);
+ xMenu->set_visible(".uno:DeleteAllAnnotationByAuthor", !bReadOnly);
+ xMenu->set_visible(".uno:DeleteAllAnnotation", !bReadOnly);
+
+ int nInsertPos = 2;
+
+ auto xFrame = mrContents.DocShell()->GetViewShell()->GetViewFrame()->GetFrame().GetFrameInterface();
+ OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(xFrame));
+
+ bool bEditable = !mrContents.IsProtected() && !bReadOnly;
+ if (bEditable)
+ {
+ SfxItemSet aSet(mrContents.GetOutlinerView()->GetAttribs());
+
+ xMenu->insert(nInsertPos++, ".uno:Bold",
+ vcl::CommandInfoProvider::GetMenuLabelForCommand(
+ vcl::CommandInfoProvider::GetCommandProperties(".uno:Bold", aModuleName)),
+ nullptr, nullptr, vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Bold", xFrame),
+ TRISTATE_TRUE);
+
+ if ( aSet.GetItemState( EE_CHAR_WEIGHT ) == SfxItemState::SET )
+ {
+ if( aSet.Get( EE_CHAR_WEIGHT ).GetWeight() == WEIGHT_BOLD )
+ xMenu->set_active(".uno:Bold", true);
+ }
+
+ xMenu->insert(nInsertPos++, ".uno:Italic",
+ vcl::CommandInfoProvider::GetMenuLabelForCommand(
+ vcl::CommandInfoProvider::GetCommandProperties(".uno:Italic", aModuleName)),
+ nullptr, nullptr, vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Italic", xFrame),
+ TRISTATE_TRUE);
+
+ if ( aSet.GetItemState( EE_CHAR_ITALIC ) == SfxItemState::SET )
+ {
+ if( aSet.Get( EE_CHAR_ITALIC ).GetPosture() != ITALIC_NONE )
+ xMenu->set_active(".uno:Italic", true);
+
+ }
+
+ xMenu->insert(nInsertPos++, ".uno:Underline",
+ vcl::CommandInfoProvider::GetMenuLabelForCommand(
+ vcl::CommandInfoProvider::GetCommandProperties(".uno:Underline", aModuleName)),
+ nullptr, nullptr, vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Underline", xFrame),
+ TRISTATE_TRUE);
+
+ if ( aSet.GetItemState( EE_CHAR_UNDERLINE ) == SfxItemState::SET )
+ {
+ if( aSet.Get( EE_CHAR_UNDERLINE ).GetLineStyle() != LINESTYLE_NONE )
+ xMenu->set_active(".uno:Underline", true);
+ }
+
+ xMenu->insert(nInsertPos++, ".uno:Strikeout",
+ vcl::CommandInfoProvider::GetMenuLabelForCommand(
+ vcl::CommandInfoProvider::GetCommandProperties(".uno:Strikeout", aModuleName)),
+ nullptr, nullptr, vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Strikeout", xFrame),
+ TRISTATE_TRUE);
+
+ if ( aSet.GetItemState( EE_CHAR_STRIKEOUT ) == SfxItemState::SET )
+ {
+ if( aSet.Get( EE_CHAR_STRIKEOUT ).GetStrikeout() != STRIKEOUT_NONE )
+ xMenu->set_active(".uno:Strikeout", true);
+ }
+
+ xMenu->insert_separator(nInsertPos++, "separator2");
+ }
+
+ xMenu->insert(nInsertPos++, ".uno:Copy",
+ vcl::CommandInfoProvider::GetMenuLabelForCommand(
+ vcl::CommandInfoProvider::GetCommandProperties(".uno:Copy", aModuleName)),
+ nullptr, nullptr, vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Copy", xFrame),
+ TRISTATE_INDET);
+
+ xMenu->insert(nInsertPos++, ".uno:Paste",
+ vcl::CommandInfoProvider::GetMenuLabelForCommand(
+ vcl::CommandInfoProvider::GetCommandProperties(".uno:Paste", aModuleName)),
+ nullptr, nullptr, vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Paste", xFrame),
+ TRISTATE_INDET);
+
+ bool bCanPaste = false;
+ if (bEditable)
+ {
+ TransferableDataHelper aDataHelper(TransferableDataHelper::CreateFromClipboard(GetClipboard()));
+ bCanPaste = aDataHelper.GetFormatCount() != 0;
+ }
+
+ xMenu->insert_separator(nInsertPos++, "separator3");
+
+ xMenu->set_sensitive(".uno:Copy", mrContents.GetOutlinerView()->HasSelection());
+ xMenu->set_sensitive(".uno:Paste", bCanPaste);
+
+ auto sId = xMenu->popup_at_rect(pPopupParent, aRect);
+
+ if (sId == ".uno:ReplyToAnnotation")
+ {
+ const SfxUnoAnyItem aItem( SID_REPLYTO_POSTIT, Any( xAnnotation ) );
+ pDispatcher->ExecuteList(SID_REPLYTO_POSTIT,
+ SfxCallMode::ASYNCHRON, { &aItem });
+ }
+ else if (sId == ".uno:DeleteAnnotation")
+ {
+ const SfxUnoAnyItem aItem( SID_DELETE_POSTIT, Any( xAnnotation ) );
+ pDispatcher->ExecuteList(SID_DELETE_POSTIT, SfxCallMode::ASYNCHRON,
+ { &aItem });
+ }
+ else if (sId == ".uno:DeleteAllAnnotationByAuthor")
+ {
+ const SfxStringItem aItem( SID_DELETEALLBYAUTHOR_POSTIT, sAuthor );
+ pDispatcher->ExecuteList( SID_DELETEALLBYAUTHOR_POSTIT,
+ SfxCallMode::ASYNCHRON, { &aItem });
+ }
+ else if (sId == ".uno:DeleteAllAnnotation")
+ pDispatcher->Execute( SID_DELETEALL_POSTIT );
+ else if (sId == ".uno:Copy")
+ {
+ mrContents.GetOutlinerView()->Copy();
+ }
+ else if (sId == ".uno:Paste")
+ {
+ mrContents.GetOutlinerView()->PasteSpecial();
+ mrContents.DoResize();
+ }
+ else if (!sId.isEmpty())
+ {
+ SfxItemSet aEditAttr(mrContents.GetOutlinerView()->GetAttribs());
+ SfxItemSet aNewAttr(mrContents.GetOutliner()->GetEmptyItemSet());
+
+ if (sId == ".uno:Bold")
+ {
+ FontWeight eFW = aEditAttr.Get( EE_CHAR_WEIGHT ).GetWeight();
+ aNewAttr.Put( SvxWeightItem( eFW == WEIGHT_NORMAL ? WEIGHT_BOLD : WEIGHT_NORMAL, EE_CHAR_WEIGHT ) );
+ }
+ else if (sId == ".uno:Italic")
+ {
+ FontItalic eFI = aEditAttr.Get( EE_CHAR_ITALIC ).GetPosture();
+ aNewAttr.Put( SvxPostureItem( eFI == ITALIC_NORMAL ? ITALIC_NONE : ITALIC_NORMAL, EE_CHAR_ITALIC ) );
+ }
+ else if (sId == ".uno:Underline")
+ {
+ FontLineStyle eFU = aEditAttr. Get( EE_CHAR_UNDERLINE ).GetLineStyle();
+ aNewAttr.Put( SvxUnderlineItem( eFU == LINESTYLE_SINGLE ? LINESTYLE_NONE : LINESTYLE_SINGLE, EE_CHAR_UNDERLINE ) );
+ }
+ else if (sId == ".uno:Strikeout")
+ {
+ FontStrikeout eFSO = aEditAttr.Get( EE_CHAR_STRIKEOUT ).GetStrikeout();
+ aNewAttr.Put( SvxCrossedOutItem( eFSO == STRIKEOUT_SINGLE ? STRIKEOUT_NONE : STRIKEOUT_SINGLE, EE_CHAR_STRIKEOUT ) );
+ }
+
+ mrContents.GetOutlinerView()->SetAttribs( aNewAttr );
+ }
+
+ return true;
+ }
+ return WeldEditView::Command(rCEvt);
+}
+
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sd/source/ui/annotations/annotationwindow.hxx b/sd/source/ui/annotations/annotationwindow.hxx
new file mode 100644
index 0000000000..a150ebf1c8
--- /dev/null
+++ b/sd/source/ui/annotations/annotationwindow.hxx
@@ -0,0 +1,143 @@
+/* -*- 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/weld.hxx>
+#include <tools/long.hxx>
+#include <svx/weldeditview.hxx>
+
+namespace com::sun::star::office { class XAnnotation; }
+
+class OutlinerView;
+class Outliner;
+class SvxLanguageItem;
+class SdDrawDocument;
+
+namespace sd {
+
+class AnnotationManagerImpl;
+class DrawDocShell;
+class TextApiObject;
+
+class AnnotationWindow;
+
+class AnnotationTextWindow : public WeldEditView
+{
+private:
+ AnnotationWindow& mrContents;
+
+public:
+ AnnotationTextWindow(AnnotationWindow& rContents);
+
+ virtual EditView* GetEditView() const override;
+
+ virtual EditEngine* GetEditEngine() const override;
+
+ virtual void EditViewScrollStateChange() override;
+
+ void SetDrawingArea(weld::DrawingArea* pDrawingArea) override;
+
+ virtual void Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect) override;
+ virtual bool KeyInput(const KeyEvent& rKeyEvt) override;
+ virtual bool Command(const CommandEvent& rCEvt) override;
+};
+
+class AnnotationWindow final
+{
+private:
+ std::unique_ptr<weld::Builder> mxBuilder;
+ std::unique_ptr<weld::Popover> mxPopover;
+ std::unique_ptr<weld::Widget> mxContainer;
+
+ DrawDocShell* mpDocShell;
+ SdDrawDocument* mpDoc;
+
+ bool mbReadonly;
+ bool mbProtected;
+
+ css::uno::Reference< css::office::XAnnotation > mxAnnotation;
+
+public:
+ Color maColor;
+ Color maColorDark;
+ Color maColorLight;
+
+private:
+ vcl::Font maLabelFont;
+
+ std::unique_ptr<OutlinerView> mpOutlinerView;
+ std::unique_ptr<::Outliner> mpOutliner;
+
+ std::unique_ptr<weld::ScrolledWindow> mxVScrollbar;
+ std::unique_ptr<WeldEditView> mxTextControl;
+ std::unique_ptr<weld::CustomWeld> mxTextControlWin;
+ std::unique_ptr<weld::Label> mxMeta;
+ std::unique_ptr<weld::MenuButton> mxMenuButton;
+
+ DECL_LINK(ScrollHdl, weld::ScrolledWindow&, void);
+ DECL_LINK(MenuItemSelectedHdl, const OUString&, void);
+
+ void FillMenuButton();
+ void InitControls();
+
+ void SetMapMode(const MapMode& rNewMapMode);
+ void setAnnotation(const css::uno::Reference<css::office::XAnnotation>& xAnnotation);
+
+ static sal_Int32 GetPrefScrollbarWidth() { return 16; }
+public:
+ AnnotationWindow(weld::Window* pParent, const ::tools::Rectangle& rRect, DrawDocShell* pDocShell,
+ const css::uno::Reference<css::office::XAnnotation>& xAnnotation);
+
+ void connect_closed(const Link<weld::Popover&, void>& rLink) { mxPopover->connect_closed(rLink); }
+
+ void DoResize();
+ void ResizeIfNecessary(::tools::Long aOldHeight, ::tools::Long aNewHeight);
+ void SetScrollbar();
+ void StartEdit();
+
+ const css::uno::Reference<css::office::XAnnotation>& getAnnotation() const { return mxAnnotation; }
+
+ void SaveToDocument();
+
+ ::tools::Long GetPostItTextHeight();
+
+ DrawDocShell* DocShell() { return mpDocShell; }
+
+ void SetLanguage(const SvxLanguageItem &aNewItem);
+
+ void Rescale();
+
+ void ToggleInsMode();
+
+ bool IsProtected() const { return mbProtected; }
+
+ OutlinerView* GetOutlinerView() { return mpOutlinerView.get();}
+ ::Outliner* GetOutliner() { return mpOutliner.get();}
+ ~AnnotationWindow();
+
+ void SetColor();
+};
+
+TextApiObject* getTextApiObject( const css::uno::Reference< css::office::XAnnotation >& xAnnotation );
+
+
+} // namespace sd
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */