1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <unotextmarkup.hxx>
22 #include <comphelper/servicehelper.hxx>
23 #include <o3tl/safeint.hxx>
24 #include <osl/diagnose.h>
25 #include <svl/listener.hxx>
27 #include <vcl/svapp.hxx>
28 #include <SwSmartTagMgr.hxx>
29 #include <com/sun/star/lang/IndexOutOfBoundsException.hpp>
30 #include <com/sun/star/text/TextMarkupType.hpp>
31 #include <com/sun/star/text/TextMarkupDescriptor.hpp>
32 #include <com/sun/star/container/ElementExistException.hpp>
33 #include <com/sun/star/container/XStringKeyMap.hpp>
35 #include <SwGrammarMarkUp.hxx>
36 #include <TextCursorHelper.hxx>
37 #include <GrammarContact.hxx>
39 #include <com/sun/star/lang/XUnoTunnel.hpp>
40 #include <com/sun/star/text/XTextRange.hpp>
44 #include <unotextrange.hxx>
45 #include <modeltoviewhelper.hxx>
47 using namespace ::com::sun::star
;
49 struct SwXTextMarkup::Impl
52 SwTextNode
* m_pTextNode
;
53 ModelToViewHelper
const m_ConversionMap
;
55 Impl(SwTextNode
* const pTextNode
, ModelToViewHelper aMap
)
56 : m_pTextNode(pTextNode
)
57 , m_ConversionMap(std::move(aMap
))
60 StartListening(pTextNode
->GetNotifier());
63 virtual void Notify(const SfxHint
& rHint
) override
;
66 SwXTextMarkup::SwXTextMarkup(
67 SwTextNode
*const pTextNode
, const ModelToViewHelper
& rMap
)
68 : m_pImpl(new Impl(pTextNode
, rMap
))
72 SwXTextMarkup::~SwXTextMarkup()
76 SwTextNode
* SwXTextMarkup::GetTextNode()
78 return m_pImpl
->m_pTextNode
;
81 void SwXTextMarkup::ClearTextNode()
83 m_pImpl
->m_pTextNode
= nullptr;
84 m_pImpl
->EndListeningAll();
87 const ModelToViewHelper
& SwXTextMarkup::GetConversionMap() const
89 return m_pImpl
->m_ConversionMap
;
92 uno::Reference
< container::XStringKeyMap
> SAL_CALL
SwXTextMarkup::getMarkupInfoContainer()
94 return new SwXStringKeyMap
;
97 void SAL_CALL
SwXTextMarkup::commitTextRangeMarkup(::sal_Int32 nType
, const OUString
& aIdentifier
, const uno::Reference
< text::XTextRange
> & xRange
,
98 const uno::Reference
< container::XStringKeyMap
> & xMarkupInfoContainer
)
100 SolarMutexGuard aGuard
;
102 if (auto pRange
= dynamic_cast<SwXTextRange
*>(xRange
.get()))
104 SwDoc
& rDoc
= pRange
->GetDoc();
106 SwUnoInternalPaM
aPam(rDoc
);
108 ::sw::XTextRangeToSwPaM(aPam
, xRange
);
110 auto [startPos
, endPos
] = aPam
.StartEnd(); // SwPosition*
112 commitStringMarkup (nType
, aIdentifier
, startPos
->GetContentIndex(), endPos
->GetContentIndex() - startPos
->GetContentIndex(), xMarkupInfoContainer
);
114 else if (auto pCursor
= dynamic_cast<OTextCursorHelper
*>(xRange
.get()))
116 SwPaM
& rPam(*pCursor
->GetPaM());
118 auto [startPos
, endPos
] = rPam
.StartEnd(); // SwPosition*
120 commitStringMarkup (nType
, aIdentifier
, startPos
->GetContentIndex(), endPos
->GetContentIndex() - startPos
->GetContentIndex(), xMarkupInfoContainer
);
124 void SAL_CALL
SwXTextMarkup::commitStringMarkup(
126 const OUString
& rIdentifier
,
129 const uno::Reference
< container::XStringKeyMap
> & xMarkupInfoContainer
)
131 SolarMutexGuard aGuard
;
133 // paragraph already dead or modified?
134 if (!m_pImpl
->m_pTextNode
|| nLength
<= 0)
137 if ( nType
== text::TextMarkupType::SMARTTAG
&&
138 !SwSmartTagMgr::Get().IsSmartTagTypeEnabled( rIdentifier
) )
141 // get appropriate list to use...
142 SwWrongList
* pWList
= nullptr;
143 bool bRepaint
= false;
144 if ( nType
== text::TextMarkupType::SPELLCHECK
)
146 pWList
= m_pImpl
->m_pTextNode
->GetWrong();
149 pWList
= new SwWrongList( WRONGLIST_SPELL
);
150 m_pImpl
->m_pTextNode
->SetWrong( std::unique_ptr
<SwWrongList
>(pWList
) );
153 else if ( nType
== text::TextMarkupType::PROOFREADING
|| nType
== text::TextMarkupType::SENTENCE
)
155 sw::GrammarContact
* pGrammarContact
= sw::getGrammarContactFor(*m_pImpl
->m_pTextNode
);
156 if( pGrammarContact
)
158 pWList
= pGrammarContact
->getGrammarCheck(*m_pImpl
->m_pTextNode
, true);
159 OSL_ENSURE( pWList
, "GrammarContact _has_ to deliver a wrong list" );
163 pWList
= m_pImpl
->m_pTextNode
->GetGrammarCheck();
166 m_pImpl
->m_pTextNode
->SetGrammarCheck( std::make_unique
<SwGrammarMarkUp
>() );
167 pWList
= m_pImpl
->m_pTextNode
->GetGrammarCheck();
170 bRepaint
= pWList
== m_pImpl
->m_pTextNode
->GetGrammarCheck();
171 if( pWList
->GetBeginInv() < COMPLETE_STRING
)
172 static_cast<SwGrammarMarkUp
*>(pWList
)->ClearGrammarList();
174 else if ( nType
== text::TextMarkupType::SMARTTAG
)
176 pWList
= m_pImpl
->m_pTextNode
->GetSmartTags();
179 pWList
= new SwWrongList( WRONGLIST_SMARTTAG
);
180 m_pImpl
->m_pTextNode
->SetSmartTags( std::unique_ptr
<SwWrongList
>(pWList
) );
185 OSL_FAIL( "Unknown mark-up type" );
189 const ModelToViewHelper::ModelPosition aStartPos
=
190 m_pImpl
->m_ConversionMap
.ConvertToModelPosition( nStart
);
191 const ModelToViewHelper::ModelPosition aEndPos
=
192 m_pImpl
->m_ConversionMap
.ConvertToModelPosition( nStart
+ nLength
- 1);
194 const bool bStartInField
= aStartPos
.mbIsField
;
195 const bool bEndInField
= aEndPos
.mbIsField
;
196 bool bCommit
= false;
198 if ( bStartInField
&& bEndInField
&& aStartPos
.mnPos
== aEndPos
.mnPos
)
200 nStart
= aStartPos
.mnSubPos
;
201 const sal_Int32 nFieldPosModel
= aStartPos
.mnPos
;
202 const sal_uInt16 nInsertPos
= pWList
->GetWrongPos( nFieldPosModel
);
204 SwWrongList
* pSubList
= pWList
->SubList( nInsertPos
);
207 if( nType
== text::TextMarkupType::PROOFREADING
|| nType
== text::TextMarkupType::SENTENCE
)
208 pSubList
= new SwGrammarMarkUp();
210 pSubList
= new SwWrongList( pWList
->GetWrongListType() );
211 pWList
->InsertSubList( nFieldPosModel
, 1, nInsertPos
, pSubList
);
217 else if ( !bStartInField
&& !bEndInField
)
219 nStart
= aStartPos
.mnPos
;
221 nLength
= aEndPos
.mnPos
+ 1 - aStartPos
.mnPos
;
223 else if( nType
== text::TextMarkupType::PROOFREADING
|| nType
== text::TextMarkupType::SENTENCE
)
226 nStart
= aStartPos
.mnPos
;
227 sal_Int32 nEnd
= aEndPos
.mnPos
;
228 if( bStartInField
&& nType
!= text::TextMarkupType::SENTENCE
)
230 const sal_Int32 nFieldPosModel
= aStartPos
.mnPos
;
231 const sal_uInt16 nInsertPos
= pWList
->GetWrongPos( nFieldPosModel
);
232 SwWrongList
* pSubList
= pWList
->SubList( nInsertPos
);
235 pSubList
= new SwGrammarMarkUp();
236 pWList
->InsertSubList( nFieldPosModel
, 1, nInsertPos
, pSubList
);
238 const sal_Int32 nTmpStart
=
239 m_pImpl
->m_ConversionMap
.ConvertToViewPosition(aStartPos
.mnPos
);
240 const sal_Int32 nTmpLen
=
241 m_pImpl
->m_ConversionMap
.ConvertToViewPosition(aStartPos
.mnPos
+ 1)
242 - nTmpStart
- aStartPos
.mnSubPos
;
245 pSubList
->Insert( rIdentifier
, xMarkupInfoContainer
, aStartPos
.mnSubPos
, nTmpLen
);
249 if( bEndInField
&& nType
!= text::TextMarkupType::SENTENCE
)
251 const sal_Int32 nFieldPosModel
= aEndPos
.mnPos
;
252 const sal_uInt16 nInsertPos
= pWList
->GetWrongPos( nFieldPosModel
);
253 SwWrongList
* pSubList
= pWList
->SubList( nInsertPos
);
256 pSubList
= new SwGrammarMarkUp();
257 pWList
->InsertSubList( nFieldPosModel
, 1, nInsertPos
, pSubList
);
259 const sal_Int32 nTmpLen
= aEndPos
.mnSubPos
+ 1;
260 pSubList
->Insert( rIdentifier
, xMarkupInfoContainer
, 0, nTmpLen
);
265 nLength
= nEnd
- nStart
;
272 if( nType
== text::TextMarkupType::SENTENCE
)
273 static_cast<SwGrammarMarkUp
*>(pWList
)->setSentence( nStart
);
275 pWList
->Insert( rIdentifier
, xMarkupInfoContainer
, nStart
, nLength
);
279 sw::finishGrammarCheckFor(*m_pImpl
->m_pTextNode
);
282 static void lcl_commitGrammarMarkUp(
283 const ModelToViewHelper
& rConversionMap
,
284 SwGrammarMarkUp
* pWList
,
286 const OUString
& rIdentifier
,
289 const uno::Reference
< container::XStringKeyMap
> & xMarkupInfoContainer
)
291 OSL_ENSURE( nType
== text::TextMarkupType::PROOFREADING
|| nType
== text::TextMarkupType::SENTENCE
, "Wrong mark-up type" );
292 const ModelToViewHelper::ModelPosition aStartPos
=
293 rConversionMap
.ConvertToModelPosition( nStart
);
294 const ModelToViewHelper::ModelPosition aEndPos
=
295 rConversionMap
.ConvertToModelPosition( nStart
+ nLength
- 1);
297 const bool bStartInField
= aStartPos
.mbIsField
;
298 const bool bEndInField
= aEndPos
.mbIsField
;
299 bool bCommit
= false;
301 if ( bStartInField
&& bEndInField
&& aStartPos
.mnPos
== aEndPos
.mnPos
)
303 nStart
= aStartPos
.mnSubPos
;
304 const sal_Int32 nFieldPosModel
= aStartPos
.mnPos
;
305 const sal_uInt16 nInsertPos
= pWList
->GetWrongPos( nFieldPosModel
);
307 SwGrammarMarkUp
* pSubList
= static_cast<SwGrammarMarkUp
*>(pWList
->SubList( nInsertPos
));
310 pSubList
= new SwGrammarMarkUp();
311 pWList
->InsertSubList( nFieldPosModel
, 1, nInsertPos
, pSubList
);
317 else if ( !bStartInField
&& !bEndInField
)
319 nStart
= aStartPos
.mnPos
;
321 nLength
= aEndPos
.mnPos
+ 1 - aStartPos
.mnPos
;
326 nStart
= aStartPos
.mnPos
;
327 sal_Int32 nEnd
= aEndPos
.mnPos
;
328 if( bStartInField
&& nType
!= text::TextMarkupType::SENTENCE
)
330 const sal_Int32 nFieldPosModel
= aStartPos
.mnPos
;
331 const sal_uInt16 nInsertPos
= pWList
->GetWrongPos( nFieldPosModel
);
332 SwGrammarMarkUp
* pSubList
= static_cast<SwGrammarMarkUp
*>(pWList
->SubList( nInsertPos
));
335 pSubList
= new SwGrammarMarkUp();
336 pWList
->InsertSubList( nFieldPosModel
, 1, nInsertPos
, pSubList
);
338 const sal_Int32 nTmpStart
= rConversionMap
.ConvertToViewPosition( aStartPos
.mnPos
);
339 const sal_Int32 nTmpLen
= rConversionMap
.ConvertToViewPosition( aStartPos
.mnPos
+ 1 )
340 - nTmpStart
- aStartPos
.mnSubPos
;
342 pSubList
->Insert( rIdentifier
, xMarkupInfoContainer
, aStartPos
.mnSubPos
, nTmpLen
);
345 if( bEndInField
&& nType
!= text::TextMarkupType::SENTENCE
)
347 const sal_Int32 nFieldPosModel
= aEndPos
.mnPos
;
348 const sal_uInt16 nInsertPos
= pWList
->GetWrongPos( nFieldPosModel
);
349 SwGrammarMarkUp
* pSubList
= static_cast<SwGrammarMarkUp
*>(pWList
->SubList( nInsertPos
));
352 pSubList
= new SwGrammarMarkUp();
353 pWList
->InsertSubList( nFieldPosModel
, 1, nInsertPos
, pSubList
);
355 const sal_Int32 nTmpLen
= aEndPos
.mnSubPos
+ 1;
356 pSubList
->Insert( rIdentifier
, xMarkupInfoContainer
, 0, nTmpLen
);
361 nLength
= nEnd
- nStart
;
368 if( nType
== text::TextMarkupType::SENTENCE
)
369 pWList
->setSentence( nStart
+nLength
);
371 pWList
->Insert( rIdentifier
, xMarkupInfoContainer
, nStart
, nLength
);
375 void SAL_CALL
SwXTextMarkup::commitMultiTextMarkup(
376 const uno::Sequence
< text::TextMarkupDescriptor
> &rMarkups
)
378 SolarMutexGuard aGuard
;
380 // paragraph already dead or modified?
381 if (!m_pImpl
->m_pTextNode
)
384 // for grammar checking there should be exactly one sentence markup
385 // and 0..n grammar markups.
386 // Different markups are not expected but may be applied anyway since
387 // that should be no problem...
388 // but it has to be implemented, at the moment only this function is for
389 // grammar markups and sentence markup only!
390 const text::TextMarkupDescriptor
*pSentenceMarkUp
= nullptr;
391 for( const text::TextMarkupDescriptor
&rDesc
: rMarkups
)
393 if (rDesc
.nType
== text::TextMarkupType::SENTENCE
)
395 if (pSentenceMarkUp
!= nullptr)
396 throw lang::IllegalArgumentException(); // there is already one sentence markup
397 pSentenceMarkUp
= &rDesc
;
399 else if( rDesc
.nType
!= text::TextMarkupType::PROOFREADING
)
403 if( pSentenceMarkUp
== nullptr )
406 // get appropriate list to use...
407 SwGrammarMarkUp
* pWList
= nullptr;
408 bool bRepaint
= false;
409 sw::GrammarContact
* pGrammarContact
= sw::getGrammarContactFor(*m_pImpl
->m_pTextNode
);
410 if( pGrammarContact
)
412 pWList
= pGrammarContact
->getGrammarCheck(*m_pImpl
->m_pTextNode
, true);
413 OSL_ENSURE( pWList
, "GrammarContact _has_ to deliver a wrong list" );
417 pWList
= m_pImpl
->m_pTextNode
->GetGrammarCheck();
420 m_pImpl
->m_pTextNode
->SetGrammarCheck( std::make_unique
<SwGrammarMarkUp
>() );
421 pWList
= m_pImpl
->m_pTextNode
->GetGrammarCheck();
422 pWList
->SetInvalid( 0, COMPLETE_STRING
);
425 bRepaint
= pWList
== m_pImpl
->m_pTextNode
->GetGrammarCheck();
427 bool bAcceptGrammarError
= false;
428 if( pWList
->GetBeginInv() < COMPLETE_STRING
)
430 const ModelToViewHelper::ModelPosition aSentenceEnd
=
431 m_pImpl
->m_ConversionMap
.ConvertToModelPosition(
432 pSentenceMarkUp
->nOffset
+ pSentenceMarkUp
->nLength
);
433 bAcceptGrammarError
= aSentenceEnd
.mnPos
> pWList
->GetBeginInv();
434 pWList
->ClearGrammarList( aSentenceEnd
.mnPos
);
437 if( bAcceptGrammarError
)
439 for( const text::TextMarkupDescriptor
&rDesc
: rMarkups
)
441 lcl_commitGrammarMarkUp(m_pImpl
->m_ConversionMap
, pWList
, rDesc
.nType
,
442 rDesc
.aIdentifier
, rDesc
.nOffset
, rDesc
.nLength
, rDesc
.xMarkupInfoContainer
);
448 const text::TextMarkupDescriptor
&rDesc
= *pSentenceMarkUp
;
449 lcl_commitGrammarMarkUp(m_pImpl
->m_ConversionMap
, pWList
, rDesc
.nType
,
450 rDesc
.aIdentifier
, rDesc
.nOffset
, rDesc
.nLength
, rDesc
.xMarkupInfoContainer
);
454 sw::finishGrammarCheckFor(*m_pImpl
->m_pTextNode
);
457 void SwXTextMarkup::Impl::Notify(const SfxHint
& rHint
)
459 DBG_TESTSOLARMUTEX();
460 if(rHint
.GetId() == SfxHintId::Dying
)
462 m_pTextNode
= nullptr;
466 SwXStringKeyMap::SwXStringKeyMap()
470 uno::Any SAL_CALL
SwXStringKeyMap::getValue(const OUString
& aKey
)
472 std::map
< OUString
, uno::Any
>::const_iterator aIter
= maMap
.find( aKey
);
473 if ( aIter
== maMap
.end() )
474 throw container::NoSuchElementException();
476 return (*aIter
).second
;
479 sal_Bool SAL_CALL
SwXStringKeyMap::hasValue(const OUString
& aKey
)
481 return maMap
.find( aKey
) != maMap
.end();
484 void SAL_CALL
SwXStringKeyMap::insertValue(const OUString
& aKey
, const uno::Any
& aValue
)
486 std::map
< OUString
, uno::Any
>::const_iterator aIter
= maMap
.find( aKey
);
487 if ( aIter
!= maMap
.end() )
488 throw container::ElementExistException();
490 maMap
[ aKey
] = aValue
;
493 ::sal_Int32 SAL_CALL
SwXStringKeyMap::getCount()
498 OUString SAL_CALL
SwXStringKeyMap::getKeyByIndex(::sal_Int32 nIndex
)
500 if ( o3tl::make_unsigned(nIndex
) >= maMap
.size() )
501 throw lang::IndexOutOfBoundsException();
506 uno::Any SAL_CALL
SwXStringKeyMap::getValueByIndex(::sal_Int32 nIndex
)
508 if ( o3tl::make_unsigned(nIndex
) >= maMap
.size() )
509 throw lang::IndexOutOfBoundsException();
514 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */