bump product version to 6.4.0.3
[LibreOffice.git] / sd / source / ui / annotations / annotationmanager.cxx
blobb3b81b8c37aecdddb3f399fb9ef113c8c40d41c8
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 += " " + 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) +
554 " (" + getAnnotationDateTimeString( xAnnotation ) + "): \"";
556 OUString sQuote( pTextApi->GetText() );
558 if( sQuote.isEmpty() )
559 sQuote = "...";
560 aStr += sQuote + "\"\n";
562 for( sal_Int32 nIdx = 0; nIdx >= 0; )
563 pOutliner->Insert( aStr.getToken( 0, '\n', nIdx ), EE_PARA_APPEND, -1 );
565 if( pOutliner->GetParagraphCount() > 1 )
567 SfxItemSet aAnswerSet( pOutliner->GetEmptyItemSet() );
568 aAnswerSet.Put(SvxPostureItem(ITALIC_NORMAL,EE_CHAR_ITALIC));
570 ESelection aSel;
571 aSel.nEndPara = pOutliner->GetParagraphCount()-2;
572 aSel.nEndPos = pOutliner->GetText( pOutliner->GetParagraph( aSel.nEndPara ) ).getLength();
574 pOutliner->QuickSetAttribs( aAnswerSet, aSel );
577 if (!sReplyText.isEmpty())
578 pOutliner->Insert(sReplyText);
580 std::unique_ptr< OutlinerParaObject > pOPO( pOutliner->CreateParaObject() );
581 pTextApi->SetText(*pOPO);
583 OUString sReplyAuthor;
584 if (comphelper::LibreOfficeKit::isActive())
585 sReplyAuthor = mrBase.GetMainViewShell()->GetView()->GetAuthor();
586 else
588 SvtUserOptions aUserOptions;
589 sReplyAuthor = aUserOptions.GetFullName();
590 xAnnotation->setInitials( aUserOptions.GetID() );
593 xAnnotation->setAuthor( sReplyAuthor );
594 // set current time to reply
595 xAnnotation->setDateTime( getCurrentDateTime() );
597 // Tell our LOK clients about this (comment modification)
598 LOKCommentNotifyAll(CommentNotificationType::Modify, xAnnotation);
600 UpdateTags(true);
601 SelectAnnotation( xAnnotation, true );
604 void AnnotationManagerImpl::DeleteAnnotation( const Reference< XAnnotation >& xAnnotation )
606 SdPage* pPage = GetCurrentPage();
608 if( xAnnotation.is() && pPage )
610 if( mpDoc->IsUndoEnabled() )
611 mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) );
613 pPage->removeAnnotation( xAnnotation );
615 if( mpDoc->IsUndoEnabled() )
616 mpDoc->EndUndo();
618 UpdateTags();
622 void AnnotationManagerImpl::DeleteAnnotationsByAuthor( const OUString& sAuthor )
624 if( mpDoc->IsUndoEnabled() )
625 mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) );
627 SdPage* pPage = nullptr;
630 pPage = GetNextPage( pPage, true );
632 if( pPage && !pPage->getAnnotations().empty() )
634 AnnotationVector aAnnotations( pPage->getAnnotations() );
635 for( Reference< XAnnotation >& xAnnotation : aAnnotations )
637 if( xAnnotation->getAuthor() == sAuthor )
639 if( mxSelectedAnnotation == xAnnotation )
640 mxSelectedAnnotation.clear();
641 pPage->removeAnnotation( xAnnotation );
645 } while( pPage );
647 if( mpDoc->IsUndoEnabled() )
648 mpDoc->EndUndo();
651 void AnnotationManagerImpl::DeleteAllAnnotations()
653 if( mpDoc->IsUndoEnabled() )
654 mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) );
656 SdPage* pPage = nullptr;
659 pPage = GetNextPage( pPage, true );
661 if( pPage && !pPage->getAnnotations().empty() )
664 AnnotationVector aAnnotations( pPage->getAnnotations() );
665 for( const auto& rxAnnotation : aAnnotations )
667 pPage->removeAnnotation( rxAnnotation );
671 while( pPage );
673 mxSelectedAnnotation.clear();
675 if( mpDoc->IsUndoEnabled() )
676 mpDoc->EndUndo();
679 void AnnotationManagerImpl::GetAnnotationState(SfxItemSet& rSet)
681 SdPage* pCurrentPage = GetCurrentPage();
683 const bool bReadOnly = mrBase.GetDocShell()->IsReadOnly();
684 const bool bWrongPageKind = (pCurrentPage == nullptr) || (pCurrentPage->GetPageKind() != PageKind::Standard);
686 const SvtSaveOptions::ODFDefaultVersion nCurrentODFVersion( SvtSaveOptions().GetODFDefaultVersion() );
688 if( bReadOnly || bWrongPageKind || (nCurrentODFVersion <= SvtSaveOptions::ODFVER_012) )
689 rSet.DisableItem( SID_INSERT_POSTIT );
691 rSet.Put(SfxBoolItem(SID_TOGGLE_NOTES, mbShowAnnotations));
693 Reference< XAnnotation > xAnnotation;
694 GetSelectedAnnotation( xAnnotation );
696 // Don't disable these slot in case of LOK, as postit doesn't need to
697 // selected before doing an operation on it in LOK
698 if( (!xAnnotation.is() && !comphelper::LibreOfficeKit::isActive()) || bReadOnly )
700 rSet.DisableItem( SID_DELETE_POSTIT );
701 rSet.DisableItem( SID_EDIT_POSTIT );
704 SdPage* pPage = nullptr;
706 bool bHasAnnotations = false;
709 pPage = GetNextPage( pPage, true );
711 if( pPage && !pPage->getAnnotations().empty() )
712 bHasAnnotations = true;
714 while( pPage && !bHasAnnotations );
716 if( !bHasAnnotations || bReadOnly )
718 rSet.DisableItem( SID_DELETEALL_POSTIT );
721 if( bWrongPageKind || !bHasAnnotations )
723 rSet.DisableItem( SID_PREVIOUS_POSTIT );
724 rSet.DisableItem( SID_NEXT_POSTIT );
728 void AnnotationManagerImpl::SelectNextAnnotation(bool bForeward)
730 ShowAnnotations( true );
732 Reference< XAnnotation > xCurrent;
733 GetSelectedAnnotation( xCurrent );
734 SdPage* pPage = GetCurrentPage();
735 if( !pPage )
736 return;
738 AnnotationVector aAnnotations( pPage->getAnnotations() );
740 if( bForeward )
742 if( xCurrent.is() )
744 auto iter = std::find(aAnnotations.begin(), aAnnotations.end(), xCurrent);
745 if (iter != aAnnotations.end())
747 ++iter;
748 if( iter != aAnnotations.end() )
750 SelectAnnotation( (*iter) );
751 return;
755 else if( !aAnnotations.empty() )
757 SelectAnnotation( *(aAnnotations.begin()) );
758 return;
761 else
763 if( xCurrent.is() )
765 auto iter = std::find(aAnnotations.begin(), aAnnotations.end(), xCurrent);
766 if (iter != aAnnotations.end() && iter != aAnnotations.begin())
768 --iter;
769 SelectAnnotation( (*iter) );
770 return;
773 else if( !aAnnotations.empty() )
775 AnnotationVector::iterator iter( aAnnotations.end() );
776 SelectAnnotation( *(--iter) );
777 return;
781 mxSelectedAnnotation.clear();
786 pPage = GetNextPage( pPage, bForeward );
788 if( pPage && !pPage->getAnnotations().empty() )
790 // switch to next/previous slide with annotations
791 std::shared_ptr<DrawViewShell> pDrawViewShell(std::dynamic_pointer_cast<DrawViewShell>(mrBase.GetMainViewShell()));
792 if (pDrawViewShell != nullptr)
794 pDrawViewShell->ChangeEditMode(pPage->IsMasterPage() ? EditMode::MasterPage : EditMode::Page, false);
795 pDrawViewShell->SwitchPage((pPage->GetPageNum() - 1) >> 1);
797 SfxDispatcher* pDispatcher = getDispatcher( mrBase );
798 if( pDispatcher )
799 pDispatcher->Execute( bForeward ? SID_NEXT_POSTIT : SID_PREVIOUS_POSTIT );
801 return;
805 while( pPage );
807 // The question text depends on the search direction.
808 bool bImpress = mpDoc->GetDocumentType() == DocumentType::Impress;
809 const char* pStringId;
810 if(bForeward)
811 pStringId = bImpress ? STR_ANNOTATION_WRAP_FORWARD : STR_ANNOTATION_WRAP_FORWARD_DRAW;
812 else
813 pStringId = bImpress ? STR_ANNOTATION_WRAP_BACKWARD : STR_ANNOTATION_WRAP_BACKWARD_DRAW;
815 // Pop up question box that asks the user whether to wrap around.
816 // The dialog is made modal with respect to the whole application.
817 std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr,
818 VclMessageType::Question, VclButtonsType::YesNo,
819 SdResId(pStringId)));
820 xQueryBox->set_default_response(RET_YES);
821 if (xQueryBox->run() != RET_YES)
822 break;
824 while( true );
827 void AnnotationManagerImpl::onTagSelected( AnnotationTag const & rTag )
829 mxSelectedAnnotation = rTag.GetAnnotation();
830 invalidateSlots();
833 void AnnotationManagerImpl::onTagDeselected( AnnotationTag const & rTag )
835 if( rTag.GetAnnotation() == mxSelectedAnnotation )
837 mxSelectedAnnotation.clear();
838 invalidateSlots();
842 void AnnotationManagerImpl::SelectAnnotation( const css::uno::Reference< css::office::XAnnotation >& xAnnotation, bool bEdit /* = sal_False */ )
844 mxSelectedAnnotation = xAnnotation;
846 auto iter = std::find_if(maTagVector.begin(), maTagVector.end(),
847 [&xAnnotation](const rtl::Reference<AnnotationTag>& rxTag) { return rxTag->GetAnnotation() == xAnnotation; });
848 if (iter != maTagVector.end())
850 SmartTagReference xTag( (*iter).get() );
851 mrBase.GetMainViewShell()->GetView()->getSmartTags().select( xTag );
852 (*iter)->OpenPopup( bEdit );
856 void AnnotationManagerImpl::GetSelectedAnnotation( css::uno::Reference< css::office::XAnnotation >& xAnnotation )
858 xAnnotation = mxSelectedAnnotation;
861 void AnnotationManagerImpl::invalidateSlots()
863 SfxBindings* pBindings = getBindings( mrBase );
864 if( pBindings )
866 pBindings->Invalidate( SID_INSERT_POSTIT );
867 pBindings->Invalidate( SID_DELETE_POSTIT );
868 pBindings->Invalidate( SID_DELETEALL_POSTIT );
869 pBindings->Invalidate( SID_PREVIOUS_POSTIT );
870 pBindings->Invalidate( SID_NEXT_POSTIT );
871 pBindings->Invalidate( SID_UNDO );
872 pBindings->Invalidate( SID_REDO );
876 void AnnotationManagerImpl::onSelectionChanged()
878 if( !(mxView.is() && mrBase.GetDrawView()) )
879 return;
883 Reference< XAnnotationAccess > xPage( mxView->getCurrentPage(), UNO_QUERY );
885 if( xPage != mxCurrentPage )
887 mxCurrentPage = xPage;
889 UpdateTags(true);
892 catch( Exception& )
894 OSL_FAIL( "sd::AnnotationManagerImpl::onSelectionChanged(), exception caught!" );
898 void AnnotationManagerImpl::UpdateTags( bool bSynchron )
900 if( bSynchron )
902 if( mnUpdateTagsEvent )
903 Application::RemoveUserEvent( mnUpdateTagsEvent );
905 UpdateTagsHdl(nullptr);
907 else
909 if( !mnUpdateTagsEvent && mxView.is() )
910 mnUpdateTagsEvent = Application::PostUserEvent( LINK( this, AnnotationManagerImpl, UpdateTagsHdl ) );
914 IMPL_LINK_NOARG(AnnotationManagerImpl, UpdateTagsHdl, void*, void)
916 mnUpdateTagsEvent = nullptr;
917 DisposeTags();
919 if( mbShowAnnotations )
920 CreateTags();
922 if( mrBase.GetDrawView() )
923 static_cast< ::sd::View* >( mrBase.GetDrawView() )->updateHandles();
925 invalidateSlots();
928 void AnnotationManagerImpl::CreateTags()
931 if( !(mxCurrentPage.is() && mpDoc) )
932 return;
936 int nIndex = 1;
937 maFont = Application::GetSettings().GetStyleSettings().GetAppFont();
939 rtl::Reference< AnnotationTag > xSelectedTag;
941 Reference< XAnnotationEnumeration > xEnum( mxCurrentPage->createAnnotationEnumeration() );
942 while( xEnum->hasMoreElements() )
944 Reference< XAnnotation > xAnnotation( xEnum->nextElement() );
945 Color aColor( GetColorLight( mpDoc->GetAnnotationAuthorIndex( xAnnotation->getAuthor() ) ) );
946 rtl::Reference< AnnotationTag > xTag( new AnnotationTag( *this, *mrBase.GetMainViewShell()->GetView(), xAnnotation, aColor, nIndex++, maFont ) );
947 maTagVector.push_back(xTag);
949 if( xAnnotation == mxSelectedAnnotation )
951 xSelectedTag = xTag;
955 if( xSelectedTag.is() )
957 SmartTagReference xTag( xSelectedTag.get() );
958 mrBase.GetMainViewShell()->GetView()->getSmartTags().select( xTag );
960 else
962 // no tag, no selection!
963 mxSelectedAnnotation.clear();
966 catch( Exception& )
968 OSL_FAIL( "sd::AnnotationManagerImpl::onSelectionChanged(), exception caught!" );
972 void AnnotationManagerImpl::DisposeTags()
974 for (auto& rxTag : maTagVector)
976 rxTag->Dispose();
979 maTagVector.clear();
982 void AnnotationManagerImpl::addListener()
984 Link<tools::EventMultiplexerEvent&,void> aLink( LINK(this,AnnotationManagerImpl,EventMultiplexerListener) );
985 mrBase.GetEventMultiplexer()->AddEventListener(aLink);
988 void AnnotationManagerImpl::removeListener()
990 Link<tools::EventMultiplexerEvent&,void> aLink( LINK(this,AnnotationManagerImpl,EventMultiplexerListener) );
991 mrBase.GetEventMultiplexer()->RemoveEventListener( aLink );
994 IMPL_LINK(AnnotationManagerImpl,EventMultiplexerListener,
995 tools::EventMultiplexerEvent&, rEvent, void)
997 switch (rEvent.meEventId)
999 case EventMultiplexerEventId::CurrentPageChanged:
1000 case EventMultiplexerEventId::EditViewSelection:
1001 onSelectionChanged();
1002 break;
1004 case EventMultiplexerEventId::MainViewRemoved:
1005 mxView.clear();
1006 onSelectionChanged();
1007 break;
1009 case EventMultiplexerEventId::MainViewAdded:
1010 mxView.set( mrBase.GetController(), UNO_QUERY );
1011 onSelectionChanged();
1012 break;
1014 default: break;
1018 namespace
1020 sal_uInt16 IdentToSID(const OString& rIdent)
1022 if (rIdent == "reply")
1023 return SID_REPLYTO_POSTIT;
1024 else if (rIdent == "delete")
1025 return SID_DELETE_POSTIT;
1026 else if (rIdent == "deleteby")
1027 return SID_DELETEALLBYAUTHOR_POSTIT;
1028 else if (rIdent == "deleteall")
1029 return SID_DELETEALL_POSTIT;
1030 else if (rIdent == "copy")
1031 return SID_COPY;
1032 else if (rIdent == "paste")
1033 return SID_PASTE;
1034 else if (rIdent == "bold")
1035 return SID_ATTR_CHAR_WEIGHT;
1036 else if (rIdent == "italic")
1037 return SID_ATTR_CHAR_POSTURE;
1038 else if (rIdent == "underline")
1039 return SID_ATTR_CHAR_UNDERLINE;
1040 else if (rIdent == "strike")
1041 return SID_ATTR_CHAR_STRIKEOUT;
1042 return 0;
1046 void AnnotationManagerImpl::ExecuteAnnotationContextMenu( const Reference< XAnnotation >& xAnnotation, vcl::Window* pParent, const ::tools::Rectangle& rContextRect, bool bButtonMenu /* = false */ )
1048 SfxDispatcher* pDispatcher( getDispatcher( mrBase ) );
1049 if( !pDispatcher )
1050 return;
1052 const bool bReadOnly = mrBase.GetDocShell()->IsReadOnly();
1054 AnnotationWindow* pAnnotationWindow = bButtonMenu ? nullptr : dynamic_cast< AnnotationWindow* >( pParent );
1056 if( bReadOnly && !pAnnotationWindow )
1057 return;
1059 OUString sUIFile;
1060 if (pAnnotationWindow)
1061 sUIFile = "modules/simpress/ui/annotationmenu.ui";
1062 else
1063 sUIFile = "modules/simpress/ui/annotationtagmenu.ui";
1064 VclBuilder aBuilder(nullptr, VclBuilderContainer::getUIRootDir(), sUIFile, "");
1065 VclPtr<PopupMenu> pMenu(aBuilder.get_menu("menu"));
1067 SvtUserOptions aUserOptions;
1068 OUString sCurrentAuthor( aUserOptions.GetFullName() );
1069 OUString sAuthor( xAnnotation->getAuthor() );
1071 OUString aStr(pMenu->GetItemText(pMenu->GetItemId("deleteby")));
1072 OUString aReplace( sAuthor );
1073 if( aReplace.isEmpty() )
1074 aReplace = SdResId( STR_ANNOTATION_NOAUTHOR );
1075 aStr = aStr.replaceFirst("%1", aReplace);
1076 pMenu->SetItemText(pMenu->GetItemId("deleteby"), aStr);
1077 pMenu->EnableItem(pMenu->GetItemId("reply"), (sAuthor != sCurrentAuthor) && !bReadOnly);
1078 pMenu->EnableItem(pMenu->GetItemId("delete"), xAnnotation.is() && !bReadOnly);
1079 pMenu->EnableItem(pMenu->GetItemId("deleteby"), !bReadOnly);
1080 pMenu->EnableItem(pMenu->GetItemId("deleteall"), !bReadOnly);
1082 if( pAnnotationWindow )
1084 if( pAnnotationWindow->IsProtected() || bReadOnly )
1086 pMenu->EnableItem(pMenu->GetItemId("bold"), false);
1087 pMenu->EnableItem(pMenu->GetItemId("italic"), false);
1088 pMenu->EnableItem(pMenu->GetItemId("underline"), false);
1089 pMenu->EnableItem(pMenu->GetItemId("strike"), false);
1090 pMenu->EnableItem(pMenu->GetItemId("paste"), false);
1092 else
1094 SfxItemSet aSet(pAnnotationWindow->getView()->GetAttribs());
1096 if ( aSet.GetItemState( EE_CHAR_WEIGHT ) == SfxItemState::SET )
1098 if( aSet.Get( EE_CHAR_WEIGHT ).GetWeight() == WEIGHT_BOLD )
1099 pMenu->CheckItem("bold");
1102 if ( aSet.GetItemState( EE_CHAR_ITALIC ) == SfxItemState::SET )
1104 if( aSet.Get( EE_CHAR_ITALIC ).GetPosture() != ITALIC_NONE )
1105 pMenu->CheckItem("italic");
1108 if ( aSet.GetItemState( EE_CHAR_UNDERLINE ) == SfxItemState::SET )
1110 if( aSet.Get( EE_CHAR_UNDERLINE ).GetLineStyle() != LINESTYLE_NONE )
1111 pMenu->CheckItem("underline");
1114 if ( aSet.GetItemState( EE_CHAR_STRIKEOUT ) == SfxItemState::SET )
1116 if( aSet.Get( EE_CHAR_STRIKEOUT ).GetStrikeout() != STRIKEOUT_NONE )
1117 pMenu->CheckItem("strike");
1119 TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromSystemClipboard( pAnnotationWindow ) );
1120 pMenu->EnableItem(pMenu->GetItemId("paste"), aDataHelper.GetFormatCount() != 0);
1123 pMenu->EnableItem(pMenu->GetItemId("copy"), pAnnotationWindow->getView()->HasSelection());
1126 // set slot images
1127 Reference< css::frame::XFrame > xFrame( mrBase.GetMainViewShell()->GetViewFrame()->GetFrame().GetFrameInterface() );
1128 if( xFrame.is() )
1130 for( sal_uInt16 nPos = 0; nPos < pMenu->GetItemCount(); nPos++ )
1132 sal_uInt16 nId = pMenu->GetItemId( nPos );
1133 if (!pMenu->IsItemEnabled(nId))
1134 continue;
1136 Image aImage( vcl::CommandInfoProvider::GetImageForCommand( pMenu->GetItemCommand( nId ), xFrame ) );
1137 if( !!aImage )
1138 pMenu->SetItemImage( nId, aImage );
1142 // tdf#99388 and tdf#99712 make known that PopupMenu is active at parent to
1143 // allow suppressing closing of that window if needed
1144 setPopupMenuActive(true);
1146 sal_uInt16 nId = pMenu->Execute( pParent, rContextRect, PopupMenuFlags::ExecuteDown|PopupMenuFlags::NoMouseUpClose );
1147 nId = IdentToSID(pMenu->GetItemIdent(nId));
1149 // tdf#99388 and tdf#99712 reset flag, need to be done before reacting
1150 // since closing it is one possible reaction
1151 setPopupMenuActive(false);
1153 switch( nId )
1155 case SID_REPLYTO_POSTIT:
1157 const SfxUnoAnyItem aItem( SID_REPLYTO_POSTIT, Any( xAnnotation ) );
1158 pDispatcher->ExecuteList(SID_REPLYTO_POSTIT,
1159 SfxCallMode::ASYNCHRON, { &aItem });
1160 break;
1162 case SID_DELETE_POSTIT:
1164 const SfxUnoAnyItem aItem( SID_DELETE_POSTIT, Any( xAnnotation ) );
1165 pDispatcher->ExecuteList(SID_DELETE_POSTIT, SfxCallMode::ASYNCHRON,
1166 { &aItem });
1167 break;
1169 case SID_DELETEALLBYAUTHOR_POSTIT:
1171 const SfxStringItem aItem( SID_DELETEALLBYAUTHOR_POSTIT, sAuthor );
1172 pDispatcher->ExecuteList( SID_DELETEALLBYAUTHOR_POSTIT,
1173 SfxCallMode::ASYNCHRON, { &aItem });
1174 break;
1176 case SID_DELETEALL_POSTIT:
1177 pDispatcher->Execute( SID_DELETEALL_POSTIT );
1178 break;
1179 case SID_COPY:
1180 case SID_PASTE:
1181 case SID_ATTR_CHAR_WEIGHT:
1182 case SID_ATTR_CHAR_POSTURE:
1183 case SID_ATTR_CHAR_UNDERLINE:
1184 case SID_ATTR_CHAR_STRIKEOUT:
1185 if( pAnnotationWindow )
1186 pAnnotationWindow->ExecuteSlot( nId );
1187 break;
1191 Color AnnotationManagerImpl::GetColor(sal_uInt16 aAuthorIndex)
1193 if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode())
1195 static const Color aArrayNormal[] = {
1196 COL_AUTHOR1_NORMAL, COL_AUTHOR2_NORMAL, COL_AUTHOR3_NORMAL,
1197 COL_AUTHOR4_NORMAL, COL_AUTHOR5_NORMAL, COL_AUTHOR6_NORMAL,
1198 COL_AUTHOR7_NORMAL, COL_AUTHOR8_NORMAL, COL_AUTHOR9_NORMAL };
1200 return aArrayNormal[ aAuthorIndex % SAL_N_ELEMENTS( aArrayNormal ) ];
1203 return COL_WHITE;
1206 Color AnnotationManagerImpl::GetColorLight(sal_uInt16 aAuthorIndex)
1208 if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode())
1210 static const Color aArrayLight[] = {
1211 COL_AUTHOR1_LIGHT, COL_AUTHOR2_LIGHT, COL_AUTHOR3_LIGHT,
1212 COL_AUTHOR4_LIGHT, COL_AUTHOR5_LIGHT, COL_AUTHOR6_LIGHT,
1213 COL_AUTHOR7_LIGHT, COL_AUTHOR8_LIGHT, COL_AUTHOR9_LIGHT };
1215 return aArrayLight[ aAuthorIndex % SAL_N_ELEMENTS( aArrayLight ) ];
1218 return COL_WHITE;
1221 Color AnnotationManagerImpl::GetColorDark(sal_uInt16 aAuthorIndex)
1223 if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode())
1225 static const Color aArrayAnkor[] = {
1226 COL_AUTHOR1_DARK, COL_AUTHOR2_DARK, COL_AUTHOR3_DARK,
1227 COL_AUTHOR4_DARK, COL_AUTHOR5_DARK, COL_AUTHOR6_DARK,
1228 COL_AUTHOR7_DARK, COL_AUTHOR8_DARK, COL_AUTHOR9_DARK };
1230 return aArrayAnkor[ aAuthorIndex % SAL_N_ELEMENTS( aArrayAnkor ) ];
1233 return COL_WHITE;
1236 SdPage* AnnotationManagerImpl::GetNextPage( SdPage const * pPage, bool bForward )
1238 if( pPage == nullptr )
1240 if (bForward)
1241 return mpDoc->GetSdPage(0, PageKind::Standard ); // first page
1242 else
1243 return mpDoc->GetMasterSdPage( mpDoc->GetMasterSdPageCount(PageKind::Standard) - 1, PageKind::Standard ); // last page
1246 sal_uInt16 nPageNum = (pPage->GetPageNum() - 1) >> 1;
1248 // first all non master pages
1249 if( !pPage->IsMasterPage() )
1251 if( bForward )
1253 if( nPageNum >= mpDoc->GetSdPageCount(PageKind::Standard)-1 )
1255 // we reached end of draw pages, start with master pages (skip handout master for draw)
1256 return mpDoc->GetMasterSdPage( (mpDoc->GetDocumentType() == DocumentType::Impress) ? 0 : 1, PageKind::Standard );
1258 nPageNum++;
1260 else
1262 if( nPageNum == 0 )
1263 return nullptr; // we are already on the first draw page, finished
1265 nPageNum--;
1267 return mpDoc->GetSdPage(nPageNum, PageKind::Standard);
1269 else
1271 if( bForward )
1273 if( nPageNum >= mpDoc->GetMasterSdPageCount(PageKind::Standard)-1 )
1275 return nullptr; // we reached the end, there is nothing more to see here
1277 nPageNum++;
1279 else
1281 if( nPageNum == (mpDoc->GetDocumentType() == DocumentType::Impress ? 0 : 1) )
1283 // we reached beginning of master pages, start with end if pages
1284 return mpDoc->GetSdPage( mpDoc->GetSdPageCount(PageKind::Standard)-1, PageKind::Standard );
1287 nPageNum--;
1289 return mpDoc->GetMasterSdPage(nPageNum,PageKind::Standard);
1293 SdPage* AnnotationManagerImpl::GetCurrentPage()
1295 if (mrBase.GetMainViewShell().get())
1296 return mrBase.GetMainViewShell()->getCurrentPage();
1297 return nullptr;
1300 AnnotationManager::AnnotationManager( ViewShellBase& rViewShellBase )
1301 : mxImpl( new AnnotationManagerImpl( rViewShellBase ) )
1303 mxImpl->init();
1306 AnnotationManager::~AnnotationManager()
1308 mxImpl->dispose();
1311 void AnnotationManager::ExecuteAnnotation(SfxRequest const & rRequest)
1313 mxImpl->ExecuteAnnotation( rRequest );
1316 void AnnotationManager::GetAnnotationState(SfxItemSet& rItemSet)
1318 mxImpl->GetAnnotationState(rItemSet);
1323 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */