diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:51:28 +0000 |
commit | 940b4d1848e8c70ab7642901a68594e8016caffc (patch) | |
tree | eb72f344ee6c3d9b80a7ecc079ea79e9fba8676d /sd/source/ui/annotations | |
parent | Initial commit. (diff) | |
download | libreoffice-upstream.tar.xz libreoffice-upstream.zip |
Adding upstream version 1:7.0.4.upstream/1%7.0.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sd/source/ui/annotations')
-rw-r--r-- | sd/source/ui/annotations/annotationmanager.cxx | 1323 | ||||
-rw-r--r-- | sd/source/ui/annotations/annotationmanagerimpl.hxx | 150 | ||||
-rw-r--r-- | sd/source/ui/annotations/annotationtag.cxx | 671 | ||||
-rw-r--r-- | sd/source/ui/annotations/annotationtag.hxx | 91 | ||||
-rw-r--r-- | sd/source/ui/annotations/annotationwindow.cxx | 752 | ||||
-rw-r--r-- | sd/source/ui/annotations/annotationwindow.hxx | 145 |
6 files changed, 3132 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..512ed85a7 --- /dev/null +++ b/sd/source/ui/annotations/annotationmanager.cxx @@ -0,0 +1,1323 @@ +/* -*- 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/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/commandinfoprovider.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/menu.hxx> +#include <vcl/weld.hxx> + +#include <sal/macros.h> +#include <svl/itempool.hxx> +#include <unotools/localedatawrapper.hxx> +#include <unotools/useroptions.hxx> +#include <unotools/syslocale.hxx> +#include <unotools/saveopt.hxx> + +#include <tools/datetime.hxx> + +#include <sfx2/viewfrm.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/request.hxx> +#include <sfx2/dispatch.hxx> + +#include <editeng/editeng.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/outlobj.hxx> +#include <editeng/postitem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/crossedoutitem.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 SfxItemPool* s_pAnnotationPool = nullptr; + if( s_pAnnotationPool == nullptr ) + { + 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; +} + +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 ) +: AnnotationManagerImplBase( m_aMutex ) +, mrBase( rViewShellBase ) +, mpDoc( rViewShellBase.GetDocument() ) +, mbShowAnnotations( true ) +, mbPopupMenuActive( false ) +, 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& ) + { + OSL_FAIL( "sd::AnnotationManagerImpl::AnnotationManagerImpl(), Exception caught!" ); + } + + try + { + Reference<XEventBroadcaster> xModel (mrBase.GetDocShell()->GetModel(), UNO_QUERY_THROW ); + Reference<XEventListener> xListener( this ); + xModel->addEventListener( xListener ); + } + catch( Exception& ) + { + } +} + +// WeakComponentImplHelper +void SAL_CALL AnnotationManagerImpl::disposing () +{ + 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) + { + const SfxPoolItem* pPoolItem = nullptr; + if (SfxItemState::SET == pArgs->GetItemState(SID_ATTR_POSTIT_TEXT, true, &pPoolItem)) + { + sText = static_cast<const SfxStringItem*>(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; + sal_uInt32 nId = 0; + OUString sText; + if (!pArgs) + return; + + if (mpDoc->IsUndoEnabled()) + mpDoc->BegUndo(SdResId(STR_ANNOTATION_UNDO_EDIT)); + + const SfxPoolItem* pPoolItem = nullptr; + if (SfxItemState::SET == pArgs->GetItemState(SID_ATTR_POSTIT_ID, true, &pPoolItem)) + { + nId = static_cast<const SvxPostItIdItem*>(pPoolItem)->GetValue().toUInt32(); + xAnnotation = GetAnnotationById(nId); + } + if (SfxItemState::SET == pArgs->GetItemState(SID_ATTR_POSTIT_TEXT, true, &pPoolItem)) + sText = static_cast<const SfxStringItem*>(pPoolItem)->GetValue(); + + if (xAnnotation.is() && !sText.isEmpty()) + { + CreateChangeUndo(xAnnotation); + + // 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< long >( aPoint.X * 100.0 ) ); + aTagRect.SetTop( sal::static_int_cast< long >( aPoint.Y * 100.0 ) ); + aTagRect.SetRight( aTagRect.Left() + width - 1 ); + aTagRect.SetBottom( aTagRect.Top() + height - 1 ); + + if( aNewRect.IsOver( 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(); + sal_uInt32 nReplyId = 0; // Id of the comment to reply to + OUString sReplyText; + if( pArgs ) + { + const SfxPoolItem* pPoolItem = nullptr; + if( SfxItemState::SET == pArgs->GetItemState( SID_ATTR_POSTIT_ID, true, &pPoolItem ) ) + { + 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; + + std::unique_ptr< ::Outliner > pOutliner( new ::Outliner(GetAnnotationPool(),OutlinerMode::TextObject) ); + + SdDrawDocument::SetCalcFieldValueHdl( pOutliner.get() ); + pOutliner->SetUpdateMode( 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; ) + pOutliner->Insert( aStr.getToken( 0, '\n', nIdx ), EE_PARA_APPEND, -1 ); + + if( pOutliner->GetParagraphCount() > 1 ) + { + SfxItemSet aAnswerSet( pOutliner->GetEmptyItemSet() ); + aAnswerSet.Put(SvxPostureItem(ITALIC_NORMAL,EE_CHAR_ITALIC)); + + ESelection aSel; + aSel.nEndPara = pOutliner->GetParagraphCount()-2; + aSel.nEndPos = pOutliner->GetText( pOutliner->GetParagraph( aSel.nEndPara ) ).getLength(); + + pOutliner->QuickSetAttribs( aAnswerSet, aSel ); + } + + if (!sReplyText.isEmpty()) + pOutliner->Insert(sReplyText); + + std::unique_ptr< OutlinerParaObject > pOPO( pOutliner->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( const OUString& 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( SvtSaveOptions().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 bForeward) +{ + ShowAnnotations( true ); + + Reference< XAnnotation > xCurrent; + GetSelectedAnnotation( xCurrent ); + SdPage* pPage = GetCurrentPage(); + if( !pPage ) + return; + + AnnotationVector aAnnotations( pPage->getAnnotations() ); + + if( bForeward ) + { + 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, bForeward ); + + 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( bForeward ? SID_NEXT_POSTIT : SID_PREVIOUS_POSTIT ); + + return; + } + } + } + while( pPage ); + + // The question text depends on the search direction. + bool bImpress = mpDoc->GetDocumentType() == DocumentType::Impress; + const char* pStringId; + if(bForeward) + 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).get() ); + 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& ) + { + OSL_FAIL( "sd::AnnotationManagerImpl::onSelectionChanged(), exception caught!" ); + } +} + +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; + + 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, *mrBase.GetMainViewShell()->GetView(), xAnnotation, aColor, nIndex++, maFont ) ); + maTagVector.push_back(xTag); + + if( xAnnotation == mxSelectedAnnotation ) + { + xSelectedTag = xTag; + } + } + + if( xSelectedTag.is() ) + { + SmartTagReference xTag( xSelectedTag.get() ); + mrBase.GetMainViewShell()->GetView()->getSmartTags().select( xTag ); + } + else + { + // no tag, no selection! + mxSelectedAnnotation.clear(); + } + } + catch( Exception& ) + { + OSL_FAIL( "sd::AnnotationManagerImpl::onSelectionChanged(), exception caught!" ); + } +} + +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; + } +} + +namespace +{ + sal_uInt16 IdentToSID(const OString& rIdent) + { + if (rIdent == "reply") + return SID_REPLYTO_POSTIT; + else if (rIdent == "delete") + return SID_DELETE_POSTIT; + else if (rIdent == "deleteby") + return SID_DELETEALLBYAUTHOR_POSTIT; + else if (rIdent == "deleteall") + return SID_DELETEALL_POSTIT; + else if (rIdent == "copy") + return SID_COPY; + else if (rIdent == "paste") + return SID_PASTE; + else if (rIdent == "bold") + return SID_ATTR_CHAR_WEIGHT; + else if (rIdent == "italic") + return SID_ATTR_CHAR_POSTURE; + else if (rIdent == "underline") + return SID_ATTR_CHAR_UNDERLINE; + else if (rIdent == "strike") + return SID_ATTR_CHAR_STRIKEOUT; + return 0; + } +} + +void AnnotationManagerImpl::ExecuteAnnotationContextMenu( const Reference< XAnnotation >& xAnnotation, vcl::Window* pParent, const ::tools::Rectangle& rContextRect, bool bButtonMenu /* = false */ ) +{ + SfxDispatcher* pDispatcher( getDispatcher( mrBase ) ); + if( !pDispatcher ) + return; + + const bool bReadOnly = mrBase.GetDocShell()->IsReadOnly(); + + AnnotationWindow* pAnnotationWindow = bButtonMenu ? nullptr : dynamic_cast< AnnotationWindow* >( pParent ); + + if( bReadOnly && !pAnnotationWindow ) + return; + + OUString sUIFile; + if (pAnnotationWindow) + sUIFile = "modules/simpress/ui/annotationmenu.ui"; + else + sUIFile = "modules/simpress/ui/annotationtagmenu.ui"; + VclBuilder aBuilder(nullptr, VclBuilderContainer::getUIRootDir(), sUIFile, ""); + VclPtr<PopupMenu> pMenu(aBuilder.get_menu("menu")); + + SvtUserOptions aUserOptions; + OUString sCurrentAuthor( aUserOptions.GetFullName() ); + OUString sAuthor( xAnnotation->getAuthor() ); + + OUString aStr(pMenu->GetItemText(pMenu->GetItemId("deleteby"))); + OUString aReplace( sAuthor ); + if( aReplace.isEmpty() ) + aReplace = SdResId( STR_ANNOTATION_NOAUTHOR ); + aStr = aStr.replaceFirst("%1", aReplace); + pMenu->SetItemText(pMenu->GetItemId("deleteby"), aStr); + pMenu->EnableItem(pMenu->GetItemId("reply"), (sAuthor != sCurrentAuthor) && !bReadOnly); + pMenu->EnableItem(pMenu->GetItemId("delete"), xAnnotation.is() && !bReadOnly); + pMenu->EnableItem(pMenu->GetItemId("deleteby"), !bReadOnly); + pMenu->EnableItem(pMenu->GetItemId("deleteall"), !bReadOnly); + + if( pAnnotationWindow ) + { + if( pAnnotationWindow->IsProtected() || bReadOnly ) + { + pMenu->EnableItem(pMenu->GetItemId("bold"), false); + pMenu->EnableItem(pMenu->GetItemId("italic"), false); + pMenu->EnableItem(pMenu->GetItemId("underline"), false); + pMenu->EnableItem(pMenu->GetItemId("strike"), false); + pMenu->EnableItem(pMenu->GetItemId("paste"), false); + } + else + { + SfxItemSet aSet(pAnnotationWindow->getView()->GetAttribs()); + + if ( aSet.GetItemState( EE_CHAR_WEIGHT ) == SfxItemState::SET ) + { + if( aSet.Get( EE_CHAR_WEIGHT ).GetWeight() == WEIGHT_BOLD ) + pMenu->CheckItem("bold"); + } + + if ( aSet.GetItemState( EE_CHAR_ITALIC ) == SfxItemState::SET ) + { + if( aSet.Get( EE_CHAR_ITALIC ).GetPosture() != ITALIC_NONE ) + pMenu->CheckItem("italic"); + + } + if ( aSet.GetItemState( EE_CHAR_UNDERLINE ) == SfxItemState::SET ) + { + if( aSet.Get( EE_CHAR_UNDERLINE ).GetLineStyle() != LINESTYLE_NONE ) + pMenu->CheckItem("underline"); + } + + if ( aSet.GetItemState( EE_CHAR_STRIKEOUT ) == SfxItemState::SET ) + { + if( aSet.Get( EE_CHAR_STRIKEOUT ).GetStrikeout() != STRIKEOUT_NONE ) + pMenu->CheckItem("strike"); + } + TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromSystemClipboard( pAnnotationWindow ) ); + pMenu->EnableItem(pMenu->GetItemId("paste"), aDataHelper.GetFormatCount() != 0); + } + + pMenu->EnableItem(pMenu->GetItemId("copy"), pAnnotationWindow->getView()->HasSelection()); + } + + // set slot images + Reference< css::frame::XFrame > xFrame( mrBase.GetMainViewShell()->GetViewFrame()->GetFrame().GetFrameInterface() ); + if( xFrame.is() ) + { + for( sal_uInt16 nPos = 0; nPos < pMenu->GetItemCount(); nPos++ ) + { + sal_uInt16 nId = pMenu->GetItemId( nPos ); + if (!pMenu->IsItemEnabled(nId)) + continue; + + Image aImage( vcl::CommandInfoProvider::GetImageForCommand( pMenu->GetItemCommand( nId ), xFrame ) ); + if( !!aImage ) + pMenu->SetItemImage( nId, aImage ); + } + } + + // tdf#99388 and tdf#99712 make known that PopupMenu is active at parent to + // allow suppressing closing of that window if needed + setPopupMenuActive(true); + + sal_uInt16 nId = pMenu->Execute( pParent, rContextRect, PopupMenuFlags::ExecuteDown|PopupMenuFlags::NoMouseUpClose ); + nId = IdentToSID(pMenu->GetItemIdent(nId)); + + // tdf#99388 and tdf#99712 reset flag, need to be done before reacting + // since closing it is one possible reaction + setPopupMenuActive(false); + + switch( nId ) + { + case SID_REPLYTO_POSTIT: + { + const SfxUnoAnyItem aItem( SID_REPLYTO_POSTIT, Any( xAnnotation ) ); + pDispatcher->ExecuteList(SID_REPLYTO_POSTIT, + SfxCallMode::ASYNCHRON, { &aItem }); + break; + } + case SID_DELETE_POSTIT: + { + const SfxUnoAnyItem aItem( SID_DELETE_POSTIT, Any( xAnnotation ) ); + pDispatcher->ExecuteList(SID_DELETE_POSTIT, SfxCallMode::ASYNCHRON, + { &aItem }); + break; + } + case SID_DELETEALLBYAUTHOR_POSTIT: + { + const SfxStringItem aItem( SID_DELETEALLBYAUTHOR_POSTIT, sAuthor ); + pDispatcher->ExecuteList( SID_DELETEALLBYAUTHOR_POSTIT, + SfxCallMode::ASYNCHRON, { &aItem }); + break; + } + case SID_DELETEALL_POSTIT: + pDispatcher->Execute( SID_DELETEALL_POSTIT ); + break; + case SID_COPY: + case SID_PASTE: + case SID_ATTR_CHAR_WEIGHT: + case SID_ATTR_CHAR_POSTURE: + case SID_ATTR_CHAR_UNDERLINE: + case SID_ATTR_CHAR_STRIKEOUT: + if( pAnnotationWindow ) + pAnnotationWindow->ExecuteSlot( nId ); + break; + } +} + +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..92d27cd8a --- /dev/null +++ b/sd/source/ui/annotations/annotationmanagerimpl.hxx @@ -0,0 +1,150 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SD_SOURCE_UI_ANNOTATIONS_ANNOTATIONMANAGERIMPL_HXX +#define INCLUDED_SD_SOURCE_UI_ANNOTATIONS_ANNOTATIONMANAGERIMPL_HXX + +#include <com/sun/star/document/XEventListener.hpp> + +#include <rtl/ustring.hxx> + +#include <cppuhelper/basemutex.hxx> +#include <cppuhelper/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; + +namespace sd +{ + +class ViewShellBase; +typedef std::vector< rtl::Reference< AnnotationTag > > AnnotationTagVector; + +namespace tools { +class EventMultiplexerEvent; +} + +typedef ::cppu::WeakComponentImplHelper < + css::document::XEventListener + > AnnotationManagerImplBase; + +class AnnotationManagerImpl : private ::cppu::BaseMutex, public AnnotationManagerImplBase +{ +public: + explicit AnnotationManagerImpl( ViewShellBase& rViewShellBase ); + + void init(); + + // WeakComponentImplHelper + virtual void SAL_CALL disposing () 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 bForeward); + + 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( const OUString& sAuthor ); + void DeleteAllAnnotations(); + + void ExecuteAnnotationContextMenu( const css::uno::Reference< css::office::XAnnotation >& xAnnotation, vcl::Window* pParent, const ::tools::Rectangle& rContextRect, bool bButtonMenu = false ); + + 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 bForeward ); + + SdPage* GetCurrentPage(); + + SdDrawDocument* GetDoc() { return mpDoc; } + + void ShowAnnotations(bool bShow); + + // tdf#99388 and tdf#99712 flag to transport if the PopupMenu is active + bool getPopupMenuActive() const { return mbPopupMenuActive; } + void setPopupMenuActive(bool bNew) { mbPopupMenuActive = bNew; } + +private: + ViewShellBase& mrBase; + SdDrawDocument* mpDoc; + + AnnotationTagVector maTagVector; + + css::uno::Reference< css::drawing::XDrawView > mxView; + css::uno::Reference< css::office::XAnnotationAccess > mxCurrentPage; + css::uno::Reference< css::office::XAnnotation > mxSelectedAnnotation; + + bool mbShowAnnotations; + bool mbPopupMenuActive; + 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(); + +} + +#endif // INCLUDED_SD_SOURCE_UI_ANNOTATIONS_ANNOTATIONMANAGERIMPL_HXX + +/* 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..67d6366de --- /dev/null +++ b/sd/source/ui/annotations/annotationtag.cxx @@ -0,0 +1,671 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <com/sun/star/geometry/RealPoint2D.hpp> +#include <com/sun/star/office/XAnnotation.hpp> + +#include <vcl/commandevent.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> + +#include <svx/sdr/overlay/overlayanimatedbitmapex.hxx> +#include <svx/sdr/overlay/overlaybitmapex.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 <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; +static 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; + + // 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 ) +, mnClosePopupEvent( nullptr ) +, 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 ) + { + vcl::Window* pWindow = mrView.GetViewShell()->GetActiveWindow(); + if( pWindow ) + { + ::tools::Rectangle aContextRect(rCEvt.GetMousePosPixel(),Size(1,1)); + mrManager.ExecuteAnnotationContextMenu( mxAnnotation, pWindow, 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 ) +{ + long nX = 0; + 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(); + 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<long>(aPosition.X * 100.0), static_cast<long>(aPosition.Y * 100.0) ); + pHdl->SetPos( aBasePos ); + + rHandlerList.AddHdl( std::move(pHdl) ); +} + +void AnnotationTag::disposing() +{ + if( mpListenWindow ) + { + mpListenWindow->RemoveEventListener( LINK(this, AnnotationTag, WindowEventHandler)); + } + + if( mnClosePopupEvent ) + { + Application::RemoveUserEvent( mnClosePopupEvent ); + mnClosePopupEvent = nullptr; + } + + 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<long>(aPosition.X * 100.0), static_cast<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 ) + { + vcl::Window* pWindow = dynamic_cast< vcl::Window* >( getView().GetFirstOutputDevice() ); + if( pWindow ) + { + RealPoint2D aPosition( mxAnnotation->getPosition() ); + Point aPos( pWindow->OutputToScreenPixel( pWindow->LogicToPixel( Point( static_cast<long>(aPosition.X * 100.0), static_cast<long>(aPosition.Y * 100.0) ) ) ) ); + + aPos.AdjustX(4 ); // magic! + aPos.AdjustY(1 ); + + ::tools::Rectangle aRect( aPos, maSize ); + + mpAnnotationWindow.reset( VclPtr<AnnotationWindow>::Create( mrManager, mrView.GetDocSh(), pWindow->GetWindow(GetWindowType::Frame) ) ); + mpAnnotationWindow->InitControls(); + mpAnnotationWindow->setAnnotation(mxAnnotation); + + sal_uInt16 nArrangeIndex = 0; + Point aPopupPos( FloatingWindow::CalcFloatingPosition( mpAnnotationWindow.get(), aRect, FloatWinPopupFlags::Right, nArrangeIndex ) ); + Size aPopupSize( 320, 240 ); + + mpAnnotationWindow->SetPosSizePixel( aPopupPos, aPopupSize ); + mpAnnotationWindow->DoResize(); + + mpAnnotationWindow->Show(); + mpAnnotationWindow->GrabFocus(); + mpAnnotationWindow->AddEventListener( LINK(this, AnnotationTag, WindowEventHandler)); + } + } + + if( bEdit && mpAnnotationWindow ) + mpAnnotationWindow->StartEdit(); +} + +void AnnotationTag::ClosePopup() +{ + if( mpAnnotationWindow ) + { + mpAnnotationWindow->RemoveEventListener( LINK(this, AnnotationTag, WindowEventHandler)); + mpAnnotationWindow->Deactivate(); + mpAnnotationWindow.disposeAndClear(); + } +} + +IMPL_LINK(AnnotationTag, WindowEventHandler, VclWindowEvent&, rEvent, void) +{ + vcl::Window* pWindow = rEvent.GetWindow(); + + if( !pWindow ) + return; + + if( pWindow == mpAnnotationWindow.get() ) + { + if( rEvent.GetId() == VclEventId::WindowDeactivate ) + { + // tdf#99388 and tdf#99712 if PopupMenu is active, suppress + // deletion of the AnnotationWindow which is triggered by + // it losing focus + if (!mrManager.getPopupMenuActive()) + { + if( mnClosePopupEvent ) + Application::RemoveUserEvent( mnClosePopupEvent ); + + mnClosePopupEvent = Application::PostUserEvent( LINK( this, AnnotationTag, ClosePopupHdl ) ); + } + } + } + else if( pWindow == mpListenWindow ) + { + 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.get() == nullptr ) + 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; + } + } +} + +IMPL_LINK_NOARG(AnnotationTag, ClosePopupHdl, void*, void) +{ + mnClosePopupEvent = nullptr; + ClosePopup(); +} + +} // 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..f05b62cd8 --- /dev/null +++ b/sd/source/ui/annotations/annotationtag.hxx @@ -0,0 +1,91 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SD_SOURCE_UI_ANNOTATIONS_ANNOTATIONTAG_HXX +#define INCLUDED_SD_SOURCE_UI_ANNOTATIONS_ANNOTATIONTAG_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( ClosePopupHdl, void*, void ); + + AnnotationManagerImpl& mrManager; + css::uno::Reference< css::office::XAnnotation > mxAnnotation; + VclPtr<AnnotationWindow> mpAnnotationWindow; + Color maColor; + int mnIndex; + const vcl::Font& mrFont; + Size maSize; + ImplSVEvent * mnClosePopupEvent; + VclPtr<vcl::Window> mpListenWindow; + Point maMouseDownPos; +}; + +} // end of namespace sd + +#endif // INCLUDED_SD_SOURCE_UI_ANNOTATIONS_ANNOTATIONTAG_HXX + +/* 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..828045c6b --- /dev/null +++ b/sd/source/ui/annotations/annotationwindow.cxx @@ -0,0 +1,752 @@ +/* -*- 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 <vcl/commandevent.hxx> +#include <vcl/vclenum.hxx> +#include <vcl/scrbar.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 METABUTTON_AREA_WIDTH 30 +#define POSTIT_META_HEIGHT sal_Int32(30) + +namespace sd { + +static Color ColorFromAlphaColor(sal_uInt8 aTransparency, Color const &aFront, Color const &aBack ) +{ + return Color(static_cast<sal_uInt8>(aFront.GetRed() * aTransparency/double(255) + aBack.GetRed() * (1-aTransparency/double(255))), + static_cast<sal_uInt8>(aFront.GetGreen() * aTransparency/double(255) + aBack.GetGreen() * (1-aTransparency/double(255))), + static_cast<sal_uInt8>(aFront.GetBlue() * aTransparency/double(255) + aBack.GetBlue() * (1-aTransparency/double(255)))); +} + +/************ AnnotationTextWindow **********************************/ + +AnnotationTextWindow::AnnotationTextWindow( AnnotationWindow* pParent, WinBits nBits ) +: Control(pParent, nBits) +, mpOutlinerView(nullptr) +, mpAnnotationWindow( pParent ) +{ +} + +AnnotationTextWindow::~AnnotationTextWindow() +{ + disposeOnce(); +} + +void AnnotationTextWindow::dispose() +{ + mpAnnotationWindow.clear(); + Control::dispose(); +} + +void AnnotationTextWindow::Paint( vcl::RenderContext& /*rRenderContext*/, const ::tools::Rectangle& rRect) +{ + const bool bHighContrast = Application::GetSettings().GetStyleSettings().GetHighContrastMode(); + if ( !bHighContrast ) + { + DrawGradient(::tools::Rectangle(Point(0,0),PixelToLogic(GetSizePixel())), + Gradient(GradientStyle::Linear,mpAnnotationWindow->maColorLight,mpAnnotationWindow->maColor)); + } + + if( mpOutlinerView ) + { + Color aBackgroundColor( mpAnnotationWindow->maColor ); + if( bHighContrast ) + { + aBackgroundColor = GetSettings().GetStyleSettings().GetWindowColor(); + } + + mpOutlinerView->SetBackgroundColor( aBackgroundColor ); + + mpOutlinerView->Paint( rRect ); + } +} + +void AnnotationTextWindow::KeyInput( const KeyEvent& rKeyEvt ) +{ + const vcl::KeyCode& rKeyCode = rKeyEvt.GetKeyCode(); + sal_uInt16 nKey = rKeyCode.GetCode(); + + if ((rKeyCode.IsMod1() && rKeyCode.IsMod2()) && ((nKey == KEY_PAGEUP) || (nKey == KEY_PAGEDOWN))) + { + SfxDispatcher* pDispatcher = mpAnnotationWindow->DocShell()->GetViewShell()->GetViewFrame()->GetDispatcher(); + if( pDispatcher ) + pDispatcher->Execute( nKey == KEY_PAGEDOWN ? SID_NEXT_POSTIT : SID_PREVIOUS_POSTIT ); + } + else if (nKey == KEY_INSERT) + { + if (!rKeyCode.IsMod1() && !rKeyCode.IsMod2()) + mpAnnotationWindow->ToggleInsMode(); + } + else + { + long aOldHeight = mpAnnotationWindow->GetPostItTextHeight(); + bool bDone = false; + + /// HACK: need to switch off processing of Undo/Redo in Outliner + if ( !( (nKey == KEY_Z || nKey == KEY_Y) && rKeyCode.IsMod1()) ) + { + bool bIsProtected = mpAnnotationWindow->IsProtected(); + if (!bIsProtected || !EditEngine::DoesKeyChangeText(rKeyEvt) ) + + bDone = mpOutlinerView->PostKeyEvent( rKeyEvt ); + } + if (bDone) + { + mpAnnotationWindow->ResizeIfNecessary(aOldHeight,mpAnnotationWindow->GetPostItTextHeight()); + } + else + { + Control::KeyInput(rKeyEvt); + } + } +} + +void AnnotationTextWindow::MouseMove( const MouseEvent& rMEvt ) +{ + if ( mpOutlinerView ) + { + mpOutlinerView->MouseMove( rMEvt ); + SetPointer( mpOutlinerView->GetPointer( rMEvt.GetPosPixel() ) ); + } +} + +void AnnotationTextWindow::MouseButtonDown( const MouseEvent& rMEvt ) +{ + GrabFocus(); + if ( mpOutlinerView ) + mpOutlinerView->MouseButtonDown( rMEvt ); +} + +void AnnotationTextWindow::MouseButtonUp( const MouseEvent& rMEvt ) +{ + if ( mpOutlinerView ) + mpOutlinerView->MouseButtonUp( rMEvt ); +} + +void AnnotationTextWindow::Command( const CommandEvent& rCEvt ) +{ + if ( rCEvt.GetCommand() == CommandEventId::ContextMenu ) + { + mpAnnotationWindow->Command(rCEvt); + } + else + { + if ( mpOutlinerView ) + mpOutlinerView->Command( rCEvt ); + else + Window::Command(rCEvt); + } +} + +OUString AnnotationTextWindow::GetSurroundingText() const +{ + if( mpOutlinerView ) + { + EditEngine *aEditEngine = mpOutlinerView->GetEditView().GetEditEngine(); + if( mpOutlinerView->HasSelection() ) + return mpOutlinerView->GetSelected(); + else + { + ESelection aSelection = mpOutlinerView->GetEditView().GetSelection(); + return aEditEngine->GetText(aSelection.nStartPara); + } + } + return OUString(); +} + +Selection AnnotationTextWindow::GetSurroundingTextSelection() const +{ + if( mpOutlinerView ) + { + if( mpOutlinerView->HasSelection() ) + return Selection( 0, mpOutlinerView->GetSelected().getLength() ); + else + { + ESelection aSelection = mpOutlinerView->GetEditView().GetSelection(); + return Selection( aSelection.nStartPos, aSelection.nEndPos ); + } + } + else + return Selection( 0, 0 ); +} + +/************** AnnotationWindow***********************************++*/ + +AnnotationWindow::AnnotationWindow( AnnotationManagerImpl& rManager, DrawDocShell* pDocShell, vcl::Window* pParent ) +: FloatingWindow(pParent, WB_BORDER) +, mrManager( rManager ) +, mpDocShell( pDocShell ) +, mpDoc( pDocShell->GetDoc() ) +, mpVScrollbar(nullptr) +, mbReadonly(pDocShell->IsReadOnly()) +, mbProtected(false) +, mbMouseOverButton(false) +, mpTextWindow(nullptr) +, mpMeta(nullptr) +{ + EnableAlwaysOnTop(); +} + +AnnotationWindow::~AnnotationWindow() +{ + disposeOnce(); +} + +void AnnotationWindow::dispose() +{ + mpMeta.disposeAndClear(); + mpOutlinerView.reset(); + mpOutliner.reset(); + mpOutliner = nullptr; + mpVScrollbar.disposeAndClear(); + mpTextWindow.disposeAndClear(); + FloatingWindow::dispose(); +} + +void AnnotationWindow::InitControls() +{ + // actual window which holds the user text + mpTextWindow = VclPtr<AnnotationTextWindow>::Create(this, WB_NODIALOGCONTROL); + mpTextWindow->SetPointer(PointerStyle::Text); + + // window control for author and date + mpMeta = VclPtr<VclMultiLineEdit>::Create(this,0); + mpMeta->SetReadOnly(); + mpMeta->SetRightToLeft(AllSettings::GetLayoutRTL()); + mpMeta->AlwaysDisableInput(true); + mpMeta->SetCallHandlersOnInputDisabled(true); + + // we should leave this setting alone, but for this we need a better layout algo + // with variable meta size height + AllSettings aSettings = mpMeta->GetSettings(); + StyleSettings aStyleSettings = aSettings.GetStyleSettings(); + vcl::Font aFont = aStyleSettings.GetFieldFont(); + aFont.SetFontHeight(8); + aStyleSettings.SetFieldFont(aFont); + aSettings.SetStyleSettings(aStyleSettings); + mpMeta->SetSettings(aSettings); + + mpOutliner.reset( new ::Outliner(GetAnnotationPool(),OutlinerMode::TextObject) ); + SdDrawDocument::SetCalcFieldValueHdl( mpOutliner.get() ); + mpOutliner->SetUpdateMode( true ); + Rescale(); + + OutputDevice* pDev = Doc()->GetRefDevice(); + if( pDev ) + { + mpOutliner->SetRefDevice( pDev ); + } + + mpTextWindow->EnableRTL( false ); + mpOutlinerView.reset( new OutlinerView ( mpOutliner.get(), mpTextWindow ) ); + mpOutliner->InsertView(mpOutlinerView.get() ); + mpTextWindow->SetOutlinerView(mpOutlinerView.get()); + mpOutlinerView->SetOutputArea( PixelToLogic( ::tools::Rectangle(0,0,1,1) ) ); + + //create Scrollbars + mpVScrollbar = VclPtr<ScrollBar>::Create(this, WB_3DLOOK |WB_VSCROLL|WB_DRAG); + mpVScrollbar->EnableNativeWidget(false); + mpVScrollbar->EnableRTL( false ); + mpVScrollbar->SetScrollHdl(LINK(this, AnnotationWindow, ScrollHdl)); + mpVScrollbar->EnableDrag(); + + EEControlBits nCntrl = mpOutliner->GetControlWord(); + nCntrl |= EEControlBits::PASTESPECIAL | EEControlBits::AUTOCORRECT | EEControlBits::USECHARATTRIBS | EEControlBits::NOCOLORS; + mpOutliner->SetControlWord(nCntrl); + + Engine()->SetModifyHdl( Link<LinkParamNone*,void>() ); + Engine()->EnableUndo( false ); + + Engine()->ClearModifyFlag(); + Engine()->GetUndoManager().Clear(); + Engine()->EnableUndo( true ); + + Invalidate(); + + SetLanguage(SvxLanguageItem( Doc()->GetLanguage( EE_CHAR_LANGUAGE ), SID_ATTR_LANGUAGE )); + + mpMeta->Show(); + mpVScrollbar->Show(); + mpTextWindow->Show(); +} + +void AnnotationWindow::StartEdit() +{ + getView()->SetSelection(ESelection(EE_PARA_MAX_COUNT,EE_TEXTPOS_MAX_COUNT,EE_PARA_MAX_COUNT,EE_TEXTPOS_MAX_COUNT)); + getView()->ShowCursor(); +} + +void AnnotationWindow::Rescale() +{ + MapMode aMode(MapUnit::Map100thMM); + aMode.SetOrigin( Point() ); + mpOutliner->SetRefMapMode( aMode ); + SetMapMode( aMode ); + mpTextWindow->SetMapMode( aMode ); + if ( mpMeta ) + { + vcl::Font aFont( mpMeta->GetSettings().GetStyleSettings().GetFieldFont() ); + sal_Int32 nHeight = long(aFont.GetFontHeight() * aMode.GetScaleY()); + aFont.SetFontHeight( nHeight ); + mpMeta->SetControlFont( aFont ); + } +} + +void AnnotationWindow::DoResize() +{ + unsigned long aWidth = GetSizePixel().Width(); + long aHeight = GetSizePixel().Height() - POSTIT_META_HEIGHT; + + mpOutliner->SetPaperSize( PixelToLogic( Size(aWidth,aHeight) ) ) ; + long aTextHeight = LogicToPixel( mpOutliner->CalcTextSize()).Height(); + + if( aTextHeight > aHeight ) + { // we need vertical scrollbars and have to reduce the width + aWidth -= GetScrollbarWidth(); + mpVScrollbar->Show(); + } + else + { + mpVScrollbar->Hide(); + } + + mpTextWindow->setPosSizePixel(0,0,aWidth, aHeight); + + if( mbReadonly ) + mpMeta->setPosSizePixel(0,aHeight,GetSizePixel().Width(),POSTIT_META_HEIGHT); + else + mpMeta->setPosSizePixel(0,aHeight,GetSizePixel().Width()-METABUTTON_AREA_WIDTH,POSTIT_META_HEIGHT); + + mpOutliner->SetPaperSize( PixelToLogic( Size(aWidth,aHeight) ) ) ; + mpOutlinerView->SetOutputArea( PixelToLogic( ::tools::Rectangle(0,0,aWidth,aHeight) ) ); + if (!mpVScrollbar->IsVisible()) + { // if we do not have a scrollbar anymore, we want to see the complete text + mpOutlinerView->SetVisArea( PixelToLogic( ::tools::Rectangle(0,0,aWidth,aHeight) ) ); + } + mpVScrollbar->setPosSizePixel( 0 + aWidth, 0, GetScrollbarWidth(), aHeight ); + mpVScrollbar->SetVisibleSize( PixelToLogic(Size(0,aHeight)).Height() ); + mpVScrollbar->SetPageSize( PixelToLogic(Size(0,aHeight)).Height() * 8 / 10 ); + mpVScrollbar->SetLineSize( mpOutliner->GetTextHeight() / 10 ); + SetScrollbar(); + mpVScrollbar->SetRange( Range(0, mpOutliner->GetTextHeight())); + + Point aPos( mpMeta->GetPosPixel()); + Point aBase( aPos.X() + aPos.X() + GetSizePixel().Width(), aPos.Y() ); + Point aLeft = PixelToLogic( Point( aBase.X() - (METABUTTON_WIDTH+5), aBase.Y()+17 ) ); + Point aRight = PixelToLogic( Point( aBase.X() - (METABUTTON_WIDTH-1), aBase.Y()+17 ) ); + Point aBottom = PixelToLogic( Point( aBase.X() - (METABUTTON_WIDTH+2), aBase.Y()+20 ) ); + + maPopupTriangle.clear(); + maPopupTriangle.append(basegfx::B2DPoint(aLeft.X(),aLeft.Y())); + maPopupTriangle.append(basegfx::B2DPoint(aRight.X(),aRight.Y())); + maPopupTriangle.append(basegfx::B2DPoint(aBottom.X(),aBottom.Y())); + maPopupTriangle.setClosed(true); + maRectMetaButton = PixelToLogic( ::tools::Rectangle( Point( + aPos.X()+GetSizePixel().Width()-(METABUTTON_WIDTH+10), + aPos.Y()+5 ), + Size( METABUTTON_WIDTH, METABUTTON_HEIGHT ) ) ); + +} + +void AnnotationWindow::SetScrollbar() +{ + mpVScrollbar->SetThumbPos(mpOutlinerView->GetVisArea().Top()); +} + +void AnnotationWindow::ResizeIfNecessary(long aOldHeight, long aNewHeight) +{ + if (aOldHeight != aNewHeight) + { + DoResize(); + Invalidate(); + } + else + { + SetScrollbar(); + } +} + +void AnnotationWindow::SetLanguage(const SvxLanguageItem &aNewItem) +{ + Engine()->SetModifyHdl( Link<LinkParamNone*,void>() ); + ESelection aOld = getView()->GetSelection(); + + ESelection aNewSelection( 0, 0, Engine()->GetParagraphCount()-1, EE_TEXTPOS_ALL ); + getView()->SetSelection( aNewSelection ); + SfxItemSet aEditAttr(getView()->GetAttribs()); + aEditAttr.Put(aNewItem); + getView()->SetAttribs( aEditAttr ); + + getView()->SetSelection(aOld); + + Invalidate(); +} + +void AnnotationWindow::ToggleInsMode() +{ + if( mpOutlinerView ) + { + SfxBindings &rBnd = mpDocShell->GetViewShell()->GetViewFrame()->GetBindings(); + rBnd.Invalidate(SID_ATTR_INSERT); + rBnd.Update(SID_ATTR_INSERT); + } +} + +long AnnotationWindow::GetPostItTextHeight() +{ + return mpOutliner ? LogicToPixel(mpOutliner->CalcTextSize()).Height() : 0; +} + +IMPL_LINK(AnnotationWindow, ScrollHdl, ScrollBar*, pScroll, void) +{ + long nDiff = getView()->GetEditView().GetVisArea().Top() - pScroll->GetThumbPos(); + getView()->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(); + + Engine()->Clear(); + TextApiObject* pTextApi = getTextApiObject( mxAnnotation ); + + if( pTextApi ) + { + std::unique_ptr< OutlinerParaObject > pOPO( pTextApi->CreateText() ); + Engine()->SetText(*pOPO); + } + + Engine()->ClearModifyFlag(); + Engine()->GetUndoManager().Clear(); + + Invalidate(); + + OUString sMeta( xAnnotation->getAuthor() ); + OUString sDateTime( getAnnotationDateTimeString(xAnnotation) ); + + if( !sDateTime.isEmpty() ) + { + if( !sMeta.isEmpty() ) + sMeta += "\n"; + + sMeta += sDateTime; + } + mpMeta->SetText(sMeta); +} + +void AnnotationWindow::SetColor() +{ + sal_uInt16 nAuthorIdx = mpDoc->GetAnnotationAuthorIndex( mxAnnotation->getAuthor() ); + + const bool bHighContrast = Application::GetSettings().GetStyleSettings().GetHighContrastMode(); + if( bHighContrast ) + { + StyleSettings aStyleSettings = GetSettings().GetStyleSettings(); + + maColor = aStyleSettings.GetWindowColor(); + maColorDark = maColor; + maColorLight = aStyleSettings.GetWindowTextColor(); + } + else + { + maColor = AnnotationManagerImpl::GetColor( nAuthorIdx ); + maColorDark = AnnotationManagerImpl::GetColorDark( nAuthorIdx ); + maColorLight = AnnotationManagerImpl::GetColorLight( nAuthorIdx ); + } + + mpOutlinerView->SetBackgroundColor(maColor); + Engine()->SetBackgroundColor(maColor); + + { + SvtAccessibilityOptions aOptions; + Engine()->ForceAutoColor( bHighContrast || aOptions.GetIsAutomaticFontColor() ); + } + + mpMeta->SetControlBackground(maColor); + AllSettings aSettings = mpMeta->GetSettings(); + StyleSettings aStyleSettings = aSettings.GetStyleSettings(); + aStyleSettings.SetFieldTextColor( bHighContrast ? maColorLight : maColorDark); + aSettings.SetStyleSettings(aStyleSettings); + mpMeta->SetSettings(aSettings); + + AllSettings aSettings2 = mpVScrollbar->GetSettings(); + StyleSettings aStyleSettings2 = aSettings2.GetStyleSettings(); + aStyleSettings2.SetButtonTextColor(Color(0,0,0)); + aStyleSettings2.SetCheckedColor(maColorLight); //hintergund + aStyleSettings2.SetShadowColor(maColorDark); + aStyleSettings2.SetFaceColor(maColor); + aSettings2.SetStyleSettings(aStyleSettings2); + mpVScrollbar->SetSettings(aSettings2); +} + +void AnnotationWindow::Deactivate() +{ + //tdf#99388 and tdf#99712, don't deactivate if we lose focus because of our + //own popup + if (mrManager.getPopupMenuActive()) + return; + + if (!mpOutliner) //in dispose + return; + + Reference< XAnnotation > xAnnotation( mxAnnotation ); + + // write changed text back to annotation + if ( Engine()->IsModified() ) + { + TextApiObject* pTextApi = getTextApiObject( xAnnotation ); + + if( pTextApi ) + { + std::unique_ptr<OutlinerParaObject> pOPO = Engine()->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(); + } + + } + } + Engine()->ClearModifyFlag(); + + Engine()->GetUndoManager().Clear(); +} + +void AnnotationWindow::Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& rRect) +{ + FloatingWindow::Paint(rRenderContext, rRect); + + if(!(mpMeta->IsVisible() && !mbReadonly)) + return; + + const bool bHighContrast = Application::GetSettings().GetStyleSettings().GetHighContrastMode(); + //draw left over space + if ( bHighContrast ) + SetFillColor(COL_BLACK); + else + SetFillColor(maColor); + SetLineColor(); + DrawRect(PixelToLogic(::tools::Rectangle(Point(mpMeta->GetPosPixel().X()+mpMeta->GetSizePixel().Width(),mpMeta->GetPosPixel().Y()),Size(METABUTTON_AREA_WIDTH,mpMeta->GetSizePixel().Height())))); + + if ( bHighContrast ) + { + //draw rect around button + SetFillColor(COL_BLACK); + SetLineColor(COL_WHITE); + } + else + { + //draw button + Gradient aGradient; + if (mbMouseOverButton) + aGradient = Gradient(GradientStyle::Linear,ColorFromAlphaColor(80,maColorDark,maColor),ColorFromAlphaColor(15,maColorDark,maColor)); + else + aGradient = Gradient(GradientStyle::Linear,ColorFromAlphaColor(15,maColorDark,maColor),ColorFromAlphaColor(80,maColorDark,maColor)); + DrawGradient(maRectMetaButton,aGradient); + //draw rect around button + SetFillColor(); + SetLineColor(ColorFromAlphaColor(90,maColorDark,maColor)); + } + DrawRect(maRectMetaButton); + + //draw arrow + if( bHighContrast ) + SetFillColor(COL_WHITE); + else + SetFillColor(COL_BLACK); + SetLineColor(); + DrawPolygon( ::tools::Polygon(maPopupTriangle)); +} + +void AnnotationWindow::MouseMove( const MouseEvent& rMEvt ) +{ + if( mbReadonly ) + return; + + if (maRectMetaButton.IsInside(PixelToLogic(rMEvt.GetPosPixel()))) + { + if (!mbMouseOverButton) + { + Invalidate(maRectMetaButton); + mbMouseOverButton = true; + } + } + else + { + if (mbMouseOverButton) + { + Invalidate(maRectMetaButton); + mbMouseOverButton = false; + } + } +} + +void AnnotationWindow::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if (!mbReadonly && maRectMetaButton.IsInside(PixelToLogic(rMEvt.GetPosPixel())) && rMEvt.IsLeft()) + { + // context menu + ::tools::Rectangle aRect(LogicToPixel(maRectMetaButton.BottomLeft()),LogicToPixel(maRectMetaButton.BottomLeft())); + mrManager.ExecuteAnnotationContextMenu( mxAnnotation, static_cast<vcl::Window*>(this), aRect, true ); + } +} + +void AnnotationWindow::Command( const CommandEvent& rCEvt ) +{ + if ( rCEvt.GetCommand() == CommandEventId::ContextMenu ) + { + if( mpMeta->IsVisible() &&(mpMeta->GetPosPixel().Y() < rCEvt.GetMousePosPixel().Y()) ) + return; + mrManager.ExecuteAnnotationContextMenu( mxAnnotation, this, ::tools::Rectangle(rCEvt.GetMousePosPixel(),Size(1,1)) ); + } + else + { + FloatingWindow::Command(rCEvt); + } +} + +void AnnotationWindow::GetFocus() +{ + if( mpTextWindow ) + mpTextWindow->GrabFocus(); + else + FloatingWindow::GetFocus(); +} + +void AnnotationWindow::ExecuteSlot( sal_uInt16 nSID ) +{ + if( nSID == SID_COPY ) + { + getView()->Copy(); + } + else if( nSID == SID_PASTE ) + { + getView()->PasteSpecial(); + DoResize(); + } + else + { + SfxItemSet aEditAttr(getView()->GetAttribs()); + SfxItemSet aNewAttr(mpOutliner->GetEmptyItemSet()); + + switch( nSID ) + { + case SID_ATTR_CHAR_WEIGHT: + { + FontWeight eFW = aEditAttr.Get( EE_CHAR_WEIGHT ).GetWeight(); + aNewAttr.Put( SvxWeightItem( eFW == WEIGHT_NORMAL ? WEIGHT_BOLD : WEIGHT_NORMAL, EE_CHAR_WEIGHT ) ); + } + break; + case SID_ATTR_CHAR_POSTURE: + { + FontItalic eFI = aEditAttr.Get( EE_CHAR_ITALIC ).GetPosture(); + aNewAttr.Put( SvxPostureItem( eFI == ITALIC_NORMAL ? ITALIC_NONE : ITALIC_NORMAL, EE_CHAR_ITALIC ) ); + } + break; + case SID_ATTR_CHAR_UNDERLINE: + { + FontLineStyle eFU = aEditAttr. Get( EE_CHAR_UNDERLINE ).GetLineStyle(); + aNewAttr.Put( SvxUnderlineItem( eFU == LINESTYLE_SINGLE ? LINESTYLE_NONE : LINESTYLE_SINGLE, EE_CHAR_UNDERLINE ) ); + } + break; + case SID_ATTR_CHAR_STRIKEOUT: + { + FontStrikeout eFSO = aEditAttr.Get( EE_CHAR_STRIKEOUT ).GetStrikeout(); + aNewAttr.Put( SvxCrossedOutItem( eFSO == STRIKEOUT_SINGLE ? STRIKEOUT_NONE : STRIKEOUT_SINGLE, EE_CHAR_STRIKEOUT ) ); + } + break; + } + getView()->SetAttribs( aNewAttr ); + } +} + +} + +/* 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..aa28144bd --- /dev/null +++ b/sd/source/ui/annotations/annotationwindow.hxx @@ -0,0 +1,145 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_SD_SOURCE_UI_ANNOTATIONS_ANNOTATIONWINDOW_HXX +#define INCLUDED_SD_SOURCE_UI_ANNOTATIONS_ANNOTATIONWINDOW_HXX + +#include <vcl/ctrl.hxx> +#include <vcl/floatwin.hxx> +#include <vcl/vclmedit.hxx> +#include <vcl/scrbar.hxx> + +#include <basegfx/polygon/b2dpolygon.hxx> + +namespace com::sun::star::office { class XAnnotation; } + +class OutlinerView; +class Outliner; +class SvxLanguageItem; +class SdDrawDocument; + +namespace sd { + +class AnnotationManagerImpl; +class AnnotationWindow; +class DrawDocShell; +class TextApiObject; + +class AnnotationTextWindow : public Control +{ +private: + OutlinerView* mpOutlinerView; + VclPtr<AnnotationWindow> mpAnnotationWindow; + +protected: + virtual void Paint( vcl::RenderContext& /*rRenderContext*/, const ::tools::Rectangle& rRect) override; + virtual void KeyInput( const KeyEvent& rKeyEvt ) override; + virtual void MouseMove( const MouseEvent& rMEvt ) override; + virtual void MouseButtonDown( const MouseEvent& rMEvt ) override; + virtual void MouseButtonUp( const MouseEvent& rMEvt ) override; + virtual void Command( const CommandEvent& rCEvt ) override; + +public: + AnnotationTextWindow( AnnotationWindow* pParent, WinBits nBits ); + virtual ~AnnotationTextWindow() override; + virtual void dispose() override; + + void SetOutlinerView( OutlinerView* pOutlinerView ) { mpOutlinerView = pOutlinerView; } + + virtual OUString GetSurroundingText() const override; + virtual Selection GetSurroundingTextSelection() const override; +}; + +class AnnotationWindow : public FloatingWindow +{ + private: + AnnotationManagerImpl& mrManager; + DrawDocShell* mpDocShell; + SdDrawDocument* mpDoc; + + std::unique_ptr<OutlinerView> mpOutlinerView; + std::unique_ptr<::Outliner> mpOutliner; + VclPtr<ScrollBar> mpVScrollbar; + css::uno::Reference< css::office::XAnnotation > mxAnnotation; + bool mbReadonly; + bool mbProtected; + bool mbMouseOverButton; + VclPtr<AnnotationTextWindow> mpTextWindow; + VclPtr<VclMultiLineEdit> mpMeta; + ::tools::Rectangle maRectMetaButton; + basegfx::B2DPolygon maPopupTriangle; + + protected: + DECL_LINK(ScrollHdl, ScrollBar*, void); + + public: + AnnotationWindow( AnnotationManagerImpl& rManager, DrawDocShell* pDocShell, vcl::Window* pParent ); + virtual ~AnnotationWindow() override; + virtual void dispose() override; + + void StartEdit(); + + void setAnnotation( const css::uno::Reference< css::office::XAnnotation >& xAnnotation ); + + void ExecuteSlot( sal_uInt16 nSID ); + + DrawDocShell* DocShell() { return mpDocShell; } + OutlinerView* getView() { return mpOutlinerView.get(); } + ::Outliner* Engine() { return mpOutliner.get(); } + SdDrawDocument* Doc() { return mpDoc; } + + long GetPostItTextHeight(); + + void InitControls(); + void DoResize(); + void ResizeIfNecessary(long aOldHeight, long aNewHeight); + void SetScrollbar(); + + void Rescale(); + + bool IsProtected() const { return mbProtected; } + + void SetLanguage(const SvxLanguageItem &aNewItem); + + static sal_Int32 GetScrollbarWidth() { return 16; } + + void ToggleInsMode(); + + virtual void Deactivate() override; + virtual void Paint( vcl::RenderContext& /*rRenderContext*/, const ::tools::Rectangle& rRect) override; + virtual void MouseMove( const MouseEvent& rMEvt ) override; + virtual void MouseButtonDown( const MouseEvent& rMEvt ) override; + virtual void Command( const CommandEvent& rCEvt ) override; + virtual void GetFocus() override; + + void SetColor(); + + Color maColor; + Color maColorDark; + Color maColorLight; +}; + +TextApiObject* getTextApiObject( const css::uno::Reference< css::office::XAnnotation >& xAnnotation ); + + +} // namespace sd + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |