diff options
Diffstat (limited to 'sd/source/ui/annotations')
-rw-r--r-- | sd/source/ui/annotations/annotationmanager.cxx | 1220 | ||||
-rw-r--r-- | sd/source/ui/annotations/annotationmanagerimpl.hxx | 141 | ||||
-rw-r--r-- | sd/source/ui/annotations/annotationtag.cxx | 662 | ||||
-rw-r--r-- | sd/source/ui/annotations/annotationtag.hxx | 89 | ||||
-rw-r--r-- | sd/source/ui/annotations/annotationwindow.cxx | 802 | ||||
-rw-r--r-- | sd/source/ui/annotations/annotationwindow.hxx | 143 |
6 files changed, 3057 insertions, 0 deletions
diff --git a/sd/source/ui/annotations/annotationmanager.cxx b/sd/source/ui/annotations/annotationmanager.cxx new file mode 100644 index 000000000..ab9fe0c1a --- /dev/null +++ b/sd/source/ui/annotations/annotationmanager.cxx @@ -0,0 +1,1220 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/drawing/XDrawView.hpp> +#include <com/sun/star/frame/XController.hpp> +#include <com/sun/star/geometry/RealPoint2D.hpp> +#include <com/sun/star/text/XText.hpp> +#include <com/sun/star/document/XEventBroadcaster.hpp> +#include <com/sun/star/office/XAnnotationAccess.hpp> +#include <comphelper/lok.hxx> +#include <svx/svxids.hrc> + +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +#include <sal/macros.h> +#include <svl/itempool.hxx> +#include <svl/intitem.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/useroptions.hxx> +#include <unotools/syslocale.hxx> +#include <unotools/saveopt.hxx> + +#include <tools/datetime.hxx> +#include <tools/UnitConversion.hxx> +#include <tools/diagnose_ex.h> + +#include <sfx2/viewfrm.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/request.hxx> +#include <sfx2/dispatch.hxx> + +#include <editeng/editeng.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/postitem.hxx> + +#include <svx/postattr.hxx> + +#include <annotationmanager.hxx> +#include "annotationmanagerimpl.hxx" +#include "annotationwindow.hxx" +#include <strings.hrc> + +#include <Annotation.hxx> +#include <DrawDocShell.hxx> +#include <DrawViewShell.hxx> +#include <sdresid.hxx> +#include <EventMultiplexer.hxx> +#include <ViewShellBase.hxx> +#include <sdpage.hxx> +#include <drawdoc.hxx> +#include <textapi.hxx> +#include <optsitem.hxx> +#include <sdmod.hxx> + +#include <memory> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::drawing; +using namespace ::com::sun::star::document; +using namespace ::com::sun::star::geometry; +using namespace ::com::sun::star::container; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::text; +using namespace ::com::sun::star::view; +using namespace ::com::sun::star::style; +using namespace ::com::sun::star::frame; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::ui; +using namespace ::com::sun::star::task; +using namespace ::com::sun::star::office; + +namespace sd { + +SfxItemPool* GetAnnotationPool() +{ + static rtl::Reference<SfxItemPool> s_pAnnotationPool; + if( !s_pAnnotationPool ) + { + s_pAnnotationPool = EditEngine::CreatePool(); + s_pAnnotationPool->SetPoolDefaultItem(SvxFontHeightItem(423,100,EE_CHAR_FONTHEIGHT)); + + vcl::Font aAppFont( Application::GetSettings().GetStyleSettings().GetAppFont() ); + s_pAnnotationPool->SetPoolDefaultItem(SvxFontItem(aAppFont.GetFamilyType(),aAppFont.GetFamilyName(),"",PITCH_DONTKNOW,RTL_TEXTENCODING_DONTKNOW,EE_CHAR_FONTINFO)); + } + + return s_pAnnotationPool.get(); +} + +static SfxBindings* getBindings( ViewShellBase const & rBase ) +{ + if( rBase.GetMainViewShell() && rBase.GetMainViewShell()->GetViewFrame() ) + return &rBase.GetMainViewShell()->GetViewFrame()->GetBindings(); + + return nullptr; +} + +static SfxDispatcher* getDispatcher( ViewShellBase const & rBase ) +{ + if( rBase.GetMainViewShell() && rBase.GetMainViewShell()->GetViewFrame() ) + return rBase.GetMainViewShell()->GetViewFrame()->GetDispatcher(); + + return nullptr; +} + +css::util::DateTime getCurrentDateTime() +{ + DateTime aCurrentDate( DateTime::SYSTEM ); + return css::util::DateTime( 0, aCurrentDate.GetSec(), + aCurrentDate.GetMin(), aCurrentDate.GetHour(), + aCurrentDate.GetDay(), aCurrentDate.GetMonth(), + aCurrentDate.GetYear(), false ); +} + +OUString getAnnotationDateTimeString( const Reference< XAnnotation >& xAnnotation ) +{ + OUString sRet; + if( xAnnotation.is() ) + { + const SvtSysLocale aSysLocale; + const LocaleDataWrapper& rLocalData = aSysLocale.GetLocaleData(); + + css::util::DateTime aDateTime( xAnnotation->getDateTime() ); + + Date aSysDate( Date::SYSTEM ); + Date aDate( aDateTime.Day, aDateTime.Month, aDateTime.Year ); + if (aDate==aSysDate) + sRet = SdResId(STR_ANNOTATION_TODAY); + else if (aDate == (aSysDate-1)) + sRet = SdResId(STR_ANNOTATION_YESTERDAY); + else if (aDate.IsValidAndGregorian() ) + sRet = rLocalData.getDate(aDate); + + ::tools::Time aTime( aDateTime ); + if(aTime.GetTime() != 0) + sRet += " " + rLocalData.getTime( aTime,false ); + } + return sRet; +} + +AnnotationManagerImpl::AnnotationManagerImpl( ViewShellBase& rViewShellBase ) +: mrBase( rViewShellBase ) +, mpDoc( rViewShellBase.GetDocument() ) +, mbShowAnnotations( true ) +, mnUpdateTagsEvent( nullptr ) +{ + SdOptions* pOptions = SD_MOD()->GetSdOptions(mpDoc->GetDocumentType()); + if( pOptions ) + mbShowAnnotations = pOptions->IsShowComments(); +} + +void AnnotationManagerImpl::init() +{ + // get current controller and initialize listeners + try + { + addListener(); + mxView.set(mrBase.GetController(), UNO_QUERY); + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION( "sd", "sd::AnnotationManagerImpl::AnnotationManagerImpl()" ); + } + + try + { + Reference<XEventBroadcaster> xModel (mrBase.GetDocShell()->GetModel(), UNO_QUERY_THROW ); + Reference<XEventListener> xListener( this ); + xModel->addEventListener( xListener ); + } + catch( Exception& ) + { + } +} + +// WeakComponentImplHelper +void AnnotationManagerImpl::disposing (std::unique_lock<std::mutex>&) +{ + try + { + Reference<XEventBroadcaster> xModel (mrBase.GetDocShell()->GetModel(), UNO_QUERY_THROW ); + Reference<XEventListener> xListener( this ); + xModel->removeEventListener( xListener ); + } + catch( Exception& ) + { + } + + removeListener(); + DisposeTags(); + + if( mnUpdateTagsEvent ) + { + Application::RemoveUserEvent( mnUpdateTagsEvent ); + mnUpdateTagsEvent = nullptr; + } + + mxView.clear(); + mxCurrentPage.clear(); +} + +// XEventListener +void SAL_CALL AnnotationManagerImpl::notifyEvent( const css::document::EventObject& aEvent ) +{ + if( !(aEvent.EventName == "OnAnnotationInserted" || aEvent.EventName == "OnAnnotationRemoved" || aEvent.EventName == "OnAnnotationChanged") ) + return; + + // AnnotationInsertion and modification is not handled here because when + // a new annotation is inserted, it consists of OnAnnotationInserted + // followed by a chain of OnAnnotationChanged (called for setting each + // of the annotation attributes - author, text etc.). This is not what a + // LOK client wants. So only handle removal here as annotation removal + // consists of only one event - 'OnAnnotationRemoved' + if ( aEvent.EventName == "OnAnnotationRemoved" ) + { + Reference< XAnnotation > xAnnotation( aEvent.Source, uno::UNO_QUERY ); + if ( xAnnotation.is() ) + { + LOKCommentNotify(CommentNotificationType::Remove, &mrBase, xAnnotation); + } + } + + UpdateTags(); +} + +void SAL_CALL AnnotationManagerImpl::disposing( const css::lang::EventObject& /*Source*/ ) +{ +} + +Reference<XAnnotation> AnnotationManagerImpl::GetAnnotationById(sal_uInt32 nAnnotationId) +{ + SdPage* pPage = nullptr; + do + { + pPage = GetNextPage(pPage, true); + if( pPage && !pPage->getAnnotations().empty() ) + { + AnnotationVector aAnnotations(pPage->getAnnotations()); + auto iter = std::find_if(aAnnotations.begin(), aAnnotations.end(), + [nAnnotationId](const Reference<XAnnotation>& xAnnotation) { + return sd::getAnnotationId(xAnnotation) == nAnnotationId; + }); + if (iter != aAnnotations.end()) + return *iter; + } + } while( pPage ); + + Reference<XAnnotation> xAnnotationEmpty; + return xAnnotationEmpty; +} + +void AnnotationManagerImpl::ShowAnnotations( bool bShow ) +{ + // enforce show annotations if a new annotation is inserted + if( mbShowAnnotations != bShow ) + { + mbShowAnnotations = bShow; + + SdOptions* pOptions = SD_MOD()->GetSdOptions(mpDoc->GetDocumentType()); + if( pOptions ) + pOptions->SetShowComments( mbShowAnnotations ); + + UpdateTags(); + } +} + +void AnnotationManagerImpl::ExecuteAnnotation(SfxRequest const & rReq ) +{ + switch( rReq.GetSlot() ) + { + case SID_INSERT_POSTIT: + ExecuteInsertAnnotation( rReq ); + break; + case SID_DELETE_POSTIT: + case SID_DELETEALL_POSTIT: + case SID_DELETEALLBYAUTHOR_POSTIT: + ExecuteDeleteAnnotation( rReq ); + break; + case SID_EDIT_POSTIT: + ExecuteEditAnnotation( rReq ); + break; + case SID_PREVIOUS_POSTIT: + case SID_NEXT_POSTIT: + SelectNextAnnotation( rReq.GetSlot() == SID_NEXT_POSTIT ); + break; + case SID_REPLYTO_POSTIT: + ExecuteReplyToAnnotation( rReq ); + break; + case SID_TOGGLE_NOTES: + ShowAnnotations( !mbShowAnnotations ); + break; + } +} + +void AnnotationManagerImpl::ExecuteInsertAnnotation(SfxRequest const & rReq) +{ + if (!comphelper::LibreOfficeKit::isActive() || comphelper::LibreOfficeKit::isTiledAnnotations()) + ShowAnnotations(true); + + const SfxItemSet* pArgs = rReq.GetArgs(); + OUString sText; + if (pArgs) + { + if (const SfxStringItem* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_TEXT)) + { + sText = pPoolItem->GetValue(); + } + } + + InsertAnnotation(sText); +} + +void AnnotationManagerImpl::ExecuteDeleteAnnotation(SfxRequest const & rReq) +{ + ShowAnnotations( true ); + + const SfxItemSet* pArgs = rReq.GetArgs(); + + switch( rReq.GetSlot() ) + { + case SID_DELETEALL_POSTIT: + DeleteAllAnnotations(); + break; + case SID_DELETEALLBYAUTHOR_POSTIT: + if( pArgs ) + { + const SfxPoolItem* pPoolItem = nullptr; + if( SfxItemState::SET == pArgs->GetItemState( SID_DELETEALLBYAUTHOR_POSTIT, true, &pPoolItem ) ) + { + OUString sAuthor( static_cast<const SfxStringItem*>( pPoolItem )->GetValue() ); + DeleteAnnotationsByAuthor( sAuthor ); + } + } + break; + case SID_DELETE_POSTIT: + { + Reference< XAnnotation > xAnnotation; + sal_uInt32 nId = 0; + if( pArgs ) + { + const SfxPoolItem* pPoolItem = nullptr; + if( SfxItemState::SET == pArgs->GetItemState( SID_DELETE_POSTIT, true, &pPoolItem ) ) + static_cast<const SfxUnoAnyItem*>(pPoolItem)->GetValue() >>= xAnnotation; + if( SfxItemState::SET == pArgs->GetItemState( SID_ATTR_POSTIT_ID, true, &pPoolItem ) ) + nId = static_cast<const SvxPostItIdItem*>(pPoolItem)->GetValue().toUInt32(); + } + + if (nId != 0) + xAnnotation = GetAnnotationById(nId); + else if( !xAnnotation.is() ) + GetSelectedAnnotation( xAnnotation ); + + DeleteAnnotation( xAnnotation ); + } + break; + } + + UpdateTags(); +} + +void AnnotationManagerImpl::ExecuteEditAnnotation(SfxRequest const & rReq) +{ + const SfxItemSet* pArgs = rReq.GetArgs(); + Reference< XAnnotation > xAnnotation; + OUString sText; + sal_Int32 nPositionX = -1; + sal_Int32 nPositionY = -1; + + if (!pArgs) + return; + + if (mpDoc->IsUndoEnabled()) + mpDoc->BegUndo(SdResId(STR_ANNOTATION_UNDO_EDIT)); + + if (const SvxPostItIdItem* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_ID)) + { + sal_uInt32 nId = pPoolItem->GetValue().toUInt32(); + xAnnotation = GetAnnotationById(nId); + } + if (const SfxStringItem* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_TEXT)) + sText = pPoolItem->GetValue(); + + if (const SfxInt32Item* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_POSITION_X)) + nPositionX = pPoolItem->GetValue(); + + if (const SfxInt32Item* pPoolItem = pArgs->GetItemIfSet(SID_ATTR_POSTIT_POSITION_Y)) + nPositionY = pPoolItem->GetValue(); + + if (xAnnotation.is()) + { + CreateChangeUndo(xAnnotation); + + if (nPositionX >= 0 && nPositionY >= 0) + { + double fX = convertTwipToMm100(nPositionX) / 100.0; + double fY = convertTwipToMm100(nPositionY) / 100.0; + xAnnotation->setPosition({fX, fY}); + } + + if (!sText.isEmpty()) + { + // TODO: Not allow other authors to change others' comments ? + Reference<XText> xText(xAnnotation->getTextRange()); + xText->setString(sText); + } + + LOKCommentNotifyAll(CommentNotificationType::Modify, xAnnotation); + } + + if (mpDoc->IsUndoEnabled()) + mpDoc->EndUndo(); + + UpdateTags(true); +} + +void AnnotationManagerImpl::InsertAnnotation(const OUString& rText) +{ + SdPage* pPage = GetCurrentPage(); + if( !pPage ) + return; + + if( mpDoc->IsUndoEnabled() ) + mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_INSERT ) ); + + // find free space for new annotation + int y = 0, x = 0; + + AnnotationVector aAnnotations( pPage->getAnnotations() ); + if( !aAnnotations.empty() ) + { + const int page_width = pPage->GetSize().Width(); + const int width = 1000; + const int height = 800; + ::tools::Rectangle aTagRect; + + while( true ) + { + ::tools::Rectangle aNewRect( x, y, x + width - 1, y + height - 1 ); + bool bFree = true; + + for( const auto& rxAnnotation : aAnnotations ) + { + RealPoint2D aPoint( rxAnnotation->getPosition() ); + aTagRect.SetLeft( sal::static_int_cast< ::tools::Long >( aPoint.X * 100.0 ) ); + aTagRect.SetTop( sal::static_int_cast< ::tools::Long >( aPoint.Y * 100.0 ) ); + aTagRect.SetRight( aTagRect.Left() + width - 1 ); + aTagRect.SetBottom( aTagRect.Top() + height - 1 ); + + if( aNewRect.Overlaps( aTagRect ) ) + { + bFree = false; + break; + } + } + + if( !bFree ) + { + x += width; + if( x > page_width ) + { + x = 0; + y += height; + } + } + else + { + break; + } + } + } + + Reference< XAnnotation > xAnnotation; + pPage->createAnnotation( xAnnotation ); + + OUString sAuthor; + if (comphelper::LibreOfficeKit::isActive()) + sAuthor = mrBase.GetMainViewShell()->GetView()->GetAuthor(); + else + { + SvtUserOptions aUserOptions; + sAuthor = aUserOptions.GetFullName(); + xAnnotation->setInitials( aUserOptions.GetID() ); + } + + if (!rText.isEmpty()) + { + Reference<XText> xText(xAnnotation->getTextRange()); + xText->setString(rText); + } + + // set current author to new annotation + xAnnotation->setAuthor( sAuthor ); + // set current time to new annotation + xAnnotation->setDateTime( getCurrentDateTime() ); + + // set position + RealPoint2D aPos( static_cast<double>(x) / 100.0, static_cast<double>(y) / 100.0 ); + xAnnotation->setPosition( aPos ); + + if( mpDoc->IsUndoEnabled() ) + mpDoc->EndUndo(); + + // Tell our LOK clients about new comment added + LOKCommentNotifyAll(CommentNotificationType::Add, xAnnotation); + + UpdateTags(true); + SelectAnnotation( xAnnotation, true ); +} + +void AnnotationManagerImpl::ExecuteReplyToAnnotation( SfxRequest const & rReq ) +{ + Reference< XAnnotation > xAnnotation; + const SfxItemSet* pArgs = rReq.GetArgs(); + OUString sReplyText; + if( pArgs ) + { + const SfxPoolItem* pPoolItem = nullptr; + if( SfxItemState::SET == pArgs->GetItemState( SID_ATTR_POSTIT_ID, true, &pPoolItem ) ) + { + sal_uInt32 nReplyId = 0; // Id of the comment to reply to + nReplyId = static_cast<const SvxPostItIdItem*>(pPoolItem)->GetValue().toUInt32(); + xAnnotation = GetAnnotationById(nReplyId); + } + else if( SfxItemState::SET == pArgs->GetItemState( rReq.GetSlot(), true, &pPoolItem ) ) + static_cast<const SfxUnoAnyItem*>( pPoolItem )->GetValue() >>= xAnnotation; + + if( SfxItemState::SET == pArgs->GetItemState( SID_ATTR_POSTIT_TEXT, true, &pPoolItem ) ) + sReplyText = static_cast<const SvxPostItTextItem*>( pPoolItem )->GetValue(); + } + + TextApiObject* pTextApi = getTextApiObject( xAnnotation ); + if( !pTextApi ) + return; + + ::Outliner aOutliner( GetAnnotationPool(),OutlinerMode::TextObject ); + + SdDrawDocument::SetCalcFieldValueHdl( &aOutliner ); + aOutliner.SetUpdateLayout( true ); + + OUString aStr(SdResId(STR_ANNOTATION_REPLY)); + OUString sAuthor( xAnnotation->getAuthor() ); + if( sAuthor.isEmpty() ) + sAuthor = SdResId( STR_ANNOTATION_NOAUTHOR ); + + aStr = aStr.replaceFirst("%1", sAuthor) + + " (" + getAnnotationDateTimeString( xAnnotation ) + "): \""; + + OUString sQuote( pTextApi->GetText() ); + + if( sQuote.isEmpty() ) + sQuote = "..."; + aStr += sQuote + "\"\n"; + + for( sal_Int32 nIdx = 0; nIdx >= 0; ) + aOutliner.Insert( aStr.getToken( 0, '\n', nIdx ), EE_PARA_APPEND, -1 ); + + if( aOutliner.GetParagraphCount() > 1 ) + { + SfxItemSet aAnswerSet( aOutliner.GetEmptyItemSet() ); + aAnswerSet.Put(SvxPostureItem(ITALIC_NORMAL,EE_CHAR_ITALIC)); + + ESelection aSel; + aSel.nEndPara = aOutliner.GetParagraphCount()-2; + aSel.nEndPos = aOutliner.GetText( aOutliner.GetParagraph( aSel.nEndPara ) ).getLength(); + + aOutliner.QuickSetAttribs( aAnswerSet, aSel ); + } + + if (!sReplyText.isEmpty()) + aOutliner.Insert(sReplyText); + + std::optional< OutlinerParaObject > pOPO( aOutliner.CreateParaObject() ); + pTextApi->SetText(*pOPO); + + OUString sReplyAuthor; + if (comphelper::LibreOfficeKit::isActive()) + sReplyAuthor = mrBase.GetMainViewShell()->GetView()->GetAuthor(); + else + { + SvtUserOptions aUserOptions; + sReplyAuthor = aUserOptions.GetFullName(); + xAnnotation->setInitials( aUserOptions.GetID() ); + } + + xAnnotation->setAuthor( sReplyAuthor ); + // set current time to reply + xAnnotation->setDateTime( getCurrentDateTime() ); + + // Tell our LOK clients about this (comment modification) + LOKCommentNotifyAll(CommentNotificationType::Modify, xAnnotation); + + UpdateTags(true); + SelectAnnotation( xAnnotation, true ); +} + +void AnnotationManagerImpl::DeleteAnnotation( const Reference< XAnnotation >& xAnnotation ) +{ + SdPage* pPage = GetCurrentPage(); + + if( xAnnotation.is() && pPage ) + { + if( mpDoc->IsUndoEnabled() ) + mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) ); + + pPage->removeAnnotation( xAnnotation ); + + if( mpDoc->IsUndoEnabled() ) + mpDoc->EndUndo(); + + UpdateTags(); + } +} + +void AnnotationManagerImpl::DeleteAnnotationsByAuthor( std::u16string_view sAuthor ) +{ + if( mpDoc->IsUndoEnabled() ) + mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) ); + + SdPage* pPage = nullptr; + do + { + pPage = GetNextPage( pPage, true ); + + if( pPage && !pPage->getAnnotations().empty() ) + { + AnnotationVector aAnnotations( pPage->getAnnotations() ); + for( Reference< XAnnotation >& xAnnotation : aAnnotations ) + { + if( xAnnotation->getAuthor() == sAuthor ) + { + if( mxSelectedAnnotation == xAnnotation ) + mxSelectedAnnotation.clear(); + pPage->removeAnnotation( xAnnotation ); + } + } + } + } while( pPage ); + + if( mpDoc->IsUndoEnabled() ) + mpDoc->EndUndo(); +} + +void AnnotationManagerImpl::DeleteAllAnnotations() +{ + if( mpDoc->IsUndoEnabled() ) + mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_DELETE ) ); + + SdPage* pPage = nullptr; + do + { + pPage = GetNextPage( pPage, true ); + + if( pPage && !pPage->getAnnotations().empty() ) + { + + AnnotationVector aAnnotations( pPage->getAnnotations() ); + for( const auto& rxAnnotation : aAnnotations ) + { + pPage->removeAnnotation( rxAnnotation ); + } + } + } + while( pPage ); + + mxSelectedAnnotation.clear(); + + if( mpDoc->IsUndoEnabled() ) + mpDoc->EndUndo(); +} + +void AnnotationManagerImpl::GetAnnotationState(SfxItemSet& rSet) +{ + SdPage* pCurrentPage = GetCurrentPage(); + + const bool bReadOnly = mrBase.GetDocShell()->IsReadOnly(); + const bool bWrongPageKind = (pCurrentPage == nullptr) || (pCurrentPage->GetPageKind() != PageKind::Standard); + + const SvtSaveOptions::ODFSaneDefaultVersion nCurrentODFVersion( GetODFSaneDefaultVersion() ); + + if (bReadOnly || bWrongPageKind || (nCurrentODFVersion <= SvtSaveOptions::ODFSVER_012)) + rSet.DisableItem( SID_INSERT_POSTIT ); + + rSet.Put(SfxBoolItem(SID_TOGGLE_NOTES, mbShowAnnotations)); + + Reference< XAnnotation > xAnnotation; + GetSelectedAnnotation( xAnnotation ); + + // Don't disable these slot in case of LOK, as postit doesn't need to + // selected before doing an operation on it in LOK + if( (!xAnnotation.is() && !comphelper::LibreOfficeKit::isActive()) || bReadOnly ) + { + rSet.DisableItem( SID_DELETE_POSTIT ); + rSet.DisableItem( SID_EDIT_POSTIT ); + } + + SdPage* pPage = nullptr; + + bool bHasAnnotations = false; + do + { + pPage = GetNextPage( pPage, true ); + + if( pPage && !pPage->getAnnotations().empty() ) + bHasAnnotations = true; + } + while( pPage && !bHasAnnotations ); + + if( !bHasAnnotations || bReadOnly ) + { + rSet.DisableItem( SID_DELETEALL_POSTIT ); + } + + if( bWrongPageKind || !bHasAnnotations ) + { + rSet.DisableItem( SID_PREVIOUS_POSTIT ); + rSet.DisableItem( SID_NEXT_POSTIT ); + } +} + +void AnnotationManagerImpl::SelectNextAnnotation(bool bForward) +{ + ShowAnnotations( true ); + + Reference< XAnnotation > xCurrent; + GetSelectedAnnotation( xCurrent ); + SdPage* pPage = GetCurrentPage(); + if( !pPage ) + return; + + AnnotationVector aAnnotations( pPage->getAnnotations() ); + + if( bForward ) + { + if( xCurrent.is() ) + { + auto iter = std::find(aAnnotations.begin(), aAnnotations.end(), xCurrent); + if (iter != aAnnotations.end()) + { + ++iter; + if( iter != aAnnotations.end() ) + { + SelectAnnotation( *iter ); + return; + } + } + } + else if( !aAnnotations.empty() ) + { + SelectAnnotation( *(aAnnotations.begin()) ); + return; + } + } + else + { + if( xCurrent.is() ) + { + auto iter = std::find(aAnnotations.begin(), aAnnotations.end(), xCurrent); + if (iter != aAnnotations.end() && iter != aAnnotations.begin()) + { + --iter; + SelectAnnotation( *iter ); + return; + } + } + else if( !aAnnotations.empty() ) + { + AnnotationVector::iterator iter( aAnnotations.end() ); + SelectAnnotation( *(--iter) ); + return; + } + } + + mxSelectedAnnotation.clear(); + do + { + do + { + pPage = GetNextPage( pPage, bForward ); + + if( pPage && !pPage->getAnnotations().empty() ) + { + // switch to next/previous slide with annotations + std::shared_ptr<DrawViewShell> pDrawViewShell(std::dynamic_pointer_cast<DrawViewShell>(mrBase.GetMainViewShell())); + if (pDrawViewShell != nullptr) + { + pDrawViewShell->ChangeEditMode(pPage->IsMasterPage() ? EditMode::MasterPage : EditMode::Page, false); + pDrawViewShell->SwitchPage((pPage->GetPageNum() - 1) >> 1); + + SfxDispatcher* pDispatcher = getDispatcher( mrBase ); + if( pDispatcher ) + pDispatcher->Execute( bForward ? SID_NEXT_POSTIT : SID_PREVIOUS_POSTIT ); + + return; + } + } + } + while( pPage ); + + // The question text depends on the search direction. + bool bImpress = mpDoc->GetDocumentType() == DocumentType::Impress; + TranslateId pStringId; + if(bForward) + pStringId = bImpress ? STR_ANNOTATION_WRAP_FORWARD : STR_ANNOTATION_WRAP_FORWARD_DRAW; + else + pStringId = bImpress ? STR_ANNOTATION_WRAP_BACKWARD : STR_ANNOTATION_WRAP_BACKWARD_DRAW; + + // Pop up question box that asks the user whether to wrap around. + // The dialog is made modal with respect to the whole application. + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Question, VclButtonsType::YesNo, + SdResId(pStringId))); + xQueryBox->set_default_response(RET_YES); + if (xQueryBox->run() != RET_YES) + break; + } + while( true ); +} + +void AnnotationManagerImpl::onTagSelected( AnnotationTag const & rTag ) +{ + mxSelectedAnnotation = rTag.GetAnnotation(); + invalidateSlots(); +} + +void AnnotationManagerImpl::onTagDeselected( AnnotationTag const & rTag ) +{ + if( rTag.GetAnnotation() == mxSelectedAnnotation ) + { + mxSelectedAnnotation.clear(); + invalidateSlots(); + } +} + +void AnnotationManagerImpl::SelectAnnotation( const css::uno::Reference< css::office::XAnnotation >& xAnnotation, bool bEdit /* = sal_False */ ) +{ + mxSelectedAnnotation = xAnnotation; + + auto iter = std::find_if(maTagVector.begin(), maTagVector.end(), + [&xAnnotation](const rtl::Reference<AnnotationTag>& rxTag) { return rxTag->GetAnnotation() == xAnnotation; }); + if (iter != maTagVector.end()) + { + SmartTagReference xTag( *iter ); + mrBase.GetMainViewShell()->GetView()->getSmartTags().select( xTag ); + (*iter)->OpenPopup( bEdit ); + } +} + +void AnnotationManagerImpl::GetSelectedAnnotation( css::uno::Reference< css::office::XAnnotation >& xAnnotation ) +{ + xAnnotation = mxSelectedAnnotation; +} + +void AnnotationManagerImpl::invalidateSlots() +{ + SfxBindings* pBindings = getBindings( mrBase ); + if( pBindings ) + { + pBindings->Invalidate( SID_INSERT_POSTIT ); + pBindings->Invalidate( SID_DELETE_POSTIT ); + pBindings->Invalidate( SID_DELETEALL_POSTIT ); + pBindings->Invalidate( SID_PREVIOUS_POSTIT ); + pBindings->Invalidate( SID_NEXT_POSTIT ); + pBindings->Invalidate( SID_UNDO ); + pBindings->Invalidate( SID_REDO ); + } +} + +void AnnotationManagerImpl::onSelectionChanged() +{ + if( !(mxView.is() && mrBase.GetDrawView()) ) + return; + + try + { + Reference< XAnnotationAccess > xPage( mxView->getCurrentPage(), UNO_QUERY ); + + if( xPage != mxCurrentPage ) + { + mxCurrentPage = xPage; + + UpdateTags(true); + } + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION( "sd", "sd::AnnotationManagerImpl::onSelectionChanged()" ); + } +} + +void AnnotationManagerImpl::UpdateTags( bool bSynchron ) +{ + if( bSynchron ) + { + if( mnUpdateTagsEvent ) + Application::RemoveUserEvent( mnUpdateTagsEvent ); + + UpdateTagsHdl(nullptr); + } + else + { + if( !mnUpdateTagsEvent && mxView.is() ) + mnUpdateTagsEvent = Application::PostUserEvent( LINK( this, AnnotationManagerImpl, UpdateTagsHdl ) ); + } +} + +IMPL_LINK_NOARG(AnnotationManagerImpl, UpdateTagsHdl, void*, void) +{ + mnUpdateTagsEvent = nullptr; + DisposeTags(); + + if( mbShowAnnotations ) + CreateTags(); + + if( mrBase.GetDrawView() ) + static_cast< ::sd::View* >( mrBase.GetDrawView() )->updateHandles(); + + invalidateSlots(); +} + +void AnnotationManagerImpl::CreateTags() +{ + if( !(mxCurrentPage.is() && mpDoc) ) + return; + + auto xViewShell = mrBase.GetMainViewShell(); + if (!xViewShell) + return; + + try + { + int nIndex = 1; + maFont = Application::GetSettings().GetStyleSettings().GetAppFont(); + + rtl::Reference< AnnotationTag > xSelectedTag; + + Reference< XAnnotationEnumeration > xEnum( mxCurrentPage->createAnnotationEnumeration() ); + while( xEnum->hasMoreElements() ) + { + Reference< XAnnotation > xAnnotation( xEnum->nextElement() ); + Color aColor( GetColorLight( mpDoc->GetAnnotationAuthorIndex( xAnnotation->getAuthor() ) ) ); + rtl::Reference< AnnotationTag > xTag( new AnnotationTag( *this, *xViewShell->GetView(), xAnnotation, aColor, nIndex++, maFont ) ); + maTagVector.push_back(xTag); + + if( xAnnotation == mxSelectedAnnotation ) + { + xSelectedTag = xTag; + } + } + + if( xSelectedTag.is() ) + { + SmartTagReference xTag( xSelectedTag ); + mrBase.GetMainViewShell()->GetView()->getSmartTags().select( xTag ); + } + else + { + // no tag, no selection! + mxSelectedAnnotation.clear(); + } + } + catch( Exception& ) + { + TOOLS_WARN_EXCEPTION( "sd", "sd::AnnotationManagerImpl::onSelectionChanged()" ); + } +} + +void AnnotationManagerImpl::DisposeTags() +{ + for (auto& rxTag : maTagVector) + { + rxTag->Dispose(); + } + + maTagVector.clear(); +} + +void AnnotationManagerImpl::addListener() +{ + Link<tools::EventMultiplexerEvent&,void> aLink( LINK(this,AnnotationManagerImpl,EventMultiplexerListener) ); + mrBase.GetEventMultiplexer()->AddEventListener(aLink); +} + +void AnnotationManagerImpl::removeListener() +{ + Link<tools::EventMultiplexerEvent&,void> aLink( LINK(this,AnnotationManagerImpl,EventMultiplexerListener) ); + mrBase.GetEventMultiplexer()->RemoveEventListener( aLink ); +} + +IMPL_LINK(AnnotationManagerImpl,EventMultiplexerListener, + tools::EventMultiplexerEvent&, rEvent, void) +{ + switch (rEvent.meEventId) + { + case EventMultiplexerEventId::CurrentPageChanged: + case EventMultiplexerEventId::EditViewSelection: + onSelectionChanged(); + break; + + case EventMultiplexerEventId::MainViewRemoved: + mxView.clear(); + onSelectionChanged(); + break; + + case EventMultiplexerEventId::MainViewAdded: + mxView.set( mrBase.GetController(), UNO_QUERY ); + onSelectionChanged(); + break; + + default: break; + } +} + +void AnnotationManagerImpl::ExecuteAnnotationTagContextMenu(const Reference<XAnnotation>& xAnnotation, weld::Widget* pParent, const ::tools::Rectangle& rContextRect) +{ + SfxDispatcher* pDispatcher( getDispatcher( mrBase ) ); + if( !pDispatcher ) + return; + + const bool bReadOnly = mrBase.GetDocShell()->IsReadOnly(); + + if (bReadOnly) + return; + + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pParent, "modules/simpress/ui/annotationtagmenu.ui")); + std::unique_ptr<weld::Menu> xMenu(xBuilder->weld_menu("menu")); + + SvtUserOptions aUserOptions; + OUString sCurrentAuthor( aUserOptions.GetFullName() ); + OUString sAuthor( xAnnotation->getAuthor() ); + + OUString aStr(xMenu->get_label(".uno:DeleteAllAnnotationByAuthor")); + OUString aReplace( sAuthor ); + if( aReplace.isEmpty() ) + aReplace = SdResId( STR_ANNOTATION_NOAUTHOR ); + aStr = aStr.replaceFirst("%1", aReplace); + xMenu->set_label(".uno:DeleteAllAnnotationByAuthor", aStr); + + bool bShowReply = sAuthor != sCurrentAuthor; + xMenu->set_visible(".uno:ReplyToAnnotation", bShowReply); + xMenu->set_visible("separator", bShowReply); + xMenu->set_visible(".uno:DeleteAnnotation", xAnnotation.is()); + + auto sId = xMenu->popup_at_rect(pParent, rContextRect); + + if (sId == ".uno:ReplyToAnnotation") + { + const SfxUnoAnyItem aItem( SID_REPLYTO_POSTIT, Any( xAnnotation ) ); + pDispatcher->ExecuteList(SID_REPLYTO_POSTIT, + SfxCallMode::ASYNCHRON, { &aItem }); + } + else if (sId == ".uno:DeleteAnnotation") + { + const SfxUnoAnyItem aItem( SID_DELETE_POSTIT, Any( xAnnotation ) ); + pDispatcher->ExecuteList(SID_DELETE_POSTIT, SfxCallMode::ASYNCHRON, + { &aItem }); + } + else if (sId == ".uno:DeleteAllAnnotationByAuthor") + { + const SfxStringItem aItem( SID_DELETEALLBYAUTHOR_POSTIT, sAuthor ); + pDispatcher->ExecuteList( SID_DELETEALLBYAUTHOR_POSTIT, + SfxCallMode::ASYNCHRON, { &aItem }); + } + else if (sId == ".uno:DeleteAllAnnotation") + pDispatcher->Execute( SID_DELETEALL_POSTIT ); +} + +Color AnnotationManagerImpl::GetColor(sal_uInt16 aAuthorIndex) +{ + if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + { + static const Color aArrayNormal[] = { + COL_AUTHOR1_NORMAL, COL_AUTHOR2_NORMAL, COL_AUTHOR3_NORMAL, + COL_AUTHOR4_NORMAL, COL_AUTHOR5_NORMAL, COL_AUTHOR6_NORMAL, + COL_AUTHOR7_NORMAL, COL_AUTHOR8_NORMAL, COL_AUTHOR9_NORMAL }; + + return aArrayNormal[ aAuthorIndex % SAL_N_ELEMENTS( aArrayNormal ) ]; + } + + return COL_WHITE; +} + +Color AnnotationManagerImpl::GetColorLight(sal_uInt16 aAuthorIndex) +{ + if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + { + static const Color aArrayLight[] = { + COL_AUTHOR1_LIGHT, COL_AUTHOR2_LIGHT, COL_AUTHOR3_LIGHT, + COL_AUTHOR4_LIGHT, COL_AUTHOR5_LIGHT, COL_AUTHOR6_LIGHT, + COL_AUTHOR7_LIGHT, COL_AUTHOR8_LIGHT, COL_AUTHOR9_LIGHT }; + + return aArrayLight[ aAuthorIndex % SAL_N_ELEMENTS( aArrayLight ) ]; + } + + return COL_WHITE; +} + +Color AnnotationManagerImpl::GetColorDark(sal_uInt16 aAuthorIndex) +{ + if (!Application::GetSettings().GetStyleSettings().GetHighContrastMode()) + { + static const Color aArrayAnkor[] = { + COL_AUTHOR1_DARK, COL_AUTHOR2_DARK, COL_AUTHOR3_DARK, + COL_AUTHOR4_DARK, COL_AUTHOR5_DARK, COL_AUTHOR6_DARK, + COL_AUTHOR7_DARK, COL_AUTHOR8_DARK, COL_AUTHOR9_DARK }; + + return aArrayAnkor[ aAuthorIndex % SAL_N_ELEMENTS( aArrayAnkor ) ]; + } + + return COL_WHITE; +} + +SdPage* AnnotationManagerImpl::GetNextPage( SdPage const * pPage, bool bForward ) +{ + if( pPage == nullptr ) + { + if (bForward) + return mpDoc->GetSdPage(0, PageKind::Standard ); // first page + else + return mpDoc->GetMasterSdPage( mpDoc->GetMasterSdPageCount(PageKind::Standard) - 1, PageKind::Standard ); // last page + } + + sal_uInt16 nPageNum = (pPage->GetPageNum() - 1) >> 1; + + // first all non master pages + if( !pPage->IsMasterPage() ) + { + if( bForward ) + { + if( nPageNum >= mpDoc->GetSdPageCount(PageKind::Standard)-1 ) + { + // we reached end of draw pages, start with master pages (skip handout master for draw) + return mpDoc->GetMasterSdPage( (mpDoc->GetDocumentType() == DocumentType::Impress) ? 0 : 1, PageKind::Standard ); + } + nPageNum++; + } + else + { + if( nPageNum == 0 ) + return nullptr; // we are already on the first draw page, finished + + nPageNum--; + } + return mpDoc->GetSdPage(nPageNum, PageKind::Standard); + } + else + { + if( bForward ) + { + if( nPageNum >= mpDoc->GetMasterSdPageCount(PageKind::Standard)-1 ) + { + return nullptr; // we reached the end, there is nothing more to see here + } + nPageNum++; + } + else + { + if( nPageNum == (mpDoc->GetDocumentType() == DocumentType::Impress ? 0 : 1) ) + { + // we reached beginning of master pages, start with end if pages + return mpDoc->GetSdPage( mpDoc->GetSdPageCount(PageKind::Standard)-1, PageKind::Standard ); + } + + nPageNum--; + } + return mpDoc->GetMasterSdPage(nPageNum,PageKind::Standard); + } +} + +SdPage* AnnotationManagerImpl::GetCurrentPage() +{ + if (mrBase.GetMainViewShell()) + return mrBase.GetMainViewShell()->getCurrentPage(); + return nullptr; +} + +AnnotationManager::AnnotationManager( ViewShellBase& rViewShellBase ) +: mxImpl( new AnnotationManagerImpl( rViewShellBase ) ) +{ + mxImpl->init(); +} + +AnnotationManager::~AnnotationManager() +{ + mxImpl->dispose(); +} + +void AnnotationManager::ExecuteAnnotation(SfxRequest const & rRequest) +{ + mxImpl->ExecuteAnnotation( rRequest ); +} + +void AnnotationManager::GetAnnotationState(SfxItemSet& rItemSet) +{ + mxImpl->GetAnnotationState(rItemSet); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/annotations/annotationmanagerimpl.hxx b/sd/source/ui/annotations/annotationmanagerimpl.hxx new file mode 100644 index 000000000..c5871d90c --- /dev/null +++ b/sd/source/ui/annotations/annotationmanagerimpl.hxx @@ -0,0 +1,141 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/document/XEventListener.hpp> + +#include <rtl/ustring.hxx> + +#include <comphelper/compbase.hxx> + +#include "annotationtag.hxx" + +namespace com::sun::star::drawing { class XDrawView; } +namespace com::sun::star::office { class XAnnotationAccess; } +namespace com::sun::star::office { class XAnnotation; } + +class SfxRequest; +class SdPage; +class SdDrawDocument; +struct ImplSVEvent; + +namespace sd +{ + +class ViewShellBase; + +namespace tools { +class EventMultiplexerEvent; +} + +typedef comphelper::WeakComponentImplHelper < + css::document::XEventListener + > AnnotationManagerImplBase; + +class AnnotationManagerImpl : public AnnotationManagerImplBase +{ +public: + explicit AnnotationManagerImpl( ViewShellBase& rViewShellBase ); + + void init(); + + // WeakComponentImplHelper + virtual void disposing (std::unique_lock<std::mutex>&) override; + + // XEventListener + virtual void SAL_CALL notifyEvent( const css::document::EventObject& Event ) override; + virtual void SAL_CALL disposing( const css::lang::EventObject& Source ) override; + + void ExecuteAnnotation (SfxRequest const & rRequest); + void GetAnnotationState (SfxItemSet& rItemSet); + + void ExecuteInsertAnnotation(SfxRequest const & rReq); + void ExecuteDeleteAnnotation(SfxRequest const & rReq); + void ExecuteEditAnnotation(SfxRequest const & rReq); + void ExecuteReplyToAnnotation(SfxRequest const & rReq); + + void SelectNextAnnotation(bool bForward); + + void SelectAnnotation( const css::uno::Reference< css::office::XAnnotation >& xAnnotation, bool bEdit = false ); + void GetSelectedAnnotation( css::uno::Reference< css::office::XAnnotation >& xAnnotation ); + + void InsertAnnotation(const OUString& rText); + void DeleteAnnotation( const css::uno::Reference< css::office::XAnnotation >& xAnnotation ); + void DeleteAnnotationsByAuthor( std::u16string_view sAuthor ); + void DeleteAllAnnotations(); + + void ExecuteAnnotationTagContextMenu(const css::uno::Reference<css::office::XAnnotation>& xAnnotation, weld::Widget* pParent, const ::tools::Rectangle& rContextRect); + + static Color GetColorDark(sal_uInt16 aAuthorIndex); + static Color GetColorLight(sal_uInt16 aAuthorIndex); + static Color GetColor(sal_uInt16 aAuthorIndex); + + // callbacks + void onTagSelected( AnnotationTag const & rTag ); + void onTagDeselected( AnnotationTag const & rTag ); + + void onSelectionChanged(); + + void addListener(); + void removeListener(); + + void invalidateSlots(); + + DECL_LINK(EventMultiplexerListener, tools::EventMultiplexerEvent&, void); + DECL_LINK(UpdateTagsHdl, void *, void); + + void UpdateTags(bool bSynchron = false); + void CreateTags(); + void DisposeTags(); + + SdPage* GetNextPage( SdPage const * pPage, bool bForward ); + + SdPage* GetCurrentPage(); + + SdDrawDocument* GetDoc() { return mpDoc; } + + void ShowAnnotations(bool bShow); + +private: + ViewShellBase& mrBase; + SdDrawDocument* mpDoc; + + std::vector< rtl::Reference< AnnotationTag > > maTagVector; + + css::uno::Reference< css::drawing::XDrawView > mxView; + css::uno::Reference< css::office::XAnnotationAccess > mxCurrentPage; + css::uno::Reference< css::office::XAnnotation > mxSelectedAnnotation; + + bool mbShowAnnotations; + ImplSVEvent * mnUpdateTagsEvent; + vcl::Font maFont; + + css::uno::Reference<css::office::XAnnotation> GetAnnotationById(sal_uInt32 nAnnotationId); +}; + +OUString getAnnotationDateTimeString( const css::uno::Reference< css::office::XAnnotation >& xAnnotation ); + +SfxItemPool* GetAnnotationPool(); + +css::util::DateTime getCurrentDateTime(); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/annotations/annotationtag.cxx b/sd/source/ui/annotations/annotationtag.cxx new file mode 100644 index 000000000..cfd632dcc --- /dev/null +++ b/sd/source/ui/annotations/annotationtag.cxx @@ -0,0 +1,662 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/geometry/RealPoint2D.hpp> +#include <com/sun/star/office/XAnnotation.hpp> + +#include <rtl/ustrbuf.hxx> + +#include <vcl/commandevent.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/weldutils.hxx> + +#include <svx/sdr/overlay/overlayanimatedbitmapex.hxx> +#include <svx/sdr/overlay/overlaybitmapex.hxx> +#include <svx/sdr/overlay/overlaypolypolygon.hxx> +#include <svx/svdpagv.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <svx/svddrgmt.hxx> +#include <tools/debug.hxx> + +#include <View.hxx> +#include <sdresid.hxx> +#include <strings.hrc> +#include "annotationmanagerimpl.hxx" +#include "annotationwindow.hxx" +#include "annotationtag.hxx" +#include <Annotation.hxx> +#include <ViewShell.hxx> +#include <Window.hxx> +#include <drawdoc.hxx> + +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::lang; +using namespace ::com::sun::star::drawing; +using namespace ::com::sun::star::office; +using namespace ::com::sun::star::geometry; + +namespace sd +{ + +const sal_uInt32 SMART_TAG_HDL_NUM = SAL_MAX_UINT32; +const int DRGPIX = 2; // Drag MinMove in Pixel + +static OUString getInitials( const OUString& rName ) +{ + OUStringBuffer sInitials; + + const sal_Unicode * pStr = rName.getStr(); + sal_Int32 nLength = rName.getLength(); + + while( nLength ) + { + // skip whitespace + while( nLength && (*pStr <= ' ') ) + { + nLength--; pStr++; + } + + // take letter + if( nLength ) + { + sInitials.append(*pStr); + nLength--; pStr++; + } + + // skip letters until whitespace + while( nLength && (*pStr > ' ') ) + { + nLength--; pStr++; + } + } + + return sInitials.makeStringAndClear(); +} + +namespace { + +class AnnotationDragMove : public SdrDragMove +{ +public: + AnnotationDragMove(SdrDragView& rNewView, const rtl::Reference <AnnotationTag >& xTag); + virtual bool BeginSdrDrag() override; + virtual bool EndSdrDrag(bool bCopy) override; + virtual void MoveSdrDrag(const Point& rNoSnapPnt) override; + virtual void CancelSdrDrag() override; + +private: + rtl::Reference <AnnotationTag > mxTag; + Point maOrigin; +}; + +} + +AnnotationDragMove::AnnotationDragMove(SdrDragView& rNewView, const rtl::Reference <AnnotationTag >& xTag) +: SdrDragMove(rNewView) +, mxTag( xTag ) +{ +} + +bool AnnotationDragMove::BeginSdrDrag() +{ + DragStat().SetRef1(GetDragHdl()->GetPos()); + DragStat().SetShown(!DragStat().IsShown()); + + maOrigin = GetDragHdl()->GetPos(); + DragStat().SetActionRect(::tools::Rectangle(maOrigin,maOrigin)); + + return true; +} + +void AnnotationDragMove::MoveSdrDrag(const Point& rNoSnapPnt) +{ + Point aPnt(rNoSnapPnt); + + if (DragStat().CheckMinMoved(rNoSnapPnt)) + { + if (aPnt!=DragStat().GetNow()) + { + Hide(); + DragStat().NextMove(aPnt); + GetDragHdl()->SetPos( maOrigin + Point( DragStat().GetDX(), DragStat().GetDY() ) ); + Show(); + DragStat().SetActionRect(::tools::Rectangle(aPnt,aPnt)); + } + } +} + +bool AnnotationDragMove::EndSdrDrag(bool /*bCopy*/) +{ + Hide(); + if( mxTag.is() ) + mxTag->Move( DragStat().GetDX(), DragStat().GetDY() ); + return true; +} + +void AnnotationDragMove::CancelSdrDrag() +{ + Hide(); +} + +namespace { + +class AnnotationHdl : public SmartHdl +{ +public: + AnnotationHdl( const SmartTagReference& xTag, const Reference< XAnnotation >& xAnnotation, const Point& rPnt ); + + virtual void CreateB2dIAObject() override; + virtual bool IsFocusHdl() const override; + +private: + Reference< XAnnotation > mxAnnotation; + rtl::Reference< AnnotationTag > mxTag; +}; + +} + +AnnotationHdl::AnnotationHdl( const SmartTagReference& xTag, const Reference< XAnnotation >& xAnnotation, const Point& rPnt ) +: SmartHdl( xTag, rPnt, SdrHdlKind::SmartTag ) +, mxAnnotation( xAnnotation ) +, mxTag( dynamic_cast< AnnotationTag* >( xTag.get() ) ) +{ +} + +void AnnotationHdl::CreateB2dIAObject() +{ + // first throw away old one + GetRidOfIAObject(); + + if (!mxAnnotation.is()) + return; + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + + const Point aTagPos( GetPos() ); + basegfx::B2DPoint aPosition( aTagPos.X(), aTagPos.Y() ); + + const bool bFocused = IsFocusHdl() && pHdlList && (pHdlList->GetFocusHdl() == this); + + BitmapEx aBitmapEx( mxTag->CreateAnnotationBitmap(mxTag->isSelected()) ); + BitmapEx aBitmapEx2; + if( bFocused ) + aBitmapEx2 = mxTag->CreateAnnotationBitmap(!mxTag->isSelected() ); + + if(!pHdlList) + return; + + SdrMarkView* pView = pHdlList->GetView(); + + if(!pView || pView->areMarkHandlesHidden()) + return; + + SdrPageView* pPageView = pView->GetSdrPageView(); + + if(!pPageView) + return; + + for(sal_uInt32 b = 0; b < pPageView->PageWindowCount(); b++) + { + // const SdrPageViewWinRec& rPageViewWinRec = rPageViewWinList[b]; + const SdrPageWindow& rPageWindow = *pPageView->GetPageWindow(b); + + SdrPaintWindow& rPaintWindow = rPageWindow.GetPaintWindow(); + const rtl::Reference< sdr::overlay::OverlayManager >& xManager = rPageWindow.GetOverlayManager(); + if(rPaintWindow.OutputToWindow() && xManager.is() ) + { + std::unique_ptr<sdr::overlay::OverlayObject> pOverlayObject; + + auto* pAnnotation = dynamic_cast<sd::Annotation*>(mxAnnotation.get()); + + if (pAnnotation && pAnnotation->hasCustomAnnotationMarker()) + { + CustomAnnotationMarker& rCustomAnnotationMarker = pAnnotation->getCustomAnnotationMarker(); + + auto& rPolygons = rCustomAnnotationMarker.maPolygons; + if (!rPolygons.empty()) + { + basegfx::B2DPolyPolygon aPolyPolygon; + for (auto const & rPolygon : rPolygons) + aPolyPolygon.append(rPolygon); + + pOverlayObject.reset(new sdr::overlay::OverlayPolyPolygon( + aPolyPolygon, + rCustomAnnotationMarker.maLineColor, + rCustomAnnotationMarker.mnLineWidth, + rCustomAnnotationMarker.maFillColor)); + } + } + else + { + // animate focused handles + if(bFocused) + { + const sal_uInt64 nBlinkTime = rStyleSettings.GetCursorBlinkTime(); + + pOverlayObject.reset(new sdr::overlay::OverlayAnimatedBitmapEx(aPosition, aBitmapEx, aBitmapEx2, nBlinkTime, 0, 0, 0, 0 )); + } + else + { + pOverlayObject.reset(new sdr::overlay::OverlayBitmapEx( aPosition, aBitmapEx, 0, 0 )); + } + } + + // OVERLAYMANAGER + insertNewlyCreatedOverlayObjectForSdrHdl( + std::move(pOverlayObject), + rPageWindow.GetObjectContact(), + *xManager); + } + } +} + +bool AnnotationHdl::IsFocusHdl() const +{ + return true; +} + +AnnotationTag::AnnotationTag( AnnotationManagerImpl& rManager, ::sd::View& rView, const Reference< XAnnotation >& xAnnotation, Color const & rColor, int nIndex, const vcl::Font& rFont ) +: SmartTag( rView ) +, mrManager( rManager ) +, mxAnnotation( xAnnotation ) +, maColor( rColor ) +, mnIndex( nIndex ) +, mrFont( rFont ) +, mpListenWindow( nullptr ) +{ +} + +AnnotationTag::~AnnotationTag() +{ + DBG_ASSERT( !mxAnnotation.is(), "sd::AnnotationTag::~AnnotationTag(), dispose me first!" ); + Dispose(); +} + +/** returns true if the AnnotationTag handled the event. */ +bool AnnotationTag::MouseButtonDown( const MouseEvent& rMEvt, SmartHdl& /*rHdl*/ ) +{ + if( !mxAnnotation.is() ) + return false; + + bool bRet = false; + if( !isSelected() ) + { + SmartTagReference xTag( this ); + mrView.getSmartTags().select( xTag ); + bRet = true; + } + + if( rMEvt.IsLeft() && !rMEvt.IsRight() ) + { + vcl::Window* pWindow = mrView.GetViewShell()->GetActiveWindow(); + if( pWindow ) + { + maMouseDownPos = pWindow->PixelToLogic( rMEvt.GetPosPixel() ); + + if( mpListenWindow ) + mpListenWindow->RemoveEventListener( LINK(this, AnnotationTag, WindowEventHandler)); + + mpListenWindow = pWindow; + mpListenWindow->AddEventListener( LINK(this, AnnotationTag, WindowEventHandler)); + } + + bRet = true; + } + + return bRet; +} + +/** returns true if the SmartTag consumes this event. */ +bool AnnotationTag::KeyInput( const KeyEvent& rKEvt ) +{ + if( !mxAnnotation.is() ) + return false; + + sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode(); + switch( nCode ) + { + case KEY_DELETE: + mrManager.DeleteAnnotation( mxAnnotation ); + return true; + + case KEY_DOWN: + case KEY_UP: + case KEY_LEFT: + case KEY_RIGHT: + return OnMove( rKEvt ); + + case KEY_ESCAPE: + { + SmartTagReference xThis( this ); + mrView.getSmartTags().deselect(); + return true; + } + + case KEY_TAB: + mrManager.SelectNextAnnotation(!rKEvt.GetKeyCode().IsShift()); + return true; + + case KEY_RETURN: + case KEY_SPACE: + OpenPopup( true ); + return true; + + default: + return false; + } +} + +/** returns true if the SmartTag consumes this event. */ +bool AnnotationTag::Command( const CommandEvent& rCEvt ) +{ + if (rCEvt.GetCommand() != CommandEventId::ContextMenu) + return false; + if (vcl::Window* pWindow = mrView.GetViewShell()->GetActiveWindow()) + { + ::tools::Rectangle aContextRect(rCEvt.GetMousePosPixel(),Size(1,1)); + weld::Window* pParent = weld::GetPopupParent(*pWindow, aContextRect); + mrManager.ExecuteAnnotationTagContextMenu(mxAnnotation, pParent, aContextRect); + return true; + } + return false; +} + +void AnnotationTag::Move( int nDX, int nDY ) +{ + if( !mxAnnotation.is() ) + return; + + if( mrManager.GetDoc()->IsUndoEnabled() ) + mrManager.GetDoc()->BegUndo( SdResId( STR_ANNOTATION_UNDO_MOVE ) ); + + RealPoint2D aPosition( mxAnnotation->getPosition() ); + aPosition.X += static_cast<double>(nDX) / 100.0; + aPosition.Y += static_cast<double>(nDY) / 100.0; + mxAnnotation->setPosition( aPosition ); + + if( mrManager.GetDoc()->IsUndoEnabled() ) + mrManager.GetDoc()->EndUndo(); + + mrView.updateHandles(); +} + +bool AnnotationTag::OnMove( const KeyEvent& rKEvt ) +{ + ::tools::Long nX = 0; + ::tools::Long nY = 0; + + switch( rKEvt.GetKeyCode().GetCode() ) + { + case KEY_UP: nY = -1; break; + case KEY_DOWN: nY = 1; break; + case KEY_LEFT: nX = -1; break; + case KEY_RIGHT: nX = 1; break; + default: break; + } + + if(rKEvt.GetKeyCode().IsMod2()) + { + OutputDevice* pOut = mrView.GetViewShell()->GetActiveWindow()->GetOutDev(); + Size aLogicSizeOnePixel = pOut ? pOut->PixelToLogic(Size(1,1)) : Size(100, 100); + nX *= aLogicSizeOnePixel.Width(); + nY *= aLogicSizeOnePixel.Height(); + } + else + { + // old, fixed move distance + nX *= 100; + nY *= 100; + } + + if( nX || nY ) + { + // move the annotation + Move( nX, nY ); + } + + return true; +} + +void AnnotationTag::CheckPossibilities() +{ +} + +sal_Int32 AnnotationTag::GetMarkablePointCount() const +{ + return 0; +} + +sal_Int32 AnnotationTag::GetMarkedPointCount() const +{ + return 0; +} + +bool AnnotationTag::MarkPoint(SdrHdl& /*rHdl*/, bool /*bUnmark*/ ) +{ + return false; +} + +bool AnnotationTag::MarkPoints(const ::tools::Rectangle* /*pRect*/, bool /*bUnmark*/ ) +{ + return false; +} + +bool AnnotationTag::getContext( SdrViewContext& /*rContext*/ ) +{ + return false; +} + +void AnnotationTag::addCustomHandles( SdrHdlList& rHandlerList ) +{ + if( !mxAnnotation.is() ) + return; + + SmartTagReference xThis( this ); + std::unique_ptr<AnnotationHdl> pHdl(new AnnotationHdl( xThis, mxAnnotation, Point() )); + pHdl->SetObjHdlNum( SMART_TAG_HDL_NUM ); + pHdl->SetPageView( mrView.GetSdrPageView() ); + + RealPoint2D aPosition( mxAnnotation->getPosition() ); + Point aBasePos( static_cast<::tools::Long>(aPosition.X * 100.0), static_cast<::tools::Long>(aPosition.Y * 100.0) ); + pHdl->SetPos( aBasePos ); + + rHandlerList.AddHdl( std::move(pHdl) ); +} + +void AnnotationTag::disposing() +{ + if( mpListenWindow ) + { + mpListenWindow->RemoveEventListener( LINK(this, AnnotationTag, WindowEventHandler)); + } + + mxAnnotation.clear(); + ClosePopup(); + SmartTag::disposing(); +} + +void AnnotationTag::select() +{ + SmartTag::select(); + + mrManager.onTagSelected( *this ); + + vcl::Window* pWindow = mrView.GetViewShell()->GetActiveWindow(); + if( pWindow ) + { + RealPoint2D aPosition( mxAnnotation->getPosition() ); + Point aPos( static_cast<::tools::Long>(aPosition.X * 100.0), static_cast<::tools::Long>(aPosition.Y * 100.0) ); + + ::tools::Rectangle aVisRect( aPos, pWindow->PixelToLogic(maSize) ); + mrView.MakeVisible(aVisRect, *pWindow); + } +} + +void AnnotationTag::deselect() +{ + SmartTag::deselect(); + + ClosePopup(); + + mrManager.onTagDeselected( *this ); +} + +BitmapEx AnnotationTag::CreateAnnotationBitmap( bool bSelected ) +{ + ScopedVclPtrInstance< VirtualDevice > pVDev; + + OUString sInitials(mxAnnotation->getInitials()); + if (sInitials.isEmpty()) + sInitials = getInitials(mxAnnotation->getAuthor()); + + OUString sAuthor(sInitials + " " + OUString::number(mnIndex)); + + pVDev->SetFont( mrFont ); + + const int BORDER_X = 4; // pixels + const int BORDER_Y = 4; // pixels + + maSize = Size( pVDev->GetTextWidth( sAuthor ) + 2*BORDER_X, pVDev->GetTextHeight() + 2*BORDER_Y ); + pVDev->SetOutputSizePixel( maSize, false ); + + Color aBorderColor( maColor ); + + if( bSelected ) + { + aBorderColor.Invert(); + } + else + { + if( maColor.IsDark() ) + { + aBorderColor.IncreaseLuminance( 32 ); + } + else + { + aBorderColor.DecreaseLuminance( 32 ); + } + } + + Point aPos; + ::tools::Rectangle aBorderRect( aPos, maSize ); + pVDev->SetLineColor(aBorderColor); + pVDev->SetFillColor(maColor); + pVDev->DrawRect( aBorderRect ); + + pVDev->SetTextColor( maColor.IsDark() ? COL_WHITE : COL_BLACK ); + pVDev->DrawText( Point( BORDER_X, BORDER_Y ), sAuthor ); + + return pVDev->GetBitmapEx( aPos, maSize ); +} + +void AnnotationTag::OpenPopup( bool bEdit ) +{ + if( !mxAnnotation.is() ) + return; + + if( !mpAnnotationWindow ) + { + OutputDevice* pOut = getView().GetFirstOutputDevice(); + vcl::Window* pWindow = pOut ? pOut->GetOwnerWindow() : nullptr; + if( pWindow ) + { + RealPoint2D aPosition( mxAnnotation->getPosition() ); + Point aPos(pWindow->LogicToPixel( Point( static_cast<::tools::Long>(aPosition.X * 100.0), static_cast<::tools::Long>(aPosition.Y * 100.0) ) ) ); + + aPos.AdjustX(4 ); // magic! + aPos.AdjustY(1 ); + + ::tools::Rectangle aRect( aPos, maSize ); + + weld::Window* pParent = weld::GetPopupParent(*pWindow, aRect); + mpAnnotationWindow.reset(new AnnotationWindow(pParent, aRect, mrView.GetDocSh(), mxAnnotation)); + mpAnnotationWindow->connect_closed(LINK(this, AnnotationTag, PopupModeEndHdl)); + } + } + + if (bEdit && mpAnnotationWindow) + mpAnnotationWindow->StartEdit(); +} + +IMPL_LINK_NOARG(AnnotationTag, PopupModeEndHdl, weld::Popover&, void) +{ + ClosePopup(); +} + +void AnnotationTag::ClosePopup() +{ + if (mpAnnotationWindow) + { + mpAnnotationWindow->SaveToDocument(); + mpAnnotationWindow.reset(); + } +} + +IMPL_LINK(AnnotationTag, WindowEventHandler, VclWindowEvent&, rEvent, void) +{ + vcl::Window* pWindow = rEvent.GetWindow(); + + if( !pWindow ) + return; + + if( pWindow != mpListenWindow ) + return; + + switch( rEvent.GetId() ) + { + case VclEventId::WindowMouseButtonUp: + { + // if we stop pressing the button without a mouse move we open the popup + mpListenWindow->RemoveEventListener( LINK(this, AnnotationTag, WindowEventHandler)); + mpListenWindow = nullptr; + if( !mpAnnotationWindow ) + OpenPopup(false); + } + break; + case VclEventId::WindowMouseMove: + { + // if we move the mouse after a button down we want to start dragging + mpListenWindow->RemoveEventListener( LINK(this, AnnotationTag, WindowEventHandler)); + mpListenWindow = nullptr; + + SdrHdl* pHdl = mrView.PickHandle(maMouseDownPos); + if( pHdl ) + { + mrView.BrkAction(); + const sal_uInt16 nDrgLog = static_cast<sal_uInt16>(pWindow->PixelToLogic(Size(DRGPIX,0)).Width()); + + rtl::Reference< AnnotationTag > xTag( this ); + + SdrDragMethod* pDragMethod = new AnnotationDragMove( mrView, xTag ); + mrView.BegDragObj(maMouseDownPos, nullptr, pHdl, nDrgLog, pDragMethod ); + } + } + break; + case VclEventId::ObjectDying: + mpListenWindow = nullptr; + break; + default: break; + } +} + +} // end of namespace sd + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/annotations/annotationtag.hxx b/sd/source/ui/annotations/annotationtag.hxx new file mode 100644 index 000000000..23dcde13a --- /dev/null +++ b/sd/source/ui/annotations/annotationtag.hxx @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <vcl/vclevent.hxx> + +#include <smarttag.hxx> +#include "annotationwindow.hxx" + +namespace com::sun::star::office { class XAnnotation; } + +namespace sd { + +class View; +class AnnotationManagerImpl; + +class AnnotationTag final : public SmartTag +{ +public: + AnnotationTag( AnnotationManagerImpl& rManager, ::sd::View& rView, const css::uno::Reference< css::office::XAnnotation >& xAnnotation, Color const & rColor, int nIndex, const vcl::Font& rFont ); + virtual ~AnnotationTag() override; + + /// @return true if the SmartTag handled the event. + virtual bool MouseButtonDown( const MouseEvent&, SmartHdl& ) override; + + /// @return true if the SmartTag consumes this event. + virtual bool KeyInput( const KeyEvent& rKEvt ) override; + + /// @return true if the SmartTag consumes this event. + virtual bool Command( const CommandEvent& rCEvt ) override; + + // callbacks from sdr view + virtual sal_Int32 GetMarkablePointCount() const override; + virtual sal_Int32 GetMarkedPointCount() const override; + virtual bool MarkPoint(SdrHdl& rHdl, bool bUnmark) override; + virtual void CheckPossibilities() override; + virtual bool MarkPoints(const ::tools::Rectangle* pRect, bool bUnmark) override; + + void Move( int nDX, int nDY ); + bool OnMove( const KeyEvent& rKEvt ); + + BitmapEx CreateAnnotationBitmap(bool); + + const css::uno::Reference< css::office::XAnnotation >& GetAnnotation() const { return mxAnnotation; } + + void OpenPopup( bool bEdit ); + void ClosePopup(); + +private: + virtual void addCustomHandles( SdrHdlList& rHandlerList ) override; + virtual bool getContext( SdrViewContext& rContext ) override; + virtual void disposing() override; + virtual void select() override; + virtual void deselect() override; + + DECL_LINK( WindowEventHandler, VclWindowEvent&, void ); + DECL_LINK(PopupModeEndHdl, weld::Popover&, void); + + AnnotationManagerImpl& mrManager; + css::uno::Reference< css::office::XAnnotation > mxAnnotation; + std::unique_ptr<AnnotationWindow> mpAnnotationWindow; + Color maColor; + int mnIndex; + const vcl::Font& mrFont; + Size maSize; + VclPtr<vcl::Window> mpListenWindow; + Point maMouseDownPos; +}; + +} // end of namespace sd + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/annotations/annotationwindow.cxx b/sd/source/ui/annotations/annotationwindow.cxx new file mode 100644 index 000000000..6c1210575 --- /dev/null +++ b/sd/source/ui/annotations/annotationwindow.cxx @@ -0,0 +1,802 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <editeng/eeitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/editview.hxx> +#include <editeng/editstat.hxx> +#include <editeng/outliner.hxx> +#include <editeng/editeng.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/postitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <svx/svxids.hrc> +#include <unotools/useroptions.hxx> + +#include <sfx2/viewfrm.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <svl/stritem.hxx> + +#include <vcl/commandevent.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <vcl/vclenum.hxx> +#include <vcl/svapp.hxx> +#include <vcl/gradient.hxx> +#include <vcl/settings.hxx> +#include <vcl/ptrstyle.hxx> + +#include <strings.hrc> +#include "annotationwindow.hxx" +#include "annotationmanagerimpl.hxx" + +#include <com/sun/star/office/XAnnotation.hpp> +#include <DrawDocShell.hxx> +#include <ViewShell.hxx> +#include <drawdoc.hxx> +#include <textapi.hxx> +#include <sdresid.hxx> + +#include <memory> + +using namespace ::sd; +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; +using namespace ::com::sun::star::office; +using namespace ::com::sun::star::text; + +#define METABUTTON_WIDTH 16 +#define METABUTTON_HEIGHT 18 +#define POSTIT_META_HEIGHT sal_Int32(30) + +namespace sd { + +void AnnotationTextWindow::Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect) +{ + Size aSize = GetOutputSizePixel(); + + const bool bHighContrast = Application::GetSettings().GetStyleSettings().GetHighContrastMode(); + if (!bHighContrast) + { + rRenderContext.DrawGradient(::tools::Rectangle(Point(0,0), rRenderContext.PixelToLogic(aSize)), + Gradient(GradientStyle::Linear, mrContents.maColorLight, mrContents.maColor)); + } + + DoPaint(rRenderContext, rRect); +} + +void AnnotationTextWindow::EditViewScrollStateChange() +{ + mrContents.SetScrollbar(); +} + +bool AnnotationTextWindow::KeyInput(const KeyEvent& rKeyEvt) +{ + const vcl::KeyCode& rKeyCode = rKeyEvt.GetKeyCode(); + sal_uInt16 nKey = rKeyCode.GetCode(); + + bool bDone = false; + + if ((rKeyCode.IsMod1() && rKeyCode.IsMod2()) && ((nKey == KEY_PAGEUP) || (nKey == KEY_PAGEDOWN))) + { + SfxDispatcher* pDispatcher = mrContents.DocShell()->GetViewShell()->GetViewFrame()->GetDispatcher(); + if( pDispatcher ) + pDispatcher->Execute( nKey == KEY_PAGEDOWN ? SID_NEXT_POSTIT : SID_PREVIOUS_POSTIT ); + bDone = true; + } + else if (nKey == KEY_INSERT) + { + if (!rKeyCode.IsMod1() && !rKeyCode.IsMod2()) + mrContents.ToggleInsMode(); + bDone = true; + } + else + { + ::tools::Long aOldHeight = mrContents.GetPostItTextHeight(); + + /// HACK: need to switch off processing of Undo/Redo in Outliner + if ( !( (nKey == KEY_Z || nKey == KEY_Y) && rKeyCode.IsMod1()) ) + { + bool bIsProtected = mrContents.IsProtected(); + if (!bIsProtected || !EditEngine::DoesKeyChangeText(rKeyEvt) ) + { + if (EditView* pEditView = GetEditView()) + { + bDone = pEditView->PostKeyEvent(rKeyEvt); + if (!bDone && rKeyEvt.GetKeyCode().IsMod1() && !rKeyEvt.GetKeyCode().IsMod2()) + { + if (nKey == KEY_A) + { + EditEngine* pEditEngine = GetEditEngine(); + sal_Int32 nPar = pEditEngine->GetParagraphCount(); + if (nPar) + { + sal_Int32 nLen = pEditEngine->GetTextLen(nPar - 1); + pEditView->SetSelection(ESelection(0, 0, nPar - 1, nLen)); + } + bDone = true; + } + } + } + } + } + if (bDone) + { + mrContents.ResizeIfNecessary(aOldHeight, mrContents.GetPostItTextHeight()); + } + } + + return bDone; +} + +AnnotationTextWindow::AnnotationTextWindow(AnnotationWindow& rContents) + : mrContents(rContents) +{ +} + +EditView* AnnotationTextWindow::GetEditView() const +{ + OutlinerView* pOutlinerView = mrContents.GetOutlinerView(); + if (!pOutlinerView) + return nullptr; + return &pOutlinerView->GetEditView(); +} + +EditEngine* AnnotationTextWindow::GetEditEngine() const +{ + OutlinerView* pOutlinerView = mrContents.GetOutlinerView(); + if (!pOutlinerView) + return nullptr; + return pOutlinerView->GetEditView().GetEditEngine(); +} + +void AnnotationTextWindow::SetDrawingArea(weld::DrawingArea* pDrawingArea) +{ + Size aSize(0, 0); + pDrawingArea->set_size_request(aSize.Width(), aSize.Height()); + + SetOutputSizePixel(aSize); + + weld::CustomWidgetController::SetDrawingArea(pDrawingArea); + + EnableRTL(false); + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + Color aBgColor = rStyleSettings.GetWindowColor(); + + OutputDevice& rDevice = pDrawingArea->get_ref_device(); + + rDevice.SetMapMode(MapMode(MapUnit::Map100thMM)); + rDevice.SetBackground(aBgColor); + + Size aOutputSize(rDevice.PixelToLogic(aSize)); + + EditView* pEditView = GetEditView(); + pEditView->setEditViewCallbacks(this); + + EditEngine* pEditEngine = GetEditEngine(); + pEditEngine->SetPaperSize(aOutputSize); + pEditEngine->SetRefDevice(&rDevice); + + pEditView->SetOutputArea(::tools::Rectangle(Point(0, 0), aOutputSize)); + pEditView->SetBackgroundColor(aBgColor); + + pDrawingArea->set_cursor(PointerStyle::Text); + + InitAccessible(); +} + +// see SwAnnotationWin in sw for something similar +AnnotationWindow::AnnotationWindow(weld::Window* pParent, const ::tools::Rectangle& rRect, + DrawDocShell* pDocShell, + const Reference<XAnnotation>& xAnnotation) + : mxBuilder(Application::CreateBuilder(pParent, "modules/simpress/ui/annotation.ui")) + , mxPopover(mxBuilder->weld_popover("Annotation")) + , mxContainer(mxBuilder->weld_widget("container")) + , mpDocShell(pDocShell) + , mpDoc(pDocShell->GetDoc()) + , mbReadonly(pDocShell->IsReadOnly()) + , mbProtected(false) +{ + mxContainer->set_size_request(320, 240); + mxPopover->popup_at_rect(pParent, rRect); + + InitControls(); + setAnnotation(xAnnotation); + FillMenuButton(); + + DoResize(); + + mxTextControl->GrabFocus(); +} + +AnnotationWindow::~AnnotationWindow() +{ +} + +void AnnotationWindow::InitControls() +{ + // window control for author and date + mxMeta = mxBuilder->weld_label("meta"); + mxMeta->set_direction(AllSettings::GetLayoutRTL()); + + maLabelFont = Application::GetSettings().GetStyleSettings().GetLabelFont(); + maLabelFont.SetFontHeight(8); + + // we should leave this setting alone, but for this we need a better layout algo + // with variable meta size height + mxMeta->set_font(maLabelFont); + + mpOutliner.reset( new ::Outliner(GetAnnotationPool(),OutlinerMode::TextObject) ); + SdDrawDocument::SetCalcFieldValueHdl( mpOutliner.get() ); + mpOutliner->SetUpdateLayout( true ); + + if (OutputDevice* pDev = mpDoc->GetRefDevice()) + mpOutliner->SetRefDevice( pDev ); + + mpOutlinerView.reset( new OutlinerView ( mpOutliner.get(), nullptr) ); + mpOutliner->InsertView(mpOutlinerView.get() ); + + //create Scrollbars + mxVScrollbar = mxBuilder->weld_scrolled_window("scrolledwindow", true); + + // actual window which holds the user text + mxTextControl.reset(new AnnotationTextWindow(*this)); + mxTextControlWin.reset(new weld::CustomWeld(*mxBuilder, "editview", *mxTextControl)); + mxTextControl->SetPointer(PointerStyle::Text); + + Rescale(); + OutputDevice& rDevice = mxTextControl->GetDrawingArea()->get_ref_device(); + + mxVScrollbar->set_direction(false); + mxVScrollbar->connect_vadjustment_changed(LINK(this, AnnotationWindow, ScrollHdl)); + + mpOutlinerView->SetBackgroundColor(COL_TRANSPARENT); + mpOutlinerView->SetOutputArea(rDevice.PixelToLogic(::tools::Rectangle(0, 0, 1, 1))); + + mxMenuButton = mxBuilder->weld_menu_button("menubutton"); + if (mbReadonly) + mxMenuButton->hide(); + else + { + mxMenuButton->set_size_request(METABUTTON_WIDTH, METABUTTON_HEIGHT); + mxMenuButton->connect_selected(LINK(this, AnnotationWindow, MenuItemSelectedHdl)); + } + + EEControlBits nCntrl = mpOutliner->GetControlWord(); + nCntrl |= EEControlBits::PASTESPECIAL | EEControlBits::AUTOCORRECT | EEControlBits::USECHARATTRIBS | EEControlBits::NOCOLORS; + mpOutliner->SetControlWord(nCntrl); + + mpOutliner->SetModifyHdl( Link<LinkParamNone*,void>() ); + mpOutliner->EnableUndo( false ); + + mpOutliner->ClearModifyFlag(); + mpOutliner->GetUndoManager().Clear(); + mpOutliner->EnableUndo( true ); + + SetLanguage(SvxLanguageItem(mpDoc->GetLanguage(EE_CHAR_LANGUAGE), SID_ATTR_LANGUAGE)); + + mxTextControl->GrabFocus(); +} + +IMPL_LINK(AnnotationWindow, MenuItemSelectedHdl, const OString&, rIdent, void) +{ + SfxDispatcher* pDispatcher = mpDocShell->GetViewShell()->GetViewFrame()->GetDispatcher(); + if (!pDispatcher) + return; + + if (rIdent == ".uno:ReplyToAnnotation") + { + const SfxUnoAnyItem aItem( SID_REPLYTO_POSTIT, Any( mxAnnotation ) ); + pDispatcher->ExecuteList(SID_REPLYTO_POSTIT, + SfxCallMode::ASYNCHRON, { &aItem }); + } + else if (rIdent == ".uno:DeleteAnnotation") + { + const SfxUnoAnyItem aItem( SID_DELETE_POSTIT, Any( mxAnnotation ) ); + pDispatcher->ExecuteList(SID_DELETE_POSTIT, SfxCallMode::ASYNCHRON, + { &aItem }); + } + else if (rIdent == ".uno:DeleteAllAnnotationByAuthor") + { + const SfxStringItem aItem( SID_DELETEALLBYAUTHOR_POSTIT, mxAnnotation->getAuthor() ); + pDispatcher->ExecuteList( SID_DELETEALLBYAUTHOR_POSTIT, + SfxCallMode::ASYNCHRON, { &aItem }); + } + else if (rIdent == ".uno:DeleteAllAnnotation") + pDispatcher->Execute( SID_DELETEALL_POSTIT ); +} + +void AnnotationWindow::FillMenuButton() +{ + SvtUserOptions aUserOptions; + OUString sCurrentAuthor( aUserOptions.GetFullName() ); + OUString sAuthor( mxAnnotation->getAuthor() ); + + OUString aStr(mxMenuButton->get_item_label(".uno:DeleteAllAnnotationByAuthor")); + OUString aReplace( sAuthor ); + if( aReplace.isEmpty() ) + aReplace = SdResId( STR_ANNOTATION_NOAUTHOR ); + aStr = aStr.replaceFirst("%1", aReplace); + mxMenuButton->set_item_label(".uno:DeleteAllAnnotationByAuthor", aStr); + + bool bShowReply = sAuthor != sCurrentAuthor && !mbReadonly; + mxMenuButton->set_item_visible(".uno:ReplyToAnnotation", bShowReply); + mxMenuButton->set_item_visible("separator", bShowReply); + mxMenuButton->set_item_visible(".uno:DeleteAnnotation", mxAnnotation.is() && !mbReadonly); + mxMenuButton->set_item_visible(".uno:DeleteAllAnnotationByAuthor", !mbReadonly); + mxMenuButton->set_item_visible(".uno:DeleteAllAnnotation", !mbReadonly); +} + +void AnnotationWindow::StartEdit() +{ + GetOutlinerView()->SetSelection(ESelection(EE_PARA_MAX_COUNT,EE_TEXTPOS_MAX_COUNT,EE_PARA_MAX_COUNT,EE_TEXTPOS_MAX_COUNT)); + GetOutlinerView()->ShowCursor(); +} + +void AnnotationWindow::SetMapMode(const MapMode& rNewMapMode) +{ + OutputDevice& rDevice = mxTextControl->GetDrawingArea()->get_ref_device(); + rDevice.SetMapMode(rNewMapMode); +} + +void AnnotationWindow::Rescale() +{ + MapMode aMode(MapUnit::Map100thMM); + aMode.SetOrigin( Point() ); + mpOutliner->SetRefMapMode( aMode ); + SetMapMode( aMode ); + + if (mxMeta) + { + vcl::Font aFont = maLabelFont; + sal_Int32 nHeight = ::tools::Long(aFont.GetFontHeight() * aMode.GetScaleY()); + aFont.SetFontHeight( nHeight ); + mxMeta->set_font(aFont); + } +} + +void AnnotationWindow::DoResize() +{ + OutputDevice& rDevice = mxTextControl->GetDrawingArea()->get_ref_device(); + + ::tools::Long aHeight = mxContainer->get_preferred_size().Height(); + ::tools::ULong aWidth = mxContainer->get_preferred_size().Width(); + + aHeight -= POSTIT_META_HEIGHT; + + mpOutliner->SetPaperSize( rDevice.PixelToLogic( Size(aWidth, aHeight) ) ) ; + ::tools::Long aTextHeight = rDevice.LogicToPixel(mpOutliner->CalcTextSize()).Height(); + + if( aTextHeight > aHeight ) + { + const int nThickness = mxVScrollbar->get_scroll_thickness(); + if (nThickness) + { + // we need vertical scrollbars and have to reduce the width + aWidth -= nThickness; + mpOutliner->SetPaperSize(rDevice.PixelToLogic(Size(aWidth, aHeight))); + } + mxVScrollbar->set_vpolicy(VclPolicyType::ALWAYS); + } + else + { + mxVScrollbar->set_vpolicy(VclPolicyType::NEVER); + } + + ::tools::Rectangle aOutputArea = rDevice.PixelToLogic(::tools::Rectangle(0, 0, aWidth, aHeight)); + if (mxVScrollbar->get_vpolicy() == VclPolicyType::NEVER) + { + // if we do not have a scrollbar anymore, we want to see the complete text + mpOutlinerView->SetVisArea(aOutputArea); + } + mpOutlinerView->SetOutputArea(aOutputArea); + mpOutlinerView->ShowCursor(true, true); + + int nUpper = mpOutliner->GetTextHeight(); + int nCurrentDocPos = mpOutlinerView->GetVisArea().Top(); + int nStepIncrement = mpOutliner->GetTextHeight() / 10; + int nPageIncrement = rDevice.PixelToLogic(Size(0,aHeight)).Height() * 8 / 10; + int nPageSize = rDevice.PixelToLogic(Size(0,aHeight)).Height(); + + /* limit the page size to below nUpper because gtk's gtk_scrolled_window_start_deceleration has + effectively... + + lower = gtk_adjustment_get_lower + upper = gtk_adjustment_get_upper - gtk_adjustment_get_page_size + + and requires that upper > lower or the deceleration animation never ends + */ + nPageSize = std::min(nPageSize, nUpper); + + mxVScrollbar->vadjustment_configure(nCurrentDocPos, 0, nUpper, + nStepIncrement, nPageIncrement, nPageSize); +} + +void AnnotationWindow::SetScrollbar() +{ + mxVScrollbar->vadjustment_set_value(mpOutlinerView->GetVisArea().Top()); +} + +void AnnotationWindow::ResizeIfNecessary(::tools::Long aOldHeight, ::tools::Long aNewHeight) +{ + if (aOldHeight != aNewHeight) + DoResize(); + else + SetScrollbar(); +} + +void AnnotationWindow::SetLanguage(const SvxLanguageItem &aNewItem) +{ + mpOutliner->SetModifyHdl( Link<LinkParamNone*,void>() ); + ESelection aOld = GetOutlinerView()->GetSelection(); + + ESelection aNewSelection( 0, 0, mpOutliner->GetParagraphCount()-1, EE_TEXTPOS_ALL ); + GetOutlinerView()->SetSelection( aNewSelection ); + SfxItemSet aEditAttr(GetOutlinerView()->GetAttribs()); + aEditAttr.Put(aNewItem); + GetOutlinerView()->SetAttribs( aEditAttr ); + + GetOutlinerView()->SetSelection(aOld); + + mxTextControl->Invalidate(); +} + +void AnnotationWindow::ToggleInsMode() +{ + if( mpOutlinerView ) + { + SfxBindings &rBnd = mpDocShell->GetViewShell()->GetViewFrame()->GetBindings(); + rBnd.Invalidate(SID_ATTR_INSERT); + rBnd.Update(SID_ATTR_INSERT); + } +} + +::tools::Long AnnotationWindow::GetPostItTextHeight() +{ + OutputDevice& rDevice = mxTextControl->GetDrawingArea()->get_ref_device(); + return mpOutliner ? rDevice.LogicToPixel(mpOutliner->CalcTextSize()).Height() : 0; +} + +IMPL_LINK(AnnotationWindow, ScrollHdl, weld::ScrolledWindow&, rScrolledWindow, void) +{ + ::tools::Long nDiff = GetOutlinerView()->GetEditView().GetVisArea().Top() - rScrolledWindow.vadjustment_get_value(); + GetOutlinerView()->Scroll( 0, nDiff ); +} + +TextApiObject* getTextApiObject( const Reference< XAnnotation >& xAnnotation ) +{ + if( xAnnotation.is() ) + { + Reference< XText > xText( xAnnotation->getTextRange() ); + return TextApiObject::getImplementation( xText ); + } + return nullptr; +} + +void AnnotationWindow::setAnnotation( const Reference< XAnnotation >& xAnnotation ) +{ + if( (xAnnotation == mxAnnotation) || !xAnnotation.is() ) + return; + + mxAnnotation = xAnnotation; + + SetColor(); + + SvtUserOptions aUserOptions; + mbProtected = aUserOptions.GetFullName() != xAnnotation->getAuthor(); + + mpOutliner->Clear(); + TextApiObject* pTextApi = getTextApiObject( mxAnnotation ); + + if( pTextApi ) + { + std::optional< OutlinerParaObject > pOPO( pTextApi->CreateText() ); + mpOutliner->SetText(*pOPO); + } + + mpOutliner->ClearModifyFlag(); + mpOutliner->GetUndoManager().Clear(); + +//TODO Invalidate(); + + OUString sMeta( xAnnotation->getAuthor() ); + OUString sDateTime( getAnnotationDateTimeString(xAnnotation) ); + + if( !sDateTime.isEmpty() ) + { + if( !sMeta.isEmpty() ) + sMeta += "\n"; + + sMeta += sDateTime; + } + mxMeta->set_label(sMeta); +} + +void AnnotationWindow::SetColor() +{ + sal_uInt16 nAuthorIdx = mpDoc->GetAnnotationAuthorIndex( mxAnnotation->getAuthor() ); + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + const bool bHighContrast = rStyleSettings.GetHighContrastMode(); + if( bHighContrast ) + { + maColor = rStyleSettings.GetWindowColor(); + maColorDark = maColor; + maColorLight = rStyleSettings.GetWindowTextColor(); + } + else + { + maColor = AnnotationManagerImpl::GetColor( nAuthorIdx ); + maColorDark = AnnotationManagerImpl::GetColorDark( nAuthorIdx ); + maColorLight = AnnotationManagerImpl::GetColorLight( nAuthorIdx ); + } + + { + SvtAccessibilityOptions aOptions; + mpOutliner->ForceAutoColor( bHighContrast || aOptions.GetIsAutomaticFontColor() ); + } + + mxPopover->set_background(maColor); + mxMenuButton->set_background(maColor); + + mxMeta->set_font_color(bHighContrast ? maColorLight : maColorDark); + + mxVScrollbar->customize_scrollbars(maColorLight, + maColorDark, + maColor); + mxVScrollbar->set_scroll_thickness(GetPrefScrollbarWidth()); +} + +void AnnotationWindow::SaveToDocument() +{ + Reference< XAnnotation > xAnnotation( mxAnnotation ); + + // write changed text back to annotation + if (mpOutliner->IsModified()) + { + TextApiObject* pTextApi = getTextApiObject( xAnnotation ); + + if( pTextApi ) + { + std::optional<OutlinerParaObject> pOPO = mpOutliner->CreateParaObject(); + if( pOPO ) + { + if( mpDoc->IsUndoEnabled() ) + mpDoc->BegUndo( SdResId( STR_ANNOTATION_UNDO_EDIT ) ); + + pTextApi->SetText( *pOPO ); + pOPO.reset(); + + // set current time to changed annotation + xAnnotation->setDateTime( getCurrentDateTime() ); + + if( mpDoc->IsUndoEnabled() ) + mpDoc->EndUndo(); + + mpDocShell->SetModified(); + } + + } + } + mpOutliner->ClearModifyFlag(); + + mpOutliner->GetUndoManager().Clear(); +} + +bool AnnotationTextWindow::Command(const CommandEvent& rCEvt) +{ + if (rCEvt.GetCommand() == CommandEventId::ContextMenu) + { + const bool bReadOnly = mrContents.DocShell()->IsReadOnly(); + if (bReadOnly) + return true; + + SfxDispatcher* pDispatcher = mrContents.DocShell()->GetViewShell()->GetViewFrame()->GetDispatcher(); + if( !pDispatcher ) + return true; + + if (IsMouseCaptured()) + { + // so the menu can capture it and the EditView doesn't get the button release and change its + // selection on a successful button click + ReleaseMouse(); + } + + ::tools::Rectangle aRect(rCEvt.GetMousePosPixel(), Size(1, 1)); + weld::Widget* pPopupParent = GetDrawingArea(); + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pPopupParent, "modules/simpress/ui/annotationtagmenu.ui")); + std::unique_ptr<weld::Menu> xMenu(xBuilder->weld_menu("menu")); + + auto xAnnotation = mrContents.getAnnotation(); + + SvtUserOptions aUserOptions; + OUString sCurrentAuthor( aUserOptions.GetFullName() ); + OUString sAuthor( xAnnotation->getAuthor() ); + + OUString aStr(xMenu->get_label(".uno:DeleteAllAnnotationByAuthor")); + OUString aReplace( sAuthor ); + if( aReplace.isEmpty() ) + aReplace = SdResId( STR_ANNOTATION_NOAUTHOR ); + aStr = aStr.replaceFirst("%1", aReplace); + xMenu->set_label(".uno:DeleteAllAnnotationByAuthor", aStr); + + bool bShowReply = sAuthor != sCurrentAuthor && !bReadOnly; + xMenu->set_visible(".uno:ReplyToAnnotation", bShowReply); + xMenu->set_visible("separator", bShowReply); + xMenu->set_visible(".uno:DeleteAnnotation", xAnnotation.is() && !bReadOnly); + xMenu->set_visible(".uno:DeleteAllAnnotationByAuthor", !bReadOnly); + xMenu->set_visible(".uno:DeleteAllAnnotation", !bReadOnly); + + int nInsertPos = 2; + + auto xFrame = mrContents.DocShell()->GetViewShell()->GetViewFrame()->GetFrame().GetFrameInterface(); + OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(xFrame)); + + bool bEditable = !mrContents.IsProtected() && !bReadOnly; + if (bEditable) + { + SfxItemSet aSet(mrContents.GetOutlinerView()->GetAttribs()); + + xMenu->insert(nInsertPos++, ".uno:Bold", + vcl::CommandInfoProvider::GetMenuLabelForCommand( + vcl::CommandInfoProvider::GetCommandProperties(".uno:Bold", aModuleName)), + nullptr, nullptr, vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Bold", xFrame), + TRISTATE_TRUE); + + if ( aSet.GetItemState( EE_CHAR_WEIGHT ) == SfxItemState::SET ) + { + if( aSet.Get( EE_CHAR_WEIGHT ).GetWeight() == WEIGHT_BOLD ) + xMenu->set_active(".uno:Bold", true); + } + + xMenu->insert(nInsertPos++, ".uno:Italic", + vcl::CommandInfoProvider::GetMenuLabelForCommand( + vcl::CommandInfoProvider::GetCommandProperties(".uno:Italic", aModuleName)), + nullptr, nullptr, vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Italic", xFrame), + TRISTATE_TRUE); + + if ( aSet.GetItemState( EE_CHAR_ITALIC ) == SfxItemState::SET ) + { + if( aSet.Get( EE_CHAR_ITALIC ).GetPosture() != ITALIC_NONE ) + xMenu->set_active(".uno:Italic", true); + + } + + xMenu->insert(nInsertPos++, ".uno:Underline", + vcl::CommandInfoProvider::GetMenuLabelForCommand( + vcl::CommandInfoProvider::GetCommandProperties(".uno:Underline", aModuleName)), + nullptr, nullptr, vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Underline", xFrame), + TRISTATE_TRUE); + + if ( aSet.GetItemState( EE_CHAR_UNDERLINE ) == SfxItemState::SET ) + { + if( aSet.Get( EE_CHAR_UNDERLINE ).GetLineStyle() != LINESTYLE_NONE ) + xMenu->set_active(".uno:Underline", true); + } + + xMenu->insert(nInsertPos++, ".uno:Strikeout", + vcl::CommandInfoProvider::GetMenuLabelForCommand( + vcl::CommandInfoProvider::GetCommandProperties(".uno:Strikeout", aModuleName)), + nullptr, nullptr, vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Strikeout", xFrame), + TRISTATE_TRUE); + + if ( aSet.GetItemState( EE_CHAR_STRIKEOUT ) == SfxItemState::SET ) + { + if( aSet.Get( EE_CHAR_STRIKEOUT ).GetStrikeout() != STRIKEOUT_NONE ) + xMenu->set_active(".uno:Strikeout", true); + } + + xMenu->insert_separator(nInsertPos++, "separator2"); + } + + xMenu->insert(nInsertPos++, ".uno:Copy", + vcl::CommandInfoProvider::GetMenuLabelForCommand( + vcl::CommandInfoProvider::GetCommandProperties(".uno:Copy", aModuleName)), + nullptr, nullptr, vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Copy", xFrame), + TRISTATE_INDET); + + xMenu->insert(nInsertPos++, ".uno:Paste", + vcl::CommandInfoProvider::GetMenuLabelForCommand( + vcl::CommandInfoProvider::GetCommandProperties(".uno:Paste", aModuleName)), + nullptr, nullptr, vcl::CommandInfoProvider::GetXGraphicForCommand(".uno:Paste", xFrame), + TRISTATE_INDET); + + bool bCanPaste = false; + if (bEditable) + { + TransferableDataHelper aDataHelper(TransferableDataHelper::CreateFromClipboard(GetClipboard())); + bCanPaste = aDataHelper.GetFormatCount() != 0; + } + + xMenu->insert_separator(nInsertPos++, "separator3"); + + xMenu->set_sensitive(".uno:Copy", mrContents.GetOutlinerView()->HasSelection()); + xMenu->set_sensitive(".uno:Paste", bCanPaste); + + auto sId = xMenu->popup_at_rect(pPopupParent, aRect); + + if (sId == ".uno:ReplyToAnnotation") + { + const SfxUnoAnyItem aItem( SID_REPLYTO_POSTIT, Any( xAnnotation ) ); + pDispatcher->ExecuteList(SID_REPLYTO_POSTIT, + SfxCallMode::ASYNCHRON, { &aItem }); + } + else if (sId == ".uno:DeleteAnnotation") + { + const SfxUnoAnyItem aItem( SID_DELETE_POSTIT, Any( xAnnotation ) ); + pDispatcher->ExecuteList(SID_DELETE_POSTIT, SfxCallMode::ASYNCHRON, + { &aItem }); + } + else if (sId == ".uno:DeleteAllAnnotationByAuthor") + { + const SfxStringItem aItem( SID_DELETEALLBYAUTHOR_POSTIT, sAuthor ); + pDispatcher->ExecuteList( SID_DELETEALLBYAUTHOR_POSTIT, + SfxCallMode::ASYNCHRON, { &aItem }); + } + else if (sId == ".uno:DeleteAllAnnotation") + pDispatcher->Execute( SID_DELETEALL_POSTIT ); + else if (sId == ".uno:Copy") + { + mrContents.GetOutlinerView()->Copy(); + } + else if (sId == ".uno:Paste") + { + mrContents.GetOutlinerView()->PasteSpecial(); + mrContents.DoResize(); + } + else if (!sId.isEmpty()) + { + SfxItemSet aEditAttr(mrContents.GetOutlinerView()->GetAttribs()); + SfxItemSet aNewAttr(mrContents.GetOutliner()->GetEmptyItemSet()); + + if (sId == ".uno:Bold") + { + FontWeight eFW = aEditAttr.Get( EE_CHAR_WEIGHT ).GetWeight(); + aNewAttr.Put( SvxWeightItem( eFW == WEIGHT_NORMAL ? WEIGHT_BOLD : WEIGHT_NORMAL, EE_CHAR_WEIGHT ) ); + } + else if (sId == ".uno:Italic") + { + FontItalic eFI = aEditAttr.Get( EE_CHAR_ITALIC ).GetPosture(); + aNewAttr.Put( SvxPostureItem( eFI == ITALIC_NORMAL ? ITALIC_NONE : ITALIC_NORMAL, EE_CHAR_ITALIC ) ); + } + else if (sId == ".uno:Underline") + { + FontLineStyle eFU = aEditAttr. Get( EE_CHAR_UNDERLINE ).GetLineStyle(); + aNewAttr.Put( SvxUnderlineItem( eFU == LINESTYLE_SINGLE ? LINESTYLE_NONE : LINESTYLE_SINGLE, EE_CHAR_UNDERLINE ) ); + } + else if (sId == ".uno:Strikeout") + { + FontStrikeout eFSO = aEditAttr.Get( EE_CHAR_STRIKEOUT ).GetStrikeout(); + aNewAttr.Put( SvxCrossedOutItem( eFSO == STRIKEOUT_SINGLE ? STRIKEOUT_NONE : STRIKEOUT_SINGLE, EE_CHAR_STRIKEOUT ) ); + } + + mrContents.GetOutlinerView()->SetAttribs( aNewAttr ); + } + + return true; + } + return WeldEditView::Command(rCEvt); +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/annotations/annotationwindow.hxx b/sd/source/ui/annotations/annotationwindow.hxx new file mode 100644 index 000000000..558cc6165 --- /dev/null +++ b/sd/source/ui/annotations/annotationwindow.hxx @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <vcl/weld.hxx> +#include <tools/long.hxx> +#include <svx/weldeditview.hxx> + +namespace com::sun::star::office { class XAnnotation; } + +class OutlinerView; +class Outliner; +class SvxLanguageItem; +class SdDrawDocument; + +namespace sd { + +class AnnotationManagerImpl; +class DrawDocShell; +class TextApiObject; + +class AnnotationWindow; + +class AnnotationTextWindow : public WeldEditView +{ +private: + AnnotationWindow& mrContents; + +public: + AnnotationTextWindow(AnnotationWindow& rContents); + + virtual EditView* GetEditView() const override; + + virtual EditEngine* GetEditEngine() const override; + + virtual void EditViewScrollStateChange() override; + + void SetDrawingArea(weld::DrawingArea* pDrawingArea) override; + + virtual void Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect) override; + virtual bool KeyInput(const KeyEvent& rKeyEvt) override; + virtual bool Command(const CommandEvent& rCEvt) override; +}; + +class AnnotationWindow final +{ +private: + std::unique_ptr<weld::Builder> mxBuilder; + std::unique_ptr<weld::Popover> mxPopover; + std::unique_ptr<weld::Widget> mxContainer; + + DrawDocShell* mpDocShell; + SdDrawDocument* mpDoc; + + bool mbReadonly; + bool mbProtected; + + css::uno::Reference< css::office::XAnnotation > mxAnnotation; + +public: + Color maColor; + Color maColorDark; + Color maColorLight; + +private: + vcl::Font maLabelFont; + + std::unique_ptr<OutlinerView> mpOutlinerView; + std::unique_ptr<::Outliner> mpOutliner; + + std::unique_ptr<weld::ScrolledWindow> mxVScrollbar; + std::unique_ptr<WeldEditView> mxTextControl; + std::unique_ptr<weld::CustomWeld> mxTextControlWin; + std::unique_ptr<weld::Label> mxMeta; + std::unique_ptr<weld::MenuButton> mxMenuButton; + + DECL_LINK(ScrollHdl, weld::ScrolledWindow&, void); + DECL_LINK(MenuItemSelectedHdl, const OString&, void); + + void FillMenuButton(); + void InitControls(); + + void SetMapMode(const MapMode& rNewMapMode); + void setAnnotation(const css::uno::Reference<css::office::XAnnotation>& xAnnotation); + + static sal_Int32 GetPrefScrollbarWidth() { return 16; } +public: + AnnotationWindow(weld::Window* pParent, const ::tools::Rectangle& rRect, DrawDocShell* pDocShell, + const css::uno::Reference<css::office::XAnnotation>& xAnnotation); + + void connect_closed(const Link<weld::Popover&, void>& rLink) { mxPopover->connect_closed(rLink); } + + void DoResize(); + void ResizeIfNecessary(::tools::Long aOldHeight, ::tools::Long aNewHeight); + void SetScrollbar(); + void StartEdit(); + + const css::uno::Reference<css::office::XAnnotation>& getAnnotation() const { return mxAnnotation; } + + void SaveToDocument(); + + ::tools::Long GetPostItTextHeight(); + + DrawDocShell* DocShell() { return mpDocShell; } + + void SetLanguage(const SvxLanguageItem &aNewItem); + + void Rescale(); + + void ToggleInsMode(); + + bool IsProtected() const { return mbProtected; } + + OutlinerView* GetOutlinerView() { return mpOutlinerView.get();} + ::Outliner* GetOutliner() { return mpOutliner.get();} + ~AnnotationWindow(); + + void SetColor(); +}; + +TextApiObject* getTextApiObject( const css::uno::Reference< css::office::XAnnotation >& xAnnotation ); + + +} // namespace sd + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |