Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sd / source / ui / annotations / annotationmanager.cxx
blob81d6b3c0176bc91fe78b9a2d955242e4eb211a04
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <com/sun/star/drawing/XDrawView.hpp>
21 #include <com/sun/star/frame/XController.hpp>
22 #include <com/sun/star/geometry/RealPoint2D.hpp>
23 #include <com/sun/star/text/XText.hpp>
24 #include <com/sun/star/document/XEventBroadcaster.hpp>
25 #include <com/sun/star/office/XAnnotationAccess.hpp>
26 #include <comphelper/lok.hxx>
27 #include <svx/svxids.hrc>
29 #include <vcl/settings.hxx>
30 #include <vcl/svapp.hxx>
31 #include <vcl/weld.hxx>
33 #include <sal/macros.h>
34 #include <svl/itempool.hxx>
35 #include <svl/intitem.hxx>
36 #include <unotools/localedatawrapper.hxx>
37 #include <unotools/useroptions.hxx>
38 #include <unotools/syslocale.hxx>
39 #include <unotools/saveopt.hxx>
41 #include <tools/datetime.hxx>
42 #include <tools/UnitConversion.hxx>
43 #include <comphelper/diagnose_ex.hxx>
45 #include <sfx2/viewfrm.hxx>
46 #include <sfx2/bindings.hxx>
47 #include <sfx2/request.hxx>
48 #include <sfx2/dispatch.hxx>
50 #include <editeng/editeng.hxx>
51 #include <editeng/eeitem.hxx>
52 #include <editeng/fontitem.hxx>
53 #include <editeng/fhgtitem.hxx>
54 #include <editeng/outlobj.hxx>
55 #include <editeng/postitem.hxx>
57 #include <svx/postattr.hxx>
59 #include <annotationmanager.hxx>
60 #include "annotationmanagerimpl.hxx"
61 #include "annotationwindow.hxx"
62 #include <strings.hrc>
64 #include <Annotation.hxx>
65 #include <DrawDocShell.hxx>
66 #include <DrawViewShell.hxx>
67 #include <sdresid.hxx>
68 #include <EventMultiplexer.hxx>
69 #include <ViewShellBase.hxx>
70 #include <sdpage.hxx>
71 #include <drawdoc.hxx>
72 #include <textapi.hxx>
73 #include <optsitem.hxx>
74 #include <sdmod.hxx>
76 #include <memory>
78 using namespace ::com::sun::star;
79 using namespace ::com::sun::star::uno;
80 using namespace ::com::sun::star::drawing;
81 using namespace ::com::sun::star::document;
82 using namespace ::com::sun::star::geometry;
83 using namespace ::com::sun::star::container;
84 using namespace ::com::sun::star::beans;
85 using namespace ::com::sun::star::text;
86 using namespace ::com::sun::star::view;
87 using namespace ::com::sun::star::style;
88 using namespace ::com::sun::star::frame;
89 using namespace ::com::sun::star::lang;
90 using namespace ::com::sun::star::ui;
91 using namespace ::com::sun::star::task;
92 using namespace ::com::sun::star::office;
94 namespace sd {
96 SfxItemPool* GetAnnotationPool()
98 static rtl::Reference<SfxItemPool> s_pAnnotationPool;
99 if( !s_pAnnotationPool )
101 s_pAnnotationPool = EditEngine::CreatePool();
102 s_pAnnotationPool->SetPoolDefaultItem(SvxFontHeightItem(423,100,EE_CHAR_FONTHEIGHT));
104 vcl::Font aAppFont( Application::GetSettings().GetStyleSettings().GetAppFont() );
105 s_pAnnotationPool->SetPoolDefaultItem(SvxFontItem(aAppFont.GetFamilyType(),aAppFont.GetFamilyName(),"",PITCH_DONTKNOW,RTL_TEXTENCODING_DONTKNOW,EE_CHAR_FONTINFO));
108 return s_pAnnotationPool.get();
111 static SfxBindings* getBindings( ViewShellBase const & rBase )
113 if( rBase.GetMainViewShell() && rBase.GetMainViewShell()->GetViewFrame() )
114 return &rBase.GetMainViewShell()->GetViewFrame()->GetBindings();
116 return nullptr;
119 static SfxDispatcher* getDispatcher( ViewShellBase const & rBase )
121 if( rBase.GetMainViewShell() && rBase.GetMainViewShell()->GetViewFrame() )
122 return rBase.GetMainViewShell()->GetViewFrame()->GetDispatcher();
124 return nullptr;
127 css::util::DateTime getCurrentDateTime()
129 DateTime aCurrentDate( DateTime::SYSTEM );
130 return css::util::DateTime( 0, aCurrentDate.GetSec(),
131 aCurrentDate.GetMin(), aCurrentDate.GetHour(),
132 aCurrentDate.GetDay(), aCurrentDate.GetMonth(),
133 aCurrentDate.GetYear(), false );
136 OUString getAnnotationDateTimeString( const Reference< XAnnotation >& xAnnotation )
138 OUString sRet;
139 if( xAnnotation.is() )
141 const SvtSysLocale aSysLocale;
142 const LocaleDataWrapper& rLocalData = aSysLocale.GetLocaleData();
144 css::util::DateTime aDateTime( xAnnotation->getDateTime() );
146 Date aSysDate( Date::SYSTEM );
147 Date aDate( aDateTime.Day, aDateTime.Month, aDateTime.Year );
148 if (aDate==aSysDate)
149 sRet = SdResId(STR_ANNOTATION_TODAY);
150 else if (aDate == (aSysDate-1))
151 sRet = SdResId(STR_ANNOTATION_YESTERDAY);
152 else if (aDate.IsValidAndGregorian() )
153 sRet = rLocalData.getDate(aDate);
155 ::tools::Time aTime( aDateTime );
156 if(aTime.GetTime() != 0)
157 sRet += " " + rLocalData.getTime( aTime,false );
159 return sRet;
162 AnnotationManagerImpl::AnnotationManagerImpl( ViewShellBase& rViewShellBase )
163 : mrBase( rViewShellBase )
164 , mpDoc( rViewShellBase.GetDocument() )
165 , mbShowAnnotations( true )
166 , mnUpdateTagsEvent( nullptr )
168 SdOptions* pOptions = SD_MOD()->GetSdOptions(mpDoc->GetDocumentType());
169 if( pOptions )
170 mbShowAnnotations = pOptions->IsShowComments();
173 void AnnotationManagerImpl::init()
175 // get current controller and initialize listeners
178 addListener();
179 mxView.set(mrBase.GetController(), UNO_QUERY);
181 catch( Exception& )
183 TOOLS_WARN_EXCEPTION( "sd", "sd::AnnotationManagerImpl::AnnotationManagerImpl()" );
188 Reference<XEventBroadcaster> xModel (mrBase.GetDocShell()->GetModel(), UNO_QUERY_THROW );
189 Reference<XEventListener> xListener( this );
190 xModel->addEventListener( xListener );
192 catch( Exception& )
197 // WeakComponentImplHelper
198 void AnnotationManagerImpl::disposing (std::unique_lock<std::mutex>&)
202 Reference<XEventBroadcaster> xModel (mrBase.GetDocShell()->GetModel(), UNO_QUERY_THROW );
203 Reference<XEventListener> xListener( this );
204 xModel->removeEventListener( xListener );
206 catch( Exception& )
210 removeListener();
211 DisposeTags();
213 if( mnUpdateTagsEvent )
215 Application::RemoveUserEvent( mnUpdateTagsEvent );
216 mnUpdateTagsEvent = nullptr;
219 mxView.clear();
220 mxCurrentPage.clear();
223 // XEventListener
224 void SAL_CALL AnnotationManagerImpl::notifyEvent( const css::document::EventObject& aEvent )
226 if( !(aEvent.EventName == "OnAnnotationInserted" || aEvent.EventName == "OnAnnotationRemoved" || aEvent.EventName == "OnAnnotationChanged") )
227 return;
229 // AnnotationInsertion and modification is not handled here because when
230 // a new annotation is inserted, it consists of OnAnnotationInserted
231 // followed by a chain of OnAnnotationChanged (called for setting each
232 // of the annotation attributes - author, text etc.). This is not what a
233 // LOK client wants. So only handle removal here as annotation removal
234 // consists of only one event - 'OnAnnotationRemoved'
235 if ( aEvent.EventName == "OnAnnotationRemoved" )
237 Reference< XAnnotation > xAnnotation( aEvent.Source, uno::UNO_QUERY );
238 if ( xAnnotation.is() )
240 LOKCommentNotify(CommentNotificationType::Remove, &mrBase, xAnnotation);
244 UpdateTags();
247 void SAL_CALL AnnotationManagerImpl::disposing( const css::lang::EventObject& /*Source*/ )
251 Reference<XAnnotation> AnnotationManagerImpl::GetAnnotationById(sal_uInt32 nAnnotationId)
253 SdPage* pPage = nullptr;
256 pPage = GetNextPage(pPage, true);
257 if( pPage && !pPage->getAnnotations().empty() )
259 AnnotationVector aAnnotations(pPage->getAnnotations());
260 auto iter = std::find_if(aAnnotations.begin(), aAnnotations.end(),
261 [nAnnotationId](const Reference<XAnnotation>& xAnnotation) {
262 return sd::getAnnotationId(xAnnotation) == nAnnotationId;
264 if (iter != aAnnotations.end())
265 return *iter;
267 } while( pPage );
269 Reference<XAnnotation> xAnnotationEmpty;
270 return xAnnotationEmpty;
273 void AnnotationManagerImpl::ShowAnnotations( bool bShow )
275 // enforce show annotations if a new annotation is inserted
276 if( mbShowAnnotations != bShow )
278 mbShowAnnotations = bShow;
280 SdOptions* pOptions = SD_MOD()->GetSdOptions(mpDoc->GetDocumentType());
281 if( pOptions )
282 pOptions->SetShowComments( mbShowAnnotations );
284 UpdateTags();
288 void AnnotationManagerImpl::ExecuteAnnotation(SfxRequest const & rReq )
290 switch( rReq.GetSlot() )
292 case SID_INSERT_POSTIT:
293 ExecuteInsertAnnotation( rReq );
294 break;
295 case SID_DELETE_POSTIT:
296 case SID_DELETEALL_POSTIT:
297 case SID_DELETEALLBYAUTHOR_POSTIT:
298 ExecuteDeleteAnnotation( rReq );
299 break;
300 case SID_EDIT_POSTIT:
301 ExecuteEditAnnotation( rReq );
302 break;
303 case SID_PREVIOUS_POSTIT:
304 case SID_NEXT_POSTIT:
305 SelectNextAnnotation( rReq.GetSlot() == SID_NEXT_POSTIT );
306 break;
307 case SID_REPLYTO_POSTIT:
308 ExecuteReplyToAnnotation( rReq );
309 break;
310 case SID_TOGGLE_NOTES:
311 ShowAnnotations( !mbShowAnnotations );
312 break;
316 void AnnotationManagerImpl::ExecuteInsertAnnotation(SfxRequest const & rReq)
318 if (!comphelper::LibreOfficeKit::isActive() || comphelper::LibreOfficeKit::isTiledAnnotations())
319 ShowAnnotations(true);
321 const SfxItemSet* pArgs = rReq.GetArgs();
322 OUString sText;
323 if (pArgs)
325 if (const SfxStringItem* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_TEXT))
327 sText = pPoolItem->GetValue();
331 InsertAnnotation(sText);
334 void AnnotationManagerImpl::ExecuteDeleteAnnotation(SfxRequest const & rReq)
336 ShowAnnotations( true );
338 const SfxItemSet* pArgs = rReq.GetArgs();
340 switch( rReq.GetSlot() )
342 case SID_DELETEALL_POSTIT:
343 DeleteAllAnnotations();
344 break;
345 case SID_DELETEALLBYAUTHOR_POSTIT:
346 if( pArgs )
348 const SfxPoolItem* pPoolItem = nullptr;
349 if( SfxItemState::SET == pArgs->GetItemState( SID_DELETEALLBYAUTHOR_POSTIT, true, &pPoolItem ) )
351 OUString sAuthor( static_cast<const SfxStringItem*>( pPoolItem )->GetValue() );
352 DeleteAnnotationsByAuthor( sAuthor );
355 break;
356 case SID_DELETE_POSTIT:
358 Reference< XAnnotation > xAnnotation;
359 sal_uInt32 nId = 0;
360 if( pArgs )
362 const SfxPoolItem* pPoolItem = nullptr;
363 if( SfxItemState::SET == pArgs->GetItemState( SID_DELETE_POSTIT, true, &pPoolItem ) )
364 static_cast<const SfxUnoAnyItem*>(pPoolItem)->GetValue() >>= xAnnotation;
365 if( SfxItemState::SET == pArgs->GetItemState( SID_ATTR_POSTIT_ID, true, &pPoolItem ) )
366 nId = static_cast<const SvxPostItIdItem*>(pPoolItem)->GetValue().toUInt32();
369 if (nId != 0)
370 xAnnotation = GetAnnotationById(nId);
371 else if( !xAnnotation.is() )
372 GetSelectedAnnotation( xAnnotation );
374 DeleteAnnotation( xAnnotation );
376 break;
379 UpdateTags();
382 void AnnotationManagerImpl::ExecuteEditAnnotation(SfxRequest const & rReq)
384 const SfxItemSet* pArgs = rReq.GetArgs();
385 Reference< XAnnotation > xAnnotation;
386 OUString sText;
387 sal_Int32 nPositionX = -1;
388 sal_Int32 nPositionY = -1;
390 if (!pArgs)
391 return;
393 if (mpDoc->IsUndoEnabled())
394 mpDoc->BegUndo(SdResId(STR_ANNOTATION_UNDO_EDIT));
396 if (const SvxPostItIdItem* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_ID))
398 sal_uInt32 nId = pPoolItem->GetValue().toUInt32();
399 xAnnotation = GetAnnotationById(nId);
401 if (const SfxStringItem* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_TEXT))
402 sText = pPoolItem->GetValue();
404 if (const SfxInt32Item* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_POSITION_X))
405 nPositionX = pPoolItem->GetValue();
407 if (const SfxInt32Item* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_POSITION_Y))
408 nPositionY = pPoolItem->GetValue();
410 if (xAnnotation.is())
412 CreateChangeUndo(xAnnotation);
414 if (nPositionX >= 0 && nPositionY >= 0)
416 double fX = convertTwipToMm100(nPositionX) / 100.0;
417 double fY = convertTwipToMm100(nPositionY) / 100.0;
418 xAnnotation->setPosition({fX, fY});
421 if (!sText.isEmpty())
423 // TODO: Not allow other authors to change others' comments ?
424 Reference<XText> xText(xAnnotation->getTextRange());
425 xText->setString(sText);
428 LOKCommentNotifyAll(CommentNotificationType::Modify, xAnnotation);
431 if (mpDoc->IsUndoEnabled())
432 mpDoc->EndUndo();
434 UpdateTags(true);
437 void AnnotationManagerImpl::InsertAnnotation(const OUString& rText)
439 SdPage* pPage = GetCurrentPage();
440 if( !pPage )
441 return;
443 if( mpDoc->IsUndoEnabled() )
444 mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_INSERT ) );
446 // find free space for new annotation
447 int y = 0, x = 0;
449 AnnotationVector aAnnotations( pPage->getAnnotations() );
450 if( !aAnnotations.empty() )
452 const int page_width = pPage->GetSize().Width();
453 const int width = 1000;
454 const int height = 800;
455 ::tools::Rectangle aTagRect;
457 while( true )
459 ::tools::Rectangle aNewRect( x, y, x + width - 1, y + height - 1 );
460 bool bFree = true;
462 for( const auto& rxAnnotation : aAnnotations )
464 RealPoint2D aPoint( rxAnnotation->getPosition() );
465 aTagRect.SetLeft( sal::static_int_cast< ::tools::Long >( aPoint.X * 100.0 ) );
466 aTagRect.SetTop( sal::static_int_cast< ::tools::Long >( aPoint.Y * 100.0 ) );
467 aTagRect.SetRight( aTagRect.Left() + width - 1 );
468 aTagRect.SetBottom( aTagRect.Top() + height - 1 );
470 if( aNewRect.Overlaps( aTagRect ) )
472 bFree = false;
473 break;
477 if( !bFree )
479 x += width;
480 if( x > page_width )
482 x = 0;
483 y += height;
486 else
488 break;
493 Reference< XAnnotation > xAnnotation;
494 pPage->createAnnotation( xAnnotation );
496 OUString sAuthor;
497 if (comphelper::LibreOfficeKit::isActive())
498 sAuthor = mrBase.GetMainViewShell()->GetView()->GetAuthor();
499 else
501 SvtUserOptions aUserOptions;
502 sAuthor = aUserOptions.GetFullName();
503 xAnnotation->setInitials( aUserOptions.GetID() );
506 if (!rText.isEmpty())
508 Reference<XText> xText(xAnnotation->getTextRange());
509 xText->setString(rText);
512 // set current author to new annotation
513 xAnnotation->setAuthor( sAuthor );
514 // set current time to new annotation
515 xAnnotation->setDateTime( getCurrentDateTime() );
517 // set position
518 RealPoint2D aPos( static_cast<double>(x) / 100.0, static_cast<double>(y) / 100.0 );
519 xAnnotation->setPosition( aPos );
521 if( mpDoc->IsUndoEnabled() )
522 mpDoc->EndUndo();
524 // Tell our LOK clients about new comment added
525 LOKCommentNotifyAll(CommentNotificationType::Add, xAnnotation);
527 UpdateTags(true);
528 SelectAnnotation( xAnnotation, true );
531 void AnnotationManagerImpl::ExecuteReplyToAnnotation( SfxRequest const & rReq )
533 Reference< XAnnotation > xAnnotation;
534 const SfxItemSet* pArgs = rReq.GetArgs();
535 OUString sReplyText;
536 if( pArgs )
538 const SfxPoolItem* pPoolItem = nullptr;
539 if( SfxItemState::SET == pArgs->GetItemState( SID_ATTR_POSTIT_ID, true, &pPoolItem ) )
541 sal_uInt32 nReplyId = 0; // Id of the comment to reply to
542 nReplyId = static_cast<const SvxPostItIdItem*>(pPoolItem)->GetValue().toUInt32();
543 xAnnotation = GetAnnotationById(nReplyId);
545 else if( SfxItemState::SET == pArgs->GetItemState( rReq.GetSlot(), true, &pPoolItem ) )
546 static_cast<const SfxUnoAnyItem*>( pPoolItem )->GetValue() >>= xAnnotation;
548 if( SfxItemState::SET == pArgs->GetItemState( SID_ATTR_POSTIT_TEXT, true, &pPoolItem ) )
549 sReplyText = static_cast<const SvxPostItTextItem*>( pPoolItem )->GetValue();
552 TextApiObject* pTextApi = getTextApiObject( xAnnotation );
553 if( !pTextApi )
554 return;
556 ::Outliner aOutliner( GetAnnotationPool(),OutlinerMode::TextObject );
558 SdDrawDocument::SetCalcFieldValueHdl( &aOutliner );
559 aOutliner.SetUpdateLayout( true );
561 OUString aStr(SdResId(STR_ANNOTATION_REPLY));
562 OUString sAuthor( xAnnotation->getAuthor() );
563 if( sAuthor.isEmpty() )
564 sAuthor = SdResId( STR_ANNOTATION_NOAUTHOR );
566 aStr = aStr.replaceFirst("%1", sAuthor) +
567 " (" + getAnnotationDateTimeString( xAnnotation ) + "): \"";
569 OUString sQuote( pTextApi->GetText() );
571 if( sQuote.isEmpty() )
572 sQuote = "...";
573 aStr += sQuote + "\"\n";
575 for( sal_Int32 nIdx = 0; nIdx >= 0; )
576 aOutliner.Insert( aStr.getToken( 0, '\n', nIdx ), EE_PARA_APPEND, -1 );
578 if( aOutliner.GetParagraphCount() > 1 )
580 SfxItemSet aAnswerSet( aOutliner.GetEmptyItemSet() );
581 aAnswerSet.Put(SvxPostureItem(ITALIC_NORMAL,EE_CHAR_ITALIC));
583 ESelection aSel;
584 aSel.nEndPara = aOutliner.GetParagraphCount()-2;
585 aSel.nEndPos = aOutliner.GetText( aOutliner.GetParagraph( aSel.nEndPara ) ).getLength();
587 aOutliner.QuickSetAttribs( aAnswerSet, aSel );
590 if (!sReplyText.isEmpty())
591 aOutliner.Insert(sReplyText);
593 std::optional< OutlinerParaObject > pOPO( aOutliner.CreateParaObject() );
594 pTextApi->SetText(*pOPO);
596 OUString sReplyAuthor;
597 if (comphelper::LibreOfficeKit::isActive())
598 sReplyAuthor = mrBase.GetMainViewShell()->GetView()->GetAuthor();
599 else
601 SvtUserOptions aUserOptions;
602 sReplyAuthor = aUserOptions.GetFullName();
603 xAnnotation->setInitials( aUserOptions.GetID() );
606 xAnnotation->setAuthor( sReplyAuthor );
607 // set current time to reply
608 xAnnotation->setDateTime( getCurrentDateTime() );
610 // Tell our LOK clients about this (comment modification)
611 LOKCommentNotifyAll(CommentNotificationType::Modify, xAnnotation);
613 UpdateTags(true);
614 SelectAnnotation( xAnnotation, true );
617 void AnnotationManagerImpl::DeleteAnnotation( const Reference< XAnnotation >& xAnnotation )
619 SdPage* pPage = GetCurrentPage();
621 if( xAnnotation.is() && pPage )
623 if( mpDoc->IsUndoEnabled() )
624 mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) );
626 pPage->removeAnnotation( xAnnotation );
628 if( mpDoc->IsUndoEnabled() )
629 mpDoc->EndUndo();
631 UpdateTags();
635 void AnnotationManagerImpl::DeleteAnnotationsByAuthor( std::u16string_view sAuthor )
637 if( mpDoc->IsUndoEnabled() )
638 mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) );
640 SdPage* pPage = nullptr;
643 pPage = GetNextPage( pPage, true );
645 if( pPage && !pPage->getAnnotations().empty() )
647 AnnotationVector aAnnotations( pPage->getAnnotations() );
648 for( Reference< XAnnotation >& xAnnotation : aAnnotations )
650 if( xAnnotation->getAuthor() == sAuthor )
652 if( mxSelectedAnnotation == xAnnotation )
653 mxSelectedAnnotation.clear();
654 pPage->removeAnnotation( xAnnotation );
658 } while( pPage );
660 if( mpDoc->IsUndoEnabled() )
661 mpDoc->EndUndo();
664 void AnnotationManagerImpl::DeleteAllAnnotations()
666 if( mpDoc->IsUndoEnabled() )
667 mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) );
669 SdPage* pPage = nullptr;
672 pPage = GetNextPage( pPage, true );
674 if( pPage && !pPage->getAnnotations().empty() )
677 AnnotationVector aAnnotations( pPage->getAnnotations() );
678 for( const auto& rxAnnotation : aAnnotations )
680 pPage->removeAnnotation( rxAnnotation );
684 while( pPage );
686 mxSelectedAnnotation.clear();
688 if( mpDoc->IsUndoEnabled() )
689 mpDoc->EndUndo();
692 void AnnotationManagerImpl::GetAnnotationState(SfxItemSet& rSet)
694 SdPage* pCurrentPage = GetCurrentPage();
696 const bool bReadOnly = mrBase.GetDocShell()->IsReadOnly();
697 const bool bWrongPageKind = (pCurrentPage == nullptr) || (pCurrentPage->GetPageKind() != PageKind::Standard);
699 const SvtSaveOptions::ODFSaneDefaultVersion nCurrentODFVersion( GetODFSaneDefaultVersion() );
701 if (bReadOnly || bWrongPageKind || (nCurrentODFVersion <= SvtSaveOptions::ODFSVER_012))
702 rSet.DisableItem( SID_INSERT_POSTIT );
704 rSet.Put(SfxBoolItem(SID_TOGGLE_NOTES, mbShowAnnotations));
706 Reference< XAnnotation > xAnnotation;
707 GetSelectedAnnotation( xAnnotation );
709 // Don't disable these slot in case of LOK, as postit doesn't need to
710 // selected before doing an operation on it in LOK
711 if( (!xAnnotation.is() && !comphelper::LibreOfficeKit::isActive()) || bReadOnly )
713 rSet.DisableItem( SID_DELETE_POSTIT );
714 rSet.DisableItem( SID_EDIT_POSTIT );
717 SdPage* pPage = nullptr;
719 bool bHasAnnotations = false;
722 pPage = GetNextPage( pPage, true );
724 if( pPage && !pPage->getAnnotations().empty() )
725 bHasAnnotations = true;
727 while( pPage && !bHasAnnotations );
729 if( !bHasAnnotations || bReadOnly )
731 rSet.DisableItem( SID_DELETEALL_POSTIT );
734 if( bWrongPageKind || !bHasAnnotations )
736 rSet.DisableItem( SID_PREVIOUS_POSTIT );
737 rSet.DisableItem( SID_NEXT_POSTIT );
741 void AnnotationManagerImpl::SelectNextAnnotation(bool bForward)
743 ShowAnnotations( true );
745 Reference< XAnnotation > xCurrent;
746 GetSelectedAnnotation( xCurrent );
747 SdPage* pPage = GetCurrentPage();
748 if( !pPage )
749 return;
751 AnnotationVector aAnnotations( pPage->getAnnotations() );
753 if( bForward )
755 if( xCurrent.is() )
757 auto iter = std::find(aAnnotations.begin(), aAnnotations.end(), xCurrent);
758 if (iter != aAnnotations.end())
760 ++iter;
761 if( iter != aAnnotations.end() )
763 SelectAnnotation( *iter );
764 return;
768 else if( !aAnnotations.empty() )
770 SelectAnnotation( *(aAnnotations.begin()) );
771 return;
774 else
776 if( xCurrent.is() )
778 auto iter = std::find(aAnnotations.begin(), aAnnotations.end(), xCurrent);
779 if (iter != aAnnotations.end() && iter != aAnnotations.begin())
781 --iter;
782 SelectAnnotation( *iter );
783 return;
786 else if( !aAnnotations.empty() )
788 AnnotationVector::iterator iter( aAnnotations.end() );
789 SelectAnnotation( *(--iter) );
790 return;
794 mxSelectedAnnotation.clear();
799 pPage = GetNextPage( pPage, bForward );
801 if( pPage && !pPage->getAnnotations().empty() )
803 // switch to next/previous slide with annotations
804 std::shared_ptr<DrawViewShell> pDrawViewShell(std::dynamic_pointer_cast<DrawViewShell>(mrBase.GetMainViewShell()));
805 if (pDrawViewShell != nullptr)
807 pDrawViewShell->ChangeEditMode(pPage->IsMasterPage() ? EditMode::MasterPage : EditMode::Page, false);
808 pDrawViewShell->SwitchPage((pPage->GetPageNum() - 1) >> 1);
810 SfxDispatcher* pDispatcher = getDispatcher( mrBase );
811 if( pDispatcher )
812 pDispatcher->Execute( bForward ? SID_NEXT_POSTIT : SID_PREVIOUS_POSTIT );
814 return;
818 while( pPage );
820 // The question text depends on the search direction.
821 bool bImpress = mpDoc->GetDocumentType() == DocumentType::Impress;
822 TranslateId pStringId;
823 if(bForward)
824 pStringId = bImpress ? STR_ANNOTATION_WRAP_FORWARD : STR_ANNOTATION_WRAP_FORWARD_DRAW;
825 else
826 pStringId = bImpress ? STR_ANNOTATION_WRAP_BACKWARD : STR_ANNOTATION_WRAP_BACKWARD_DRAW;
828 // Pop up question box that asks the user whether to wrap around.
829 // The dialog is made modal with respect to the whole application.
830 std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr,
831 VclMessageType::Question, VclButtonsType::YesNo,
832 SdResId(pStringId)));
833 xQueryBox->set_default_response(RET_YES);
834 if (xQueryBox->run() != RET_YES)
835 break;
837 while( true );
840 void AnnotationManagerImpl::onTagSelected( AnnotationTag const & rTag )
842 mxSelectedAnnotation = rTag.GetAnnotation();
843 invalidateSlots();
846 void AnnotationManagerImpl::onTagDeselected( AnnotationTag const & rTag )
848 if( rTag.GetAnnotation() == mxSelectedAnnotation )
850 mxSelectedAnnotation.clear();
851 invalidateSlots();
855 void AnnotationManagerImpl::SelectAnnotation( const css::uno::Reference< css::office::XAnnotation >& xAnnotation, bool bEdit /* = sal_False */ )
857 mxSelectedAnnotation = xAnnotation;
859 auto iter = std::find_if(maTagVector.begin(), maTagVector.end(),
860 [&xAnnotation](const rtl::Reference<AnnotationTag>& rxTag) { return rxTag->GetAnnotation() == xAnnotation; });
861 if (iter != maTagVector.end())
863 SmartTagReference xTag( *iter );
864 mrBase.GetMainViewShell()->GetView()->getSmartTags().select( xTag );
865 (*iter)->OpenPopup( bEdit );
869 void AnnotationManagerImpl::GetSelectedAnnotation( css::uno::Reference< css::office::XAnnotation >& xAnnotation )
871 xAnnotation = mxSelectedAnnotation;
874 void AnnotationManagerImpl::invalidateSlots()
876 SfxBindings* pBindings = getBindings( mrBase );
877 if( pBindings )
879 pBindings->Invalidate( SID_INSERT_POSTIT );
880 pBindings->Invalidate( SID_DELETE_POSTIT );
881 pBindings->Invalidate( SID_DELETEALL_POSTIT );
882 pBindings->Invalidate( SID_PREVIOUS_POSTIT );
883 pBindings->Invalidate( SID_NEXT_POSTIT );
884 pBindings->Invalidate( SID_UNDO );
885 pBindings->Invalidate( SID_REDO );
889 void AnnotationManagerImpl::onSelectionChanged()
891 if( !(mxView.is() && mrBase.GetDrawView()) )
892 return;
896 Reference< XAnnotationAccess > xPage( mxView->getCurrentPage(), UNO_QUERY );
898 if( xPage != mxCurrentPage )
900 mxCurrentPage = xPage;
902 UpdateTags(true);
905 catch( Exception& )
907 TOOLS_WARN_EXCEPTION( "sd", "sd::AnnotationManagerImpl::onSelectionChanged()" );
911 void AnnotationManagerImpl::UpdateTags( bool bSynchron )
913 if( bSynchron )
915 if( mnUpdateTagsEvent )
916 Application::RemoveUserEvent( mnUpdateTagsEvent );
918 UpdateTagsHdl(nullptr);
920 else
922 if( !mnUpdateTagsEvent && mxView.is() )
923 mnUpdateTagsEvent = Application::PostUserEvent( LINK( this, AnnotationManagerImpl, UpdateTagsHdl ) );
927 IMPL_LINK_NOARG(AnnotationManagerImpl, UpdateTagsHdl, void*, void)
929 mnUpdateTagsEvent = nullptr;
930 DisposeTags();
932 if( mbShowAnnotations )
933 CreateTags();
935 if( mrBase.GetDrawView() )
936 static_cast< ::sd::View* >( mrBase.GetDrawView() )->updateHandles();
938 invalidateSlots();
941 void AnnotationManagerImpl::CreateTags()
943 if( !(mxCurrentPage.is() && mpDoc) )
944 return;
946 auto xViewShell = mrBase.GetMainViewShell();
947 if (!xViewShell)
948 return;
952 int nIndex = 1;
953 maFont = Application::GetSettings().GetStyleSettings().GetAppFont();
955 rtl::Reference< AnnotationTag > xSelectedTag;
957 Reference< XAnnotationEnumeration > xEnum( mxCurrentPage->createAnnotationEnumeration() );
958 while( xEnum->hasMoreElements() )
960 Reference< XAnnotation > xAnnotation( xEnum->nextElement() );
961 Color aColor( GetColorLight( mpDoc->GetAnnotationAuthorIndex( xAnnotation->getAuthor() ) ) );
962 rtl::Reference< AnnotationTag > xTag( new AnnotationTag( *this, *xViewShell->GetView(), xAnnotation, aColor, nIndex++, maFont ) );
963 maTagVector.push_back(xTag);
965 if( xAnnotation == mxSelectedAnnotation )
967 xSelectedTag = xTag;
971 if( xSelectedTag.is() )
973 SmartTagReference xTag( xSelectedTag );
974 mrBase.GetMainViewShell()->GetView()->getSmartTags().select( xTag );
976 else
978 // no tag, no selection!
979 mxSelectedAnnotation.clear();
982 catch( Exception& )
984 TOOLS_WARN_EXCEPTION( "sd", "sd::AnnotationManagerImpl::onSelectionChanged()" );
988 void AnnotationManagerImpl::DisposeTags()
990 for (auto& rxTag : maTagVector)
992 rxTag->Dispose();
995 maTagVector.clear();
998 void AnnotationManagerImpl::addListener()
1000 Link<tools::EventMultiplexerEvent&,void> aLink( LINK(this,AnnotationManagerImpl,EventMultiplexerListener) );
1001 mrBase.GetEventMultiplexer()->AddEventListener(aLink);
1004 void AnnotationManagerImpl::removeListener()
1006 Link<tools::EventMultiplexerEvent&,void> aLink( LINK(this,AnnotationManagerImpl,EventMultiplexerListener) );
1007 mrBase.GetEventMultiplexer()->RemoveEventListener( aLink );
1010 IMPL_LINK(AnnotationManagerImpl,EventMultiplexerListener,
1011 tools::EventMultiplexerEvent&, rEvent, void)
1013 switch (rEvent.meEventId)
1015 case EventMultiplexerEventId::CurrentPageChanged:
1016 case EventMultiplexerEventId::EditViewSelection:
1017 onSelectionChanged();
1018 break;
1020 case EventMultiplexerEventId::MainViewRemoved:
1021 mxView.clear();
1022 onSelectionChanged();
1023 break;
1025 case EventMultiplexerEventId::MainViewAdded:
1026 mxView.set( mrBase.GetController(), UNO_QUERY );
1027 onSelectionChanged();
1028 break;
1030 default: break;
1034 void AnnotationManagerImpl::ExecuteAnnotationTagContextMenu(const Reference<XAnnotation>& xAnnotation, weld::Widget* pParent, const ::tools::Rectangle& rContextRect)
1036 SfxDispatcher* pDispatcher( getDispatcher( mrBase ) );
1037 if( !pDispatcher )
1038 return;
1040 const bool bReadOnly = mrBase.GetDocShell()->IsReadOnly();
1042 if (bReadOnly)
1043 return;
1045 std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pParent, "modules/simpress/ui/annotationtagmenu.ui"));
1046 std::unique_ptr<weld::Menu> xMenu(xBuilder->weld_menu("menu"));
1048 SvtUserOptions aUserOptions;
1049 OUString sCurrentAuthor( aUserOptions.GetFullName() );
1050 OUString sAuthor( xAnnotation->getAuthor() );
1052 OUString aStr(xMenu->get_label(".uno:DeleteAllAnnotationByAuthor"));
1053 OUString aReplace( sAuthor );
1054 if( aReplace.isEmpty() )
1055 aReplace = SdResId( STR_ANNOTATION_NOAUTHOR );
1056 aStr = aStr.replaceFirst("%1", aReplace);
1057 xMenu->set_label(".uno:DeleteAllAnnotationByAuthor", aStr);
1059 bool bShowReply = sAuthor != sCurrentAuthor;
1060 xMenu->set_visible(".uno:ReplyToAnnotation", bShowReply);
1061 xMenu->set_visible("separator", bShowReply);
1062 xMenu->set_visible(".uno:DeleteAnnotation", xAnnotation.is());
1064 auto sId = xMenu->popup_at_rect(pParent, rContextRect);
1066 if (sId == ".uno:ReplyToAnnotation")
1068 const SfxUnoAnyItem aItem( SID_REPLYTO_POSTIT, Any( xAnnotation ) );
1069 pDispatcher->ExecuteList(SID_REPLYTO_POSTIT,
1070 SfxCallMode::ASYNCHRON, { &aItem });
1072 else if (sId == ".uno:DeleteAnnotation")
1074 const SfxUnoAnyItem aItem( SID_DELETE_POSTIT, Any( xAnnotation ) );
1075 pDispatcher->ExecuteList(SID_DELETE_POSTIT, SfxCallMode::ASYNCHRON,
1076 { &aItem });
1078 else if (sId == ".uno:DeleteAllAnnotationByAuthor")
1080 const SfxStringItem aItem( SID_DELETEALLBYAUTHOR_POSTIT, sAuthor );
1081 pDispatcher->ExecuteList( SID_DELETEALLBYAUTHOR_POSTIT,
1082 SfxCallMode::ASYNCHRON, { &aItem });
1084 else if (sId == ".uno:DeleteAllAnnotation")
1085 pDispatcher->Execute( SID_DELETEALL_POSTIT );
1088 Color AnnotationManagerImpl::GetColor(sal_uInt16 aAuthorIndex)
1090 if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode())
1092 static const Color aArrayNormal[] = {
1093 COL_AUTHOR1_NORMAL, COL_AUTHOR2_NORMAL, COL_AUTHOR3_NORMAL,
1094 COL_AUTHOR4_NORMAL, COL_AUTHOR5_NORMAL, COL_AUTHOR6_NORMAL,
1095 COL_AUTHOR7_NORMAL, COL_AUTHOR8_NORMAL, COL_AUTHOR9_NORMAL };
1097 return aArrayNormal[ aAuthorIndex % SAL_N_ELEMENTS( aArrayNormal ) ];
1100 return COL_WHITE;
1103 Color AnnotationManagerImpl::GetColorLight(sal_uInt16 aAuthorIndex)
1105 if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode())
1107 static const Color aArrayLight[] = {
1108 COL_AUTHOR1_LIGHT, COL_AUTHOR2_LIGHT, COL_AUTHOR3_LIGHT,
1109 COL_AUTHOR4_LIGHT, COL_AUTHOR5_LIGHT, COL_AUTHOR6_LIGHT,
1110 COL_AUTHOR7_LIGHT, COL_AUTHOR8_LIGHT, COL_AUTHOR9_LIGHT };
1112 return aArrayLight[ aAuthorIndex % SAL_N_ELEMENTS( aArrayLight ) ];
1115 return COL_WHITE;
1118 Color AnnotationManagerImpl::GetColorDark(sal_uInt16 aAuthorIndex)
1120 if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode())
1122 static const Color aArrayAnkor[] = {
1123 COL_AUTHOR1_DARK, COL_AUTHOR2_DARK, COL_AUTHOR3_DARK,
1124 COL_AUTHOR4_DARK, COL_AUTHOR5_DARK, COL_AUTHOR6_DARK,
1125 COL_AUTHOR7_DARK, COL_AUTHOR8_DARK, COL_AUTHOR9_DARK };
1127 return aArrayAnkor[ aAuthorIndex % SAL_N_ELEMENTS( aArrayAnkor ) ];
1130 return COL_WHITE;
1133 SdPage* AnnotationManagerImpl::GetNextPage( SdPage const * pPage, bool bForward )
1135 if( pPage == nullptr )
1137 if (bForward)
1138 return mpDoc->GetSdPage(0, PageKind::Standard ); // first page
1139 else
1140 return mpDoc->GetMasterSdPage( mpDoc->GetMasterSdPageCount(PageKind::Standard) - 1, PageKind::Standard ); // last page
1143 sal_uInt16 nPageNum = (pPage->GetPageNum() - 1) >> 1;
1145 // first all non master pages
1146 if( !pPage->IsMasterPage() )
1148 if( bForward )
1150 if( nPageNum >= mpDoc->GetSdPageCount(PageKind::Standard)-1 )
1152 // we reached end of draw pages, start with master pages (skip handout master for draw)
1153 return mpDoc->GetMasterSdPage( (mpDoc->GetDocumentType() == DocumentType::Impress) ? 0 : 1, PageKind::Standard );
1155 nPageNum++;
1157 else
1159 if( nPageNum == 0 )
1160 return nullptr; // we are already on the first draw page, finished
1162 nPageNum--;
1164 return mpDoc->GetSdPage(nPageNum, PageKind::Standard);
1166 else
1168 if( bForward )
1170 if( nPageNum >= mpDoc->GetMasterSdPageCount(PageKind::Standard)-1 )
1172 return nullptr; // we reached the end, there is nothing more to see here
1174 nPageNum++;
1176 else
1178 if( nPageNum == (mpDoc->GetDocumentType() == DocumentType::Impress ? 0 : 1) )
1180 // we reached beginning of master pages, start with end if pages
1181 return mpDoc->GetSdPage( mpDoc->GetSdPageCount(PageKind::Standard)-1, PageKind::Standard );
1184 nPageNum--;
1186 return mpDoc->GetMasterSdPage(nPageNum,PageKind::Standard);
1190 SdPage* AnnotationManagerImpl::GetCurrentPage()
1192 if (mrBase.GetMainViewShell())
1193 return mrBase.GetMainViewShell()->getCurrentPage();
1194 return nullptr;
1197 AnnotationManager::AnnotationManager( ViewShellBase& rViewShellBase )
1198 : mxImpl( new AnnotationManagerImpl( rViewShellBase ) )
1200 mxImpl->init();
1203 AnnotationManager::~AnnotationManager()
1205 mxImpl->dispose();
1208 void AnnotationManager::ExecuteAnnotation(SfxRequest const & rRequest)
1210 mxImpl->ExecuteAnnotation( rRequest );
1213 void AnnotationManager::GetAnnotationState(SfxItemSet& rItemSet)
1215 mxImpl->GetAnnotationState(rItemSet);
1220 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */