bump product version to 6.3.0.0.beta1
[LibreOffice.git] / sd / source / ui / annotations / annotationmanager.cxx
blobaa56364c8889b7d7dc8e2b60f32d250c8d809fef
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/geometry/RealPoint2D.hpp>
22 #include <com/sun/star/text/XText.hpp>
23 #include <com/sun/star/document/XEventBroadcaster.hpp>
24 #include <com/sun/star/office/XAnnotationAccess.hpp>
25 #include <comphelper/lok.hxx>
26 #include <svx/svxids.hrc>
28 #include <vcl/commandinfoprovider.hxx>
29 #include <vcl/settings.hxx>
30 #include <vcl/svapp.hxx>
31 #include <vcl/menu.hxx>
32 #include <vcl/weld.hxx>
34 #include <sal/macros.h>
35 #include <svl/itempool.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>
43 #include <sfx2/viewfrm.hxx>
44 #include <sfx2/bindings.hxx>
45 #include <sfx2/request.hxx>
46 #include <sfx2/dispatch.hxx>
48 #include <editeng/editeng.hxx>
49 #include <editeng/eeitem.hxx>
50 #include <editeng/fontitem.hxx>
51 #include <editeng/fhgtitem.hxx>
52 #include <editeng/outlobj.hxx>
53 #include <editeng/postitem.hxx>
54 #include <editeng/wghtitem.hxx>
55 #include <editeng/udlnitem.hxx>
56 #include <editeng/crossedoutitem.hxx>
58 #include <svx/postattr.hxx>
60 #include <annotationmanager.hxx>
61 #include "annotationmanagerimpl.hxx"
62 #include "annotationwindow.hxx"
63 #include <strings.hrc>
65 #include <Annotation.hxx>
66 #include <DrawDocShell.hxx>
67 #include <DrawViewShell.hxx>
68 #include <sdresid.hxx>
69 #include <EventMultiplexer.hxx>
70 #include <ViewShellBase.hxx>
71 #include <sdpage.hxx>
72 #include <drawdoc.hxx>
73 #include <textapi.hxx>
74 #include <optsitem.hxx>
75 #include <sdmod.hxx>
77 #include <memory>
79 using namespace ::com::sun::star;
80 using namespace ::com::sun::star::uno;
81 using namespace ::com::sun::star::drawing;
82 using namespace ::com::sun::star::document;
83 using namespace ::com::sun::star::geometry;
84 using namespace ::com::sun::star::container;
85 using namespace ::com::sun::star::beans;
86 using namespace ::com::sun::star::text;
87 using namespace ::com::sun::star::view;
88 using namespace ::com::sun::star::style;
89 using namespace ::com::sun::star::frame;
90 using namespace ::com::sun::star::lang;
91 using namespace ::com::sun::star::ui;
92 using namespace ::com::sun::star::task;
93 using namespace ::com::sun::star::office;
95 namespace sd {
97 SfxItemPool* GetAnnotationPool()
99 static SfxItemPool* s_pAnnotationPool = nullptr;
100 if( s_pAnnotationPool == nullptr )
102 s_pAnnotationPool = EditEngine::CreatePool();
103 s_pAnnotationPool->SetPoolDefaultItem(SvxFontHeightItem(423,100,EE_CHAR_FONTHEIGHT));
105 vcl::Font aAppFont( Application::GetSettings().GetStyleSettings().GetAppFont() );
106 s_pAnnotationPool->SetPoolDefaultItem(SvxFontItem(aAppFont.GetFamilyType(),aAppFont.GetFamilyName(),"",PITCH_DONTKNOW,RTL_TEXTENCODING_DONTKNOW,EE_CHAR_FONTINFO));
109 return s_pAnnotationPool;
112 static SfxBindings* getBindings( ViewShellBase const & rBase )
114 if( rBase.GetMainViewShell().get() && rBase.GetMainViewShell()->GetViewFrame() )
115 return &rBase.GetMainViewShell()->GetViewFrame()->GetBindings();
117 return nullptr;
120 static SfxDispatcher* getDispatcher( ViewShellBase const & rBase )
122 if( rBase.GetMainViewShell().get() && rBase.GetMainViewShell()->GetViewFrame() )
123 return rBase.GetMainViewShell()->GetViewFrame()->GetDispatcher();
125 return nullptr;
128 css::util::DateTime getCurrentDateTime()
130 DateTime aCurrentDate( DateTime::SYSTEM );
131 return css::util::DateTime( 0, aCurrentDate.GetSec(),
132 aCurrentDate.GetMin(), aCurrentDate.GetHour(),
133 aCurrentDate.GetDay(), aCurrentDate.GetMonth(),
134 aCurrentDate.GetYear(), false );
137 OUString getAnnotationDateTimeString( const Reference< XAnnotation >& xAnnotation )
139 OUString sRet;
140 if( xAnnotation.is() )
142 const SvtSysLocale aSysLocale;
143 const LocaleDataWrapper& rLocalData = aSysLocale.GetLocaleData();
145 css::util::DateTime aDateTime( xAnnotation->getDateTime() );
147 Date aSysDate( Date::SYSTEM );
148 Date aDate( aDateTime.Day, aDateTime.Month, aDateTime.Year );
149 if (aDate==aSysDate)
150 sRet = SdResId(STR_ANNOTATION_TODAY);
151 else if (aDate == (aSysDate-1))
152 sRet = SdResId(STR_ANNOTATION_YESTERDAY);
153 else if (aDate.IsValidAndGregorian() )
154 sRet = rLocalData.getDate(aDate);
156 ::tools::Time aTime( aDateTime );
157 if(aTime.GetTime() != 0)
158 sRet = sRet + " " + rLocalData.getTime( aTime,false );
160 return sRet;
163 AnnotationManagerImpl::AnnotationManagerImpl( ViewShellBase& rViewShellBase )
164 : AnnotationManagerImplBase( m_aMutex )
165 , mrBase( rViewShellBase )
166 , mpDoc( rViewShellBase.GetDocument() )
167 , mbShowAnnotations( true )
168 , mbPopupMenuActive( false )
169 , mnUpdateTagsEvent( nullptr )
171 SdOptions* pOptions = SD_MOD()->GetSdOptions(mpDoc->GetDocumentType());
172 if( pOptions )
173 mbShowAnnotations = pOptions->IsShowComments();
176 void AnnotationManagerImpl::init()
178 // get current controller and initialize listeners
181 addListener();
182 mxView.set(mrBase.GetController(), UNO_QUERY);
184 catch( Exception& )
186 OSL_FAIL( "sd::AnnotationManagerImpl::AnnotationManagerImpl(), Exception caught!" );
191 Reference<XEventBroadcaster> xModel (mrBase.GetDocShell()->GetModel(), UNO_QUERY_THROW );
192 Reference<XEventListener> xListener( this );
193 xModel->addEventListener( xListener );
195 catch( Exception& )
200 // WeakComponentImplHelper
201 void SAL_CALL AnnotationManagerImpl::disposing ()
205 Reference<XEventBroadcaster> xModel (mrBase.GetDocShell()->GetModel(), UNO_QUERY_THROW );
206 Reference<XEventListener> xListener( this );
207 xModel->removeEventListener( xListener );
209 catch( Exception& )
213 removeListener();
214 DisposeTags();
216 if( mnUpdateTagsEvent )
218 Application::RemoveUserEvent( mnUpdateTagsEvent );
219 mnUpdateTagsEvent = nullptr;
222 mxView.clear();
223 mxCurrentPage.clear();
226 // XEventListener
227 void SAL_CALL AnnotationManagerImpl::notifyEvent( const css::document::EventObject& aEvent )
229 if( !(aEvent.EventName == "OnAnnotationInserted" || aEvent.EventName == "OnAnnotationRemoved" || aEvent.EventName == "OnAnnotationChanged") )
230 return;
232 // AnnotationInsertion and modification is not handled here because when
233 // a new annotation is inserted, it consists of OnAnnotationInserted
234 // followed by a chain of OnAnnotationChanged (called for setting each
235 // of the annotation attributes - author, text etc.). This is not what a
236 // LOK client wants. So only handle removal here as annotation removal
237 // consists of only one event - 'OnAnnotationRemoved'
238 if ( aEvent.EventName == "OnAnnotationRemoved" )
240 Reference< XAnnotation > xAnnotation( aEvent.Source, uno::UNO_QUERY );
241 if ( xAnnotation.is() )
243 LOKCommentNotify(CommentNotificationType::Remove, &mrBase, xAnnotation);
247 UpdateTags();
250 void SAL_CALL AnnotationManagerImpl::disposing( const css::lang::EventObject& /*Source*/ )
254 Reference<XAnnotation> AnnotationManagerImpl::GetAnnotationById(sal_uInt32 nAnnotationId)
256 SdPage* pPage = nullptr;
259 pPage = GetNextPage(pPage, true);
260 if( pPage && !pPage->getAnnotations().empty() )
262 AnnotationVector aAnnotations(pPage->getAnnotations());
263 auto iter = std::find_if(aAnnotations.begin(), aAnnotations.end(),
264 [nAnnotationId](const Reference<XAnnotation>& xAnnotation) {
265 return sd::getAnnotationId(xAnnotation) == nAnnotationId;
267 if (iter != aAnnotations.end())
268 return *iter;
270 } while( pPage );
272 Reference<XAnnotation> xAnnotationEmpty;
273 return xAnnotationEmpty;
276 void AnnotationManagerImpl::ShowAnnotations( bool bShow )
278 // enforce show annotations if a new annotation is inserted
279 if( mbShowAnnotations != bShow )
281 mbShowAnnotations = bShow;
283 SdOptions* pOptions = SD_MOD()->GetSdOptions(mpDoc->GetDocumentType());
284 if( pOptions )
285 pOptions->SetShowComments( mbShowAnnotations );
287 UpdateTags();
291 void AnnotationManagerImpl::ExecuteAnnotation(SfxRequest const & rReq )
293 switch( rReq.GetSlot() )
295 case SID_INSERT_POSTIT:
296 ExecuteInsertAnnotation( rReq );
297 break;
298 case SID_DELETE_POSTIT:
299 case SID_DELETEALL_POSTIT:
300 case SID_DELETEALLBYAUTHOR_POSTIT:
301 ExecuteDeleteAnnotation( rReq );
302 break;
303 case SID_EDIT_POSTIT:
304 ExecuteEditAnnotation( rReq );
305 break;
306 case SID_PREVIOUS_POSTIT:
307 case SID_NEXT_POSTIT:
308 SelectNextAnnotation( rReq.GetSlot() == SID_NEXT_POSTIT );
309 break;
310 case SID_REPLYTO_POSTIT:
311 ExecuteReplyToAnnotation( rReq );
312 break;
313 case SID_TOGGLE_NOTES:
314 ShowAnnotations( !mbShowAnnotations );
315 break;
319 void AnnotationManagerImpl::ExecuteInsertAnnotation(SfxRequest const & rReq)
321 if (!comphelper::LibreOfficeKit::isActive() || comphelper::LibreOfficeKit::isTiledAnnotations())
322 ShowAnnotations(true);
324 const SfxItemSet* pArgs = rReq.GetArgs();
325 OUString sText;
326 if (pArgs)
328 const SfxPoolItem* pPoolItem = nullptr;
329 if (SfxItemState::SET == pArgs->GetItemState(SID_ATTR_POSTIT_TEXT, true, &pPoolItem))
331 sText = static_cast<const SfxStringItem*>(pPoolItem)->GetValue();
335 InsertAnnotation(sText);
338 void AnnotationManagerImpl::ExecuteDeleteAnnotation(SfxRequest const & rReq)
340 ShowAnnotations( true );
342 const SfxItemSet* pArgs = rReq.GetArgs();
344 switch( rReq.GetSlot() )
346 case SID_DELETEALL_POSTIT:
347 DeleteAllAnnotations();
348 break;
349 case SID_DELETEALLBYAUTHOR_POSTIT:
350 if( pArgs )
352 const SfxPoolItem* pPoolItem = nullptr;
353 if( SfxItemState::SET == pArgs->GetItemState( SID_DELETEALLBYAUTHOR_POSTIT, true, &pPoolItem ) )
355 OUString sAuthor( static_cast<const SfxStringItem*>( pPoolItem )->GetValue() );
356 DeleteAnnotationsByAuthor( sAuthor );
359 break;
360 case SID_DELETE_POSTIT:
362 Reference< XAnnotation > xAnnotation;
363 sal_uInt32 nId = 0;
364 if( pArgs )
366 const SfxPoolItem* pPoolItem = nullptr;
367 if( SfxItemState::SET == pArgs->GetItemState( SID_DELETE_POSTIT, true, &pPoolItem ) )
368 static_cast<const SfxUnoAnyItem*>(pPoolItem)->GetValue() >>= xAnnotation;
369 if( SfxItemState::SET == pArgs->GetItemState( SID_ATTR_POSTIT_ID, true, &pPoolItem ) )
370 nId = static_cast<const SvxPostItIdItem*>(pPoolItem)->GetValue().toUInt32();
373 if (nId != 0)
374 xAnnotation = GetAnnotationById(nId);
375 else if( !xAnnotation.is() )
376 GetSelectedAnnotation( xAnnotation );
378 DeleteAnnotation( xAnnotation );
380 break;
383 UpdateTags();
386 void AnnotationManagerImpl::ExecuteEditAnnotation(SfxRequest const & rReq)
388 const SfxItemSet* pArgs = rReq.GetArgs();
389 Reference< XAnnotation > xAnnotation;
390 sal_uInt32 nId = 0;
391 OUString sText;
392 if (!pArgs)
393 return;
395 if (mpDoc->IsUndoEnabled())
396 mpDoc->BegUndo(SdResId(STR_ANNOTATION_UNDO_EDIT));
398 const SfxPoolItem* pPoolItem = nullptr;
399 if (SfxItemState::SET == pArgs->GetItemState(SID_ATTR_POSTIT_ID, true, &pPoolItem))
401 nId = static_cast<const SvxPostItIdItem*>(pPoolItem)->GetValue().toUInt32();
402 xAnnotation = GetAnnotationById(nId);
404 if (SfxItemState::SET == pArgs->GetItemState(SID_ATTR_POSTIT_TEXT, true, &pPoolItem))
405 sText = static_cast<const SfxStringItem*>(pPoolItem)->GetValue();
407 if (xAnnotation.is() && !sText.isEmpty())
409 CreateChangeUndo(xAnnotation);
411 // TODO: Not allow other authors to change others' comments ?
412 Reference<XText> xText(xAnnotation->getTextRange());
413 xText->setString(sText);
415 LOKCommentNotifyAll(CommentNotificationType::Modify, xAnnotation);
418 if (mpDoc->IsUndoEnabled())
419 mpDoc->EndUndo();
421 UpdateTags(true);
424 void AnnotationManagerImpl::InsertAnnotation(const OUString& rText)
426 SdPage* pPage = GetCurrentPage();
427 if( !pPage )
428 return;
430 if( mpDoc->IsUndoEnabled() )
431 mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_INSERT ) );
433 // find free space for new annotation
434 int y = 0, x = 0;
436 AnnotationVector aAnnotations( pPage->getAnnotations() );
437 if( !aAnnotations.empty() )
439 const int page_width = pPage->GetSize().Width();
440 const int width = 1000;
441 const int height = 800;
442 ::tools::Rectangle aTagRect;
444 while( true )
446 ::tools::Rectangle aNewRect( x, y, x + width - 1, y + height - 1 );
447 bool bFree = true;
449 for( const auto& rxAnnotation : aAnnotations )
451 RealPoint2D aPoint( rxAnnotation->getPosition() );
452 aTagRect.SetLeft( sal::static_int_cast< long >( aPoint.X * 100.0 ) );
453 aTagRect.SetTop( sal::static_int_cast< long >( aPoint.Y * 100.0 ) );
454 aTagRect.SetRight( aTagRect.Left() + width - 1 );
455 aTagRect.SetBottom( aTagRect.Top() + height - 1 );
457 if( aNewRect.IsOver( aTagRect ) )
459 bFree = false;
460 break;
464 if( !bFree )
466 x += width;
467 if( x > page_width )
469 x = 0;
470 y += height;
473 else
475 break;
480 Reference< XAnnotation > xAnnotation;
481 pPage->createAnnotation( xAnnotation );
483 OUString sAuthor;
484 if (comphelper::LibreOfficeKit::isActive())
485 sAuthor = mrBase.GetMainViewShell()->GetView()->GetAuthor();
486 else
488 SvtUserOptions aUserOptions;
489 sAuthor = aUserOptions.GetFullName();
490 xAnnotation->setInitials( aUserOptions.GetID() );
493 if (!rText.isEmpty())
495 Reference<XText> xText(xAnnotation->getTextRange());
496 xText->setString(rText);
499 // set current author to new annotation
500 xAnnotation->setAuthor( sAuthor );
501 // set current time to new annotation
502 xAnnotation->setDateTime( getCurrentDateTime() );
504 // set position
505 RealPoint2D aPos( static_cast<double>(x) / 100.0, static_cast<double>(y) / 100.0 );
506 xAnnotation->setPosition( aPos );
508 if( mpDoc->IsUndoEnabled() )
509 mpDoc->EndUndo();
511 // Tell our LOK clients about new comment added
512 LOKCommentNotifyAll(CommentNotificationType::Add, xAnnotation);
514 UpdateTags(true);
515 SelectAnnotation( xAnnotation, true );
518 void AnnotationManagerImpl::ExecuteReplyToAnnotation( SfxRequest const & rReq )
520 Reference< XAnnotation > xAnnotation;
521 const SfxItemSet* pArgs = rReq.GetArgs();
522 sal_uInt32 nReplyId = 0; // Id of the comment to reply to
523 OUString sReplyText;
524 if( pArgs )
526 const SfxPoolItem* pPoolItem = nullptr;
527 if( SfxItemState::SET == pArgs->GetItemState( SID_ATTR_POSTIT_ID, true, &pPoolItem ) )
529 nReplyId = static_cast<const SvxPostItIdItem*>(pPoolItem)->GetValue().toUInt32();
530 xAnnotation = GetAnnotationById(nReplyId);
532 else if( SfxItemState::SET == pArgs->GetItemState( rReq.GetSlot(), true, &pPoolItem ) )
533 static_cast<const SfxUnoAnyItem*>( pPoolItem )->GetValue() >>= xAnnotation;
535 if( SfxItemState::SET == pArgs->GetItemState( SID_ATTR_POSTIT_TEXT, true, &pPoolItem ) )
536 sReplyText = static_cast<const SvxPostItTextItem*>( pPoolItem )->GetValue();
539 TextApiObject* pTextApi = getTextApiObject( xAnnotation );
540 if( !pTextApi )
541 return;
543 std::unique_ptr< ::Outliner > pOutliner( new ::Outliner(GetAnnotationPool(),OutlinerMode::TextObject) );
545 SdDrawDocument::SetCalcFieldValueHdl( pOutliner.get() );
546 pOutliner->SetUpdateMode( true );
548 OUString aStr(SdResId(STR_ANNOTATION_REPLY));
549 OUString sAuthor( xAnnotation->getAuthor() );
550 if( sAuthor.isEmpty() )
551 sAuthor = SdResId( STR_ANNOTATION_NOAUTHOR );
553 aStr = aStr.replaceFirst("%1", sAuthor);
555 aStr += " (" + getAnnotationDateTimeString( xAnnotation ) + "): \"";
557 OUString sQuote( pTextApi->GetText() );
559 if( sQuote.isEmpty() )
560 sQuote = "...";
561 aStr += sQuote + "\"\n";
563 for( sal_Int32 nIdx = 0; nIdx >= 0; )
564 pOutliner->Insert( aStr.getToken( 0, '\n', nIdx ), EE_PARA_APPEND, -1 );
566 if( pOutliner->GetParagraphCount() > 1 )
568 SfxItemSet aAnswerSet( pOutliner->GetEmptyItemSet() );
569 aAnswerSet.Put(SvxPostureItem(ITALIC_NORMAL,EE_CHAR_ITALIC));
571 ESelection aSel;
572 aSel.nEndPara = pOutliner->GetParagraphCount()-2;
573 aSel.nEndPos = pOutliner->GetText( pOutliner->GetParagraph( aSel.nEndPara ) ).getLength();
575 pOutliner->QuickSetAttribs( aAnswerSet, aSel );
578 if (!sReplyText.isEmpty())
579 pOutliner->Insert(sReplyText);
581 std::unique_ptr< OutlinerParaObject > pOPO( pOutliner->CreateParaObject() );
582 pTextApi->SetText(*pOPO);
584 OUString sReplyAuthor;
585 if (comphelper::LibreOfficeKit::isActive())
586 sReplyAuthor = mrBase.GetMainViewShell()->GetView()->GetAuthor();
587 else
589 SvtUserOptions aUserOptions;
590 sReplyAuthor = aUserOptions.GetFullName();
591 xAnnotation->setInitials( aUserOptions.GetID() );
594 xAnnotation->setAuthor( sReplyAuthor );
595 // set current time to reply
596 xAnnotation->setDateTime( getCurrentDateTime() );
598 // Tell our LOK clients about this (comment modification)
599 LOKCommentNotifyAll(CommentNotificationType::Modify, xAnnotation);
601 UpdateTags(true);
602 SelectAnnotation( xAnnotation, true );
605 void AnnotationManagerImpl::DeleteAnnotation( const Reference< XAnnotation >& xAnnotation )
607 SdPage* pPage = GetCurrentPage();
609 if( xAnnotation.is() && pPage )
611 if( mpDoc->IsUndoEnabled() )
612 mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) );
614 pPage->removeAnnotation( xAnnotation );
616 if( mpDoc->IsUndoEnabled() )
617 mpDoc->EndUndo();
619 UpdateTags();
623 void AnnotationManagerImpl::DeleteAnnotationsByAuthor( const OUString& sAuthor )
625 if( mpDoc->IsUndoEnabled() )
626 mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) );
628 SdPage* pPage = nullptr;
631 pPage = GetNextPage( pPage, true );
633 if( pPage && !pPage->getAnnotations().empty() )
635 AnnotationVector aAnnotations( pPage->getAnnotations() );
636 for( Reference< XAnnotation >& xAnnotation : aAnnotations )
638 if( xAnnotation->getAuthor() == sAuthor )
640 if( mxSelectedAnnotation == xAnnotation )
641 mxSelectedAnnotation.clear();
642 pPage->removeAnnotation( xAnnotation );
646 } while( pPage );
648 if( mpDoc->IsUndoEnabled() )
649 mpDoc->EndUndo();
652 void AnnotationManagerImpl::DeleteAllAnnotations()
654 if( mpDoc->IsUndoEnabled() )
655 mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) );
657 SdPage* pPage = nullptr;
660 pPage = GetNextPage( pPage, true );
662 if( pPage && !pPage->getAnnotations().empty() )
665 AnnotationVector aAnnotations( pPage->getAnnotations() );
666 for( const auto& rxAnnotation : aAnnotations )
668 pPage->removeAnnotation( rxAnnotation );
672 while( pPage );
674 mxSelectedAnnotation.clear();
676 if( mpDoc->IsUndoEnabled() )
677 mpDoc->EndUndo();
680 void AnnotationManagerImpl::GetAnnotationState(SfxItemSet& rSet)
682 SdPage* pCurrentPage = GetCurrentPage();
684 const bool bReadOnly = mrBase.GetDocShell()->IsReadOnly();
685 const bool bWrongPageKind = (pCurrentPage == nullptr) || (pCurrentPage->GetPageKind() != PageKind::Standard);
687 const SvtSaveOptions::ODFDefaultVersion nCurrentODFVersion( SvtSaveOptions().GetODFDefaultVersion() );
689 if( bReadOnly || bWrongPageKind || (nCurrentODFVersion <= SvtSaveOptions::ODFVER_012) )
690 rSet.DisableItem( SID_INSERT_POSTIT );
692 rSet.Put(SfxBoolItem(SID_TOGGLE_NOTES, mbShowAnnotations));
694 Reference< XAnnotation > xAnnotation;
695 GetSelectedAnnotation( xAnnotation );
697 // Don't disable these slot in case of LOK, as postit doesn't need to
698 // selected before doing an operation on it in LOK
699 if( (!xAnnotation.is() && !comphelper::LibreOfficeKit::isActive()) || bReadOnly )
701 rSet.DisableItem( SID_DELETE_POSTIT );
702 rSet.DisableItem( SID_EDIT_POSTIT );
705 SdPage* pPage = nullptr;
707 bool bHasAnnotations = false;
710 pPage = GetNextPage( pPage, true );
712 if( pPage && !pPage->getAnnotations().empty() )
713 bHasAnnotations = true;
715 while( pPage && !bHasAnnotations );
717 if( !bHasAnnotations || bReadOnly )
719 rSet.DisableItem( SID_DELETEALL_POSTIT );
722 if( bWrongPageKind || !bHasAnnotations )
724 rSet.DisableItem( SID_PREVIOUS_POSTIT );
725 rSet.DisableItem( SID_NEXT_POSTIT );
729 void AnnotationManagerImpl::SelectNextAnnotation(bool bForeward)
731 ShowAnnotations( true );
733 Reference< XAnnotation > xCurrent;
734 GetSelectedAnnotation( xCurrent );
735 SdPage* pPage = GetCurrentPage();
736 if( !pPage )
737 return;
739 AnnotationVector aAnnotations( pPage->getAnnotations() );
741 if( bForeward )
743 if( xCurrent.is() )
745 auto iter = std::find(aAnnotations.begin(), aAnnotations.end(), xCurrent);
746 if (iter != aAnnotations.end())
748 ++iter;
749 if( iter != aAnnotations.end() )
751 SelectAnnotation( (*iter) );
752 return;
756 else if( !aAnnotations.empty() )
758 SelectAnnotation( *(aAnnotations.begin()) );
759 return;
762 else
764 if( xCurrent.is() )
766 auto iter = std::find(aAnnotations.begin(), aAnnotations.end(), xCurrent);
767 if (iter != aAnnotations.end() && iter != aAnnotations.begin())
769 --iter;
770 SelectAnnotation( (*iter) );
771 return;
774 else if( !aAnnotations.empty() )
776 AnnotationVector::iterator iter( aAnnotations.end() );
777 SelectAnnotation( *(--iter) );
778 return;
782 mxSelectedAnnotation.clear();
787 pPage = GetNextPage( pPage, bForeward );
789 if( pPage && !pPage->getAnnotations().empty() )
791 // switch to next/previous slide with annotations
792 std::shared_ptr<DrawViewShell> pDrawViewShell(std::dynamic_pointer_cast<DrawViewShell>(mrBase.GetMainViewShell()));
793 if (pDrawViewShell != nullptr)
795 pDrawViewShell->ChangeEditMode(pPage->IsMasterPage() ? EditMode::MasterPage : EditMode::Page, false);
796 pDrawViewShell->SwitchPage((pPage->GetPageNum() - 1) >> 1);
798 SfxDispatcher* pDispatcher = getDispatcher( mrBase );
799 if( pDispatcher )
800 pDispatcher->Execute( bForeward ? SID_NEXT_POSTIT : SID_PREVIOUS_POSTIT );
802 return;
806 while( pPage );
808 // The question text depends on the search direction.
809 bool bImpress = mpDoc->GetDocumentType() == DocumentType::Impress;
810 const char* pStringId;
811 if(bForeward)
812 pStringId = bImpress ? STR_ANNOTATION_WRAP_FORWARD : STR_ANNOTATION_WRAP_FORWARD_DRAW;
813 else
814 pStringId = bImpress ? STR_ANNOTATION_WRAP_BACKWARD : STR_ANNOTATION_WRAP_BACKWARD_DRAW;
816 // Pop up question box that asks the user whether to wrap around.
817 // The dialog is made modal with respect to the whole application.
818 std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr,
819 VclMessageType::Question, VclButtonsType::YesNo,
820 SdResId(pStringId)));
821 xQueryBox->set_default_response(RET_YES);
822 if (xQueryBox->run() != RET_YES)
823 break;
825 while( true );
828 void AnnotationManagerImpl::onTagSelected( AnnotationTag const & rTag )
830 mxSelectedAnnotation = rTag.GetAnnotation();
831 invalidateSlots();
834 void AnnotationManagerImpl::onTagDeselected( AnnotationTag const & rTag )
836 if( rTag.GetAnnotation() == mxSelectedAnnotation )
838 mxSelectedAnnotation.clear();
839 invalidateSlots();
843 void AnnotationManagerImpl::SelectAnnotation( const css::uno::Reference< css::office::XAnnotation >& xAnnotation, bool bEdit /* = sal_False */ )
845 mxSelectedAnnotation = xAnnotation;
847 auto iter = std::find_if(maTagVector.begin(), maTagVector.end(),
848 [&xAnnotation](const rtl::Reference<AnnotationTag>& rxTag) { return rxTag->GetAnnotation() == xAnnotation; });
849 if (iter != maTagVector.end())
851 SmartTagReference xTag( (*iter).get() );
852 mrBase.GetMainViewShell()->GetView()->getSmartTags().select( xTag );
853 (*iter)->OpenPopup( bEdit );
857 void AnnotationManagerImpl::GetSelectedAnnotation( css::uno::Reference< css::office::XAnnotation >& xAnnotation )
859 xAnnotation = mxSelectedAnnotation;
862 void AnnotationManagerImpl::invalidateSlots()
864 SfxBindings* pBindings = getBindings( mrBase );
865 if( pBindings )
867 pBindings->Invalidate( SID_INSERT_POSTIT );
868 pBindings->Invalidate( SID_DELETE_POSTIT );
869 pBindings->Invalidate( SID_DELETEALL_POSTIT );
870 pBindings->Invalidate( SID_PREVIOUS_POSTIT );
871 pBindings->Invalidate( SID_NEXT_POSTIT );
872 pBindings->Invalidate( SID_UNDO );
873 pBindings->Invalidate( SID_REDO );
877 void AnnotationManagerImpl::onSelectionChanged()
879 if( !(mxView.is() && mrBase.GetDrawView()) )
880 return;
884 Reference< XAnnotationAccess > xPage( mxView->getCurrentPage(), UNO_QUERY );
886 if( xPage != mxCurrentPage )
888 mxCurrentPage = xPage;
890 UpdateTags(true);
893 catch( Exception& )
895 OSL_FAIL( "sd::AnnotationManagerImpl::onSelectionChanged(), exception caught!" );
899 void AnnotationManagerImpl::UpdateTags( bool bSynchron )
901 if( bSynchron )
903 if( mnUpdateTagsEvent )
904 Application::RemoveUserEvent( mnUpdateTagsEvent );
906 UpdateTagsHdl(nullptr);
908 else
910 if( !mnUpdateTagsEvent && mxView.is() )
911 mnUpdateTagsEvent = Application::PostUserEvent( LINK( this, AnnotationManagerImpl, UpdateTagsHdl ) );
915 IMPL_LINK_NOARG(AnnotationManagerImpl, UpdateTagsHdl, void*, void)
917 mnUpdateTagsEvent = nullptr;
918 DisposeTags();
920 if( mbShowAnnotations )
921 CreateTags();
923 if( mrBase.GetDrawView() )
924 static_cast< ::sd::View* >( mrBase.GetDrawView() )->updateHandles();
926 invalidateSlots();
929 void AnnotationManagerImpl::CreateTags()
932 if( !(mxCurrentPage.is() && mpDoc) )
933 return;
937 int nIndex = 1;
938 maFont = Application::GetSettings().GetStyleSettings().GetAppFont();
940 rtl::Reference< AnnotationTag > xSelectedTag;
942 Reference< XAnnotationEnumeration > xEnum( mxCurrentPage->createAnnotationEnumeration() );
943 while( xEnum->hasMoreElements() )
945 Reference< XAnnotation > xAnnotation( xEnum->nextElement() );
946 Color aColor( GetColorLight( mpDoc->GetAnnotationAuthorIndex( xAnnotation->getAuthor() ) ) );
947 rtl::Reference< AnnotationTag > xTag( new AnnotationTag( *this, *mrBase.GetMainViewShell()->GetView(), xAnnotation, aColor, nIndex++, maFont ) );
948 maTagVector.push_back(xTag);
950 if( xAnnotation == mxSelectedAnnotation )
952 xSelectedTag = xTag;
956 if( xSelectedTag.is() )
958 SmartTagReference xTag( xSelectedTag.get() );
959 mrBase.GetMainViewShell()->GetView()->getSmartTags().select( xTag );
961 else
963 // no tag, no selection!
964 mxSelectedAnnotation.clear();
967 catch( Exception& )
969 OSL_FAIL( "sd::AnnotationManagerImpl::onSelectionChanged(), exception caught!" );
973 void AnnotationManagerImpl::DisposeTags()
975 for (auto& rxTag : maTagVector)
977 rxTag->Dispose();
980 maTagVector.clear();
983 void AnnotationManagerImpl::addListener()
985 Link<tools::EventMultiplexerEvent&,void> aLink( LINK(this,AnnotationManagerImpl,EventMultiplexerListener) );
986 mrBase.GetEventMultiplexer()->AddEventListener(aLink);
989 void AnnotationManagerImpl::removeListener()
991 Link<tools::EventMultiplexerEvent&,void> aLink( LINK(this,AnnotationManagerImpl,EventMultiplexerListener) );
992 mrBase.GetEventMultiplexer()->RemoveEventListener( aLink );
995 IMPL_LINK(AnnotationManagerImpl,EventMultiplexerListener,
996 tools::EventMultiplexerEvent&, rEvent, void)
998 switch (rEvent.meEventId)
1000 case EventMultiplexerEventId::CurrentPageChanged:
1001 case EventMultiplexerEventId::EditViewSelection:
1002 onSelectionChanged();
1003 break;
1005 case EventMultiplexerEventId::MainViewRemoved:
1006 mxView.clear();
1007 onSelectionChanged();
1008 break;
1010 case EventMultiplexerEventId::MainViewAdded:
1011 mxView.set( mrBase.GetController(), UNO_QUERY );
1012 onSelectionChanged();
1013 break;
1015 default: break;
1019 namespace
1021 sal_uInt16 IdentToSID(const OString& rIdent)
1023 if (rIdent == "reply")
1024 return SID_REPLYTO_POSTIT;
1025 else if (rIdent == "delete")
1026 return SID_DELETE_POSTIT;
1027 else if (rIdent == "deleteby")
1028 return SID_DELETEALLBYAUTHOR_POSTIT;
1029 else if (rIdent == "deleteall")
1030 return SID_DELETEALL_POSTIT;
1031 else if (rIdent == "copy")
1032 return SID_COPY;
1033 else if (rIdent == "paste")
1034 return SID_PASTE;
1035 else if (rIdent == "bold")
1036 return SID_ATTR_CHAR_WEIGHT;
1037 else if (rIdent == "italic")
1038 return SID_ATTR_CHAR_POSTURE;
1039 else if (rIdent == "underline")
1040 return SID_ATTR_CHAR_UNDERLINE;
1041 else if (rIdent == "strike")
1042 return SID_ATTR_CHAR_STRIKEOUT;
1043 return 0;
1047 void AnnotationManagerImpl::ExecuteAnnotationContextMenu( const Reference< XAnnotation >& xAnnotation, vcl::Window* pParent, const ::tools::Rectangle& rContextRect, bool bButtonMenu /* = false */ )
1049 SfxDispatcher* pDispatcher( getDispatcher( mrBase ) );
1050 if( !pDispatcher )
1051 return;
1053 const bool bReadOnly = mrBase.GetDocShell()->IsReadOnly();
1055 AnnotationWindow* pAnnotationWindow = bButtonMenu ? nullptr : dynamic_cast< AnnotationWindow* >( pParent );
1057 if( bReadOnly && !pAnnotationWindow )
1058 return;
1060 OUString sUIFile;
1061 if (pAnnotationWindow)
1062 sUIFile = "modules/simpress/ui/annotationmenu.ui";
1063 else
1064 sUIFile = "modules/simpress/ui/annotationtagmenu.ui";
1065 VclBuilder aBuilder(nullptr, VclBuilderContainer::getUIRootDir(), sUIFile, "");
1066 VclPtr<PopupMenu> pMenu(aBuilder.get_menu("menu"));
1068 SvtUserOptions aUserOptions;
1069 OUString sCurrentAuthor( aUserOptions.GetFullName() );
1070 OUString sAuthor( xAnnotation->getAuthor() );
1072 OUString aStr(pMenu->GetItemText(pMenu->GetItemId("deleteby")));
1073 OUString aReplace( sAuthor );
1074 if( aReplace.isEmpty() )
1075 aReplace = SdResId( STR_ANNOTATION_NOAUTHOR );
1076 aStr = aStr.replaceFirst("%1", aReplace);
1077 pMenu->SetItemText(pMenu->GetItemId("deleteby"), aStr);
1078 pMenu->EnableItem(pMenu->GetItemId("reply"), (sAuthor != sCurrentAuthor) && !bReadOnly);
1079 pMenu->EnableItem(pMenu->GetItemId("delete"), xAnnotation.is() && !bReadOnly);
1080 pMenu->EnableItem(pMenu->GetItemId("deleteby"), !bReadOnly);
1081 pMenu->EnableItem(pMenu->GetItemId("deleteall"), !bReadOnly);
1083 if( pAnnotationWindow )
1085 if( pAnnotationWindow->IsProtected() || bReadOnly )
1087 pMenu->EnableItem(pMenu->GetItemId("bold"), false);
1088 pMenu->EnableItem(pMenu->GetItemId("italic"), false);
1089 pMenu->EnableItem(pMenu->GetItemId("underline"), false);
1090 pMenu->EnableItem(pMenu->GetItemId("strike"), false);
1091 pMenu->EnableItem(pMenu->GetItemId("paste"), false);
1093 else
1095 SfxItemSet aSet(pAnnotationWindow->getView()->GetAttribs());
1097 if ( aSet.GetItemState( EE_CHAR_WEIGHT ) == SfxItemState::SET )
1099 if( aSet.Get( EE_CHAR_WEIGHT ).GetWeight() == WEIGHT_BOLD )
1100 pMenu->CheckItem("bold");
1103 if ( aSet.GetItemState( EE_CHAR_ITALIC ) == SfxItemState::SET )
1105 if( aSet.Get( EE_CHAR_ITALIC ).GetPosture() != ITALIC_NONE )
1106 pMenu->CheckItem("italic");
1109 if ( aSet.GetItemState( EE_CHAR_UNDERLINE ) == SfxItemState::SET )
1111 if( aSet.Get( EE_CHAR_UNDERLINE ).GetLineStyle() != LINESTYLE_NONE )
1112 pMenu->CheckItem("underline");
1115 if ( aSet.GetItemState( EE_CHAR_STRIKEOUT ) == SfxItemState::SET )
1117 if( aSet.Get( EE_CHAR_STRIKEOUT ).GetStrikeout() != STRIKEOUT_NONE )
1118 pMenu->CheckItem("strike");
1120 TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromSystemClipboard( pAnnotationWindow ) );
1121 pMenu->EnableItem(pMenu->GetItemId("paste"), aDataHelper.GetFormatCount() != 0);
1124 pMenu->EnableItem(pMenu->GetItemId("copy"), pAnnotationWindow->getView()->HasSelection());
1127 // set slot images
1128 Reference< css::frame::XFrame > xFrame( mrBase.GetMainViewShell()->GetViewFrame()->GetFrame().GetFrameInterface() );
1129 if( xFrame.is() )
1131 for( sal_uInt16 nPos = 0; nPos < pMenu->GetItemCount(); nPos++ )
1133 sal_uInt16 nId = pMenu->GetItemId( nPos );
1134 if (!pMenu->IsItemEnabled(nId))
1135 continue;
1137 Image aImage( vcl::CommandInfoProvider::GetImageForCommand( pMenu->GetItemCommand( nId ), xFrame ) );
1138 if( !!aImage )
1139 pMenu->SetItemImage( nId, aImage );
1143 // tdf#99388 and tdf#99712 make known that PopupMenu is active at parent to
1144 // allow suppressing closing of that window if needed
1145 setPopupMenuActive(true);
1147 sal_uInt16 nId = pMenu->Execute( pParent, rContextRect, PopupMenuFlags::ExecuteDown|PopupMenuFlags::NoMouseUpClose );
1148 nId = IdentToSID(pMenu->GetItemIdent(nId));
1150 // tdf#99388 and tdf#99712 reset flag, need to be done before reacting
1151 // since closing it is one possible reaction
1152 setPopupMenuActive(false);
1154 switch( nId )
1156 case SID_REPLYTO_POSTIT:
1158 const SfxUnoAnyItem aItem( SID_REPLYTO_POSTIT, Any( xAnnotation ) );
1159 pDispatcher->ExecuteList(SID_REPLYTO_POSTIT,
1160 SfxCallMode::ASYNCHRON, { &aItem });
1161 break;
1163 case SID_DELETE_POSTIT:
1165 const SfxUnoAnyItem aItem( SID_DELETE_POSTIT, Any( xAnnotation ) );
1166 pDispatcher->ExecuteList(SID_DELETE_POSTIT, SfxCallMode::ASYNCHRON,
1167 { &aItem });
1168 break;
1170 case SID_DELETEALLBYAUTHOR_POSTIT:
1172 const SfxStringItem aItem( SID_DELETEALLBYAUTHOR_POSTIT, sAuthor );
1173 pDispatcher->ExecuteList( SID_DELETEALLBYAUTHOR_POSTIT,
1174 SfxCallMode::ASYNCHRON, { &aItem });
1175 break;
1177 case SID_DELETEALL_POSTIT:
1178 pDispatcher->Execute( SID_DELETEALL_POSTIT );
1179 break;
1180 case SID_COPY:
1181 case SID_PASTE:
1182 case SID_ATTR_CHAR_WEIGHT:
1183 case SID_ATTR_CHAR_POSTURE:
1184 case SID_ATTR_CHAR_UNDERLINE:
1185 case SID_ATTR_CHAR_STRIKEOUT:
1186 if( pAnnotationWindow )
1187 pAnnotationWindow->ExecuteSlot( nId );
1188 break;
1192 Color AnnotationManagerImpl::GetColor(sal_uInt16 aAuthorIndex)
1194 if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode())
1196 static const Color aArrayNormal[] = {
1197 COL_AUTHOR1_NORMAL, COL_AUTHOR2_NORMAL, COL_AUTHOR3_NORMAL,
1198 COL_AUTHOR4_NORMAL, COL_AUTHOR5_NORMAL, COL_AUTHOR6_NORMAL,
1199 COL_AUTHOR7_NORMAL, COL_AUTHOR8_NORMAL, COL_AUTHOR9_NORMAL };
1201 return aArrayNormal[ aAuthorIndex % SAL_N_ELEMENTS( aArrayNormal ) ];
1204 return COL_WHITE;
1207 Color AnnotationManagerImpl::GetColorLight(sal_uInt16 aAuthorIndex)
1209 if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode())
1211 static const Color aArrayLight[] = {
1212 COL_AUTHOR1_LIGHT, COL_AUTHOR2_LIGHT, COL_AUTHOR3_LIGHT,
1213 COL_AUTHOR4_LIGHT, COL_AUTHOR5_LIGHT, COL_AUTHOR6_LIGHT,
1214 COL_AUTHOR7_LIGHT, COL_AUTHOR8_LIGHT, COL_AUTHOR9_LIGHT };
1216 return aArrayLight[ aAuthorIndex % SAL_N_ELEMENTS( aArrayLight ) ];
1219 return COL_WHITE;
1222 Color AnnotationManagerImpl::GetColorDark(sal_uInt16 aAuthorIndex)
1224 if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode())
1226 static const Color aArrayAnkor[] = {
1227 COL_AUTHOR1_DARK, COL_AUTHOR2_DARK, COL_AUTHOR3_DARK,
1228 COL_AUTHOR4_DARK, COL_AUTHOR5_DARK, COL_AUTHOR6_DARK,
1229 COL_AUTHOR7_DARK, COL_AUTHOR8_DARK, COL_AUTHOR9_DARK };
1231 return aArrayAnkor[ aAuthorIndex % SAL_N_ELEMENTS( aArrayAnkor ) ];
1234 return COL_WHITE;
1237 SdPage* AnnotationManagerImpl::GetNextPage( SdPage const * pPage, bool bForward )
1239 if( pPage == nullptr )
1241 if (bForward)
1242 return mpDoc->GetSdPage(0, PageKind::Standard ); // first page
1243 else
1244 return mpDoc->GetMasterSdPage( mpDoc->GetMasterSdPageCount(PageKind::Standard) - 1, PageKind::Standard ); // last page
1247 sal_uInt16 nPageNum = (pPage->GetPageNum() - 1) >> 1;
1249 // first all non master pages
1250 if( !pPage->IsMasterPage() )
1252 if( bForward )
1254 if( nPageNum >= mpDoc->GetSdPageCount(PageKind::Standard)-1 )
1256 // we reached end of draw pages, start with master pages (skip handout master for draw)
1257 return mpDoc->GetMasterSdPage( (mpDoc->GetDocumentType() == DocumentType::Impress) ? 0 : 1, PageKind::Standard );
1259 nPageNum++;
1261 else
1263 if( nPageNum == 0 )
1264 return nullptr; // we are already on the first draw page, finished
1266 nPageNum--;
1268 return mpDoc->GetSdPage(nPageNum, PageKind::Standard);
1270 else
1272 if( bForward )
1274 if( nPageNum >= mpDoc->GetMasterSdPageCount(PageKind::Standard)-1 )
1276 return nullptr; // we reached the end, there is nothing more to see here
1278 nPageNum++;
1280 else
1282 if( nPageNum == (mpDoc->GetDocumentType() == DocumentType::Impress ? 0 : 1) )
1284 // we reached beginning of master pages, start with end if pages
1285 return mpDoc->GetSdPage( mpDoc->GetSdPageCount(PageKind::Standard)-1, PageKind::Standard );
1288 nPageNum--;
1290 return mpDoc->GetMasterSdPage(nPageNum,PageKind::Standard);
1294 SdPage* AnnotationManagerImpl::GetCurrentPage()
1296 if (mrBase.GetMainViewShell().get())
1297 return mrBase.GetMainViewShell()->getCurrentPage();
1298 return nullptr;
1301 AnnotationManager::AnnotationManager( ViewShellBase& rViewShellBase )
1302 : mxImpl( new AnnotationManagerImpl( rViewShellBase ) )
1304 mxImpl->init();
1307 AnnotationManager::~AnnotationManager()
1309 mxImpl->dispose();
1312 void AnnotationManager::ExecuteAnnotation(SfxRequest const & rRequest)
1314 mxImpl->ExecuteAnnotation( rRequest );
1317 void AnnotationManager::GetAnnotationState(SfxItemSet& rItemSet)
1319 mxImpl->GetAnnotationState(rItemSet);
1324 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */