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 <hintids.hxx>
24 #include <splargs.hxx>
26 #include <editeng/langitem.hxx>
27 #include <editeng/fontitem.hxx>
28 #include <rtl/ustring.hxx>
29 #include <com/sun/star/text/RubyAdjust.hpp>
30 #include <com/sun/star/i18n/XBreakIterator.hpp>
31 #include <osl/diagnose.h>
33 #include "sdrhhcwrap.hxx"
38 #include <contentindex.hxx>
42 #include <fmtruby.hxx>
43 #include <breakit.hxx>
45 using namespace ::com::sun::star
;
46 using namespace ::com::sun::star::text
;
47 using namespace ::com::sun::star::uno
;
48 using namespace ::com::sun::star::linguistic2
;
49 using namespace ::com::sun::star::i18n
;
51 // Description: Turn off frame/object shell if applicable
53 static void lcl_ActivateTextShell( SwWrtShell
& rWrtSh
)
55 if( rWrtSh
.IsSelFrameMode() || rWrtSh
.IsObjSelected() )
56 rWrtSh
.EnterStdMode();
61 class SwKeepConversionDirectionStateContext
64 SwKeepConversionDirectionStateContext()
66 //!! hack to transport the current conversion direction state settings
67 //!! into the next incarnation that iterates over the drawing objects
68 //!! ( see SwHHCWrapper::~SwHHCWrapper() )
69 editeng::HangulHanjaConversion::SetUseSavedConversionDirectionState( true );
72 ~SwKeepConversionDirectionStateContext()
74 editeng::HangulHanjaConversion::SetUseSavedConversionDirectionState( false );
80 SwHHCWrapper::SwHHCWrapper(
82 const uno::Reference
< uno::XComponentContext
>& rxContext
,
83 LanguageType nSourceLanguage
,
84 LanguageType nTargetLanguage
,
85 const vcl::Font
*pTargetFont
,
86 sal_Int32 nConvOptions
,
88 bool bStart
, bool bOther
, bool bSelection
)
89 : editeng::HangulHanjaConversion(pSwView
->GetEditWin().GetFrameWeld(), rxContext
,
90 LanguageTag::convertToLocale( nSourceLanguage
),
91 LanguageTag::convertToLocale( nTargetLanguage
),
96 , m_rWrtShell( pSwView
->GetWrtShell() )
101 , m_bIsDrawObj( false )
102 , m_bIsOtherContent( bOther
)
103 , m_bStartChk( bOther
)
104 , m_bIsSelection( bSelection
)
105 , m_bStartDone( bOther
|| bStart
)
106 , m_bEndDone( false )
110 SwHHCWrapper::~SwHHCWrapper() COVERITY_NOEXCEPT_FALSE
114 SwViewShell::SetCareDialog(nullptr);
116 // check for existence of a draw view which means that there are
117 // (or previously were) draw objects present in the document.
118 // I.e. we like to check those too.
119 if ( m_bIsDrawObj
/*&& bLastRet*/ && m_pView
->GetWrtShell().HasDrawView() )
121 vcl::Cursor
*pSave
= m_pView
->GetWindow()->GetCursor();
123 SwKeepConversionDirectionStateContext aContext
;
125 SdrHHCWrapper
aSdrConvWrap( m_pView
, GetSourceLanguage(),
126 GetTargetLanguage(), GetTargetFont(),
127 GetConversionOptions(), IsInteractive() );
128 aSdrConvWrap
.StartTextConversion();
130 m_pView
->GetWindow()->SetCursor( pSave
);
134 ::EndProgress( m_pView
->GetDocShell() );
136 // finally for chinese translation we need to change the documents
137 // default language and font to the new ones to be used.
138 LanguageType nTargetLang
= GetTargetLanguage();
139 if (!IsChinese( nTargetLang
))
142 SwDoc
*pDoc
= m_pView
->GetDocShell()->GetDoc();
144 //!! Note: This also effects the default language of text boxes (EditEngine/EditView) !!
145 pDoc
->SetDefault( SvxLanguageItem( nTargetLang
, RES_CHRATR_CJK_LANGUAGE
) );
147 const vcl::Font
*pFont
= GetTargetFont();
150 SvxFontItem
aFontItem( pFont
->GetFamilyType(), pFont
->GetFamilyName(),
151 pFont
->GetStyleName(), pFont
->GetPitch(),
152 pFont
->GetCharSet(), RES_CHRATR_CJK_FONT
);
153 pDoc
->SetDefault( aFontItem
);
157 void SwHHCWrapper::GetNextPortion(
158 OUString
& rNextPortion
,
159 LanguageType
& rLangOfPortion
,
162 m_pConvArgs
->bAllowImplicitChangesForNotConvertibleText
= bAllowChanges
;
165 rNextPortion
= m_pConvArgs
->aConvText
;
166 rLangOfPortion
= m_pConvArgs
->nConvTextLang
;
170 // build last pos from currently selected text
171 SwPaM
* pCursor
= m_rWrtShell
.GetCursor();
172 m_nLastPos
= pCursor
->Start()->GetContentIndex();
175 void SwHHCWrapper::SelectNewUnit_impl( sal_Int32 nUnitStart
, sal_Int32 nUnitEnd
)
177 SwPaM
*pCursor
= m_rWrtShell
.GetCursor();
178 pCursor
->GetPoint()->SetContent( m_nLastPos
);
179 pCursor
->DeleteMark();
181 m_rWrtShell
.Right( SwCursorSkipMode::Chars
, /*bExpand*/ false,
182 o3tl::narrowing
<sal_uInt16
>(m_nUnitOffset
+ nUnitStart
), true );
184 m_rWrtShell
.Right( SwCursorSkipMode::Chars
, /*bExpand*/ true,
185 o3tl::narrowing
<sal_uInt16
>(nUnitEnd
- nUnitStart
), true );
186 // end selection now. Otherwise SHIFT+HOME (extending the selection)
187 // won't work when the dialog is closed without any replacement.
189 m_rWrtShell
.EndSelect();
192 void SwHHCWrapper::HandleNewUnit(
193 const sal_Int32 nUnitStart
, const sal_Int32 nUnitEnd
)
195 OSL_ENSURE( nUnitStart
>= 0 && nUnitEnd
>= nUnitStart
, "wrong arguments" );
196 if (0 > nUnitStart
|| nUnitStart
> nUnitEnd
)
199 lcl_ActivateTextShell( m_rWrtShell
);
201 m_rWrtShell
.StartAllAction();
203 // select current unit
204 SelectNewUnit_impl( nUnitStart
, nUnitEnd
);
206 m_rWrtShell
.EndAllAction();
209 void SwHHCWrapper::ChangeText( const OUString
&rNewText
,
210 std::u16string_view aOrigText
,
211 const uno::Sequence
< sal_Int32
> *pOffsets
,
214 //!! please see also TextConvWrapper::ChangeText with is a modified
215 //!! copy of this code
217 OSL_ENSURE( !rNewText
.isEmpty(), "unexpected empty string" );
218 if (rNewText
.isEmpty())
221 if (pOffsets
&& pCursor
) // try to keep as much attributation as possible ?
223 // remember cursor start position for later setting of the cursor
224 const SwPosition
*pStart
= pCursor
->Start();
225 const sal_Int32 nStartIndex
= pStart
->GetContentIndex();
226 SwTextNode
*pStartTextNode
= pStart
->GetNode().GetTextNode();
228 const sal_Int32 nIndices
= pOffsets
->getLength();
229 const sal_Int32
*pIndices
= pOffsets
->getConstArray();
230 sal_Int32 nConvTextLen
= rNewText
.getLength();
232 sal_Int32 nChgPos
= -1;
233 sal_Int32 nChgLen
= 0;
234 sal_Int32 nConvChgPos
= -1;
235 sal_Int32 nConvChgLen
= 0;
237 // offset to calculate the position in the text taking into
238 // account that text may have been replaced with new text of
239 // different length. Negative values allowed!
240 tools::Long nCorrectionOffset
= 0;
242 OSL_ENSURE(nIndices
== 0 || nIndices
== nConvTextLen
,
243 "mismatch between string length and sequence length!" );
245 // find all substrings that need to be replaced (and only those)
248 // get index in original text that matches nPos in new text
250 if (nPos
< nConvTextLen
)
251 nIndex
= nPos
< nIndices
? pIndices
[nPos
] : nPos
;
255 nIndex
= aOrigText
.size();
258 if (nPos
== nConvTextLen
|| /* end of string also terminates non-matching char sequence */
259 aOrigText
[nIndex
] == rNewText
[nPos
])
261 // substring that needs to be replaced found?
262 if (nChgPos
!= -1 && nConvChgPos
!= -1)
264 nChgLen
= nIndex
- nChgPos
;
265 nConvChgLen
= nPos
- nConvChgPos
;
266 OUString
aInNew( rNewText
.copy( nConvChgPos
, nConvChgLen
) );
268 // set selection to sub string to be replaced in original text
269 sal_Int32 nChgInNodeStartIndex
= nStartIndex
+ nCorrectionOffset
+ nChgPos
;
270 OSL_ENSURE( m_rWrtShell
.GetCursor()->HasMark(), "cursor misplaced (nothing selected)" );
271 m_rWrtShell
.GetCursor()->GetMark()->Assign( *pStartTextNode
, nChgInNodeStartIndex
);
272 m_rWrtShell
.GetCursor()->GetPoint()->Assign( *pStartTextNode
, nChgInNodeStartIndex
+ nChgLen
);
274 // replace selected sub string with the corresponding
275 // sub string from the new text while keeping as
276 // much from the attributes as possible
277 ChangeText_impl( aInNew
, true );
279 nCorrectionOffset
+= nConvChgLen
- nChgLen
;
287 // begin of non-matching char sequence found ?
288 if (nChgPos
== -1 && nConvChgPos
== -1)
294 if (nPos
>= nConvTextLen
)
299 // set cursor to the end of all the new text
300 // (as it would happen after ChangeText_impl (Delete and Insert)
301 // of the whole text in the 'else' branch below)
302 m_rWrtShell
.ClearMark();
303 m_rWrtShell
.GetCursor()->Start()->Assign( *pStartTextNode
, nStartIndex
+ nConvTextLen
);
307 ChangeText_impl( rNewText
, false );
311 void SwHHCWrapper::ChangeText_impl( const OUString
&rNewText
, bool bKeepAttributes
)
315 // get item set with all relevant attributes
316 SfxItemSetFixed
<RES_CHRATR_BEGIN
, RES_FRMATR_END
> aItemSet( m_rWrtShell
.GetAttrPool() );
317 // get all attributes spanning the whole selection in order to
318 // restore those for the new text
319 m_rWrtShell
.GetCurAttr( aItemSet
);
321 m_rWrtShell
.Delete(true);
322 m_rWrtShell
.Insert( rNewText
);
324 // select new inserted text (currently the Point is right after the new text)
325 if (!m_rWrtShell
.GetCursor()->HasMark())
326 m_rWrtShell
.GetCursor()->SetMark();
327 SwPosition
*pMark
= m_rWrtShell
.GetCursor()->GetMark();
328 pMark
->SetContent( pMark
->GetContentIndex() - rNewText
.getLength() );
330 // since 'SetAttr' below functions like merging with the attributes
331 // from the itemset with any existing ones we have to get rid of all
332 // all attributes now. (Those attributes that may take effect left
333 // to the position where the new text gets inserted after the old text
335 m_rWrtShell
.ResetAttr();
336 // apply previously saved attributes to new text
337 m_rWrtShell
.SetAttrSet( aItemSet
);
341 m_rWrtShell
.Delete(true);
342 m_rWrtShell
.Insert( rNewText
);
346 void SwHHCWrapper::ReplaceUnit(
347 const sal_Int32 nUnitStart
, const sal_Int32 nUnitEnd
,
348 const OUString
& rOrigText
,
349 const OUString
& rReplaceWith
,
350 const uno::Sequence
< sal_Int32
> &rOffsets
,
351 ReplacementAction eAction
,
352 LanguageType
*pNewUnitLanguage
)
354 OSL_ENSURE( nUnitStart
>= 0 && nUnitEnd
>= nUnitStart
, "wrong arguments" );
355 if (nUnitStart
< 0 || nUnitEnd
< nUnitStart
)
358 lcl_ActivateTextShell( m_rWrtShell
);
360 // replace the current word
361 m_rWrtShell
.StartAllAction();
363 // select current unit
364 SelectNewUnit_impl( nUnitStart
, nUnitEnd
);
366 OUString
aOrigText( m_rWrtShell
.GetSelText() );
367 OUString
aNewText( rReplaceWith
);
368 OSL_ENSURE( aOrigText
== rOrigText
, "!! text mismatch !!" );
369 std::unique_ptr
<SwFormatRuby
> pRuby
;
370 bool bRubyBelow
= false;
371 OUString aNewOrigText
;
376 case eReplacementBracketed
:
378 aNewText
= aOrigText
+ "(" + rReplaceWith
+ ")";
381 case eOriginalBracketed
:
383 aNewText
= rReplaceWith
+ "(" + aOrigText
+ ")";
386 case eReplacementAbove
:
388 pRuby
.reset(new SwFormatRuby( rReplaceWith
));
391 case eOriginalAbove
:
393 pRuby
.reset(new SwFormatRuby( aOrigText
));
394 aNewOrigText
= rReplaceWith
;
397 case eReplacementBelow
:
399 pRuby
.reset(new SwFormatRuby( rReplaceWith
));
403 case eOriginalBelow
:
405 pRuby
.reset(new SwFormatRuby( aOrigText
));
406 aNewOrigText
= rReplaceWith
;
411 OSL_FAIL("unexpected case" );
413 m_nUnitOffset
+= nUnitStart
+ aNewText
.getLength();
417 m_rWrtShell
.StartUndo( SwUndoId::SETRUBYATTR
);
418 if (!aNewOrigText
.isEmpty())
420 // according to FT we currently should not bother about keeping
421 // attributes in Hangul/Hanja conversion
422 ChangeText( aNewOrigText
, rOrigText
, nullptr, nullptr );
424 //!! since Delete, Insert in 'ChangeText' do not set the WrtShells
426 //!! back to false we do it now manually in order for the selection
427 //!! to be done properly in the following call to Left.
428 // We didn't fix it in Delete and Insert since it is currently
429 // unclear if someone depends on this incorrect behaviour
431 m_rWrtShell
.EndSelect();
433 m_rWrtShell
.Left( SwCursorSkipMode::Chars
, true, aNewOrigText
.getLength(), true, true );
436 pRuby
->SetPosition( o3tl::narrowing
<sal_uInt16
>(bRubyBelow
) );
437 pRuby
->SetAdjustment( RubyAdjust_CENTER
);
439 m_rWrtShell
.SetAttrItem(*pRuby
);
441 m_rWrtShell
.EndUndo( SwUndoId::SETRUBYATTR
);
445 m_rWrtShell
.StartUndo( SwUndoId::OVERWRITE
);
447 // according to FT we should currently not bother about keeping
448 // attributes in Hangul/Hanja conversion and leave that untouched.
449 // Thus we do this only for Chinese translation...
450 const bool bIsChineseConversion
= IsChinese( GetSourceLanguage() );
451 if (bIsChineseConversion
)
452 ChangeText( aNewText
, rOrigText
, &rOffsets
, m_rWrtShell
.GetCursor() );
454 ChangeText( aNewText
, rOrigText
, nullptr, nullptr );
456 // change language and font if necessary
457 if (bIsChineseConversion
)
459 m_rWrtShell
.SetMark();
460 m_rWrtShell
.GetCursor()->GetMark()->AdjustContent( -aNewText
.getLength() );
462 OSL_ENSURE( GetTargetLanguage() == LANGUAGE_CHINESE_SIMPLIFIED
|| GetTargetLanguage() == LANGUAGE_CHINESE_TRADITIONAL
,
463 "SwHHCWrapper::ReplaceUnit : unexpected target language" );
466 RES_CHRATR_CJK_FONT
, RES_CHRATR_CJK_FONT
,
467 RES_CHRATR_CJK_LANGUAGE
, RES_CHRATR_CJK_LANGUAGE
>
468 aSet( m_rWrtShell
.GetAttrPool() );
469 if (pNewUnitLanguage
)
471 aSet
.Put( SvxLanguageItem( *pNewUnitLanguage
, RES_CHRATR_CJK_LANGUAGE
) );
474 const vcl::Font
*pTargetFont
= GetTargetFont();
475 OSL_ENSURE( pTargetFont
, "target font missing?" );
476 if (pTargetFont
&& pNewUnitLanguage
)
478 SvxFontItem
aFontItem( aSet
.Get( RES_CHRATR_CJK_FONT
) );
479 aFontItem
.SetFamilyName( pTargetFont
->GetFamilyName());
480 aFontItem
.SetFamily( pTargetFont
->GetFamilyType());
481 aFontItem
.SetStyleName( pTargetFont
->GetStyleName());
482 aFontItem
.SetPitch( pTargetFont
->GetPitch());
483 aFontItem
.SetCharSet( pTargetFont
->GetCharSet() );
484 aSet
.Put( aFontItem
);
487 m_rWrtShell
.SetAttrSet( aSet
);
489 m_rWrtShell
.ClearMark();
492 m_rWrtShell
.EndUndo( SwUndoId::OVERWRITE
);
495 m_rWrtShell
.EndAllAction();
498 bool SwHHCWrapper::HasRubySupport() const
503 void SwHHCWrapper::Convert()
505 OSL_ENSURE( m_pConvArgs
== nullptr, "NULL pointer expected" );
507 SwPaM
*pCursor
= m_pView
->GetWrtShell().GetCursor();
508 auto [pSttPos
, pEndPos
] = pCursor
->StartEnd(); // SwPosition*
510 if (pSttPos
->GetNode().IsTextNode() &&
511 pEndPos
->GetNode().IsTextNode())
513 m_pConvArgs
.reset( new SwConversionArgs( GetSourceLanguage(), *pSttPos
, *pEndPos
) );
515 else // we are not in the text (maybe a graphic or OLE object is selected) let's start from the top
517 // get PaM that points to the start of the document
518 SwNode
& rNode
= m_pView
->GetDocShell()->GetDoc()->GetNodes().GetEndOfContent();
520 aPam
.Move( fnMoveBackward
, GoInDoc
); // move to start of document
522 pSttPos
= aPam
.GetPoint(); //! using a PaM here makes sure we will get only text nodes
523 SwTextNode
*pTextNode
= pSttPos
->GetNode().GetTextNode();
524 // just in case we check anyway...
525 if (!pTextNode
|| !pTextNode
->IsTextNode())
527 m_pConvArgs
.reset( new SwConversionArgs( GetSourceLanguage(), *pSttPos
, *pSttPos
) );
529 OSL_ENSURE( m_pConvArgs
->pStartPos
&& m_pConvArgs
->pStartPos
->GetNode().IsTextNode(),
530 "failed to get proper start text node" );
531 OSL_ENSURE( m_pConvArgs
->pEndPos
&& m_pConvArgs
->pEndPos
->GetNode().IsTextNode(),
532 "failed to get proper end text node" );
534 // chinese conversion specific settings
535 OSL_ENSURE( IsChinese( GetSourceLanguage() ) == IsChinese( GetTargetLanguage() ),
536 "source and target language mismatch?" );
537 if (IsChinese( GetTargetLanguage() ))
539 m_pConvArgs
->nConvTargetLang
= GetTargetLanguage();
540 m_pConvArgs
->pTargetFont
= GetTargetFont();
541 m_pConvArgs
->bAllowImplicitChangesForNotConvertibleText
= true;
544 // if it is not just a selection and we are about to begin
545 // with the current conversion for the very first time
546 // we need to find the start of the current (initial)
547 // convertible unit in order for the text conversion to give
548 // the correct result for that. Since it is easier to obtain
549 // the start of the word we use that though.
550 if (!pCursor
->HasMark()) // is not a selection?
552 // since #118246 / #117803 still occurs if the cursor is placed
553 // between the two chinese characters to be converted (because both
554 // of them are words on their own!) using the word boundary here does
555 // not work. Thus since chinese conversion is not interactive we start
556 // at the begin of the paragraph to solve the problem, i.e. have the
557 // TextConversion service get those characters together in the same call.
558 sal_Int32 nStartIdx
= -1;
559 if (editeng::HangulHanjaConversion::IsChinese( GetSourceLanguage() ) )
563 OUString
aText( m_pConvArgs
->pStartPos
->GetNode().GetTextNode()->GetText() );
564 const sal_Int32 nPos
= m_pConvArgs
->pStartPos
->GetContentIndex();
565 Boundary
aBoundary( g_pBreakIt
->GetBreakIter()->
566 getWordBoundary( aText
, nPos
, g_pBreakIt
->GetLocale( m_pConvArgs
->nConvSrcLang
),
567 WordType::DICTIONARY_WORD
, true ) );
569 // valid result found?
570 if (aBoundary
.startPos
< aText
.getLength() &&
571 aBoundary
.startPos
!= aBoundary
.endPos
)
573 nStartIdx
= aBoundary
.startPos
;
578 m_pConvArgs
->pStartPos
->SetContent( nStartIdx
);
582 if ( m_bIsOtherContent
)
583 ConvStart_impl( m_pConvArgs
.get(), SvxSpellArea::Other
);
587 ConvStart_impl( m_pConvArgs
.get(), SvxSpellArea::BodyEnd
);
592 ConvEnd_impl( m_pConvArgs
.get() );
595 bool SwHHCWrapper::ConvNext_impl( )
597 //! modified version of SvxSpellWrapper::SpellNext
599 // no change of direction so the desired region is fully processed
605 if( m_bIsOtherContent
&& m_bStartDone
&& m_bEndDone
) // document completely checked?
612 if ( m_bIsOtherContent
)
615 ConvStart_impl( m_pConvArgs
.get(), SvxSpellArea::Body
);
618 else if ( m_bStartDone
&& m_bEndDone
)
620 // body region done, ask about special region
621 if( !m_bIsSelection
&& m_rWrtShell
.HasOtherCnt() )
623 ConvStart_impl( m_pConvArgs
.get(), SvxSpellArea::Other
);
624 m_bIsOtherContent
= bGoOn
= true;
629 m_bStartChk
= !m_bStartDone
;
630 ConvStart_impl( m_pConvArgs
.get(), m_bStartChk
? SvxSpellArea::BodyStart
: SvxSpellArea::BodyEnd
);
636 void SwHHCWrapper::FindConvText_impl()
638 //! modified version of SvxSpellWrapper::FindSpellError
642 weld::WaitObject
aWait(GetUIParent());
647 bFound
= ConvContinue_impl( m_pConvArgs
.get() );
654 ConvEnd_impl( m_pConvArgs
.get() );
655 bConv
= ConvNext_impl();
660 void SwHHCWrapper::ConvStart_impl( SwConversionArgs
/* [out] */ *pConversionArgs
, SvxSpellArea eArea
)
662 m_bIsDrawObj
= SvxSpellArea::Other
== eArea
;
663 m_pView
->SpellStart( eArea
, m_bStartDone
, m_bEndDone
, /* [out] */ pConversionArgs
);
666 void SwHHCWrapper::ConvEnd_impl( SwConversionArgs
const *pConversionArgs
)
668 m_pView
->SpellEnd( pConversionArgs
);
671 bool SwHHCWrapper::ConvContinue_impl( SwConversionArgs
*pConversionArgs
)
673 bool bProgress
= !m_bIsDrawObj
&& !m_bIsSelection
;
674 pConversionArgs
->aConvText
.clear();
675 pConversionArgs
->nConvTextLang
= LANGUAGE_NONE
;
676 m_pView
->GetWrtShell().SpellContinue( &m_nPageCount
, bProgress
? &m_nPageStart
: nullptr, pConversionArgs
);
677 return !pConversionArgs
->aConvText
.isEmpty();
680 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */