android: Update app-specific/MIME type icons
[LibreOffice.git] / cui / source / dialogs / SpellDialog.cxx
blob1da34447f7793808410b38b202da0adcf23e4c17
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <memory>
21 #include "SpellAttrib.hxx"
22 #include <sfx2/bindings.hxx>
23 #include <sfx2/sfxsids.hrc>
24 #include <sfx2/viewfrm.hxx>
25 #include <svl/grabbagitem.hxx>
26 #include <svl/undo.hxx>
27 #include <tools/debug.hxx>
28 #include <unotools/lingucfg.hxx>
29 #include <editeng/colritem.hxx>
30 #include <editeng/eeitem.hxx>
31 #include <editeng/langitem.hxx>
32 #include <editeng/splwrap.hxx>
33 #include <editeng/unolingu.hxx>
34 #include <editeng/wghtitem.hxx>
35 #include <linguistic/misc.hxx>
36 #include <com/sun/star/lang/XServiceInfo.hpp>
37 #include <com/sun/star/frame/XStorable.hpp>
38 #include <com/sun/star/linguistic2/XDictionary.hpp>
39 #include <com/sun/star/linguistic2/XSpellAlternatives.hpp>
40 #include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
41 #include <com/sun/star/linguistic2/XSpellChecker1.hpp>
42 #include <sfx2/app.hxx>
43 #include <rtl/ustrbuf.hxx>
44 #include <vcl/specialchars.hxx>
45 #include <vcl/event.hxx>
46 #include <vcl/svapp.hxx>
47 #include <vcl/texteng.hxx>
48 #include <vcl/weld.hxx>
49 #include <svx/SpellDialogChildWindow.hxx>
50 #include <SpellDialog.hxx>
51 #include <optlingu.hxx>
52 #include <treeopt.hxx>
53 #include <svtools/colorcfg.hxx>
54 #include <svtools/langtab.hxx>
55 #include <sal/log.hxx>
56 #include <i18nlangtag/languagetag.hxx>
57 #include <comphelper/lok.hxx>
59 using namespace ::com::sun::star;
60 using namespace ::com::sun::star::uno;
61 using namespace ::com::sun::star::beans;
62 using namespace ::com::sun::star::linguistic2;
63 using namespace linguistic;
66 // struct SpellDialog_Impl ---------------------------------------------
68 struct SpellDialog_Impl
70 Sequence< Reference< XDictionary > > aDics;
74 #define SPELLUNDO_START 200
76 #define SPELLUNDO_CHANGE_LANGUAGE (SPELLUNDO_START + 1)
77 #define SPELLUNDO_CHANGE_TEXTENGINE (SPELLUNDO_START + 2)
78 #define SPELLUNDO_CHANGE_NEXTERROR (SPELLUNDO_START + 3)
79 #define SPELLUNDO_CHANGE_ADD_TO_DICTIONARY (SPELLUNDO_START + 4)
80 #define SPELLUNDO_CHANGE_GROUP (SPELLUNDO_START + 5) //undo list
81 #define SPELLUNDO_MOVE_ERROREND (SPELLUNDO_START + 6)
82 #define SPELLUNDO_UNDO_EDIT_MODE (SPELLUNDO_START + 7)
83 #define SPELLUNDO_ADD_IGNORE_RULE (SPELLUNDO_START + 8)
85 namespace svx{
86 class SpellUndoAction_Impl : public SfxUndoAction
88 sal_uInt16 m_nId;
89 const Link<SpellUndoAction_Impl&,void>& m_rActionLink;
90 //undo of button enabling
91 bool m_bEnableChangePB;
92 bool m_bEnableChangeAllPB;
93 //undo of MarkNextError - used in change and change all, ignore and ignore all
94 tools::Long m_nOldErrorStart;
95 tools::Long m_nOldErrorEnd;
96 bool m_bIsErrorLanguageSelected;
97 //undo of AddToDictionary
98 Reference<XDictionary> m_xDictionary;
99 OUString m_sAddedWord;
100 //move end of error - ::ChangeMarkedWord()
101 tools::Long m_nOffset;
103 public:
104 SpellUndoAction_Impl(sal_uInt16 nId, const Link<SpellUndoAction_Impl&,void>& rActionLink) :
105 m_nId(nId),
106 m_rActionLink( rActionLink),
107 m_bEnableChangePB(false),
108 m_bEnableChangeAllPB(false),
109 m_nOldErrorStart(-1),
110 m_nOldErrorEnd(-1),
111 m_bIsErrorLanguageSelected(false),
112 m_nOffset(0)
115 virtual void Undo() override;
116 sal_uInt16 GetId() const;
118 void SetEnableChangePB(){m_bEnableChangePB = true;}
119 bool IsEnableChangePB() const {return m_bEnableChangePB;}
121 void SetEnableChangeAllPB(){m_bEnableChangeAllPB = true;}
122 bool IsEnableChangeAllPB() const {return m_bEnableChangeAllPB;}
124 void SetErrorMove(tools::Long nOldStart, tools::Long nOldEnd)
126 m_nOldErrorStart = nOldStart;
127 m_nOldErrorEnd = nOldEnd;
129 tools::Long GetOldErrorStart() const { return m_nOldErrorStart;}
130 tools::Long GetOldErrorEnd() const { return m_nOldErrorEnd;}
132 void SetErrorLanguageSelected(bool bSet){ m_bIsErrorLanguageSelected = bSet;}
133 bool IsErrorLanguageSelected() const {return m_bIsErrorLanguageSelected;}
135 void SetDictionary(const Reference<XDictionary>& xDict) { m_xDictionary = xDict; }
136 const Reference<XDictionary>& GetDictionary() const { return m_xDictionary; }
137 void SetAddedWord(const OUString& rWord) {m_sAddedWord = rWord;}
138 const OUString& GetAddedWord() const { return m_sAddedWord;}
140 void SetOffset(tools::Long nSet) {m_nOffset = nSet;}
141 tools::Long GetOffset() const {return m_nOffset;}
143 }//namespace svx
144 using namespace ::svx;
146 void SpellUndoAction_Impl::Undo()
148 m_rActionLink.Call(*this);
152 sal_uInt16 SpellUndoAction_Impl::GetId()const
154 return m_nId;
157 // class SvxSpellCheckDialog ---------------------------------------------
159 SpellDialog::SpellDialog(SpellDialogChildWindow* pChildWindow,
160 weld::Window * pParent, SfxBindings* _pBindings)
161 : SfxModelessDialogController (_pBindings, pChildWindow,
162 pParent, "cui/ui/spellingdialog.ui", "SpellingDialog")
163 , aDialogUndoLink(LINK (this, SpellDialog, DialogUndoHdl))
164 , m_pInitHdlEvent(nullptr)
165 , bFocusLocked(true)
166 , rParent(*pChildWindow)
167 , pImpl( new SpellDialog_Impl )
168 , m_xAltTitle(m_xBuilder->weld_label("alttitleft"))
169 , m_xResumeFT(m_xBuilder->weld_label("resumeft"))
170 , m_xNoSuggestionsFT(m_xBuilder->weld_label("nosuggestionsft"))
171 , m_xLanguageFT(m_xBuilder->weld_label("languageft"))
172 , m_xLanguageLB(new SvxLanguageBox(m_xBuilder->weld_combo_box("languagelb")))
173 , m_xExplainFT(m_xBuilder->weld_label("explain"))
174 , m_xExplainLink(m_xBuilder->weld_link_button("explainlink"))
175 , m_xNotInDictFT(m_xBuilder->weld_label("notindictft"))
176 , m_xSentenceED(new SentenceEditWindow_Impl)
177 , m_xSuggestionFT(m_xBuilder->weld_label("suggestionsft"))
178 , m_xSuggestionLB(m_xBuilder->weld_tree_view("suggestionslb"))
179 , m_xIgnorePB(m_xBuilder->weld_button("ignore"))
180 , m_xIgnoreAllPB(m_xBuilder->weld_button("ignoreall"))
181 , m_xIgnoreRulePB(m_xBuilder->weld_button("ignorerule"))
182 , m_xAddToDictPB(m_xBuilder->weld_button("add"))
183 , m_xAddToDictMB(m_xBuilder->weld_menu_button("addmb"))
184 , m_xChangePB(m_xBuilder->weld_button("change"))
185 , m_xChangeAllPB(m_xBuilder->weld_button("changeall"))
186 , m_xAutoCorrPB(m_xBuilder->weld_button("autocorrect"))
187 , m_xCheckGrammarCB(m_xBuilder->weld_check_button("checkgrammar"))
188 , m_xOptionsPB(m_xBuilder->weld_button("options"))
189 , m_xUndoPB(m_xBuilder->weld_button("undo"))
190 , m_xClosePB(m_xBuilder->weld_button("close"))
191 , m_xToolbar(m_xBuilder->weld_toolbar("toolbar"))
192 , m_xSentenceEDWeld(new weld::CustomWeld(*m_xBuilder, "sentence", *m_xSentenceED))
194 m_xSentenceED->SetSpellDialog(this);
195 m_xSentenceED->Init(m_xToolbar.get());
197 m_sTitleSpellingGrammar = m_xDialog->get_title();
198 m_sTitleSpelling = m_xAltTitle->get_label();
200 // fdo#68794 set initial title for cases where no text has been processed
201 // yet to show its language attributes
202 OUString sTitle = rParent.HasGrammarChecking() ? m_sTitleSpellingGrammar : m_sTitleSpelling;
203 m_xDialog->set_title(m_xDialog->strip_mnemonic(sTitle.replaceFirst("$LANGUAGE ($LOCATION)", "")));
205 m_sResumeST = m_xResumeFT->get_label();
206 m_sNoSuggestionsST = m_xNoSuggestionsFT->strip_mnemonic(m_xNoSuggestionsFT->get_label());
208 Size aEdSize(m_xSuggestionLB->get_approximate_digit_width() * 60,
209 m_xSuggestionLB->get_height_rows(6));
210 m_xSuggestionLB->set_size_request(aEdSize.Width(), -1);
211 m_sIgnoreOnceST = m_xIgnorePB->get_label();
212 m_xAddToDictMB->set_help_id(m_xAddToDictPB->get_help_id());
213 xSpell = LinguMgr::GetSpellChecker();
215 Init_Impl();
217 // disable controls if service is missing
218 m_xDialog->set_sensitive(xSpell.is());
220 //InitHdl wants to use virtual methods, so it
221 //can't be called during the ctor, so init
222 //it on next event cycle post-ctor
223 m_pInitHdlEvent = Application::PostUserEvent(LINK(this, SpellDialog, InitHdl));
226 SpellDialog::~SpellDialog()
228 if (m_xOptionsDlg)
230 m_xOptionsDlg->response(RET_CANCEL);
231 m_xOptionsDlg.reset();
234 if (m_pInitHdlEvent)
235 Application::RemoveUserEvent(m_pInitHdlEvent);
236 if (pImpl)
238 // save possibly modified user-dictionaries
239 Reference< XSearchableDictionaryList > xDicList( LinguMgr::GetDictionaryList() );
240 if (xDicList.is())
241 SaveDictionaries( xDicList );
243 pImpl.reset();
247 void SpellDialog::Init_Impl()
249 // initialize handler
250 m_xClosePB->connect_clicked(LINK( this, SpellDialog, CancelHdl ) );
251 m_xChangePB->connect_clicked(LINK( this, SpellDialog, ChangeHdl ) );
252 m_xChangeAllPB->connect_clicked(LINK( this, SpellDialog, ChangeAllHdl ) );
253 m_xIgnorePB->connect_clicked(LINK( this, SpellDialog, IgnoreHdl ) );
254 m_xIgnoreAllPB->connect_clicked(LINK( this, SpellDialog, IgnoreAllHdl ) );
255 m_xIgnoreRulePB->connect_clicked(LINK( this, SpellDialog, IgnoreAllHdl ) );
256 m_xUndoPB->connect_clicked(LINK( this, SpellDialog, UndoHdl ) );
258 m_xAutoCorrPB->connect_clicked( LINK( this, SpellDialog, ExtClickHdl ) );
259 m_xCheckGrammarCB->connect_toggled( LINK( this, SpellDialog, CheckGrammarHdl ));
260 m_xOptionsPB->connect_clicked( LINK( this, SpellDialog, ExtClickHdl ) );
262 m_xSuggestionLB->connect_row_activated( LINK( this, SpellDialog, DoubleClickChangeHdl ) );
264 m_xSentenceED->SetModifyHdl(LINK ( this, SpellDialog, ModifyHdl) );
266 m_xAddToDictMB->connect_selected(LINK ( this, SpellDialog, AddToDictSelectHdl ) );
267 m_xAddToDictPB->connect_clicked(LINK ( this, SpellDialog, AddToDictClickHdl ) );
269 m_xLanguageLB->connect_changed(LINK( this, SpellDialog, LanguageSelectHdl ) );
271 // initialize language ListBox
272 m_xLanguageLB->SetLanguageList(SvxLanguageListFlags::SPELL_USED, false, false, true);
274 m_xSentenceED->ClearModifyFlag();
275 LinguMgr::GetChangeAllList()->clear();
278 void SpellDialog::UpdateBoxes_Impl(bool bCallFromSelectHdl)
280 sal_Int32 i;
281 m_xSuggestionLB->clear();
283 SpellErrorDescription aSpellErrorDescription;
284 bool bSpellErrorDescription = m_xSentenceED->GetAlternatives(aSpellErrorDescription);
286 LanguageType nAltLanguage = LANGUAGE_NONE;
287 Sequence< OUString > aNewWords;
288 bool bIsGrammarError = false;
289 if( bSpellErrorDescription )
291 nAltLanguage = LanguageTag::convertToLanguageType( aSpellErrorDescription.aLocale );
292 aNewWords = aSpellErrorDescription.aSuggestions;
293 bIsGrammarError = aSpellErrorDescription.bIsGrammarError;
294 m_xExplainLink->set_uri( aSpellErrorDescription.sExplanationURL );
295 m_xExplainFT->set_label( aSpellErrorDescription.sExplanation );
297 if( bSpellErrorDescription && !aSpellErrorDescription.sDialogTitle.isEmpty() )
299 // use this function to apply the correct image to be used...
300 SetTitle_Impl( nAltLanguage );
301 // then change the title to the one to be actually used
302 m_xDialog->set_title(m_xDialog->strip_mnemonic(aSpellErrorDescription.sDialogTitle));
304 else
305 SetTitle_Impl( nAltLanguage );
306 if( !bCallFromSelectHdl )
307 m_xLanguageLB->set_active_id( nAltLanguage );
308 int nDicts = InitUserDicts();
310 // enter alternatives
311 const OUString *pNewWords = aNewWords.getConstArray();
312 const sal_Int32 nSize = aNewWords.getLength();
313 for ( i = 0; i < nSize; ++i )
315 OUString aTmp( pNewWords[i] );
316 if (m_xSuggestionLB->find_text(aTmp) == -1)
317 m_xSuggestionLB->append_text(aTmp);
319 if(!nSize)
320 m_xSuggestionLB->append_text(m_sNoSuggestionsST);
321 m_xAutoCorrPB->set_sensitive( nSize > 0 );
323 m_xSuggestionFT->set_sensitive(nSize > 0);
324 m_xSuggestionLB->set_sensitive(nSize > 0);
325 if( nSize )
327 m_xSuggestionLB->select(0);
329 m_xChangePB->set_sensitive( nSize > 0);
330 m_xChangeAllPB->set_sensitive(nSize > 0);
331 bool bShowChangeAll = !bIsGrammarError;
332 m_xChangeAllPB->set_visible( bShowChangeAll );
333 m_xExplainFT->set_visible( !bShowChangeAll );
334 m_xLanguageLB->set_sensitive( bShowChangeAll );
335 m_xIgnoreAllPB->set_visible( bShowChangeAll );
337 m_xAddToDictMB->set_visible( bShowChangeAll && nDicts > 1 && !comphelper::LibreOfficeKit::isActive());
338 m_xAddToDictPB->set_visible( bShowChangeAll && nDicts <= 1 && !comphelper::LibreOfficeKit::isActive());
339 m_xIgnoreRulePB->set_visible( !bShowChangeAll );
340 m_xIgnoreRulePB->set_sensitive(bSpellErrorDescription && !aSpellErrorDescription.sRuleId.isEmpty());
341 m_xAutoCorrPB->set_visible( bShowChangeAll && rParent.HasAutoCorrection() );
343 bool bOldShowGrammar = m_xCheckGrammarCB->get_visible();
344 bool bOldShowExplain = m_xExplainLink->get_visible();
346 m_xCheckGrammarCB->set_visible(rParent.HasGrammarChecking());
347 m_xExplainLink->set_visible(!m_xExplainLink->get_uri().isEmpty());
348 if (m_xExplainFT->get_label().isEmpty())
350 m_xExplainFT->hide();
351 m_xExplainLink->hide();
354 if (bOldShowExplain != m_xExplainLink->get_visible() || bOldShowGrammar != m_xCheckGrammarCB->get_visible())
355 m_xDialog->resize_to_request();
358 void SpellDialog::SpellContinue_Impl(std::unique_ptr<UndoChangeGroupGuard>* pGuard, bool bUseSavedSentence, bool bIgnoreCurrentError)
360 //initially or after the last error of a sentence MarkNextError will fail
361 //then GetNextSentence() has to be called followed again by MarkNextError()
362 //MarkNextError is not initially called if the UndoEdit mode is active
363 bool bNextSentence = false;
364 if (!m_xSentenceED)
366 return;
369 if(!((!m_xSentenceED->IsUndoEditMode() && m_xSentenceED->MarkNextError( bIgnoreCurrentError, xSpell )) ||
370 ( bNextSentence = GetNextSentence_Impl(pGuard, bUseSavedSentence, m_xSentenceED->IsUndoEditMode()) && m_xSentenceED->MarkNextError( false, xSpell ))))
371 return;
373 SpellErrorDescription aSpellErrorDescription;
374 bool bSpellErrorDescription = m_xSentenceED->GetAlternatives(aSpellErrorDescription);
375 if (bSpellErrorDescription)
377 UpdateBoxes_Impl();
378 weld::Widget* aControls[] =
380 m_xNotInDictFT.get(),
381 m_xSentenceED->GetDrawingArea(),
382 m_xLanguageFT.get()
384 for (weld::Widget* pWidget : aControls)
385 pWidget->set_sensitive(true);
387 if( bNextSentence )
389 //remove undo if a new sentence is active
390 m_xSentenceED->ResetUndo();
391 m_xUndoPB->set_sensitive(false);
394 /* Initialize, asynchronous to prevent virtual calls
395 from a constructor
397 IMPL_LINK_NOARG( SpellDialog, InitHdl, void*, void)
399 m_pInitHdlEvent = nullptr;
400 m_xDialog->freeze();
401 //show or hide AutoCorrect depending on the modules abilities
402 m_xAutoCorrPB->set_visible(rParent.HasAutoCorrection());
403 SpellContinue_Impl(nullptr);
404 m_xSentenceED->ResetUndo();
405 m_xUndoPB->set_sensitive(false);
407 // get current language
408 UpdateBoxes_Impl();
410 // fill dictionary PopupMenu
411 InitUserDicts();
413 LockFocusChanges(true);
414 if(m_xSentenceED->IsEnabled())
415 m_xSentenceED->GrabFocus();
416 else if( m_xChangePB->get_sensitive() )
417 m_xChangePB->grab_focus();
418 else if( m_xIgnorePB->get_sensitive() )
419 m_xIgnorePB->grab_focus();
420 else if( m_xClosePB->get_sensitive() )
421 m_xClosePB->grab_focus();
422 LockFocusChanges(false);
423 //show grammar CheckBox depending on the modules abilities
424 m_xCheckGrammarCB->set_active(rParent.IsGrammarChecking());
425 m_xDialog->thaw();
428 IMPL_LINK( SpellDialog, ExtClickHdl, weld::Button&, rBtn, void )
430 if (m_xOptionsPB.get() == &rBtn)
431 StartSpellOptDlg_Impl();
432 else if (m_xAutoCorrPB.get() == &rBtn)
434 //get the currently selected wrong word
435 OUString sCurrentErrorText = m_xSentenceED->GetErrorText();
436 //get the wrong word from the XSpellAlternative
437 SpellErrorDescription aSpellErrorDescription;
438 bool bSpellErrorDescription = m_xSentenceED->GetAlternatives(aSpellErrorDescription);
439 if (bSpellErrorDescription)
441 OUString sWrong(aSpellErrorDescription.sErrorText);
442 //if the word has not been edited in the MultiLineEdit then
443 //the current suggestion should be used
444 //if it's not the 'no suggestions' entry
445 if(sWrong == sCurrentErrorText &&
446 m_xSuggestionLB->get_sensitive() && m_xSuggestionLB->get_selected_index() != -1 &&
447 m_sNoSuggestionsST != m_xSuggestionLB->get_selected_text())
449 sCurrentErrorText = m_xSuggestionLB->get_selected_text();
451 if(sWrong != sCurrentErrorText)
453 SvxPrepareAutoCorrect( sWrong, sCurrentErrorText );
454 LanguageType eLang = GetSelectedLang_Impl();
455 rParent.AddAutoCorrection( sWrong, sCurrentErrorText, eLang );
456 //correct the word immediately
457 ChangeHdl(*m_xAutoCorrPB);
463 IMPL_LINK_NOARG(SpellDialog, CheckGrammarHdl, weld::Toggleable&, void)
465 rParent.SetGrammarChecking(m_xCheckGrammarCB->get_active());
466 Impl_Restore(true);
469 void SpellDialog::StartSpellOptDlg_Impl()
471 auto xSet = std::make_shared<SfxItemSetFixed<SID_AUTOSPELL_CHECK,SID_AUTOSPELL_CHECK>>( SfxGetpApp()->GetPool() );
472 m_xOptionsDlg = std::make_shared<SfxSingleTabDialogController>(
473 m_xDialog.get(), xSet.get(), "content", "cui/ui/spelloptionsdialog.ui", "SpellOptionsDialog");
475 std::unique_ptr<SfxTabPage> xPage = SvxLinguTabPage::Create(m_xOptionsDlg->get_content_area(), m_xOptionsDlg.get(), xSet.get());
476 static_cast<SvxLinguTabPage*>(xPage.get())->HideGroups( GROUP_MODULES );
477 m_xOptionsDlg->SetTabPage(std::move(xPage));
478 weld::GenericDialogController::runAsync(m_xOptionsDlg, [this, xSet] (sal_uInt32 nResult) {
479 if (RET_OK == nResult)
481 InitUserDicts();
482 const SfxItemSet* pOutSet = m_xOptionsDlg->GetOutputItemSet();
483 if(pOutSet)
484 OfaTreeOptionsDialog::ApplyLanguageOptions(*pOutSet);
489 namespace
491 OUString getDotReplacementString(const OUString &rErrorText, const OUString &rSuggestedReplacement)
493 OUString aString = rErrorText;
495 //dots are sometimes part of the spelled word but they are not necessarily part of the replacement
496 bool bDot = aString.endsWith(".");
498 aString = rSuggestedReplacement;
500 if(bDot && (aString.isEmpty() || !aString.endsWith(".")))
501 aString += ".";
503 return aString;
507 OUString SpellDialog::getReplacementString() const
509 OUString sOrigString = m_xSentenceED->GetErrorText();
511 OUString sReplacement(sOrigString);
513 if(m_xSuggestionLB->get_sensitive() &&
514 m_xSuggestionLB->get_selected_index() != -1 &&
515 m_sNoSuggestionsST != m_xSuggestionLB->get_selected_text())
516 sReplacement = m_xSuggestionLB->get_selected_text();
518 return getDotReplacementString(sOrigString, sReplacement);
521 IMPL_LINK_NOARG(SpellDialog, DoubleClickChangeHdl, weld::TreeView&, bool)
523 ChangeHdl(*m_xChangePB);
524 return true;
527 /* tdf#132822 start an undo group in ctor and close it in the dtor. This can
528 then be passed to SpellContinue_Impl which can delete it in advance of its
529 natural scope to force closing the undo group if SpellContinue_Impl needs to
530 fetch a new paragraph and discard all undo information which can only be
531 done properly if there are no open undo groups */
532 class UndoChangeGroupGuard
534 private:
535 SentenceEditWindow_Impl& m_rSentenceED;
536 public:
537 UndoChangeGroupGuard(SentenceEditWindow_Impl& rSentenceED)
538 : m_rSentenceED(rSentenceED)
540 m_rSentenceED.UndoActionStart(SPELLUNDO_CHANGE_GROUP);
542 ~UndoChangeGroupGuard()
544 m_rSentenceED.UndoActionEnd();
548 IMPL_LINK_NOARG(SpellDialog, ChangeHdl, weld::Button&, void)
550 if (m_xSentenceED->IsUndoEditMode())
552 SpellContinue_Impl();
554 else
556 auto xGuard(std::make_unique<UndoChangeGroupGuard>(*m_xSentenceED));
557 OUString aString = getReplacementString();
558 m_xSentenceED->ChangeMarkedWord(aString, GetSelectedLang_Impl());
559 SpellContinue_Impl(&xGuard);
561 if(!m_xChangePB->get_sensitive())
562 m_xIgnorePB->grab_focus();
565 IMPL_LINK_NOARG(SpellDialog, ChangeAllHdl, weld::Button&, void)
567 auto xGuard(std::make_unique<UndoChangeGroupGuard>(*m_xSentenceED));
568 OUString aString = getReplacementString();
569 LanguageType eLang = GetSelectedLang_Impl();
571 // add new word to ChangeAll list
572 OUString aOldWord( m_xSentenceED->GetErrorText() );
573 SvxPrepareAutoCorrect( aOldWord, aString );
574 Reference<XDictionary> aXDictionary = LinguMgr::GetChangeAllList();
575 DictionaryError nAdded = AddEntryToDic( aXDictionary,
576 aOldWord, true,
577 aString );
579 if(nAdded == DictionaryError::NONE)
581 std::unique_ptr<SpellUndoAction_Impl> pAction(new SpellUndoAction_Impl(
582 SPELLUNDO_CHANGE_ADD_TO_DICTIONARY, aDialogUndoLink));
583 pAction->SetDictionary(aXDictionary);
584 pAction->SetAddedWord(aOldWord);
585 m_xSentenceED->AddUndoAction(std::move(pAction));
588 m_xSentenceED->ChangeMarkedWord(aString, eLang);
589 SpellContinue_Impl(&xGuard);
592 IMPL_LINK( SpellDialog, IgnoreAllHdl, weld::Button&, rButton, void )
594 auto xGuard(std::make_unique<UndoChangeGroupGuard>(*m_xSentenceED));
595 // add word to IgnoreAll list
596 Reference< XDictionary > aXDictionary = LinguMgr::GetIgnoreAllList();
597 //in case the error has been changed manually it has to be restored
598 m_xSentenceED->RestoreCurrentError();
599 if (&rButton == m_xIgnoreRulePB.get())
601 SpellErrorDescription aSpellErrorDescription;
602 bool bSpellErrorDescription = m_xSentenceED->GetAlternatives(aSpellErrorDescription);
605 if( bSpellErrorDescription && aSpellErrorDescription.xGrammarChecker.is() )
607 aSpellErrorDescription.xGrammarChecker->ignoreRule(aSpellErrorDescription.sRuleId,
608 aSpellErrorDescription.aLocale);
609 // refresh the layout (workaround to launch a dictionary event)
610 aXDictionary->setActive(false);
611 aXDictionary->setActive(true);
614 catch( const uno::Exception& )
618 else
620 OUString sErrorText(m_xSentenceED->GetErrorText());
621 DictionaryError nAdded = AddEntryToDic( aXDictionary,
622 sErrorText, false,
623 OUString() );
624 if (nAdded == DictionaryError::NONE)
626 std::unique_ptr<SpellUndoAction_Impl> pAction(new SpellUndoAction_Impl(
627 SPELLUNDO_CHANGE_ADD_TO_DICTIONARY, aDialogUndoLink));
628 pAction->SetDictionary(aXDictionary);
629 pAction->SetAddedWord(sErrorText);
630 m_xSentenceED->AddUndoAction(std::move(pAction));
634 SpellContinue_Impl(&xGuard);
637 IMPL_LINK_NOARG(SpellDialog, UndoHdl, weld::Button&, void)
639 m_xSentenceED->Undo();
640 if(!m_xSentenceED->GetUndoActionCount())
641 m_xUndoPB->set_sensitive(false);
645 IMPL_LINK( SpellDialog, DialogUndoHdl, SpellUndoAction_Impl&, rAction, void )
647 switch(rAction.GetId())
649 case SPELLUNDO_CHANGE_TEXTENGINE:
651 if(rAction.IsEnableChangePB())
652 m_xChangePB->set_sensitive(false);
653 if(rAction.IsEnableChangeAllPB())
654 m_xChangeAllPB->set_sensitive(false);
656 break;
657 case SPELLUNDO_CHANGE_NEXTERROR:
659 m_xSentenceED->MoveErrorMarkTo(static_cast<sal_Int32>(rAction.GetOldErrorStart()),
660 static_cast<sal_Int32>(rAction.GetOldErrorEnd()),
661 false);
662 if(rAction.IsErrorLanguageSelected())
664 UpdateBoxes_Impl();
667 break;
668 case SPELLUNDO_CHANGE_ADD_TO_DICTIONARY:
670 if(rAction.GetDictionary().is())
671 rAction.GetDictionary()->remove(rAction.GetAddedWord());
673 break;
674 case SPELLUNDO_MOVE_ERROREND :
676 if(rAction.GetOffset() != 0)
677 m_xSentenceED->MoveErrorEnd(rAction.GetOffset());
679 break;
680 case SPELLUNDO_UNDO_EDIT_MODE :
682 //refill the dialog with the currently spelled sentence - throw away all changes
683 SpellContinue_Impl(nullptr, true);
685 break;
686 case SPELLUNDO_ADD_IGNORE_RULE:
687 //undo of ignored rules is not supported
688 break;
692 void SpellDialog::Impl_Restore(bool bUseSavedSentence)
694 //clear the "ChangeAllList"
695 LinguMgr::GetChangeAllList()->clear();
696 //get a new sentence
697 m_xSentenceED->SetText(OUString());
698 m_xSentenceED->ResetModified();
699 //Resolves: fdo#39348 refill the dialog with the currently spelled sentence
700 SpellContinue_Impl(nullptr, bUseSavedSentence);
701 m_xIgnorePB->set_label(m_sIgnoreOnceST);
704 IMPL_LINK_NOARG(SpellDialog, IgnoreHdl, weld::Button&, void)
706 if (m_sResumeST == m_xIgnorePB->get_label())
708 Impl_Restore(false);
710 else
712 //in case the error has been changed manually it has to be restored,
713 // since the users choice now was to ignore the error
714 m_xSentenceED->RestoreCurrentError();
716 // the word is being ignored
717 SpellContinue_Impl(nullptr, false, true);
721 void SpellDialog::Close()
723 if (IsClosing())
724 return;
726 // We have to call ToggleChildWindow directly; calling SfxDispatcher's
727 // Execute() does not work here when we are in a document with protected
728 // section - in that case, the cursor can move from the editable field to
729 // the protected area, and the slots get disabled because of
730 // SfxDisableFlags::SwOnProtectedCursor (see FN_SPELL_GRAMMAR_DIALOG in .sdi).
731 if (SfxViewFrame* pViewFrame = SfxViewFrame::Current())
732 pViewFrame->ToggleChildWindow(rParent.GetType());
735 LanguageType SpellDialog::GetSelectedLang_Impl() const
737 LanguageType nLang = m_xLanguageLB->get_active_id();
738 return nLang;
741 IMPL_LINK_NOARG(SpellDialog, LanguageSelectHdl, weld::ComboBox&, void)
743 //If selected language changes, then add->list should be regenerated to
744 //match
745 InitUserDicts();
747 //if currently an error is selected then search for alternatives for
748 //this word and fill the alternatives ListBox accordingly
749 OUString sError = m_xSentenceED->GetErrorText();
750 m_xSuggestionLB->clear();
751 if (!sError.isEmpty())
753 LanguageType eLanguage = m_xLanguageLB->get_active_id();
754 Reference <XSpellAlternatives> xAlt = xSpell->spell( sError, static_cast<sal_uInt16>(eLanguage),
755 Sequence< PropertyValue >() );
756 if( xAlt.is() )
757 m_xSentenceED->SetAlternatives( xAlt );
758 else
760 m_xSentenceED->ChangeMarkedWord( sError, eLanguage );
761 SpellContinue_Impl();
764 m_xSentenceED->AddUndoAction(std::make_unique<SpellUndoAction_Impl>(SPELLUNDO_CHANGE_LANGUAGE, aDialogUndoLink));
766 SpellDialog::UpdateBoxes_Impl(true);
769 void SpellDialog::SetTitle_Impl(LanguageType nLang)
771 OUString sTitle = rParent.HasGrammarChecking() ? m_sTitleSpellingGrammar : m_sTitleSpelling;
772 sTitle = sTitle.replaceFirst( "$LANGUAGE ($LOCATION)", SvtLanguageTable::GetLanguageString(nLang) );
773 m_xDialog->set_title(m_xDialog->strip_mnemonic(sTitle));
776 int SpellDialog::InitUserDicts()
778 const LanguageType nLang = m_xLanguageLB->get_active_id();
780 const Reference< XDictionary > *pDic = nullptr;
782 // get list of dictionaries
783 Reference< XSearchableDictionaryList > xDicList( LinguMgr::GetDictionaryList() );
784 if (xDicList.is())
786 // add active, positive dictionary to dic-list (if not already done).
787 // This is to ensure that there is at least on dictionary to which
788 // words could be added.
789 Reference< XDictionary > xDic( LinguMgr::GetStandardDic() );
790 if (xDic.is())
791 xDic->setActive( true );
793 pImpl->aDics = xDicList->getDictionaries();
796 SvtLinguConfig aCfg;
798 // list suitable dictionaries
799 bool bEnable = false;
800 const sal_Int32 nSize = pImpl->aDics.getLength();
801 pDic = pImpl->aDics.getConstArray();
802 m_xAddToDictMB->clear();
803 sal_uInt16 nItemId = 1; // menu items should be enumerated from 1 and not 0
804 for (sal_Int32 i = 0; i < nSize; ++i)
806 uno::Reference< linguistic2::XDictionary > xDicTmp = pDic[i];
807 if (!xDicTmp.is() || LinguMgr::GetIgnoreAllList() == xDicTmp)
808 continue;
810 uno::Reference< frame::XStorable > xStor( xDicTmp, uno::UNO_QUERY );
811 LanguageType nActLanguage = LanguageTag( xDicTmp->getLocale() ).getLanguageType();
812 if( xDicTmp->isActive()
813 && xDicTmp->getDictionaryType() != linguistic2::DictionaryType_NEGATIVE
814 && (nLang == nActLanguage || LANGUAGE_NONE == nActLanguage )
815 && (!xStor.is() || !xStor->isReadonly()) )
817 bEnable = true;
819 OUString aDictionaryImageUrl;
820 uno::Reference< lang::XServiceInfo > xSvcInfo( xDicTmp, uno::UNO_QUERY );
821 if (xSvcInfo.is())
823 aDictionaryImageUrl = aCfg.GetSpellAndGrammarContextDictionaryImage(
824 xSvcInfo->getImplementationName());
827 m_xAddToDictMB->append_item(OUString::number(nItemId), xDicTmp->getName(), aDictionaryImageUrl);
829 ++nItemId;
832 m_xAddToDictMB->set_sensitive( bEnable );
833 m_xAddToDictPB->set_sensitive( bEnable );
835 int nDicts = nItemId-1;
837 m_xAddToDictMB->set_visible(nDicts > 1 && !comphelper::LibreOfficeKit::isActive());
838 m_xAddToDictPB->set_visible(nDicts <= 1 && !comphelper::LibreOfficeKit::isActive());
840 return nDicts;
843 IMPL_LINK_NOARG(SpellDialog, AddToDictClickHdl, weld::Button&, void)
845 AddToDictionaryExecute(OUString::number(1));
848 IMPL_LINK(SpellDialog, AddToDictSelectHdl, const OUString&, rIdent, void)
850 AddToDictionaryExecute(rIdent);
853 void SpellDialog::AddToDictionaryExecute(const OUString& rItemId)
855 auto xGuard(std::make_unique<UndoChangeGroupGuard>(*m_xSentenceED));
857 //GetErrorText() returns the current error even if the text is already
858 //manually changed
859 const OUString aNewWord = m_xSentenceED->GetErrorText();
861 OUString aDicName(m_xAddToDictMB->get_item_label(rItemId));
863 uno::Reference< linguistic2::XDictionary > xDic;
864 uno::Reference< linguistic2::XSearchableDictionaryList > xDicList( LinguMgr::GetDictionaryList() );
865 if (xDicList.is())
866 xDic = xDicList->getDictionaryByName( aDicName );
868 DictionaryError nAddRes = DictionaryError::UNKNOWN;
869 if (xDic.is())
871 nAddRes = AddEntryToDic( xDic, aNewWord, false, OUString() );
872 // save modified user-dictionary if it is persistent
873 uno::Reference< frame::XStorable > xSavDic( xDic, uno::UNO_QUERY );
874 if (xSavDic.is())
875 xSavDic->store();
877 if (nAddRes == DictionaryError::NONE)
879 std::unique_ptr<SpellUndoAction_Impl> pAction(new SpellUndoAction_Impl(
880 SPELLUNDO_CHANGE_ADD_TO_DICTIONARY, aDialogUndoLink));
881 pAction->SetDictionary( xDic );
882 pAction->SetAddedWord( aNewWord );
883 m_xSentenceED->AddUndoAction( std::move(pAction) );
885 // failed because there is already an entry?
886 if (DictionaryError::NONE != nAddRes && xDic->getEntry( aNewWord ).is())
887 nAddRes = DictionaryError::NONE;
889 if (DictionaryError::NONE != nAddRes)
891 SvxDicError(m_xDialog.get(), nAddRes);
892 return; // don't continue
895 // go on
896 SpellContinue_Impl(&xGuard);
899 IMPL_LINK_NOARG(SpellDialog, ModifyHdl, LinkParamNone*, void)
901 m_xSuggestionLB->unselect_all();
902 m_xSuggestionLB->set_sensitive(false);
903 m_xAutoCorrPB->set_sensitive(false);
904 std::unique_ptr<SpellUndoAction_Impl> pSpellAction(new SpellUndoAction_Impl(SPELLUNDO_CHANGE_TEXTENGINE, aDialogUndoLink));
905 if(!m_xChangeAllPB->get_sensitive())
907 m_xChangeAllPB->set_sensitive(true);
908 pSpellAction->SetEnableChangeAllPB();
910 if(!m_xChangePB->get_sensitive())
912 m_xChangePB->set_sensitive(true);
913 pSpellAction->SetEnableChangePB();
915 m_xSentenceED->AddUndoAction(std::move(pSpellAction));
918 IMPL_LINK_NOARG(SpellDialog, CancelHdl, weld::Button&, void)
920 //apply changes and ignored text parts first - if there are any
921 if (m_xSentenceED->IsModified())
923 rParent.ApplyChangedSentence(m_xSentenceED->CreateSpellPortions(), false);
925 Close();
928 void SpellDialog::ToplevelFocusChanged()
930 /* #i38338#
931 * FIXME: LoseFocus and GetFocus are signals from vcl that
932 * a window actually got/lost the focus, it never should be
933 * forwarded from another window, that is simply wrong.
934 * FIXME: overriding the virtual methods GetFocus and LoseFocus
935 * in SpellDialogChildWindow by making them pure is at least questionable.
936 * The only sensible thing would be to call the new Method differently,
937 * e.g. DialogGot/LostFocus or so.
939 if (!m_xDialog->get_visible() || bFocusLocked)
940 return;
942 if (m_xDialog->has_toplevel_focus())
944 //notify the child window of the focus change
945 rParent.GetFocus();
947 else
949 //notify the child window of the focus change
950 rParent.LoseFocus();
954 void SpellDialog::Activate()
956 SfxModelessDialogController::Activate();
957 ToplevelFocusChanged();
960 void SpellDialog::Deactivate()
962 SfxModelessDialogController::Deactivate();
963 ToplevelFocusChanged();
966 void SpellDialog::InvalidateDialog()
968 if( bFocusLocked )
969 return;
970 m_xIgnorePB->set_label(m_sResumeST);
971 weld::Widget* aDisableArr[] =
973 m_xNotInDictFT.get(),
974 m_xSentenceED->GetDrawingArea(),
975 m_xSuggestionFT.get(),
976 m_xSuggestionLB.get(),
977 m_xLanguageFT.get(),
978 m_xLanguageLB->get_widget(),
979 m_xIgnoreAllPB.get(),
980 m_xIgnoreRulePB.get(),
981 m_xAddToDictMB.get(),
982 m_xAddToDictPB.get(),
983 m_xChangePB.get(),
984 m_xChangeAllPB.get(),
985 m_xAutoCorrPB.get(),
986 m_xUndoPB.get()
988 for (weld::Widget* pWidget : aDisableArr)
989 pWidget->set_sensitive(false);
991 SfxModelessDialogController::Deactivate();
994 bool SpellDialog::GetNextSentence_Impl(std::unique_ptr<UndoChangeGroupGuard>* pGuard, bool bUseSavedSentence, bool bRecheck)
996 bool bRet = false;
997 if(!bUseSavedSentence)
999 //apply changes and ignored text parts
1000 rParent.ApplyChangedSentence(m_xSentenceED->CreateSpellPortions(), bRecheck);
1002 m_xSentenceED->ResetIgnoreErrorsAt();
1003 m_xSentenceED->ResetModified();
1004 SpellPortions aSentence = bUseSavedSentence ? m_aSavedSentence : rParent.GetNextWrongSentence( bRecheck );
1005 if(!bUseSavedSentence)
1006 m_aSavedSentence = aSentence;
1007 bool bHasReplaced = false;
1008 while(!aSentence.empty())
1010 //apply all changes that are already part of the "ChangeAllList"
1011 //returns true if the list still contains errors after the changes have been applied
1013 if(!ApplyChangeAllList_Impl(aSentence, bHasReplaced))
1015 rParent.ApplyChangedSentence(aSentence, bRecheck);
1016 aSentence = rParent.GetNextWrongSentence( bRecheck );
1018 else
1019 break;
1022 if(!aSentence.empty())
1024 OUStringBuffer sText;
1025 for (auto const& elem : aSentence)
1027 // hidden text has to be ignored
1028 if(!elem.bIsHidden)
1029 sText.append(elem.sText);
1031 // tdf#132822 fire undo-stack UndoActionEnd to close undo stack because we're about to throw away the paragraph entirely
1032 if (pGuard)
1033 pGuard->reset();
1034 m_xSentenceED->SetText(sText.makeStringAndClear());
1035 sal_Int32 nStartPosition = 0;
1036 sal_Int32 nEndPosition = 0;
1038 for (auto const& elem : aSentence)
1040 // hidden text has to be ignored
1041 if(!elem.bIsHidden)
1043 nEndPosition += elem.sText.getLength();
1044 if(elem.xAlternatives.is())
1046 SpellErrorDescription aDesc( false, elem.xAlternatives->getWord(),
1047 elem.xAlternatives->getLocale(), elem.xAlternatives->getAlternatives(), nullptr);
1048 SfxGrabBagItem aSpellErrorDescription(EE_CHAR_GRABBAG);
1049 aSpellErrorDescription.GetGrabBag()["SpellErrorDescription"] <<= aDesc.toSequence();
1050 m_xSentenceED->SetAttrib(aSpellErrorDescription, nStartPosition, nEndPosition);
1052 else if(elem.bIsGrammarError )
1054 beans::PropertyValues aProperties = elem.aGrammarError.aProperties;
1055 OUString sFullCommentURL;
1056 sal_Int32 i = 0;
1057 while ( sFullCommentURL.isEmpty() && i < aProperties.getLength() )
1059 if ( aProperties[i].Name == "FullCommentURL" )
1061 uno::Any aValue = aProperties[i].Value;
1062 aValue >>= sFullCommentURL;
1064 ++i;
1067 SpellErrorDescription aDesc( true,
1068 elem.sText,
1069 LanguageTag::convertToLocale( elem.eLanguage ),
1070 elem.aGrammarError.aSuggestions,
1071 elem.xGrammarChecker,
1072 &elem.sDialogTitle,
1073 &elem.aGrammarError.aFullComment,
1074 &elem.aGrammarError.aRuleIdentifier,
1075 &sFullCommentURL );
1077 SfxGrabBagItem aSpellErrorDescriptionItem(EE_CHAR_GRABBAG);
1078 aSpellErrorDescriptionItem.GetGrabBag()["SpellErrorDescription"] <<= aDesc.toSequence();
1079 m_xSentenceED->SetAttrib(aSpellErrorDescriptionItem, nStartPosition, nEndPosition);
1082 if (elem.bIsField)
1083 m_xSentenceED->SetAttrib(SvxColorItem(COL_LIGHTGRAY, EE_CHAR_BKGCOLOR), nStartPosition, nEndPosition);
1084 m_xSentenceED->SetAttrib(SvxLanguageItem(elem.eLanguage, EE_CHAR_LANGUAGE), nStartPosition, nEndPosition);
1085 nStartPosition = nEndPosition;
1088 //the edit field needs to be modified to apply the change from the ApplyChangeAllList
1089 if(!bHasReplaced)
1090 m_xSentenceED->ClearModifyFlag();
1091 m_xSentenceED->ResetUndo();
1092 m_xUndoPB->set_sensitive(false);
1093 bRet = nStartPosition > 0;
1095 return bRet;
1097 /*-------------------------------------------------------------------------
1098 replace errors that have a replacement in the ChangeAllList
1099 returns false if the result doesn't contain errors after the replacement
1100 -----------------------------------------------------------------------*/
1101 bool SpellDialog::ApplyChangeAllList_Impl(SpellPortions& rSentence, bool &bHasReplaced)
1103 bHasReplaced = false;
1104 bool bRet = true;
1105 Reference<XDictionary> xChangeAll = LinguMgr::GetChangeAllList();
1106 if(!xChangeAll->getCount())
1107 return bRet;
1108 bRet = false;
1109 for (auto & elem : rSentence)
1111 if(elem.xAlternatives.is())
1113 const OUString &rString = elem.sText;
1115 Reference<XDictionaryEntry> xEntry = xChangeAll->getEntry(rString);
1117 if(xEntry.is())
1119 elem.sText = getDotReplacementString(rString, xEntry->getReplacementText());
1120 elem.xAlternatives = nullptr;
1121 bHasReplaced = true;
1123 else
1124 bRet = true;
1126 else if( elem.bIsGrammarError )
1127 bRet = true;
1129 return bRet;
1132 SentenceEditWindow_Impl::SentenceEditWindow_Impl()
1133 : m_pSpellDialog(nullptr)
1134 , m_pToolbar(nullptr)
1135 , m_nErrorStart(0)
1136 , m_nErrorEnd(0)
1137 , m_bIsUndoEditMode(false)
1141 void SentenceEditWindow_Impl::SetDrawingArea(weld::DrawingArea* pDrawingArea)
1143 Size aSize(pDrawingArea->get_approximate_digit_width() * 60,
1144 pDrawingArea->get_text_height() * 6);
1145 pDrawingArea->set_size_request(aSize.Width(), aSize.Height());
1146 WeldEditView::SetDrawingArea(pDrawingArea);
1147 // tdf#132288 don't merge equal adjacent attributes
1148 m_xEditEngine->DisableAttributeExpanding();
1150 // tdf#142631 use document background color in this widget
1151 Color aBgColor = svtools::ColorConfig().GetColorValue(svtools::DOCCOLOR).nColor;
1152 OutputDevice& rDevice = pDrawingArea->get_ref_device();
1153 rDevice.SetBackground(aBgColor);
1154 m_xEditView->SetBackgroundColor(aBgColor);
1155 m_xEditEngine->SetBackgroundColor(aBgColor);
1158 SentenceEditWindow_Impl::~SentenceEditWindow_Impl()
1162 namespace
1164 const EECharAttrib* FindCharAttrib(int nPosition, sal_uInt16 nWhich, std::vector<EECharAttrib>& rAttribList)
1166 for (auto it = rAttribList.rbegin(); it != rAttribList.rend(); ++it)
1168 const auto& rTextAtr = *it;
1169 if (rTextAtr.pAttr->Which() != nWhich)
1170 continue;
1171 if (rTextAtr.nStart <= nPosition && rTextAtr.nEnd >= nPosition)
1173 return &rTextAtr;
1177 return nullptr;
1180 void ExtractErrorDescription(const EECharAttrib& rEECharAttrib, SpellErrorDescription& rSpellErrorDescription)
1182 css::uno::Sequence<css::uno::Any> aSequence;
1183 static_cast<const SfxGrabBagItem*>(rEECharAttrib.pAttr)->GetGrabBag().find("SpellErrorDescription")->second >>= aSequence;
1184 rSpellErrorDescription.fromSequence(aSequence);
1188 /*-------------------------------------------------------------------------
1189 The selection before inputting a key may have a range or not
1190 and it may be inside or outside of field or error attributes.
1191 A range may include the attribute partially, completely or together
1192 with surrounding text. It may also contain more than one attribute
1193 or no attribute at all.
1194 Depending on this starting conditions some actions are necessary:
1195 Attempts to delete a field are only allowed if the selection is the same
1196 as the field's selection. Otherwise the field has to be selected and the key
1197 input action has to be skipped.
1198 Input of text at the start of the field requires the field attribute to be
1199 corrected - it is not allowed to grow.
1201 In case of errors the appending of text should grow the error attribute because
1202 that is what the user usually wants to do.
1204 Backspace at the start of the attribute requires to find out if a field ends
1205 directly in front of the cursor position. In case of a field this attribute has to be
1206 selected otherwise the key input method is allowed.
1208 All changes outside of the error attributes switch the dialog mode to a "Undo edit" state that
1209 removes all visible attributes and switches off further attribute checks.
1210 Undo in this restarts the dialog with a current sentence newly presented.
1211 All changes to the sentence are undone including the ones before the "Undo edit state" has been reached
1213 We end up with 9 types of selection
1214 1 (LEFT_NO) - no range, start of attribute - can also be 3 at the same time
1215 2 (INSIDE_NO) - no range, inside of attribute
1216 3 (RIGHT_NO) - no range, end of attribute - can also be 1 at the same time
1217 4 (FULL) - range, same as attribute
1218 5 (INSIDE_YES) - range, inside of the attribute
1219 6 (BRACE)- range, from outside of the attribute to the inside or
1220 including the complete attribute and something outside,
1221 maybe more than one attribute
1222 7 (OUTSIDE_NO) - no range, not at an attribute
1223 8 (OUTSIDE_YES) - range, completely outside of all attributes
1225 What has to be done depending on the attribute type involved
1226 possible actions: UE - Undo edit mode
1227 CO - Continue, no additional action is required
1228 FS - Field has to be completely selected
1229 EX - The attribute has to be expanded to include the added text
1231 1 - backspace delete any other
1232 UE on field FS on error CO on field FS on error CO
1234 2 - on field FS on error C
1235 3 - backspace delete any other
1236 on field FS on error CO UE on field UE on error EX
1238 if 1 and 3 happen to apply both then backspace and other handling is 1 delete is 3
1240 4 - on field UE and on error CO
1241 5 - on field FS and on error CO
1242 6 - on field FS and on error UE
1243 7 - UE
1244 8 - UE
1245 -----------------------------------------------------------------------*/
1246 #define INVALID 0
1247 #define LEFT_NO 1
1248 #define INSIDE_NO 2
1249 #define RIGHT_NO 3
1250 #define FULL 4
1251 #define INSIDE_YES 5
1252 #define BRACE 6
1253 #define OUTSIDE_NO 7
1254 #define OUTSIDE_YES 8
1256 #define ACTION_UNDOEDIT 0
1257 #define ACTION_CONTINUE 1
1258 #define ACTION_SELECTFIELD 2
1259 #define ACTION_EXPAND 3
1261 bool SentenceEditWindow_Impl::KeyInput(const KeyEvent& rKeyEvt)
1263 if (rKeyEvt.GetKeyCode().GetCode() == KEY_TAB)
1264 return false;
1266 bool bConsumed = false;
1268 bool bChange = TextEngine::DoesKeyChangeText( rKeyEvt );
1269 if (bChange && !IsUndoEditMode())
1271 bConsumed = true;
1273 ESelection aCurrentSelection(m_xEditView->GetSelection());
1274 aCurrentSelection.Adjust();
1276 //determine if the selection contains a field
1277 bool bHasFieldLeft = false;
1278 bool bHasErrorLeft = false;
1280 bool bHasRange = aCurrentSelection.HasRange();
1281 sal_uInt8 nSelectionType = 0; // invalid type!
1283 std::vector<EECharAttrib> aAttribList;
1284 m_xEditEngine->GetCharAttribs(0, aAttribList);
1286 auto nCursor = aCurrentSelection.nStartPos;
1287 const EECharAttrib* pBackAttr = FindCharAttrib(nCursor, EE_CHAR_BKGCOLOR, aAttribList);
1288 const EECharAttrib* pErrorAttr = FindCharAttrib(nCursor, EE_CHAR_GRABBAG, aAttribList);
1289 const EECharAttrib* pBackAttrLeft = nullptr;
1290 const EECharAttrib* pErrorAttrLeft = nullptr;
1292 bool bHasField = pBackAttr != nullptr && (bHasRange || pBackAttr->nEnd > nCursor);
1293 bool bHasError = pErrorAttr != nullptr && (bHasRange || pErrorAttr->nEnd > nCursor);
1294 if (bHasRange)
1296 if (pBackAttr &&
1297 pBackAttr->nStart == aCurrentSelection.nStartPos &&
1298 pBackAttr->nEnd == aCurrentSelection.nEndPos)
1300 nSelectionType = FULL;
1302 else if (pErrorAttr &&
1303 pErrorAttr->nStart <= aCurrentSelection.nStartPos &&
1304 pErrorAttr->nEnd >= aCurrentSelection.nEndPos)
1306 nSelectionType = INSIDE_YES;
1308 else
1310 nSelectionType = bHasField||bHasError ? BRACE : OUTSIDE_NO;
1311 while (nCursor < aCurrentSelection.nEndPos)
1313 ++nCursor;
1314 const EECharAttrib* pIntBackAttr = FindCharAttrib(nCursor, EE_CHAR_BKGCOLOR, aAttribList);
1315 const EECharAttrib* pIntErrorAttr = FindCharAttrib(nCursor, EE_CHAR_GRABBAG, aAttribList);
1316 //if any attr has been found then BRACE
1317 if (pIntBackAttr || pIntErrorAttr)
1318 nSelectionType = BRACE;
1319 //the field has to be selected
1320 if (pIntBackAttr && !pBackAttr)
1321 pBackAttr = pIntBackAttr;
1322 bHasField |= pIntBackAttr != nullptr;
1326 else
1328 //no range selection: then 1 2 3 and 8 are possible
1329 const EECharAttrib* pCurAttr = pBackAttr ? pBackAttr : pErrorAttr;
1330 if (pCurAttr)
1332 nSelectionType = pCurAttr->nStart == aCurrentSelection.nStartPos ?
1333 LEFT_NO : pCurAttr->nEnd == aCurrentSelection.nEndPos ? RIGHT_NO : INSIDE_NO;
1335 else
1336 nSelectionType = OUTSIDE_NO;
1338 bHasFieldLeft = pBackAttr && pBackAttr->nEnd == nCursor;
1339 if(bHasFieldLeft)
1341 pBackAttrLeft = pBackAttr;
1342 pBackAttr = nullptr;
1344 bHasErrorLeft = pErrorAttr && pErrorAttr->nEnd == nCursor;
1345 if(bHasErrorLeft)
1347 pErrorAttrLeft = pErrorAttr;
1348 pErrorAttr = nullptr;
1351 //check previous position if this exists
1352 //that is a redundant in the case the attribute found above already is on the left cursor side
1353 //but it's o.k. for two errors/fields side by side
1354 if (nCursor)
1356 --nCursor;
1357 pBackAttrLeft = FindCharAttrib(nCursor, EE_CHAR_BKGCOLOR, aAttribList);
1358 pErrorAttrLeft = FindCharAttrib(nCursor, EE_CHAR_GRABBAG, aAttribList);
1359 bHasFieldLeft = pBackAttrLeft !=nullptr;
1360 bHasErrorLeft = pErrorAttrLeft != nullptr;
1361 ++nCursor;
1364 //Here we have to determine if the error found is the one currently active
1365 bool bIsErrorActive = (pErrorAttr && pErrorAttr->nStart == m_nErrorStart) ||
1366 (pErrorAttrLeft && pErrorAttrLeft->nStart == m_nErrorStart);
1368 SAL_WARN_IF(
1369 nSelectionType == INVALID, "cui.dialogs",
1370 "selection type not set");
1372 const vcl::KeyCode& rKeyCode = rKeyEvt.GetKeyCode();
1373 bool bDelete = rKeyCode.GetCode() == KEY_DELETE;
1374 bool bBackspace = rKeyCode.GetCode() == KEY_BACKSPACE;
1376 sal_Int8 nAction = ACTION_CONTINUE;
1377 switch(nSelectionType)
1379 // 1 - backspace delete any other
1380 // UE on field FS on error CO on field FS on error CO
1381 case LEFT_NO :
1382 if(bBackspace)
1384 nAction = bHasFieldLeft ? ACTION_SELECTFIELD : ACTION_UNDOEDIT;
1385 //to force the use of pBackAttrLeft
1386 pBackAttr = nullptr;
1388 else if(bDelete)
1389 nAction = bHasField ? ACTION_SELECTFIELD : ACTION_CONTINUE;
1390 else
1391 nAction = bHasError && !nCursor ? ACTION_CONTINUE :
1392 bHasError ? ACTION_EXPAND : bHasErrorLeft ? ACTION_CONTINUE : ACTION_UNDOEDIT;
1393 break;
1394 // 2 - on field FS on error C
1395 case INSIDE_NO :
1396 nAction = bHasField ? ACTION_SELECTFIELD :
1397 bIsErrorActive ? ACTION_CONTINUE : ACTION_UNDOEDIT;
1398 break;
1399 // 3 - backspace delete any other
1400 // on field FS on error CO UE on field UE on error EX
1401 case RIGHT_NO :
1402 if(bBackspace)
1403 nAction = bHasFieldLeft ? ACTION_SELECTFIELD : ACTION_CONTINUE;
1404 else if(bDelete)
1405 nAction = bHasFieldLeft && bHasError ? ACTION_CONTINUE : ACTION_UNDOEDIT;
1406 else
1407 nAction = bHasFieldLeft && bHasError ? ACTION_EXPAND :
1408 bHasError ? ACTION_CONTINUE : bHasErrorLeft ? ACTION_EXPAND :ACTION_UNDOEDIT;
1409 break;
1410 // 4 - on field UE and on error CO
1411 case FULL :
1412 nAction = ACTION_UNDOEDIT;
1413 break;
1414 // 5 - on field FS and on error CO
1415 case INSIDE_YES :
1416 nAction = bHasField ? ACTION_SELECTFIELD : ACTION_CONTINUE;
1417 break;
1418 // 6 - on field FS and on error UE
1419 case BRACE :
1420 nAction = bHasField ? ACTION_SELECTFIELD : ACTION_UNDOEDIT;
1421 break;
1422 // 7 - UE
1423 // 8 - UE
1424 case OUTSIDE_NO :
1425 case OUTSIDE_YES:
1426 nAction = ACTION_UNDOEDIT;
1427 break;
1429 //save the current paragraph
1430 sal_Int32 nCurrentLen = m_xEditEngine->GetText().getLength();
1431 if (nAction != ACTION_SELECTFIELD)
1433 m_xEditView->PostKeyEvent(rKeyEvt);
1435 else
1437 const EECharAttrib* pCharAttr = pBackAttr ? pBackAttr : pBackAttrLeft;
1438 if (pCharAttr)
1439 m_xEditView->SetSelection(ESelection(0, pCharAttr->nStart, 0, pCharAttr->nEnd));
1441 if(nAction == ACTION_EXPAND)
1443 DBG_ASSERT(pErrorAttrLeft || pErrorAttr, "where is the error");
1444 //text has been added on the right and only the 'error attribute has to be corrected
1445 if (pErrorAttrLeft)
1447 SpellErrorDescription aSpellErrorDescription;
1448 ExtractErrorDescription(*pErrorAttrLeft, aSpellErrorDescription);
1450 std::unique_ptr<SfxPoolItem> xNewError(pErrorAttrLeft->pAttr->Clone());
1451 sal_Int32 nStart = pErrorAttrLeft->nStart;
1452 sal_Int32 nEnd = pErrorAttrLeft->nEnd + 1;
1453 m_xEditEngine->RemoveAttribs(ESelection(0, nStart, 0, nEnd), false, EE_CHAR_GRABBAG);
1454 SetAttrib(*xNewError, nStart, nEnd);
1455 //only active errors move the mark
1456 if (bIsErrorActive)
1458 bool bGrammar = aSpellErrorDescription.bIsGrammarError;
1459 MoveErrorMarkTo(nStart, nEnd, bGrammar);
1462 //text has been added on the left then the error attribute has to be expanded and the
1463 //field attribute on the right - if any - has to be contracted
1464 else if (pErrorAttr)
1466 SpellErrorDescription aSpellErrorDescription;
1467 ExtractErrorDescription(*pErrorAttr, aSpellErrorDescription);
1469 //determine the change
1470 sal_Int32 nAddedChars = m_xEditEngine->GetText().getLength() - nCurrentLen;
1472 std::unique_ptr<SfxPoolItem> xNewError(pErrorAttr->pAttr->Clone());
1473 sal_Int32 nStart = pErrorAttr->nStart + nAddedChars;
1474 sal_Int32 nEnd = pErrorAttr->nEnd + nAddedChars;
1475 m_xEditEngine->RemoveAttribs(ESelection(0, nStart, 0, nEnd), false, EE_CHAR_GRABBAG);
1476 nStart = pErrorAttr->nStart;
1477 SetAttrib(*xNewError, nStart, nEnd);
1478 //only if the error is active the mark is moved here
1479 if (bIsErrorActive)
1481 bool bGrammar = aSpellErrorDescription.bIsGrammarError;
1482 MoveErrorMarkTo(nStart, nEnd, bGrammar);
1484 xNewError.reset();
1486 if (pBackAttrLeft)
1488 std::unique_ptr<SfxPoolItem> xNewBack(pBackAttrLeft->pAttr->Clone());
1489 sal_Int32 _nStart = pBackAttrLeft->nStart + nAddedChars;
1490 sal_Int32 _nEnd = pBackAttrLeft->nEnd + nAddedChars;
1491 m_xEditEngine->RemoveAttribs(ESelection(0, _nStart, 0, _nEnd), false, EE_CHAR_BKGCOLOR);
1492 _nStart = pBackAttrLeft->nStart;
1493 SetAttrib(*xNewBack, _nStart, _nEnd);
1497 else if(nAction == ACTION_UNDOEDIT)
1499 SetUndoEditMode(true);
1501 //make sure the error positions are correct after text changes
1502 //the old attribute may have been deleted
1503 //all changes inside of the current error leave the error attribute at the current
1504 //start position
1505 if (!IsUndoEditMode() && bIsErrorActive)
1507 const EECharAttrib* pFontColor = FindCharAttrib(nCursor, EE_CHAR_COLOR, aAttribList);
1508 const EECharAttrib* pErrorAttrib = FindCharAttrib(m_nErrorStart, EE_CHAR_GRABBAG, aAttribList);
1509 if (pFontColor && pErrorAttrib)
1511 m_nErrorStart = pFontColor->nStart;
1512 m_nErrorEnd = pFontColor->nEnd;
1513 if (pErrorAttrib->nStart != m_nErrorStart || pErrorAttrib->nEnd != m_nErrorEnd)
1515 std::unique_ptr<SfxPoolItem> xNewError(pErrorAttrib->pAttr->Clone());
1516 assert(pErrorAttr);
1517 m_xEditEngine->RemoveAttribs(ESelection(0, pErrorAttr->nStart, 0, pErrorAttr->nEnd), false, EE_CHAR_GRABBAG);
1518 SetAttrib(*xNewError, m_nErrorStart, m_nErrorEnd);
1522 //this is not a modification anymore
1523 if(nAction != ACTION_SELECTFIELD && !m_bIsUndoEditMode)
1524 CallModifyLink();
1526 else
1527 bConsumed = m_xEditView->PostKeyEvent(rKeyEvt);
1529 return bConsumed;
1532 void SentenceEditWindow_Impl::Init(weld::Toolbar* pToolbar)
1534 m_pToolbar = pToolbar;
1535 m_pToolbar->connect_clicked(LINK(this,SentenceEditWindow_Impl,ToolbarHdl));
1538 IMPL_LINK(SentenceEditWindow_Impl, ToolbarHdl, const OUString&, rCurItemId, void)
1540 if (rCurItemId == "paste")
1542 m_xEditView->Paste();
1543 CallModifyLink();
1545 else if (rCurItemId == "insert")
1547 if (auto pImplFncGetSpecialChars = vcl::GetGetSpecialCharsFunction())
1549 OUString aChars = pImplFncGetSpecialChars(GetDrawingArea(), m_xEditEngine->GetStandardFont(0));
1550 if (!aChars.isEmpty())
1552 ESelection aCurrentSelection(m_xEditView->GetSelection());
1553 m_xEditEngine->QuickInsertText(aChars, aCurrentSelection);
1554 CallModifyLink();
1560 bool SentenceEditWindow_Impl::MarkNextError( bool bIgnoreCurrentError, const css::uno::Reference<css::linguistic2::XSpellChecker1>& xSpell )
1562 if (bIgnoreCurrentError)
1563 m_aIgnoreErrorsAt.insert( m_nErrorStart );
1565 const sal_Int32 nTextLen = m_xEditEngine->GetTextLen(0);
1567 if (m_nErrorEnd >= nTextLen - 1)
1568 return false;
1569 //if it's not already modified the modified flag has to be reset at the end of the marking
1570 bool bModified = IsModified();
1571 bool bRet = false;
1572 const sal_Int32 nOldErrorStart = m_nErrorStart;
1573 const sal_Int32 nOldErrorEnd = m_nErrorEnd;
1575 //create a cursor behind the end of the last error
1576 //- or at 0 at the start of the sentence
1577 sal_Int32 nCursor(m_nErrorEnd ? m_nErrorEnd + 1 : 0);
1579 //search for SpellErrorDescription
1580 SpellErrorDescription aSpellErrorDescription;
1582 std::vector<EECharAttrib> aAttribList;
1583 m_xEditEngine->GetCharAttribs(0, aAttribList);
1585 //iterate over the text and search for the next error that maybe has
1586 //to be replace by a ChangeAllList replacement
1587 bool bGrammarError = false;
1588 while (nCursor < nTextLen)
1590 const SpellErrorDescription* pSpellErrorDescription = nullptr;
1591 const EECharAttrib* pEECharAttrib = nullptr;
1593 sal_Int32 nMinPos = nTextLen + 1;
1594 for (const auto& rTextAtr : aAttribList)
1596 if (rTextAtr.pAttr->Which() != EE_CHAR_GRABBAG)
1597 continue;
1598 if (rTextAtr.nEnd > nCursor && rTextAtr.nStart < nMinPos)
1600 nMinPos = rTextAtr.nStart;
1601 pEECharAttrib = &rTextAtr;
1605 if (pEECharAttrib)
1607 ExtractErrorDescription(*pEECharAttrib, aSpellErrorDescription);
1609 bGrammarError = aSpellErrorDescription.bIsGrammarError;
1610 m_nErrorStart = pEECharAttrib->nStart;
1611 m_nErrorEnd = pEECharAttrib->nEnd;
1613 pSpellErrorDescription = &aSpellErrorDescription;
1616 nCursor = std::max(nCursor, nMinPos); // move forward if possible
1618 // maybe the error found here is already in the ChangeAllList and has to be replaced
1619 Reference<XDictionary> xChangeAll = LinguMgr::GetChangeAllList();
1620 Reference<XDictionaryEntry> xEntry;
1622 if (xChangeAll->getCount() && pSpellErrorDescription &&
1623 (xEntry = xChangeAll->getEntry( pSpellErrorDescription->sErrorText )).is())
1625 OUString sReplacement(getDotReplacementString(GetErrorText(), xEntry->getReplacementText()));
1627 int nLenChange = ChangeMarkedWord(sReplacement, LanguageTag::convertToLanguageType(pSpellErrorDescription->aLocale));
1629 nCursor += sReplacement.getLength();
1631 if (nLenChange)
1632 m_xEditEngine->GetCharAttribs(0, aAttribList);
1633 // maybe the error found here is already added to the dictionary and has to be ignored
1635 else if(pSpellErrorDescription && !bGrammarError &&
1636 xSpell->isValid(GetErrorText(),
1637 static_cast<sal_uInt16>(LanguageTag::convertToLanguageType( pSpellErrorDescription->aLocale )),
1638 Sequence< PropertyValue >() ))
1640 ++nCursor;
1642 else
1643 break;
1646 //if an attrib has been found search for the end of the error string
1647 if (nCursor < nTextLen)
1649 MoveErrorMarkTo(nCursor, m_nErrorEnd, bGrammarError);
1650 bRet = true;
1651 //add an undo action
1652 std::unique_ptr<SpellUndoAction_Impl> pAction(new SpellUndoAction_Impl(
1653 SPELLUNDO_CHANGE_NEXTERROR, GetSpellDialog()->aDialogUndoLink));
1654 pAction->SetErrorMove(nOldErrorStart, nOldErrorEnd);
1656 if (GetErrorDescription(aSpellErrorDescription, nOldErrorStart))
1658 pAction->SetErrorLanguageSelected(aSpellErrorDescription.aSuggestions.hasElements() &&
1659 LanguageTag(aSpellErrorDescription.aLocale).getLanguageType() == GetSpellDialog()->m_xLanguageLB->get_active_id());
1661 else
1662 pAction->SetErrorLanguageSelected(false);
1664 AddUndoAction(std::move(pAction));
1666 else
1667 m_nErrorStart = m_nErrorEnd = nTextLen;
1668 if( !bModified )
1669 ClearModifyFlag();
1670 SpellDialog* pSpellDialog = GetSpellDialog();
1671 pSpellDialog->m_xIgnorePB->set_sensitive(bRet);
1672 pSpellDialog->m_xIgnoreAllPB->set_sensitive(bRet);
1673 pSpellDialog->m_xAutoCorrPB->set_sensitive(bRet);
1674 pSpellDialog->m_xAddToDictMB->set_sensitive(bRet);
1675 pSpellDialog->m_xAddToDictPB->set_sensitive(bRet);
1676 return bRet;
1679 void SentenceEditWindow_Impl::MoveErrorMarkTo(sal_Int32 nStart, sal_Int32 nEnd, bool bGrammarError)
1681 ESelection aAll(0, 0, 0, EE_TEXTPOS_ALL);
1682 m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_COLOR);
1683 m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT);
1684 m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT_CJK);
1685 m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT_CTL);
1687 // tdf#116566 Use color defined in the current Color Scheme
1688 Color aSpellErrorCollor = svtools::ColorConfig().GetColorValue(svtools::SPELL).nColor;
1689 Color aGrammarErrorCollor = svtools::ColorConfig().GetColorValue(svtools::GRAMMAR).nColor;
1691 SfxItemSet aSet(m_xEditEngine->GetEmptyItemSet());
1692 aSet.Put(SvxColorItem(bGrammarError ? aGrammarErrorCollor : aSpellErrorCollor, EE_CHAR_COLOR));
1693 aSet.Put(SvxWeightItem(WEIGHT_BOLD, EE_CHAR_WEIGHT));
1694 aSet.Put(SvxWeightItem(WEIGHT_BOLD, EE_CHAR_WEIGHT_CJK));
1695 aSet.Put(SvxWeightItem(WEIGHT_BOLD, EE_CHAR_WEIGHT_CTL));
1697 m_xEditEngine->QuickSetAttribs(aSet, ESelection(0, nStart, 0, nEnd));
1699 // Set the selection so the editview will autoscroll to make this visible
1700 // unless (tdf#133958) the selection already overlaps this range
1701 ESelection aCurrentSelection = m_xEditView->GetSelection();
1702 aCurrentSelection.Adjust();
1703 bool bCurrentSelectionInRange = nStart <= aCurrentSelection.nEndPos && aCurrentSelection.nStartPos <= nEnd;
1704 if (!bCurrentSelectionInRange)
1706 m_xEditView->SetSelection(ESelection(0, nStart));
1707 // tdf#157148 ensure current location is auto-scrolled to be visible
1708 m_xEditView->ShowCursor();
1711 Invalidate();
1713 m_nErrorStart = nStart;
1714 m_nErrorEnd = nEnd;
1717 int SentenceEditWindow_Impl::ChangeMarkedWord(const OUString& rNewWord, LanguageType eLanguage)
1719 std::vector<EECharAttrib> aAttribList;
1720 m_xEditEngine->GetCharAttribs(0, aAttribList);
1722 //calculate length changes
1723 auto nDiffLen = rNewWord.getLength() - m_nErrorEnd + m_nErrorStart;
1724 //Remove spell error attribute
1725 m_xEditEngine->UndoActionStart(SPELLUNDO_MOVE_ERROREND);
1726 const EECharAttrib* pErrorAttrib = FindCharAttrib(m_nErrorStart, EE_CHAR_GRABBAG, aAttribList);
1727 DBG_ASSERT(pErrorAttrib, "no error attribute found");
1728 bool bSpellErrorDescription = false;
1729 SpellErrorDescription aSpellErrorDescription;
1730 if (pErrorAttrib)
1732 ExtractErrorDescription(*pErrorAttrib, aSpellErrorDescription);
1733 m_xEditEngine->RemoveAttribs(ESelection(0, pErrorAttrib->nStart, 0, pErrorAttrib->nEnd), false, EE_CHAR_GRABBAG);
1734 bSpellErrorDescription = true;
1737 const EECharAttrib* pBackAttrib = FindCharAttrib(m_nErrorStart, EE_CHAR_BKGCOLOR, aAttribList);
1739 ESelection aSel(0, m_nErrorStart, 0, m_nErrorEnd);
1740 m_xEditEngine->QuickInsertText(rNewWord, aSel);
1742 const sal_Int32 nTextLen = m_xEditEngine->GetTextLen(0);
1744 if (nDiffLen)
1745 m_xEditEngine->GetCharAttribs(0, aAttribList);
1747 if (!m_nErrorStart)
1749 //attributes following an error at the start of the text are not moved but expanded from the
1750 //text engine - this is done to keep full-paragraph-attributes
1751 //in the current case that handling is not desired
1752 const EECharAttrib* pLangAttrib = FindCharAttrib(m_nErrorEnd, EE_CHAR_LANGUAGE, aAttribList);
1754 if (pLangAttrib && !pLangAttrib->nStart && pLangAttrib->nEnd == nTextLen)
1756 LanguageType eNewLanguage = static_cast<const SvxLanguageItem*>(pLangAttrib->pAttr)->GetLanguage();
1757 m_xEditEngine->RemoveAttribs(ESelection(0, pLangAttrib->nStart, 0, pLangAttrib->nEnd), false, EE_CHAR_LANGUAGE);
1758 SetAttrib(SvxLanguageItem(eNewLanguage, EE_CHAR_LANGUAGE), m_nErrorEnd + nDiffLen, nTextLen);
1762 // undo expanded attributes!
1763 if (pBackAttrib && pBackAttrib->nStart < m_nErrorStart && pBackAttrib->nEnd == m_nErrorEnd + nDiffLen)
1765 std::unique_ptr<SfxPoolItem> xNewBackground(pBackAttrib->pAttr->Clone());
1766 const sal_Int32 nStart = pBackAttrib->nStart;
1768 m_xEditEngine->RemoveAttribs(ESelection(0, pBackAttrib->nStart, 0, pBackAttrib->nEnd), false, EE_CHAR_BKGCOLOR);
1770 SetAttrib(*xNewBackground, nStart, m_nErrorStart);
1772 m_xEditEngine->SetModified();
1774 //adjust end position
1775 tools::Long nEndTemp = m_nErrorEnd;
1776 nEndTemp += nDiffLen;
1777 m_nErrorEnd = static_cast<sal_Int32>(nEndTemp);
1779 std::unique_ptr<SpellUndoAction_Impl> pAction(new SpellUndoAction_Impl(
1780 SPELLUNDO_MOVE_ERROREND, GetSpellDialog()->aDialogUndoLink));
1781 pAction->SetOffset(nDiffLen);
1782 AddUndoAction(std::move(pAction));
1783 if (bSpellErrorDescription)
1785 SfxGrabBagItem aSpellErrorDescriptionItem(EE_CHAR_GRABBAG);
1786 aSpellErrorDescriptionItem.GetGrabBag()["SpellErrorDescription"] <<= aSpellErrorDescription.toSequence();
1787 SetAttrib(aSpellErrorDescriptionItem, m_nErrorStart, m_nErrorEnd);
1789 SetAttrib(SvxLanguageItem(eLanguage, EE_CHAR_LANGUAGE), m_nErrorStart, m_nErrorEnd);
1790 m_xEditEngine->UndoActionEnd();
1792 Invalidate();
1794 return nDiffLen;
1797 OUString SentenceEditWindow_Impl::GetErrorText() const
1799 return m_xEditEngine->GetText(ESelection(0, m_nErrorStart, 0, m_nErrorEnd));
1802 bool SentenceEditWindow_Impl::GetErrorDescription(SpellErrorDescription& rSpellErrorDescription, sal_Int32 nPosition)
1804 std::vector<EECharAttrib> aAttribList;
1805 m_xEditEngine->GetCharAttribs(0, aAttribList);
1807 if (const EECharAttrib* pEECharAttrib = FindCharAttrib(nPosition, EE_CHAR_GRABBAG, aAttribList))
1809 ExtractErrorDescription(*pEECharAttrib, rSpellErrorDescription);
1810 return true;
1813 return false;
1816 bool SentenceEditWindow_Impl::GetAlternatives(SpellErrorDescription& rSpellErrorDescription)
1818 return GetErrorDescription(rSpellErrorDescription, m_nErrorStart);
1821 void SentenceEditWindow_Impl::RestoreCurrentError()
1823 SpellErrorDescription aSpellErrorDescription;
1824 if (GetErrorDescription(aSpellErrorDescription, m_nErrorStart))
1826 if (aSpellErrorDescription.sErrorText != GetErrorText() )
1827 ChangeMarkedWord(aSpellErrorDescription.sErrorText, LanguageTag::convertToLanguageType(aSpellErrorDescription.aLocale));
1831 void SentenceEditWindow_Impl::SetAlternatives( const Reference< XSpellAlternatives>& xAlt )
1833 OUString aWord;
1834 lang::Locale aLocale;
1835 uno::Sequence< OUString > aAlts;
1836 if (xAlt.is())
1838 aWord = xAlt->getWord();
1839 aLocale = xAlt->getLocale();
1840 aAlts = xAlt->getAlternatives();
1842 SpellErrorDescription aDesc( false, aWord, std::move(aLocale), aAlts, nullptr);
1843 SfxGrabBagItem aSpellErrorDescription(EE_CHAR_GRABBAG);
1844 aSpellErrorDescription.GetGrabBag()["SpellErrorDescription"] <<= aDesc.toSequence();
1845 SetAttrib(aSpellErrorDescription, m_nErrorStart, m_nErrorEnd);
1848 void SentenceEditWindow_Impl::SetAttrib(const SfxPoolItem& rItem, sal_Int32 nStart, sal_Int32 nEnd)
1850 SfxItemSet aSet(m_xEditEngine->GetEmptyItemSet());
1851 aSet.Put(rItem);
1852 m_xEditEngine->QuickSetAttribs(aSet, ESelection(0, nStart, 0, nEnd));
1853 Invalidate();
1856 void SentenceEditWindow_Impl::SetText( const OUString& rStr )
1858 m_nErrorStart = m_nErrorEnd = 0;
1859 m_xEditEngine->SetText(rStr);
1862 namespace {
1864 struct LanguagePosition_Impl
1866 sal_Int32 nPosition;
1867 LanguageType eLanguage;
1869 LanguagePosition_Impl(sal_Int32 nPos, LanguageType eLang) :
1870 nPosition(nPos),
1871 eLanguage(eLang)
1877 typedef std::vector<LanguagePosition_Impl> LanguagePositions_Impl;
1879 static void lcl_InsertBreakPosition_Impl(
1880 LanguagePositions_Impl& rBreakPositions, sal_Int32 nInsert, LanguageType eLanguage)
1882 LanguagePositions_Impl::iterator aStart = rBreakPositions.begin();
1883 while(aStart != rBreakPositions.end())
1885 if(aStart->nPosition == nInsert)
1887 //the language of following starts has to overwrite
1888 //the one of previous ends
1889 aStart->eLanguage = eLanguage;
1890 return;
1892 else if(aStart->nPosition > nInsert)
1895 rBreakPositions.insert(aStart, LanguagePosition_Impl(nInsert, eLanguage));
1896 return;
1898 else
1899 ++aStart;
1901 rBreakPositions.emplace_back(nInsert, eLanguage);
1904 /*-------------------------------------------------------------------------
1905 Returns the text in spell portions. Each portion contains text with an
1906 equal language and attribute. The spell alternatives are empty.
1907 -----------------------------------------------------------------------*/
1908 svx::SpellPortions SentenceEditWindow_Impl::CreateSpellPortions() const
1910 svx::SpellPortions aRet;
1912 const sal_Int32 nTextLen = m_xEditEngine->GetTextLen(0);
1914 std::vector<EECharAttrib> aAttribList;
1915 m_xEditEngine->GetCharAttribs(0, aAttribList);
1917 if (nTextLen)
1919 int nCursor(0);
1920 LanguagePositions_Impl aBreakPositions;
1921 const EECharAttrib* pLastLang = nullptr;
1922 const EECharAttrib* pLastError = nullptr;
1923 LanguageType eLang = LANGUAGE_DONTKNOW;
1924 const EECharAttrib* pError = nullptr;
1925 while (nCursor < nTextLen)
1927 const EECharAttrib* pLang = FindCharAttrib(nCursor, EE_CHAR_LANGUAGE, aAttribList);
1928 if(pLang && pLang != pLastLang)
1930 eLang = static_cast<const SvxLanguageItem*>(pLang->pAttr)->GetLanguage();
1931 lcl_InsertBreakPosition_Impl(aBreakPositions, pLang->nStart, eLang);
1932 lcl_InsertBreakPosition_Impl(aBreakPositions, pLang->nEnd, eLang);
1933 pLastLang = pLang;
1935 pError = FindCharAttrib(nCursor, EE_CHAR_GRABBAG, aAttribList);
1936 if (pError && pLastError != pError)
1938 lcl_InsertBreakPosition_Impl(aBreakPositions, pError->nStart, eLang);
1939 lcl_InsertBreakPosition_Impl(aBreakPositions, pError->nEnd, eLang);
1940 pLastError = pError;
1943 ++nCursor;
1946 if (aBreakPositions.empty())
1948 //if all content has been overwritten the attributes may have been removed, too
1949 svx::SpellPortion aPortion1;
1950 aPortion1.eLanguage = GetSpellDialog()->GetSelectedLang_Impl();
1952 aPortion1.sText = m_xEditEngine->GetText(ESelection(0, 0, 0, nTextLen));
1954 aRet.push_back(aPortion1);
1956 else
1958 LanguagePositions_Impl::iterator aStart = aBreakPositions.begin();
1959 //start should always be Null
1960 eLang = aStart->eLanguage;
1961 sal_Int32 nStart = aStart->nPosition;
1962 DBG_ASSERT(!nStart, "invalid start position - language attribute missing?");
1963 ++aStart;
1965 while(aStart != aBreakPositions.end())
1967 svx::SpellPortion aPortion1;
1968 aPortion1.eLanguage = eLang;
1970 aPortion1.sText = m_xEditEngine->GetText(ESelection(0, nStart, 0, aStart->nPosition));
1972 bool bIsIgnoreError = m_aIgnoreErrorsAt.find( nStart ) != m_aIgnoreErrorsAt.end();
1973 if( bIsIgnoreError )
1975 aPortion1.bIgnoreThisError = true;
1977 aRet.push_back(aPortion1);
1978 nStart = aStart->nPosition;
1979 eLang = aStart->eLanguage;
1980 ++aStart;
1984 // quick partly fix of #i71318. Correct fix needs to patch the EditEngine itself...
1985 // this one will only prevent text from disappearing. It may to not have the
1986 // correct language and will probably not spell checked...
1987 const sal_uInt32 nPara = m_xEditEngine->GetParagraphCount();
1988 if (nPara > 1)
1990 OUStringBuffer aLeftOverText;
1991 for (sal_uInt32 i = 1; i < nPara; ++i)
1993 aLeftOverText.append("\x0a"); // the manual line break...
1994 aLeftOverText.append(m_xEditEngine->GetText(i));
1996 if (pError)
1997 { // we need to add a new portion containing the left-over text
1998 svx::SpellPortion aPortion2;
1999 aPortion2.eLanguage = eLang;
2000 aPortion2.sText = aLeftOverText.makeStringAndClear();
2001 aRet.push_back( aPortion2 );
2003 else if (!aLeftOverText.isEmpty() && !aRet.empty())
2004 { // we just need to append the left-over text to the last portion (which had no errors)
2005 aRet[ aRet.size() - 1 ].sText += aLeftOverText;
2010 return aRet;
2013 void SentenceEditWindow_Impl::Undo()
2015 SfxUndoManager& rUndoMgr = m_xEditEngine->GetUndoManager();
2016 DBG_ASSERT(GetUndoActionCount(), "no undo actions available" );
2017 if(!GetUndoActionCount())
2018 return;
2019 bool bSaveUndoEdit = IsUndoEditMode();
2020 SpellUndoAction_Impl* pUndoAction;
2021 //if the undo edit mode is active then undo all changes until the UNDO_EDIT_MODE action has been found
2024 pUndoAction = static_cast<SpellUndoAction_Impl*>(rUndoMgr.GetUndoAction());
2025 rUndoMgr.Undo();
2026 }while(bSaveUndoEdit && SPELLUNDO_UNDO_EDIT_MODE != pUndoAction->GetId() && GetUndoActionCount());
2028 if(bSaveUndoEdit || SPELLUNDO_CHANGE_GROUP == pUndoAction->GetId())
2029 GetSpellDialog()->UpdateBoxes_Impl();
2032 void SentenceEditWindow_Impl::ResetUndo()
2034 SfxUndoManager& rUndo = m_xEditEngine->GetUndoManager();
2035 rUndo.Clear();
2038 void SentenceEditWindow_Impl::AddUndoAction( std::unique_ptr<SfxUndoAction> pAction )
2040 SfxUndoManager& rUndoMgr = m_xEditEngine->GetUndoManager();
2041 rUndoMgr.AddUndoAction(std::move(pAction));
2042 GetSpellDialog()->m_xUndoPB->set_sensitive(true);
2045 size_t SentenceEditWindow_Impl::GetUndoActionCount() const
2047 return m_xEditEngine->GetUndoManager().GetUndoActionCount();
2050 void SentenceEditWindow_Impl::UndoActionStart( sal_uInt16 nId )
2052 m_xEditEngine->UndoActionStart(nId);
2055 void SentenceEditWindow_Impl::UndoActionEnd()
2057 m_xEditEngine->UndoActionEnd();
2060 void SentenceEditWindow_Impl::MoveErrorEnd(tools::Long nOffset)
2062 // Shouldn't we always add the real signed value instead???
2063 if(nOffset > 0)
2064 m_nErrorEnd = m_nErrorEnd - static_cast<sal_Int32>(nOffset);
2065 else
2066 m_nErrorEnd = m_nErrorEnd - static_cast<sal_Int32>(-nOffset);
2070 void SentenceEditWindow_Impl::SetUndoEditMode(bool bSet)
2072 DBG_ASSERT(!bSet || m_bIsUndoEditMode != bSet, "SetUndoEditMode with equal values?");
2073 m_bIsUndoEditMode = bSet;
2074 //disable all buttons except the Change
2075 SpellDialog* pSpellDialog = GetSpellDialog();
2076 weld::Widget* aControls[] =
2078 pSpellDialog->m_xChangeAllPB.get(),
2079 pSpellDialog->m_xExplainFT.get(),
2080 pSpellDialog->m_xIgnoreAllPB.get(),
2081 pSpellDialog->m_xIgnoreRulePB.get(),
2082 pSpellDialog->m_xIgnorePB.get(),
2083 pSpellDialog->m_xSuggestionLB.get(),
2084 pSpellDialog->m_xSuggestionFT.get(),
2085 pSpellDialog->m_xLanguageFT.get(),
2086 pSpellDialog->m_xLanguageLB->get_widget(),
2087 pSpellDialog->m_xAddToDictMB.get(),
2088 pSpellDialog->m_xAddToDictPB.get(),
2089 pSpellDialog->m_xAutoCorrPB.get()
2091 for (weld::Widget* pWidget : aControls)
2092 pWidget->set_sensitive(false);
2094 //remove error marks
2095 ESelection aAll(0, 0, 0, EE_TEXTPOS_ALL);
2096 m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_COLOR);
2097 m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT);
2098 m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT_CJK);
2099 m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT_CTL);
2100 Invalidate();
2102 //put the appropriate action on the Undo-stack
2103 AddUndoAction( std::make_unique<SpellUndoAction_Impl>(
2104 SPELLUNDO_UNDO_EDIT_MODE, GetSpellDialog()->aDialogUndoLink) );
2105 pSpellDialog->m_xChangePB->set_sensitive(true);
2108 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */