Bump version to 6.4.7.2.M8
[LibreOffice.git] / cui / source / dialogs / SpellDialog.cxx
blob204a0b308a2ebca7110bd918f3a124699e40e87b
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 <vcl/edit.hxx>
44 #include <vcl/event.hxx>
45 #include <vcl/svapp.hxx>
46 #include <vcl/texteng.hxx>
47 #include <vcl/weld.hxx>
48 #include <svx/SpellDialogChildWindow.hxx>
49 #include <SpellDialog.hxx>
50 #include <optlingu.hxx>
51 #include <treeopt.hxx>
52 #include <svtools/langtab.hxx>
53 #include <sal/log.hxx>
54 #include <i18nlangtag/languagetag.hxx>
56 using namespace ::com::sun::star;
57 using namespace ::com::sun::star::uno;
58 using namespace ::com::sun::star::beans;
59 using namespace ::com::sun::star::linguistic2;
60 using namespace linguistic;
63 // struct SpellDialog_Impl ---------------------------------------------
65 struct SpellDialog_Impl
67 Sequence< Reference< XDictionary > > aDics;
71 #define SPELLUNDO_START 200
73 #define SPELLUNDO_CHANGE_LANGUAGE (SPELLUNDO_START + 1)
74 #define SPELLUNDO_CHANGE_TEXTENGINE (SPELLUNDO_START + 2)
75 #define SPELLUNDO_CHANGE_NEXTERROR (SPELLUNDO_START + 3)
76 #define SPELLUNDO_CHANGE_ADD_TO_DICTIONARY (SPELLUNDO_START + 4)
77 #define SPELLUNDO_CHANGE_GROUP (SPELLUNDO_START + 5) //undo list
78 #define SPELLUNDO_MOVE_ERROREND (SPELLUNDO_START + 6)
79 #define SPELLUNDO_UNDO_EDIT_MODE (SPELLUNDO_START + 7)
80 #define SPELLUNDO_ADD_IGNORE_RULE (SPELLUNDO_START + 8)
82 namespace svx{
83 class SpellUndoAction_Impl : public SfxUndoAction
85 sal_uInt16 m_nId;
86 const Link<SpellUndoAction_Impl&,void>& m_rActionLink;
87 //undo of button enabling
88 bool m_bEnableChangePB;
89 bool m_bEnableChangeAllPB;
90 //undo of MarkNextError - used in change and change all, ignore and ignore all
91 long m_nOldErrorStart;
92 long m_nOldErrorEnd;
93 bool m_bIsErrorLanguageSelected;
94 //undo of AddToDictionary
95 Reference<XDictionary> m_xDictionary;
96 OUString m_sAddedWord;
97 //move end of error - ::ChangeMarkedWord()
98 long m_nOffset;
100 public:
101 SpellUndoAction_Impl(sal_uInt16 nId, const Link<SpellUndoAction_Impl&,void>& rActionLink) :
102 m_nId(nId),
103 m_rActionLink( rActionLink),
104 m_bEnableChangePB(false),
105 m_bEnableChangeAllPB(false),
106 m_nOldErrorStart(-1),
107 m_nOldErrorEnd(-1),
108 m_bIsErrorLanguageSelected(false),
109 m_nOffset(0)
112 virtual void Undo() override;
113 sal_uInt16 GetId() const;
115 void SetEnableChangePB(){m_bEnableChangePB = true;}
116 bool IsEnableChangePB() const {return m_bEnableChangePB;}
118 void SetEnableChangeAllPB(){m_bEnableChangeAllPB = true;}
119 bool IsEnableChangeAllPB() const {return m_bEnableChangeAllPB;}
121 void SetErrorMove(long nOldStart, long nOldEnd)
123 m_nOldErrorStart = nOldStart;
124 m_nOldErrorEnd = nOldEnd;
126 long GetOldErrorStart() const { return m_nOldErrorStart;}
127 long GetOldErrorEnd() const { return m_nOldErrorEnd;}
129 void SetErrorLanguageSelected(bool bSet){ m_bIsErrorLanguageSelected = bSet;}
130 bool IsErrorLanguageSelected() const {return m_bIsErrorLanguageSelected;}
132 void SetDictionary(const Reference<XDictionary>& xDict) { m_xDictionary = xDict; }
133 const Reference<XDictionary>& GetDictionary() const { return m_xDictionary; }
134 void SetAddedWord(const OUString& rWord) {m_sAddedWord = rWord;}
135 const OUString& GetAddedWord() const { return m_sAddedWord;}
137 void SetOffset(long nSet) {m_nOffset = nSet;}
138 long GetOffset() const {return m_nOffset;}
140 }//namespace svx
141 using namespace ::svx;
143 void SpellUndoAction_Impl::Undo()
145 m_rActionLink.Call(*this);
149 sal_uInt16 SpellUndoAction_Impl::GetId()const
151 return m_nId;
154 // class SvxSpellCheckDialog ---------------------------------------------
156 SpellDialog::SpellDialog(SpellDialogChildWindow* pChildWindow,
157 weld::Window * pParent, SfxBindings* _pBindings)
158 : SfxModelessDialogController (_pBindings, pChildWindow,
159 pParent, "cui/ui/spellingdialog.ui", "SpellingDialog")
160 , aDialogUndoLink(LINK (this, SpellDialog, DialogUndoHdl))
161 , bFocusLocked(true)
162 , rParent(*pChildWindow)
163 , pImpl( new SpellDialog_Impl )
164 , m_xAltTitle(m_xBuilder->weld_label("alttitleft"))
165 , m_xResumeFT(m_xBuilder->weld_label("resumeft"))
166 , m_xNoSuggestionsFT(m_xBuilder->weld_label("nosuggestionsft"))
167 , m_xLanguageFT(m_xBuilder->weld_label("languageft"))
168 , m_xLanguageLB(new SvxLanguageBox(m_xBuilder->weld_combo_box("languagelb")))
169 , m_xExplainFT(m_xBuilder->weld_label("explain"))
170 , m_xExplainLink(m_xBuilder->weld_link_button("explainlink"))
171 , m_xNotInDictFT(m_xBuilder->weld_label("notindictft"))
172 , m_xSentenceED(new SentenceEditWindow_Impl)
173 , m_xSuggestionFT(m_xBuilder->weld_label("suggestionsft"))
174 , m_xSuggestionLB(m_xBuilder->weld_tree_view("suggestionslb"))
175 , m_xIgnorePB(m_xBuilder->weld_button("ignore"))
176 , m_xIgnoreAllPB(m_xBuilder->weld_button("ignoreall"))
177 , m_xIgnoreRulePB(m_xBuilder->weld_button("ignorerule"))
178 , m_xAddToDictPB(m_xBuilder->weld_button("add"))
179 , m_xAddToDictMB(m_xBuilder->weld_menu_button("addmb"))
180 , m_xChangePB(m_xBuilder->weld_button("change"))
181 , m_xChangeAllPB(m_xBuilder->weld_button("changeall"))
182 , m_xAutoCorrPB(m_xBuilder->weld_button("autocorrect"))
183 , m_xCheckGrammarCB(m_xBuilder->weld_check_button("checkgrammar"))
184 , m_xOptionsPB(m_xBuilder->weld_button("options"))
185 , m_xUndoPB(m_xBuilder->weld_button("undo"))
186 , m_xClosePB(m_xBuilder->weld_button("close"))
187 , m_xToolbar(m_xBuilder->weld_toolbar("toolbar"))
188 , m_xSentenceEDWeld(new weld::CustomWeld(*m_xBuilder, "sentence", *m_xSentenceED))
190 m_xSentenceED->SetSpellDialog(this);
191 m_xSentenceED->Init(m_xToolbar.get());
193 m_sTitleSpellingGrammar = m_xDialog->get_title();
194 m_sTitleSpelling = m_xAltTitle->get_label();
196 // fdo#68794 set initial title for cases where no text has been processed
197 // yet to show its language attributes
198 OUString sTitle = rParent.HasGrammarChecking() ? m_sTitleSpellingGrammar : m_sTitleSpelling;
199 m_xDialog->set_title(m_xDialog->strip_mnemonic(sTitle.replaceFirst("$LANGUAGE ($LOCATION)", "")));
201 m_sResumeST = m_xResumeFT->get_label();
202 m_sNoSuggestionsST = m_xNoSuggestionsFT->strip_mnemonic(m_xNoSuggestionsFT->get_label());
204 Size aEdSize(m_xSuggestionLB->get_approximate_digit_width() * 60,
205 m_xSuggestionLB->get_height_rows(6));
206 m_xSuggestionLB->set_size_request(aEdSize.Width(), -1);
207 m_sIgnoreOnceST = m_xIgnorePB->get_label();
208 m_xAddToDictMB->set_help_id(m_xAddToDictPB->get_help_id());
209 xSpell = LinguMgr::GetSpellChecker();
211 Init_Impl();
213 // disable controls if service is missing
214 m_xDialog->set_sensitive(xSpell.is());
216 //InitHdl wants to use virtual methods, so it
217 //can't be called during the ctor, so init
218 //it on next event cycle post-ctor
219 Application::PostUserEvent(LINK(this, SpellDialog, InitHdl));
222 SpellDialog::~SpellDialog()
224 if (pImpl)
226 // save possibly modified user-dictionaries
227 Reference< XSearchableDictionaryList > xDicList( LinguMgr::GetDictionaryList() );
228 if (xDicList.is())
229 SaveDictionaries( xDicList );
231 pImpl.reset();
235 void SpellDialog::Init_Impl()
237 // initialize handler
238 m_xClosePB->connect_clicked(LINK( this, SpellDialog, CancelHdl ) );
239 m_xChangePB->connect_clicked(LINK( this, SpellDialog, ChangeHdl ) );
240 m_xChangeAllPB->connect_clicked(LINK( this, SpellDialog, ChangeAllHdl ) );
241 m_xIgnorePB->connect_clicked(LINK( this, SpellDialog, IgnoreHdl ) );
242 m_xIgnoreAllPB->connect_clicked(LINK( this, SpellDialog, IgnoreAllHdl ) );
243 m_xIgnoreRulePB->connect_clicked(LINK( this, SpellDialog, IgnoreAllHdl ) );
244 m_xUndoPB->connect_clicked(LINK( this, SpellDialog, UndoHdl ) );
246 m_xAutoCorrPB->connect_clicked( LINK( this, SpellDialog, ExtClickHdl ) );
247 m_xCheckGrammarCB->connect_clicked( LINK( this, SpellDialog, CheckGrammarHdl ));
248 m_xOptionsPB->connect_clicked( LINK( this, SpellDialog, ExtClickHdl ) );
250 m_xSuggestionLB->connect_row_activated( LINK( this, SpellDialog, DoubleClickChangeHdl ) );
252 m_xSentenceED->SetModifyHdl(LINK ( this, SpellDialog, ModifyHdl) );
254 m_xAddToDictMB->connect_selected(LINK ( this, SpellDialog, AddToDictSelectHdl ) );
255 m_xAddToDictPB->connect_clicked(LINK ( this, SpellDialog, AddToDictClickHdl ) );
257 m_xLanguageLB->connect_changed(LINK( this, SpellDialog, LanguageSelectHdl ) );
259 // initialize language ListBox
260 m_xLanguageLB->SetLanguageList(SvxLanguageListFlags::SPELL_USED, false, false, true);
262 m_xSentenceED->ClearModifyFlag();
263 LinguMgr::GetChangeAllList()->clear();
266 void SpellDialog::UpdateBoxes_Impl(bool bCallFromSelectHdl)
268 sal_Int32 i;
269 m_xSuggestionLB->clear();
271 SpellErrorDescription aSpellErrorDescription;
272 bool bSpellErrorDescription = m_xSentenceED->GetAlternatives(aSpellErrorDescription);
274 LanguageType nAltLanguage = LANGUAGE_NONE;
275 Sequence< OUString > aNewWords;
276 bool bIsGrammarError = false;
277 if( bSpellErrorDescription )
279 nAltLanguage = LanguageTag::convertToLanguageType( aSpellErrorDescription.aLocale );
280 aNewWords = aSpellErrorDescription.aSuggestions;
281 bIsGrammarError = aSpellErrorDescription.bIsGrammarError;
282 m_xExplainLink->set_uri( aSpellErrorDescription.sExplanationURL );
283 m_xExplainFT->set_label( aSpellErrorDescription.sExplanation );
285 if( bSpellErrorDescription && !aSpellErrorDescription.sDialogTitle.isEmpty() )
287 // use this function to apply the correct image to be used...
288 SetTitle_Impl( nAltLanguage );
289 // then change the title to the one to be actually used
290 m_xDialog->set_title(m_xDialog->strip_mnemonic(aSpellErrorDescription.sDialogTitle));
292 else
293 SetTitle_Impl( nAltLanguage );
294 if( !bCallFromSelectHdl )
295 m_xLanguageLB->set_active_id( nAltLanguage );
296 int nDicts = InitUserDicts();
298 // enter alternatives
299 const OUString *pNewWords = aNewWords.getConstArray();
300 const sal_Int32 nSize = aNewWords.getLength();
301 for ( i = 0; i < nSize; ++i )
303 OUString aTmp( pNewWords[i] );
304 if (m_xSuggestionLB->find_text(aTmp) == -1)
305 m_xSuggestionLB->append_text(aTmp);
307 if(!nSize)
308 m_xSuggestionLB->append_text(m_sNoSuggestionsST);
309 m_xAutoCorrPB->set_sensitive( nSize > 0 );
311 m_xSuggestionFT->set_sensitive(nSize > 0);
312 m_xSuggestionLB->set_sensitive(nSize > 0);
313 if( nSize )
315 m_xSuggestionLB->select(0);
317 m_xChangePB->set_sensitive( nSize > 0);
318 m_xChangeAllPB->set_sensitive(nSize > 0);
319 bool bShowChangeAll = !bIsGrammarError;
320 m_xChangeAllPB->set_visible( bShowChangeAll );
321 m_xExplainFT->set_visible( !bShowChangeAll );
322 m_xLanguageLB->set_sensitive( bShowChangeAll );
323 m_xIgnoreAllPB->set_visible( bShowChangeAll );
325 m_xAddToDictMB->set_visible( bShowChangeAll && nDicts > 1);
326 m_xAddToDictPB->set_visible( bShowChangeAll && nDicts <= 1);
327 m_xIgnoreRulePB->set_visible( !bShowChangeAll );
328 m_xIgnoreRulePB->set_sensitive(bSpellErrorDescription && !aSpellErrorDescription.sRuleId.isEmpty());
329 m_xAutoCorrPB->set_visible( bShowChangeAll && rParent.HasAutoCorrection() );
331 bool bOldShowGrammar = m_xCheckGrammarCB->get_visible();
332 bool bOldShowExplain = m_xExplainLink->get_visible();
334 m_xCheckGrammarCB->set_visible(rParent.HasGrammarChecking());
335 m_xExplainLink->set_visible(!m_xExplainLink->get_uri().isEmpty());
336 if (m_xExplainFT->get_label().isEmpty())
338 m_xExplainFT->hide();
339 m_xExplainLink->hide();
342 if (bOldShowExplain != m_xExplainLink->get_visible() || bOldShowGrammar != m_xCheckGrammarCB->get_visible())
343 m_xDialog->resize_to_request();
346 void SpellDialog::SpellContinue_Impl(std::unique_ptr<UndoChangeGroupGuard>* pGuard, bool bUseSavedSentence, bool bIgnoreCurrentError)
348 //initially or after the last error of a sentence MarkNextError will fail
349 //then GetNextSentence() has to be called followed again by MarkNextError()
350 //MarkNextError is not initially called if the UndoEdit mode is active
351 bool bNextSentence = false;
352 if((!m_xSentenceED->IsUndoEditMode() && m_xSentenceED->MarkNextError( bIgnoreCurrentError, xSpell )) ||
353 ( bNextSentence = GetNextSentence_Impl(pGuard, bUseSavedSentence, m_xSentenceED->IsUndoEditMode()) && m_xSentenceED->MarkNextError( false, xSpell )))
355 SpellErrorDescription aSpellErrorDescription;
356 bool bSpellErrorDescription = m_xSentenceED->GetAlternatives(aSpellErrorDescription);
357 if (bSpellErrorDescription)
359 UpdateBoxes_Impl();
360 weld::Widget* aControls[] =
362 m_xNotInDictFT.get(),
363 m_xSentenceED->GetDrawingArea(),
364 m_xLanguageFT.get(),
365 nullptr
367 sal_Int32 nIdx = 0;
370 aControls[nIdx]->set_sensitive(true);
372 while(aControls[++nIdx]);
375 if( bNextSentence )
377 //remove undo if a new sentence is active
378 m_xSentenceED->ResetUndo();
379 m_xUndoPB->set_sensitive(false);
383 /* Initialize, asynchronous to prevent virtual calls
384 from a constructor
386 IMPL_LINK_NOARG( SpellDialog, InitHdl, void*, void)
388 m_xDialog->freeze();
389 //show or hide AutoCorrect depending on the modules abilities
390 m_xAutoCorrPB->set_visible(rParent.HasAutoCorrection());
391 SpellContinue_Impl(nullptr);
392 m_xSentenceED->ResetUndo();
393 m_xUndoPB->set_sensitive(false);
395 // get current language
396 UpdateBoxes_Impl();
398 // fill dictionary PopupMenu
399 InitUserDicts();
401 LockFocusChanges(true);
402 if( m_xChangePB->get_sensitive() )
403 m_xChangePB->grab_focus();
404 else if( m_xIgnorePB->get_sensitive() )
405 m_xIgnorePB->grab_focus();
406 else if( m_xClosePB->get_sensitive() )
407 m_xClosePB->grab_focus();
408 LockFocusChanges(false);
409 //show grammar CheckBox depending on the modules abilities
410 m_xCheckGrammarCB->set_active(rParent.IsGrammarChecking());
411 m_xDialog->thaw();
414 IMPL_LINK( SpellDialog, ExtClickHdl, weld::Button&, rBtn, void )
416 if (m_xOptionsPB.get() == &rBtn)
417 StartSpellOptDlg_Impl();
418 else if (m_xAutoCorrPB.get() == &rBtn)
420 //get the currently selected wrong word
421 OUString sCurrentErrorText = m_xSentenceED->GetErrorText();
422 //get the wrong word from the XSpellAlternative
423 SpellErrorDescription aSpellErrorDescription;
424 bool bSpellErrorDescription = m_xSentenceED->GetAlternatives(aSpellErrorDescription);
425 if (bSpellErrorDescription)
427 OUString sWrong(aSpellErrorDescription.sErrorText);
428 //if the word has not been edited in the MultiLineEdit then
429 //the current suggestion should be used
430 //if it's not the 'no suggestions' entry
431 if(sWrong == sCurrentErrorText &&
432 m_xSuggestionLB->get_sensitive() && m_xSuggestionLB->get_selected_index() != -1 &&
433 m_sNoSuggestionsST != m_xSuggestionLB->get_selected_text())
435 sCurrentErrorText = m_xSuggestionLB->get_selected_text();
437 if(sWrong != sCurrentErrorText)
439 SvxPrepareAutoCorrect( sWrong, sCurrentErrorText );
440 LanguageType eLang = GetSelectedLang_Impl();
441 rParent.AddAutoCorrection( sWrong, sCurrentErrorText, eLang );
447 IMPL_LINK_NOARG(SpellDialog, CheckGrammarHdl, weld::Button&, void)
449 rParent.SetGrammarChecking(m_xCheckGrammarCB->get_active());
450 Impl_Restore(true);
453 void SpellDialog::StartSpellOptDlg_Impl()
455 SfxItemSet aSet( SfxGetpApp()->GetPool(), svl::Items<SID_AUTOSPELL_CHECK,SID_AUTOSPELL_CHECK>{});
456 SfxSingleTabDialogController aDlg(m_xDialog.get(), &aSet, "cui/ui/spelloptionsdialog.ui", "SpellOptionsDialog");
458 std::unique_ptr<SfxTabPage> xPage = SvxLinguTabPage::Create(aDlg.get_content_area(), &aDlg, &aSet);
459 static_cast<SvxLinguTabPage*>(xPage.get())->HideGroups( GROUP_MODULES );
460 aDlg.SetTabPage(std::move(xPage));
461 if (RET_OK == aDlg.run())
463 InitUserDicts();
464 const SfxItemSet* pOutSet = aDlg.GetOutputItemSet();
465 if(pOutSet)
466 OfaTreeOptionsDialog::ApplyLanguageOptions(*pOutSet);
470 namespace
472 OUString getDotReplacementString(const OUString &rErrorText, const OUString &rSuggestedReplacement)
474 OUString aString = rErrorText;
476 //dots are sometimes part of the spelled word but they are not necessarily part of the replacement
477 bool bDot = aString.endsWith(".");
479 aString = rSuggestedReplacement;
481 if(bDot && (aString.isEmpty() || !aString.endsWith(".")))
482 aString += ".";
484 return aString;
488 OUString SpellDialog::getReplacementString() const
490 OUString sOrigString = m_xSentenceED->GetErrorText();
492 OUString sReplacement(sOrigString);
494 if(m_xSuggestionLB->get_sensitive() &&
495 m_xSuggestionLB->get_selected_index() != -1 &&
496 m_sNoSuggestionsST != m_xSuggestionLB->get_selected_text())
497 sReplacement = m_xSuggestionLB->get_selected_text();
499 return getDotReplacementString(sOrigString, sReplacement);
502 IMPL_LINK_NOARG(SpellDialog, DoubleClickChangeHdl, weld::TreeView&, bool)
504 ChangeHdl(*m_xChangePB);
505 return true;
508 /* tdf#132822 start an undo group in ctor and close it in the dtor. This can
509 then be passed to SpellContinue_Impl which can delete it in advance of its
510 natural scope to force closing the undo group if SpellContinue_Impl needs to
511 fetch a new paragraph and discard all undo information which can only be
512 done properly if there are no open undo groups */
513 class UndoChangeGroupGuard
515 private:
516 SentenceEditWindow_Impl& m_rSentenceED;
517 public:
518 UndoChangeGroupGuard(SentenceEditWindow_Impl& rSentenceED)
519 : m_rSentenceED(rSentenceED)
521 m_rSentenceED.UndoActionStart(SPELLUNDO_CHANGE_GROUP);
523 ~UndoChangeGroupGuard()
525 m_rSentenceED.UndoActionEnd();
529 IMPL_LINK_NOARG(SpellDialog, ChangeHdl, weld::Button&, void)
531 if (m_xSentenceED->IsUndoEditMode())
533 SpellContinue_Impl();
535 else
537 auto xGuard(std::make_unique<UndoChangeGroupGuard>(*m_xSentenceED));
538 OUString aString = getReplacementString();
539 m_xSentenceED->ChangeMarkedWord(aString, GetSelectedLang_Impl());
540 SpellContinue_Impl(&xGuard);
542 if(!m_xChangePB->get_sensitive())
543 m_xIgnorePB->grab_focus();
546 IMPL_LINK_NOARG(SpellDialog, ChangeAllHdl, weld::Button&, void)
548 auto xGuard(std::make_unique<UndoChangeGroupGuard>(*m_xSentenceED));
549 OUString aString = getReplacementString();
550 LanguageType eLang = GetSelectedLang_Impl();
552 // add new word to ChangeAll list
553 OUString aOldWord( m_xSentenceED->GetErrorText() );
554 SvxPrepareAutoCorrect( aOldWord, aString );
555 Reference<XDictionary> aXDictionary = LinguMgr::GetChangeAllList();
556 DictionaryError nAdded = AddEntryToDic( aXDictionary,
557 aOldWord, true,
558 aString );
560 if(nAdded == DictionaryError::NONE)
562 std::unique_ptr<SpellUndoAction_Impl> pAction(new SpellUndoAction_Impl(
563 SPELLUNDO_CHANGE_ADD_TO_DICTIONARY, aDialogUndoLink));
564 pAction->SetDictionary(aXDictionary);
565 pAction->SetAddedWord(aOldWord);
566 m_xSentenceED->AddUndoAction(std::move(pAction));
569 m_xSentenceED->ChangeMarkedWord(aString, eLang);
570 SpellContinue_Impl(&xGuard);
573 IMPL_LINK( SpellDialog, IgnoreAllHdl, weld::Button&, rButton, void )
575 auto xGuard(std::make_unique<UndoChangeGroupGuard>(*m_xSentenceED));
576 // add word to IgnoreAll list
577 Reference< XDictionary > aXDictionary = LinguMgr::GetIgnoreAllList();
578 //in case the error has been changed manually it has to be restored
579 m_xSentenceED->RestoreCurrentError();
580 if (&rButton == m_xIgnoreRulePB.get())
582 SpellErrorDescription aSpellErrorDescription;
583 bool bSpellErrorDescription = m_xSentenceED->GetAlternatives(aSpellErrorDescription);
586 if( bSpellErrorDescription && aSpellErrorDescription.xGrammarChecker.is() )
588 aSpellErrorDescription.xGrammarChecker->ignoreRule(aSpellErrorDescription.sRuleId,
589 aSpellErrorDescription.aLocale);
590 // refresh the layout (workaround to launch a dictionary event)
591 aXDictionary->setActive(false);
592 aXDictionary->setActive(true);
595 catch( const uno::Exception& )
599 else
601 OUString sErrorText(m_xSentenceED->GetErrorText());
602 DictionaryError nAdded = AddEntryToDic( aXDictionary,
603 sErrorText, false,
604 OUString() );
605 if (nAdded == DictionaryError::NONE)
607 std::unique_ptr<SpellUndoAction_Impl> pAction(new SpellUndoAction_Impl(
608 SPELLUNDO_CHANGE_ADD_TO_DICTIONARY, aDialogUndoLink));
609 pAction->SetDictionary(aXDictionary);
610 pAction->SetAddedWord(sErrorText);
611 m_xSentenceED->AddUndoAction(std::move(pAction));
615 SpellContinue_Impl(&xGuard);
618 IMPL_LINK_NOARG(SpellDialog, UndoHdl, weld::Button&, void)
620 m_xSentenceED->Undo();
621 if(!m_xSentenceED->GetUndoActionCount())
622 m_xUndoPB->set_sensitive(false);
626 IMPL_LINK( SpellDialog, DialogUndoHdl, SpellUndoAction_Impl&, rAction, void )
628 switch(rAction.GetId())
630 case SPELLUNDO_CHANGE_TEXTENGINE:
632 if(rAction.IsEnableChangePB())
633 m_xChangePB->set_sensitive(false);
634 if(rAction.IsEnableChangeAllPB())
635 m_xChangeAllPB->set_sensitive(false);
637 break;
638 case SPELLUNDO_CHANGE_NEXTERROR:
640 m_xSentenceED->MoveErrorMarkTo(static_cast<sal_Int32>(rAction.GetOldErrorStart()),
641 static_cast<sal_Int32>(rAction.GetOldErrorEnd()),
642 false);
643 if(rAction.IsErrorLanguageSelected())
645 UpdateBoxes_Impl();
648 break;
649 case SPELLUNDO_CHANGE_ADD_TO_DICTIONARY:
651 if(rAction.GetDictionary().is())
652 rAction.GetDictionary()->remove(rAction.GetAddedWord());
654 break;
655 case SPELLUNDO_MOVE_ERROREND :
657 if(rAction.GetOffset() != 0)
658 m_xSentenceED->MoveErrorEnd(rAction.GetOffset());
660 break;
661 case SPELLUNDO_UNDO_EDIT_MODE :
663 //refill the dialog with the currently spelled sentence - throw away all changes
664 SpellContinue_Impl(nullptr, true);
666 break;
667 case SPELLUNDO_ADD_IGNORE_RULE:
668 //undo of ignored rules is not supported
669 break;
673 void SpellDialog::Impl_Restore(bool bUseSavedSentence)
675 //clear the "ChangeAllList"
676 LinguMgr::GetChangeAllList()->clear();
677 //get a new sentence
678 m_xSentenceED->SetText(OUString());
679 m_xSentenceED->ResetModified();
680 //Resolves: fdo#39348 refill the dialog with the currently spelled sentence
681 SpellContinue_Impl(nullptr, bUseSavedSentence);
682 m_xIgnorePB->set_label(m_sIgnoreOnceST);
685 IMPL_LINK_NOARG(SpellDialog, IgnoreHdl, weld::Button&, void)
687 if (m_sResumeST == m_xIgnorePB->get_label())
689 Impl_Restore(false);
691 else
693 //in case the error has been changed manually it has to be restored,
694 // since the users choice now was to ignore the error
695 m_xSentenceED->RestoreCurrentError();
697 // the word is being ignored
698 SpellContinue_Impl(nullptr, false, true);
702 void SpellDialog::Close()
704 if (IsClosing())
705 return;
707 // We have to call ToggleChildWindow directly; calling SfxDispatcher's
708 // Execute() does not work here when we are in a document with protected
709 // section - in that case, the cursor can move from the editable field to
710 // the protected area, and the slots get disabled because of
711 // SfxDisableFlags::SwOnProtectedCursor (see FN_SPELL_GRAMMAR_DIALOG in .sdi).
712 SfxViewFrame* pViewFrame = SfxViewFrame::Current();
713 if (pViewFrame)
714 pViewFrame->ToggleChildWindow(rParent.GetType());
717 LanguageType SpellDialog::GetSelectedLang_Impl() const
719 LanguageType nLang = m_xLanguageLB->get_active_id();
720 return nLang;
723 IMPL_LINK_NOARG(SpellDialog, LanguageSelectHdl, weld::ComboBox&, void)
725 //If selected language changes, then add->list should be regenerated to
726 //match
727 InitUserDicts();
729 //if currently an error is selected then search for alternatives for
730 //this word and fill the alternatives ListBox accordingly
731 OUString sError = m_xSentenceED->GetErrorText();
732 m_xSuggestionLB->clear();
733 if (!sError.isEmpty())
735 LanguageType eLanguage = m_xLanguageLB->get_active_id();
736 Reference <XSpellAlternatives> xAlt = xSpell->spell( sError, static_cast<sal_uInt16>(eLanguage),
737 Sequence< PropertyValue >() );
738 if( xAlt.is() )
739 m_xSentenceED->SetAlternatives( xAlt );
740 else
742 m_xSentenceED->ChangeMarkedWord( sError, eLanguage );
743 SpellContinue_Impl();
746 m_xSentenceED->AddUndoAction(std::make_unique<SpellUndoAction_Impl>(SPELLUNDO_CHANGE_LANGUAGE, aDialogUndoLink));
748 SpellDialog::UpdateBoxes_Impl(true);
751 void SpellDialog::SetTitle_Impl(LanguageType nLang)
753 OUString sTitle = rParent.HasGrammarChecking() ? m_sTitleSpellingGrammar : m_sTitleSpelling;
754 sTitle = sTitle.replaceFirst( "$LANGUAGE ($LOCATION)", SvtLanguageTable::GetLanguageString(nLang) );
755 m_xDialog->set_title(m_xDialog->strip_mnemonic(sTitle));
758 int SpellDialog::InitUserDicts()
760 const LanguageType nLang = m_xLanguageLB->get_active_id();
762 const Reference< XDictionary > *pDic = nullptr;
764 // get list of dictionaries
765 Reference< XSearchableDictionaryList > xDicList( LinguMgr::GetDictionaryList() );
766 if (xDicList.is())
768 // add active, positive dictionary to dic-list (if not already done).
769 // This is to ensure that there is at least on dictionary to which
770 // words could be added.
771 Reference< XDictionary > xDic( LinguMgr::GetStandardDic() );
772 if (xDic.is())
773 xDic->setActive( true );
775 pImpl->aDics = xDicList->getDictionaries();
778 SvtLinguConfig aCfg;
780 // list suitable dictionaries
781 bool bEnable = false;
782 const sal_Int32 nSize = pImpl->aDics.getLength();
783 pDic = pImpl->aDics.getConstArray();
784 m_xAddToDictMB->clear();
785 sal_uInt16 nItemId = 1; // menu items should be enumerated from 1 and not 0
786 for (sal_Int32 i = 0; i < nSize; ++i)
788 uno::Reference< linguistic2::XDictionary > xDicTmp = pDic[i];
789 if (!xDicTmp.is() || LinguMgr::GetIgnoreAllList() == xDicTmp)
790 continue;
792 uno::Reference< frame::XStorable > xStor( xDicTmp, uno::UNO_QUERY );
793 LanguageType nActLanguage = LanguageTag( xDicTmp->getLocale() ).getLanguageType();
794 if( xDicTmp->isActive()
795 && xDicTmp->getDictionaryType() != linguistic2::DictionaryType_NEGATIVE
796 && (nLang == nActLanguage || LANGUAGE_NONE == nActLanguage )
797 && (!xStor.is() || !xStor->isReadonly()) )
799 bEnable = true;
801 OUString aDictionaryImageUrl;
802 uno::Reference< lang::XServiceInfo > xSvcInfo( xDicTmp, uno::UNO_QUERY );
803 if (xSvcInfo.is())
805 aDictionaryImageUrl = aCfg.GetSpellAndGrammarContextDictionaryImage(
806 xSvcInfo->getImplementationName());
809 m_xAddToDictMB->append_item(OUString::number(nItemId), xDicTmp->getName(), aDictionaryImageUrl);
811 ++nItemId;
814 m_xAddToDictMB->set_sensitive( bEnable );
815 m_xAddToDictPB->set_sensitive( bEnable );
817 int nDicts = nItemId-1;
819 m_xAddToDictMB->set_visible( nDicts > 1 );
820 m_xAddToDictPB->set_visible( nDicts <= 1 );
822 return nDicts;
825 IMPL_LINK_NOARG(SpellDialog, AddToDictClickHdl, weld::Button&, void)
827 AddToDictionaryExecute(OString::number(1));
830 IMPL_LINK(SpellDialog, AddToDictSelectHdl, const OString&, rIdent, void)
832 AddToDictionaryExecute(rIdent);
835 void SpellDialog::AddToDictionaryExecute(const OString& rItemId)
837 auto xGuard(std::make_unique<UndoChangeGroupGuard>(*m_xSentenceED));
839 //GetErrorText() returns the current error even if the text is already
840 //manually changed
841 const OUString aNewWord = m_xSentenceED->GetErrorText();
843 OUString aDicName(m_xAddToDictMB->get_item_label(rItemId));
845 uno::Reference< linguistic2::XDictionary > xDic;
846 uno::Reference< linguistic2::XSearchableDictionaryList > xDicList( LinguMgr::GetDictionaryList() );
847 if (xDicList.is())
848 xDic = xDicList->getDictionaryByName( aDicName );
850 DictionaryError nAddRes = DictionaryError::UNKNOWN;
851 if (xDic.is())
853 nAddRes = AddEntryToDic( xDic, aNewWord, false, OUString() );
854 // save modified user-dictionary if it is persistent
855 uno::Reference< frame::XStorable > xSavDic( xDic, uno::UNO_QUERY );
856 if (xSavDic.is())
857 xSavDic->store();
859 if (nAddRes == DictionaryError::NONE)
861 std::unique_ptr<SpellUndoAction_Impl> pAction(new SpellUndoAction_Impl(
862 SPELLUNDO_CHANGE_ADD_TO_DICTIONARY, aDialogUndoLink));
863 pAction->SetDictionary( xDic );
864 pAction->SetAddedWord( aNewWord );
865 m_xSentenceED->AddUndoAction( std::move(pAction) );
867 // failed because there is already an entry?
868 if (DictionaryError::NONE != nAddRes && xDic->getEntry( aNewWord ).is())
869 nAddRes = DictionaryError::NONE;
871 if (DictionaryError::NONE != nAddRes)
873 SvxDicError(m_xDialog.get(), nAddRes);
874 return; // don't continue
877 // go on
878 SpellContinue_Impl(&xGuard);
881 IMPL_LINK_NOARG(SpellDialog, ModifyHdl, LinkParamNone*, void)
883 m_xSuggestionLB->unselect_all();
884 m_xSuggestionLB->set_sensitive(false);
885 m_xAutoCorrPB->set_sensitive(false);
886 std::unique_ptr<SpellUndoAction_Impl> pSpellAction(new SpellUndoAction_Impl(SPELLUNDO_CHANGE_TEXTENGINE, aDialogUndoLink));
887 if(!m_xChangeAllPB->get_sensitive())
889 m_xChangeAllPB->set_sensitive(true);
890 pSpellAction->SetEnableChangeAllPB();
892 if(!m_xChangePB->get_sensitive())
894 m_xChangePB->set_sensitive(true);
895 pSpellAction->SetEnableChangePB();
897 m_xSentenceED->AddUndoAction(std::move(pSpellAction));
900 IMPL_LINK_NOARG(SpellDialog, CancelHdl, weld::Button&, void)
902 //apply changes and ignored text parts first - if there are any
903 rParent.ApplyChangedSentence(m_xSentenceED->CreateSpellPortions(), false);
904 Close();
907 void SpellDialog::ToplevelFocusChanged()
909 /* #i38338#
910 * FIXME: LoseFocus and GetFocus are signals from vcl that
911 * a window actually got/lost the focus, it never should be
912 * forwarded from another window, that is simply wrong.
913 * FIXME: overriding the virtual methods GetFocus and LoseFocus
914 * in SpellDialogChildWindow by making them pure is at least questionable.
915 * The only sensible thing would be to call the new Method differently,
916 * e.g. DialogGot/LostFocus or so.
918 if (m_xDialog->get_visible() && !bFocusLocked)
920 if (m_xDialog->has_toplevel_focus())
922 //notify the child window of the focus change
923 rParent.GetFocus();
925 else
927 //notify the child window of the focus change
928 rParent.LoseFocus();
933 void SpellDialog::Activate()
935 SfxModelessDialogController::Activate();
936 ToplevelFocusChanged();
939 void SpellDialog::Deactivate()
941 SfxModelessDialogController::Activate();
942 ToplevelFocusChanged();
945 void SpellDialog::InvalidateDialog()
947 if( bFocusLocked )
948 return;
949 m_xIgnorePB->set_label(m_sResumeST);
950 weld::Widget* aDisableArr[] =
952 m_xNotInDictFT.get(),
953 m_xSentenceED->GetDrawingArea(),
954 m_xSuggestionFT.get(),
955 m_xSuggestionLB.get(),
956 m_xLanguageFT.get(),
957 m_xLanguageLB->get_widget(),
958 m_xIgnoreAllPB.get(),
959 m_xIgnoreRulePB.get(),
960 m_xAddToDictMB.get(),
961 m_xAddToDictPB.get(),
962 m_xChangePB.get(),
963 m_xChangeAllPB.get(),
964 m_xAutoCorrPB.get(),
965 m_xUndoPB.get(),
966 nullptr
968 sal_Int16 i = 0;
969 while(aDisableArr[i])
971 aDisableArr[i]->set_sensitive(false);
972 i++;
974 SfxModelessDialogController::Deactivate();
977 bool SpellDialog::GetNextSentence_Impl(std::unique_ptr<UndoChangeGroupGuard>* pGuard, bool bUseSavedSentence, bool bRecheck)
979 bool bRet = false;
980 if(!bUseSavedSentence)
982 //apply changes and ignored text parts
983 rParent.ApplyChangedSentence(m_xSentenceED->CreateSpellPortions(), bRecheck);
985 m_xSentenceED->ResetIgnoreErrorsAt();
986 m_xSentenceED->ResetModified();
987 SpellPortions aSentence = bUseSavedSentence ? m_aSavedSentence : rParent.GetNextWrongSentence( bRecheck );
988 if(!bUseSavedSentence)
989 m_aSavedSentence = aSentence;
990 bool bHasReplaced = false;
991 while(!aSentence.empty())
993 //apply all changes that are already part of the "ChangeAllList"
994 //returns true if the list still contains errors after the changes have been applied
996 if(!ApplyChangeAllList_Impl(aSentence, bHasReplaced))
998 rParent.ApplyChangedSentence(aSentence, bRecheck);
999 aSentence = rParent.GetNextWrongSentence( bRecheck );
1001 else
1002 break;
1005 if(!aSentence.empty())
1007 OUStringBuffer sText;
1008 for (auto const& elem : aSentence)
1010 // hidden text has to be ignored
1011 if(!elem.bIsHidden)
1012 sText.append(elem.sText);
1014 // tdf#132822 fire undo-stack UndoActionEnd to close undo stack because we're about to throw away the paragraph entirely
1015 if (pGuard)
1016 pGuard->reset();
1017 m_xSentenceED->SetText(sText.makeStringAndClear());
1018 sal_Int32 nStartPosition = 0;
1019 sal_Int32 nEndPosition = 0;
1021 for (auto const& elem : aSentence)
1023 // hidden text has to be ignored
1024 if(!elem.bIsHidden)
1026 nEndPosition += elem.sText.getLength();
1027 if(elem.xAlternatives.is())
1029 uno::Reference< container::XNamed > xNamed( elem.xAlternatives, uno::UNO_QUERY );
1030 OUString sServiceName;
1031 if( xNamed.is() )
1032 sServiceName = xNamed->getName();
1033 SpellErrorDescription aDesc( false, elem.xAlternatives->getWord(),
1034 elem.xAlternatives->getLocale(), elem.xAlternatives->getAlternatives(), nullptr);
1035 SfxGrabBagItem aSpellErrorDescription(EE_CHAR_GRABBAG);
1036 aSpellErrorDescription.GetGrabBag()["SpellErrorDescription"] <<= aDesc.toSequence();
1037 m_xSentenceED->SetAttrib(aSpellErrorDescription, nStartPosition, nEndPosition);
1039 else if(elem.bIsGrammarError )
1041 beans::PropertyValues aProperties = elem.aGrammarError.aProperties;
1042 OUString sFullCommentURL;
1043 sal_Int32 i = 0;
1044 while ( sFullCommentURL.isEmpty() && i < aProperties.getLength() )
1046 if ( aProperties[i].Name == "FullCommentURL" )
1048 uno::Any aValue = aProperties[i].Value;
1049 aValue >>= sFullCommentURL;
1051 ++i;
1054 SpellErrorDescription aDesc( true,
1055 elem.sText,
1056 LanguageTag::convertToLocale( elem.eLanguage ),
1057 elem.aGrammarError.aSuggestions,
1058 elem.xGrammarChecker,
1059 &elem.sDialogTitle,
1060 &elem.aGrammarError.aFullComment,
1061 &elem.aGrammarError.aRuleIdentifier,
1062 &sFullCommentURL );
1064 SfxGrabBagItem aSpellErrorDescriptionItem(EE_CHAR_GRABBAG);
1065 aSpellErrorDescriptionItem.GetGrabBag()["SpellErrorDescription"] <<= aDesc.toSequence();
1066 m_xSentenceED->SetAttrib(aSpellErrorDescriptionItem, nStartPosition, nEndPosition);
1069 if (elem.bIsField)
1070 m_xSentenceED->SetAttrib(SvxBackgroundColorItem(COL_LIGHTGRAY, EE_CHAR_BKGCOLOR), nStartPosition, nEndPosition);
1071 m_xSentenceED->SetAttrib(SvxLanguageItem(elem.eLanguage, EE_CHAR_LANGUAGE), nStartPosition, nEndPosition);
1072 nStartPosition = nEndPosition;
1075 //the edit field needs to be modified to apply the change from the ApplyChangeAllList
1076 if(!bHasReplaced)
1077 m_xSentenceED->ClearModifyFlag();
1078 m_xSentenceED->ResetUndo();
1079 m_xUndoPB->set_sensitive(false);
1080 bRet = nStartPosition > 0;
1082 return bRet;
1084 /*-------------------------------------------------------------------------
1085 replace errors that have a replacement in the ChangeAllList
1086 returns false if the result doesn't contain errors after the replacement
1087 -----------------------------------------------------------------------*/
1088 bool SpellDialog::ApplyChangeAllList_Impl(SpellPortions& rSentence, bool &bHasReplaced)
1090 bHasReplaced = false;
1091 bool bRet = true;
1092 Reference<XDictionary> xChangeAll = LinguMgr::GetChangeAllList();
1093 if(!xChangeAll->getCount())
1094 return bRet;
1095 bRet = false;
1096 for (auto & elem : rSentence)
1098 if(elem.xAlternatives.is())
1100 const OUString &rString = elem.sText;
1102 Reference<XDictionaryEntry> xEntry = xChangeAll->getEntry(rString);
1104 if(xEntry.is())
1106 elem.sText = getDotReplacementString(rString, xEntry->getReplacementText());
1107 elem.xAlternatives = nullptr;
1108 bHasReplaced = true;
1110 else
1111 bRet = true;
1113 else if( elem.bIsGrammarError )
1114 bRet = true;
1116 return bRet;
1119 SentenceEditWindow_Impl::SentenceEditWindow_Impl()
1120 : m_pSpellDialog(nullptr)
1121 , m_pToolbar(nullptr)
1122 , m_nErrorStart(0)
1123 , m_nErrorEnd(0)
1124 , m_bIsUndoEditMode(false)
1128 void SentenceEditWindow_Impl::SetDrawingArea(weld::DrawingArea* pDrawingArea)
1130 Size aSize(pDrawingArea->get_approximate_digit_width() * 60,
1131 pDrawingArea->get_text_height() * 6);
1132 pDrawingArea->set_size_request(aSize.Width(), aSize.Height());
1133 WeldEditView::SetDrawingArea(pDrawingArea);
1134 // tdf#132288 don't merge equal adjacent attributes
1135 m_xEditEngine->DisableAttributeExpanding();
1138 SentenceEditWindow_Impl::~SentenceEditWindow_Impl()
1142 namespace
1144 const EECharAttrib* FindCharAttrib(int nPosition, sal_uInt16 nWhich, std::vector<EECharAttrib>& rAttribList)
1146 for (auto it = rAttribList.rbegin(); it != rAttribList.rend(); ++it)
1148 const auto& rTextAtr = *it;
1149 if (rTextAtr.pAttr->Which() != nWhich)
1150 continue;
1151 if (rTextAtr.nStart <= nPosition && rTextAtr.nEnd >= nPosition)
1153 return &rTextAtr;
1157 return nullptr;
1160 void ExtractErrorDescription(const EECharAttrib& rEECharAttrib, SpellErrorDescription& rSpellErrorDescription)
1162 css::uno::Sequence<css::uno::Any> aSequence;
1163 static_cast<const SfxGrabBagItem*>(rEECharAttrib.pAttr)->GetGrabBag().find("SpellErrorDescription")->second >>= aSequence;
1164 rSpellErrorDescription.fromSequence(aSequence);
1168 /*-------------------------------------------------------------------------
1169 The selection before inputting a key may have a range or not
1170 and it may be inside or outside of field or error attributes.
1171 A range may include the attribute partially, completely or together
1172 with surrounding text. It may also contain more than one attribute
1173 or no attribute at all.
1174 Depending on this starting conditions some actions are necessary:
1175 Attempts to delete a field are only allowed if the selection is the same
1176 as the field's selection. Otherwise the field has to be selected and the key
1177 input action has to be skipped.
1178 Input of text at the start of the field requires the field attribute to be
1179 corrected - it is not allowed to grow.
1181 In case of errors the appending of text should grow the error attribute because
1182 that is what the user usually wants to do.
1184 Backspace at the start of the attribute requires to find out if a field ends
1185 directly in front of the cursor position. In case of a field this attribute has to be
1186 selected otherwise the key input method is allowed.
1188 All changes outside of the error attributes switch the dialog mode to a "Undo edit" state that
1189 removes all visible attributes and switches off further attribute checks.
1190 Undo in this restarts the dialog with a current sentence newly presented.
1191 All changes to the sentence are undone including the ones before the "Undo edit state" has been reached
1193 We end up with 9 types of selection
1194 1 (LEFT_NO) - no range, start of attribute - can also be 3 at the same time
1195 2 (INSIDE_NO) - no range, inside of attribute
1196 3 (RIGHT_NO) - no range, end of attribute - can also be 1 at the same time
1197 4 (FULL) - range, same as attribute
1198 5 (INSIDE_YES) - range, inside of the attribute
1199 6 (BRACE)- range, from outside of the attribute to the inside or
1200 including the complete attribute and something outside,
1201 maybe more than one attribute
1202 7 (OUTSIDE_NO) - no range, not at an attribute
1203 8 (OUTSIDE_YES) - range, completely outside of all attributes
1205 What has to be done depending on the attribute type involved
1206 possible actions: UE - Undo edit mode
1207 CO - Continue, no additional action is required
1208 FS - Field has to be completely selected
1209 EX - The attribute has to be expanded to include the added text
1211 1 - backspace delete any other
1212 UE on field FS on error CO on field FS on error CO
1214 2 - on field FS on error C
1215 3 - backspace delete any other
1216 on field FS on error CO UE on field UE on error EX
1218 if 1 and 3 happen to apply both then backspace and other handling is 1 delete is 3
1220 4 - on field UE and on error CO
1221 5 - on field FS and on error CO
1222 6 - on field FS and on error UE
1223 7 - UE
1224 8 - UE
1225 -----------------------------------------------------------------------*/
1226 #define INVALID 0
1227 #define LEFT_NO 1
1228 #define INSIDE_NO 2
1229 #define RIGHT_NO 3
1230 #define FULL 4
1231 #define INSIDE_YES 5
1232 #define BRACE 6
1233 #define OUTSIDE_NO 7
1234 #define OUTSIDE_YES 8
1236 #define ACTION_UNDOEDIT 0
1237 #define ACTION_CONTINUE 1
1238 #define ACTION_SELECTFIELD 2
1239 #define ACTION_EXPAND 3
1241 bool SentenceEditWindow_Impl::KeyInput(const KeyEvent& rKeyEvt)
1243 if (rKeyEvt.GetKeyCode().GetCode() == KEY_TAB)
1244 return false;
1246 bool bConsumed = false;
1248 bool bChange = TextEngine::DoesKeyChangeText( rKeyEvt );
1249 if (bChange && !IsUndoEditMode())
1251 bConsumed = true;
1253 ESelection aCurrentSelection(m_xEditView->GetSelection());
1254 aCurrentSelection.Adjust();
1256 //determine if the selection contains a field
1257 bool bHasFieldLeft = false;
1258 bool bHasErrorLeft = false;
1260 bool bHasRange = aCurrentSelection.HasRange();
1261 sal_uInt8 nSelectionType = 0; // invalid type!
1263 std::vector<EECharAttrib> aAttribList;
1264 m_xEditEngine->GetCharAttribs(0, aAttribList);
1266 auto nCursor = aCurrentSelection.nStartPos;
1267 const EECharAttrib* pBackAttr = FindCharAttrib(nCursor, EE_CHAR_BKGCOLOR, aAttribList);
1268 const EECharAttrib* pErrorAttr = FindCharAttrib(nCursor, EE_CHAR_GRABBAG, aAttribList);
1269 const EECharAttrib* pBackAttrLeft = nullptr;
1270 const EECharAttrib* pErrorAttrLeft = nullptr;
1272 bool bHasField = pBackAttr != nullptr && (bHasRange || pBackAttr->nEnd > nCursor);
1273 bool bHasError = pErrorAttr != nullptr && (bHasRange || pErrorAttr->nEnd > nCursor);
1274 if (bHasRange)
1276 if (pBackAttr &&
1277 pBackAttr->nStart == aCurrentSelection.nStartPos &&
1278 pBackAttr->nEnd == aCurrentSelection.nEndPos)
1280 nSelectionType = FULL;
1282 else if (pErrorAttr &&
1283 pErrorAttr->nStart <= aCurrentSelection.nStartPos &&
1284 pErrorAttr->nEnd >= aCurrentSelection.nEndPos)
1286 nSelectionType = INSIDE_YES;
1288 else
1290 nSelectionType = bHasField||bHasError ? BRACE : OUTSIDE_NO;
1291 while (nCursor < aCurrentSelection.nEndPos)
1293 ++nCursor;
1294 const EECharAttrib* pIntBackAttr = FindCharAttrib(nCursor, EE_CHAR_BKGCOLOR, aAttribList);
1295 const EECharAttrib* pIntErrorAttr = FindCharAttrib(nCursor, EE_CHAR_GRABBAG, aAttribList);
1296 //if any attr has been found then BRACE
1297 if (pIntBackAttr || pIntErrorAttr)
1298 nSelectionType = BRACE;
1299 //the field has to be selected
1300 if (pIntBackAttr && !pBackAttr)
1301 pBackAttr = pIntBackAttr;
1302 bHasField |= pIntBackAttr != nullptr;
1306 else
1308 //no range selection: then 1 2 3 and 8 are possible
1309 const EECharAttrib* pCurAttr = pBackAttr ? pBackAttr : pErrorAttr;
1310 if (pCurAttr)
1312 nSelectionType = pCurAttr->nStart == aCurrentSelection.nStartPos ?
1313 LEFT_NO : pCurAttr->nEnd == aCurrentSelection.nEndPos ? RIGHT_NO : INSIDE_NO;
1315 else
1316 nSelectionType = OUTSIDE_NO;
1318 bHasFieldLeft = pBackAttr && pBackAttr->nEnd == nCursor;
1319 if(bHasFieldLeft)
1321 pBackAttrLeft = pBackAttr;
1322 pBackAttr = nullptr;
1324 bHasErrorLeft = pErrorAttr && pErrorAttr->nEnd == nCursor;
1325 if(bHasErrorLeft)
1327 pErrorAttrLeft = pErrorAttr;
1328 pErrorAttr = nullptr;
1331 //check previous position if this exists
1332 //that is a redundant in the case the attribute found above already is on the left cursor side
1333 //but it's o.k. for two errors/fields side by side
1334 if (nCursor)
1336 --nCursor;
1337 pBackAttrLeft = FindCharAttrib(nCursor, EE_CHAR_BKGCOLOR, aAttribList);
1338 pErrorAttrLeft = FindCharAttrib(nCursor, EE_CHAR_GRABBAG, aAttribList);
1339 bHasFieldLeft = pBackAttrLeft !=nullptr;
1340 bHasErrorLeft = pErrorAttrLeft != nullptr;
1341 ++nCursor;
1344 //Here we have to determine if the error found is the one currently active
1345 bool bIsErrorActive = (pErrorAttr && pErrorAttr->nStart == m_nErrorStart) ||
1346 (pErrorAttrLeft && pErrorAttrLeft->nStart == m_nErrorStart);
1348 SAL_WARN_IF(
1349 nSelectionType == INVALID, "cui.dialogs",
1350 "selection type not set");
1352 const vcl::KeyCode& rKeyCode = rKeyEvt.GetKeyCode();
1353 bool bDelete = rKeyCode.GetCode() == KEY_DELETE;
1354 bool bBackspace = rKeyCode.GetCode() == KEY_BACKSPACE;
1356 sal_Int8 nAction = ACTION_CONTINUE;
1357 switch(nSelectionType)
1359 // 1 - backspace delete any other
1360 // UE on field FS on error CO on field FS on error CO
1361 case LEFT_NO :
1362 if(bBackspace)
1364 nAction = bHasFieldLeft ? ACTION_SELECTFIELD : ACTION_UNDOEDIT;
1365 //to force the use of pBackAttrLeft
1366 pBackAttr = nullptr;
1368 else if(bDelete)
1369 nAction = bHasField ? ACTION_SELECTFIELD : ACTION_CONTINUE;
1370 else
1371 nAction = bHasError && !nCursor ? ACTION_CONTINUE :
1372 bHasError ? ACTION_EXPAND : bHasErrorLeft ? ACTION_CONTINUE : ACTION_UNDOEDIT;
1373 break;
1374 // 2 - on field FS on error C
1375 case INSIDE_NO :
1376 nAction = bHasField ? ACTION_SELECTFIELD :
1377 bIsErrorActive ? ACTION_CONTINUE : ACTION_UNDOEDIT;
1378 break;
1379 // 3 - backspace delete any other
1380 // on field FS on error CO UE on field UE on error EX
1381 case RIGHT_NO :
1382 if(bBackspace)
1383 nAction = bHasFieldLeft ? ACTION_SELECTFIELD : ACTION_CONTINUE;
1384 else if(bDelete)
1385 nAction = bHasFieldLeft && bHasError ? ACTION_CONTINUE : ACTION_UNDOEDIT;
1386 else
1387 nAction = bHasFieldLeft && bHasError ? ACTION_EXPAND :
1388 bHasError ? ACTION_CONTINUE : bHasErrorLeft ? ACTION_EXPAND :ACTION_UNDOEDIT;
1389 break;
1390 // 4 - on field UE and on error CO
1391 case FULL :
1392 nAction = ACTION_UNDOEDIT;
1393 break;
1394 // 5 - on field FS and on error CO
1395 case INSIDE_YES :
1396 nAction = bHasField ? ACTION_SELECTFIELD : ACTION_CONTINUE;
1397 break;
1398 // 6 - on field FS and on error UE
1399 case BRACE :
1400 nAction = bHasField ? ACTION_SELECTFIELD : ACTION_UNDOEDIT;
1401 break;
1402 // 7 - UE
1403 // 8 - UE
1404 case OUTSIDE_NO :
1405 case OUTSIDE_YES:
1406 nAction = ACTION_UNDOEDIT;
1407 break;
1409 //save the current paragraph
1410 sal_Int32 nCurrentLen = m_xEditEngine->GetText().getLength();
1411 if (nAction != ACTION_SELECTFIELD)
1413 m_xEditView->PostKeyEvent(rKeyEvt);
1415 else
1417 const EECharAttrib* pCharAttr = pBackAttr ? pBackAttr : pBackAttrLeft;
1418 if (pCharAttr)
1419 m_xEditView->SetSelection(ESelection(0, pCharAttr->nStart, 0, pCharAttr->nEnd));
1421 if(nAction == ACTION_EXPAND)
1423 DBG_ASSERT(pErrorAttrLeft || pErrorAttr, "where is the error");
1424 //text has been added on the right and only the 'error attribute has to be corrected
1425 if (pErrorAttrLeft)
1427 SpellErrorDescription aSpellErrorDescription;
1428 ExtractErrorDescription(*pErrorAttrLeft, aSpellErrorDescription);
1430 std::unique_ptr<SfxPoolItem> xNewError(pErrorAttrLeft->pAttr->Clone());
1431 sal_Int32 nStart = pErrorAttrLeft->nStart;
1432 sal_Int32 nEnd = pErrorAttrLeft->nEnd + 1;
1433 m_xEditEngine->RemoveAttribs(ESelection(0, nStart, 0, nEnd), false, EE_CHAR_GRABBAG);
1434 SetAttrib(*xNewError, nStart, nEnd);
1435 //only active errors move the mark
1436 if (bIsErrorActive)
1438 bool bGrammar = aSpellErrorDescription.bIsGrammarError;
1439 MoveErrorMarkTo(nStart, nEnd, bGrammar);
1442 //text has been added on the left then the error attribute has to be expanded and the
1443 //field attribute on the right - if any - has to be contracted
1444 else if (pErrorAttr)
1446 SpellErrorDescription aSpellErrorDescription;
1447 ExtractErrorDescription(*pErrorAttr, aSpellErrorDescription);
1449 //determine the change
1450 sal_Int32 nAddedChars = m_xEditEngine->GetText().getLength() - nCurrentLen;
1452 std::unique_ptr<SfxPoolItem> xNewError(pErrorAttr->pAttr->Clone());
1453 sal_Int32 nStart = pErrorAttr->nStart + nAddedChars;
1454 sal_Int32 nEnd = pErrorAttr->nEnd + nAddedChars;
1455 m_xEditEngine->RemoveAttribs(ESelection(0, nStart, 0, nEnd), false, EE_CHAR_GRABBAG);
1456 nStart = pErrorAttr->nStart;
1457 SetAttrib(*xNewError, nStart, nEnd);
1458 //only if the error is active the mark is moved here
1459 if (bIsErrorActive)
1461 bool bGrammar = aSpellErrorDescription.bIsGrammarError;
1462 MoveErrorMarkTo(nStart, nEnd, bGrammar);
1464 xNewError.reset();
1466 if (pBackAttrLeft)
1468 std::unique_ptr<SfxPoolItem> xNewBack(pBackAttrLeft->pAttr->Clone());
1469 sal_Int32 _nStart = pBackAttrLeft->nStart + nAddedChars;
1470 sal_Int32 _nEnd = pBackAttrLeft->nEnd + nAddedChars;
1471 m_xEditEngine->RemoveAttribs(ESelection(0, _nStart, 0, _nEnd), false, EE_CHAR_BKGCOLOR);
1472 _nStart = pBackAttrLeft->nStart;
1473 SetAttrib(*xNewBack, _nStart, _nEnd);
1477 else if(nAction == ACTION_UNDOEDIT)
1479 SetUndoEditMode(true);
1481 //make sure the error positions are correct after text changes
1482 //the old attribute may have been deleted
1483 //all changes inside of the current error leave the error attribute at the current
1484 //start position
1485 if (!IsUndoEditMode() && bIsErrorActive)
1487 const EECharAttrib* pFontColor = FindCharAttrib(nCursor, EE_CHAR_COLOR, aAttribList);
1488 const EECharAttrib* pErrorAttrib = FindCharAttrib(m_nErrorStart, EE_CHAR_GRABBAG, aAttribList);
1489 if (pFontColor && pErrorAttrib)
1491 m_nErrorStart = pFontColor->nStart;
1492 m_nErrorEnd = pFontColor->nEnd;
1493 if (pErrorAttrib->nStart != m_nErrorStart || pErrorAttrib->nEnd != m_nErrorEnd)
1495 std::unique_ptr<SfxPoolItem> xNewError(pErrorAttrib->pAttr->Clone());
1496 assert(pErrorAttr);
1497 m_xEditEngine->RemoveAttribs(ESelection(0, pErrorAttr->nStart, 0, pErrorAttr->nEnd), false, EE_CHAR_GRABBAG);
1498 SetAttrib(*xNewError, m_nErrorStart, m_nErrorEnd);
1502 //this is not a modification anymore
1503 if(nAction != ACTION_SELECTFIELD && !m_bIsUndoEditMode)
1504 CallModifyLink();
1506 else
1507 bConsumed = m_xEditView->PostKeyEvent(rKeyEvt);
1509 return bConsumed;
1512 void SentenceEditWindow_Impl::Init(weld::Toolbar* pToolbar)
1514 m_pToolbar = pToolbar;
1515 m_pToolbar->connect_clicked(LINK(this,SentenceEditWindow_Impl,ToolbarHdl));
1518 IMPL_LINK(SentenceEditWindow_Impl, ToolbarHdl, const OString&, rCurItemId, void)
1520 if (rCurItemId == "paste")
1522 m_xEditView->Paste();
1523 CallModifyLink();
1525 else if (rCurItemId == "insert")
1527 if (Edit::GetGetSpecialCharsFunction())
1529 OUString aChars = Edit::GetGetSpecialCharsFunction()(GetDrawingArea(), m_xEditEngine->GetStandardFont(0));
1530 if (!aChars.isEmpty())
1532 ESelection aCurrentSelection(m_xEditView->GetSelection());
1533 m_xEditEngine->QuickInsertText(aChars, aCurrentSelection);
1534 CallModifyLink();
1540 bool SentenceEditWindow_Impl::MarkNextError( bool bIgnoreCurrentError, const css::uno::Reference<css::linguistic2::XSpellChecker1>& xSpell )
1542 if (bIgnoreCurrentError)
1543 m_aIgnoreErrorsAt.insert( m_nErrorStart );
1545 const sal_Int32 nTextLen = m_xEditEngine->GetTextLen(0);
1547 if (m_nErrorEnd >= nTextLen - 1)
1548 return false;
1549 //if it's not already modified the modified flag has to be reset at the end of the marking
1550 bool bModified = IsModified();
1551 bool bRet = false;
1552 const sal_Int32 nOldErrorStart = m_nErrorStart;
1553 const sal_Int32 nOldErrorEnd = m_nErrorEnd;
1555 //create a cursor behind the end of the last error
1556 //- or at 0 at the start of the sentence
1557 sal_Int32 nCursor(m_nErrorEnd ? m_nErrorEnd + 1 : 0);
1559 //search for SpellErrorDescription
1560 SpellErrorDescription aSpellErrorDescription;
1562 std::vector<EECharAttrib> aAttribList;
1563 m_xEditEngine->GetCharAttribs(0, aAttribList);
1565 //iterate over the text and search for the next error that maybe has
1566 //to be replace by a ChangeAllList replacement
1567 bool bGrammarError = false;
1568 while (nCursor < nTextLen)
1570 const SpellErrorDescription* pSpellErrorDescription = nullptr;
1571 const EECharAttrib* pEECharAttrib = nullptr;
1573 sal_Int32 nMinPos = nTextLen + 1;
1574 for (const auto& rTextAtr : aAttribList)
1576 if (rTextAtr.pAttr->Which() != EE_CHAR_GRABBAG)
1577 continue;
1578 if (rTextAtr.nEnd > nCursor && rTextAtr.nStart < nMinPos)
1580 nMinPos = rTextAtr.nStart;
1581 pEECharAttrib = &rTextAtr;
1585 if (pEECharAttrib)
1587 ExtractErrorDescription(*pEECharAttrib, aSpellErrorDescription);
1589 bGrammarError = aSpellErrorDescription.bIsGrammarError;
1590 m_nErrorStart = pEECharAttrib->nStart;
1591 m_nErrorEnd = pEECharAttrib->nEnd;
1593 pSpellErrorDescription = &aSpellErrorDescription;
1596 nCursor = std::max(nCursor, nMinPos); // move forward if possible
1598 // maybe the error found here is already in the ChangeAllList and has to be replaced
1599 Reference<XDictionary> xChangeAll = LinguMgr::GetChangeAllList();
1600 Reference<XDictionaryEntry> xEntry;
1602 if (xChangeAll->getCount() && pSpellErrorDescription &&
1603 (xEntry = xChangeAll->getEntry( pSpellErrorDescription->sErrorText )).is())
1605 OUString sReplacement(getDotReplacementString(GetErrorText(), xEntry->getReplacementText()));
1607 int nLenChange = ChangeMarkedWord(sReplacement, LanguageTag::convertToLanguageType(pSpellErrorDescription->aLocale));
1609 nCursor += sReplacement.getLength();
1611 if (nLenChange)
1612 m_xEditEngine->GetCharAttribs(0, aAttribList);
1613 // maybe the error found here is already added to the dictionary and has to be ignored
1615 else if(pSpellErrorDescription && !bGrammarError &&
1616 xSpell->isValid(GetErrorText(),
1617 static_cast<sal_uInt16>(LanguageTag::convertToLanguageType( pSpellErrorDescription->aLocale )),
1618 Sequence< PropertyValue >() ))
1620 ++nCursor;
1622 else
1623 break;
1626 //if an attrib has been found search for the end of the error string
1627 if (nCursor < nTextLen)
1629 MoveErrorMarkTo(nCursor, m_nErrorEnd, bGrammarError);
1630 bRet = true;
1631 //add an undo action
1632 std::unique_ptr<SpellUndoAction_Impl> pAction(new SpellUndoAction_Impl(
1633 SPELLUNDO_CHANGE_NEXTERROR, GetSpellDialog()->aDialogUndoLink));
1634 pAction->SetErrorMove(nOldErrorStart, nOldErrorEnd);
1636 if (GetErrorDescription(aSpellErrorDescription, nOldErrorStart))
1638 pAction->SetErrorLanguageSelected(aSpellErrorDescription.aSuggestions.hasElements() &&
1639 LanguageTag(aSpellErrorDescription.aLocale).getLanguageType() == GetSpellDialog()->m_xLanguageLB->get_active_id());
1641 else
1642 pAction->SetErrorLanguageSelected(false);
1644 AddUndoAction(std::move(pAction));
1646 else
1647 m_nErrorStart = m_nErrorEnd = nTextLen;
1648 if( !bModified )
1649 ClearModifyFlag();
1650 SpellDialog* pSpellDialog = GetSpellDialog();
1651 pSpellDialog->m_xIgnorePB->set_sensitive(bRet);
1652 pSpellDialog->m_xIgnoreAllPB->set_sensitive(bRet);
1653 pSpellDialog->m_xAutoCorrPB->set_sensitive(bRet);
1654 pSpellDialog->m_xAddToDictMB->set_sensitive(bRet);
1655 pSpellDialog->m_xAddToDictPB->set_sensitive(bRet);
1656 return bRet;
1659 void SentenceEditWindow_Impl::MoveErrorMarkTo(sal_Int32 nStart, sal_Int32 nEnd, bool bGrammarError)
1661 ESelection aAll(0, 0, 0, EE_TEXTPOS_ALL);
1662 m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_COLOR);
1663 m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT);
1664 m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT_CJK);
1665 m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT_CTL);
1667 SfxItemSet aSet(m_xEditEngine->GetEmptyItemSet());
1668 aSet.Put(SvxColorItem(bGrammarError ? COL_LIGHTBLUE : COL_LIGHTRED, EE_CHAR_COLOR));
1669 aSet.Put(SvxWeightItem(WEIGHT_BOLD, EE_CHAR_WEIGHT));
1670 aSet.Put(SvxWeightItem(WEIGHT_BOLD, EE_CHAR_WEIGHT_CJK));
1671 aSet.Put(SvxWeightItem(WEIGHT_BOLD, EE_CHAR_WEIGHT_CTL));
1673 m_xEditEngine->QuickSetAttribs(aSet, ESelection(0, nStart, 0, nEnd));
1675 // Set the selection so the editview will autoscroll to make this visible
1676 // unless (tdf#133958) the selection already overlaps this range
1677 ESelection aCurrentSelection = m_xEditView->GetSelection();
1678 aCurrentSelection.Adjust();
1679 bool bCurrentSelectionInRange = nStart <= aCurrentSelection.nEndPos && aCurrentSelection.nStartPos <= nEnd;
1680 if (!bCurrentSelectionInRange)
1682 m_xEditView->SetSelection(ESelection(0, nStart));
1685 Invalidate();
1687 m_nErrorStart = nStart;
1688 m_nErrorEnd = nEnd;
1691 int SentenceEditWindow_Impl::ChangeMarkedWord(const OUString& rNewWord, LanguageType eLanguage)
1693 std::vector<EECharAttrib> aAttribList;
1694 m_xEditEngine->GetCharAttribs(0, aAttribList);
1696 //calculate length changes
1697 auto nDiffLen = rNewWord.getLength() - m_nErrorEnd + m_nErrorStart;
1698 //Remove spell error attribute
1699 m_xEditEngine->UndoActionStart(SPELLUNDO_MOVE_ERROREND);
1700 const EECharAttrib* pErrorAttrib = FindCharAttrib(m_nErrorStart, EE_CHAR_GRABBAG, aAttribList);
1701 DBG_ASSERT(pErrorAttrib, "no error attribute found");
1702 bool bSpellErrorDescription = false;
1703 SpellErrorDescription aSpellErrorDescription;
1704 if (pErrorAttrib)
1706 ExtractErrorDescription(*pErrorAttrib, aSpellErrorDescription);
1707 m_xEditEngine->RemoveAttribs(ESelection(0, pErrorAttrib->nStart, 0, pErrorAttrib->nEnd), false, EE_CHAR_GRABBAG);
1708 bSpellErrorDescription = true;
1711 const EECharAttrib* pBackAttrib = FindCharAttrib(m_nErrorStart, EE_CHAR_BKGCOLOR, aAttribList);
1713 ESelection aSel(0, m_nErrorStart, 0, m_nErrorEnd);
1714 m_xEditEngine->QuickInsertText(rNewWord, aSel);
1716 const sal_Int32 nTextLen = m_xEditEngine->GetTextLen(0);
1718 if (nDiffLen)
1719 m_xEditEngine->GetCharAttribs(0, aAttribList);
1721 if (!m_nErrorStart)
1723 //attributes following an error at the start of the text are not moved but expanded from the
1724 //text engine - this is done to keep full-paragraph-attributes
1725 //in the current case that handling is not desired
1726 const EECharAttrib* pLangAttrib = FindCharAttrib(m_nErrorEnd, EE_CHAR_LANGUAGE, aAttribList);
1728 if (pLangAttrib && !pLangAttrib->nStart && pLangAttrib->nEnd == nTextLen)
1730 LanguageType eNewLanguage = static_cast<const SvxLanguageItem*>(pLangAttrib->pAttr)->GetLanguage();
1731 m_xEditEngine->RemoveAttribs(ESelection(0, pLangAttrib->nStart, 0, pLangAttrib->nEnd), false, EE_CHAR_LANGUAGE);
1732 SetAttrib(SvxLanguageItem(eNewLanguage, EE_CHAR_LANGUAGE), m_nErrorEnd + nDiffLen, nTextLen);
1736 // undo expanded attributes!
1737 if (pBackAttrib && pBackAttrib->nStart < m_nErrorStart && pBackAttrib->nEnd == m_nErrorEnd + nDiffLen)
1739 std::unique_ptr<SfxPoolItem> xNewBackground(pBackAttrib->pAttr->Clone());
1740 const sal_Int32 nStart = pBackAttrib->nStart;
1742 m_xEditEngine->RemoveAttribs(ESelection(0, pBackAttrib->nStart, 0, pBackAttrib->nEnd), false, EE_CHAR_BKGCOLOR);
1744 SetAttrib(*xNewBackground, nStart, m_nErrorStart);
1746 m_xEditEngine->SetModified();
1748 //adjust end position
1749 long nEndTemp = m_nErrorEnd;
1750 nEndTemp += nDiffLen;
1751 m_nErrorEnd = static_cast<sal_Int32>(nEndTemp);
1753 std::unique_ptr<SpellUndoAction_Impl> pAction(new SpellUndoAction_Impl(
1754 SPELLUNDO_MOVE_ERROREND, GetSpellDialog()->aDialogUndoLink));
1755 pAction->SetOffset(nDiffLen);
1756 AddUndoAction(std::move(pAction));
1757 if (bSpellErrorDescription)
1759 SfxGrabBagItem aSpellErrorDescriptionItem(EE_CHAR_GRABBAG);
1760 aSpellErrorDescriptionItem.GetGrabBag()["SpellErrorDescription"] <<= aSpellErrorDescription.toSequence();
1761 SetAttrib(aSpellErrorDescriptionItem, m_nErrorStart, m_nErrorEnd);
1763 SetAttrib(SvxLanguageItem(eLanguage, EE_CHAR_LANGUAGE), m_nErrorStart, m_nErrorEnd);
1764 m_xEditEngine->UndoActionEnd();
1766 Invalidate();
1768 return nDiffLen;
1771 OUString SentenceEditWindow_Impl::GetErrorText() const
1773 return m_xEditEngine->GetText(ESelection(0, m_nErrorStart, 0, m_nErrorEnd));
1776 bool SentenceEditWindow_Impl::GetErrorDescription(SpellErrorDescription& rSpellErrorDescription, sal_Int32 nPosition)
1778 std::vector<EECharAttrib> aAttribList;
1779 m_xEditEngine->GetCharAttribs(0, aAttribList);
1781 if (const EECharAttrib* pEECharAttrib = FindCharAttrib(nPosition, EE_CHAR_GRABBAG, aAttribList))
1783 ExtractErrorDescription(*pEECharAttrib, rSpellErrorDescription);
1784 return true;
1787 return false;
1790 bool SentenceEditWindow_Impl::GetAlternatives(SpellErrorDescription& rSpellErrorDescription)
1792 return GetErrorDescription(rSpellErrorDescription, m_nErrorStart);
1795 void SentenceEditWindow_Impl::RestoreCurrentError()
1797 SpellErrorDescription aSpellErrorDescription;
1798 if (GetErrorDescription(aSpellErrorDescription, m_nErrorStart))
1800 if (aSpellErrorDescription.sErrorText != GetErrorText() )
1801 ChangeMarkedWord(aSpellErrorDescription.sErrorText, LanguageTag::convertToLanguageType(aSpellErrorDescription.aLocale));
1805 void SentenceEditWindow_Impl::SetAlternatives( const Reference< XSpellAlternatives>& xAlt )
1807 OUString aWord;
1808 lang::Locale aLocale;
1809 uno::Sequence< OUString > aAlts;
1810 OUString sServiceName;
1811 if (xAlt.is())
1813 aWord = xAlt->getWord();
1814 aLocale = xAlt->getLocale();
1815 aAlts = xAlt->getAlternatives();
1816 uno::Reference< container::XNamed > xNamed( xAlt, uno::UNO_QUERY );
1817 if (xNamed.is())
1818 sServiceName = xNamed->getName();
1820 SpellErrorDescription aDesc( false, aWord, aLocale, aAlts, nullptr);
1821 SfxGrabBagItem aSpellErrorDescription(EE_CHAR_GRABBAG);
1822 aSpellErrorDescription.GetGrabBag()["SpellErrorDescription"] <<= aDesc.toSequence();
1823 SetAttrib(aSpellErrorDescription, m_nErrorStart, m_nErrorEnd);
1826 void SentenceEditWindow_Impl::SetAttrib(const SfxPoolItem& rItem, sal_Int32 nStart, sal_Int32 nEnd)
1828 SfxItemSet aSet(m_xEditEngine->GetEmptyItemSet());
1829 aSet.Put(rItem);
1830 m_xEditEngine->QuickSetAttribs(aSet, ESelection(0, nStart, 0, nEnd));
1831 Invalidate();
1834 void SentenceEditWindow_Impl::SetText( const OUString& rStr )
1836 m_nErrorStart = m_nErrorEnd = 0;
1837 m_xEditEngine->SetText(rStr);
1840 struct LanguagePosition_Impl
1842 sal_Int32 nPosition;
1843 LanguageType eLanguage;
1845 LanguagePosition_Impl(sal_Int32 nPos, LanguageType eLang) :
1846 nPosition(nPos),
1847 eLanguage(eLang)
1850 typedef std::vector<LanguagePosition_Impl> LanguagePositions_Impl;
1852 static void lcl_InsertBreakPosition_Impl(
1853 LanguagePositions_Impl& rBreakPositions, sal_Int32 nInsert, LanguageType eLanguage)
1855 LanguagePositions_Impl::iterator aStart = rBreakPositions.begin();
1856 while(aStart != rBreakPositions.end())
1858 if(aStart->nPosition == nInsert)
1860 //the language of following starts has to overwrite
1861 //the one of previous ends
1862 aStart->eLanguage = eLanguage;
1863 return;
1865 else if(aStart->nPosition > nInsert)
1868 rBreakPositions.insert(aStart, LanguagePosition_Impl(nInsert, eLanguage));
1869 return;
1871 else
1872 ++aStart;
1874 rBreakPositions.emplace_back(nInsert, eLanguage);
1877 /*-------------------------------------------------------------------------
1878 Returns the text in spell portions. Each portion contains text with an
1879 equal language and attribute. The spell alternatives are empty.
1880 -----------------------------------------------------------------------*/
1881 svx::SpellPortions SentenceEditWindow_Impl::CreateSpellPortions() const
1883 svx::SpellPortions aRet;
1885 const sal_Int32 nTextLen = m_xEditEngine->GetTextLen(0);
1887 std::vector<EECharAttrib> aAttribList;
1888 m_xEditEngine->GetCharAttribs(0, aAttribList);
1890 if (nTextLen)
1892 int nCursor(0);
1893 LanguagePositions_Impl aBreakPositions;
1894 const EECharAttrib* pLastLang = nullptr;
1895 const EECharAttrib* pLastError = nullptr;
1896 LanguageType eLang = LANGUAGE_DONTKNOW;
1897 const EECharAttrib* pError = nullptr;
1898 while (nCursor < nTextLen)
1900 const EECharAttrib* pLang = FindCharAttrib(nCursor, EE_CHAR_LANGUAGE, aAttribList);
1901 if(pLang && pLang != pLastLang)
1903 eLang = static_cast<const SvxLanguageItem*>(pLang->pAttr)->GetLanguage();
1904 lcl_InsertBreakPosition_Impl(aBreakPositions, pLang->nStart, eLang);
1905 lcl_InsertBreakPosition_Impl(aBreakPositions, pLang->nEnd, eLang);
1906 pLastLang = pLang;
1908 pError = FindCharAttrib(nCursor, EE_CHAR_GRABBAG, aAttribList);
1909 if (pError && pLastError != pError)
1911 lcl_InsertBreakPosition_Impl(aBreakPositions, pError->nStart, eLang);
1912 lcl_InsertBreakPosition_Impl(aBreakPositions, pError->nEnd, eLang);
1913 pLastError = pError;
1916 ++nCursor;
1919 if (aBreakPositions.empty())
1921 //if all content has been overwritten the attributes may have been removed, too
1922 svx::SpellPortion aPortion1;
1923 aPortion1.eLanguage = GetSpellDialog()->GetSelectedLang_Impl();
1925 aPortion1.sText = m_xEditEngine->GetText(ESelection(0, 0, 0, nTextLen));
1927 aRet.push_back(aPortion1);
1929 else
1931 LanguagePositions_Impl::iterator aStart = aBreakPositions.begin();
1932 //start should always be Null
1933 eLang = aStart->eLanguage;
1934 sal_Int32 nStart = aStart->nPosition;
1935 DBG_ASSERT(!nStart, "invalid start position - language attribute missing?");
1936 ++aStart;
1938 while(aStart != aBreakPositions.end())
1940 svx::SpellPortion aPortion1;
1941 aPortion1.eLanguage = eLang;
1943 aPortion1.sText = m_xEditEngine->GetText(ESelection(0, nStart, 0, aStart->nPosition));
1945 bool bIsIgnoreError = m_aIgnoreErrorsAt.find( nStart ) != m_aIgnoreErrorsAt.end();
1946 if( bIsIgnoreError )
1948 aPortion1.bIgnoreThisError = true;
1950 aRet.push_back(aPortion1);
1951 nStart = aStart->nPosition;
1952 eLang = aStart->eLanguage;
1953 ++aStart;
1957 // quick partly fix of #i71318. Correct fix needs to patch the EditEngine itself...
1958 // this one will only prevent text from disappearing. It may to not have the
1959 // correct language and will probably not spell checked...
1960 const sal_uInt32 nPara = m_xEditEngine->GetParagraphCount();
1961 if (nPara > 1)
1963 OUStringBuffer aLeftOverText;
1964 for (sal_uInt32 i = 1; i < nPara; ++i)
1966 aLeftOverText.append("\x0a"); // the manual line break...
1967 aLeftOverText.append(m_xEditEngine->GetText(i));
1969 if (pError)
1970 { // we need to add a new portion containing the left-over text
1971 svx::SpellPortion aPortion2;
1972 aPortion2.eLanguage = eLang;
1973 aPortion2.sText = aLeftOverText.makeStringAndClear();
1974 aRet.push_back( aPortion2 );
1976 else
1977 { // we just need to append the left-over text to the last portion (which had no errors)
1978 aRet[ aRet.size() - 1 ].sText += aLeftOverText;
1983 return aRet;
1986 void SentenceEditWindow_Impl::Undo()
1988 SfxUndoManager& rUndoMgr = m_xEditEngine->GetUndoManager();
1989 DBG_ASSERT(GetUndoActionCount(), "no undo actions available" );
1990 if(!GetUndoActionCount())
1991 return;
1992 bool bSaveUndoEdit = IsUndoEditMode();
1993 SpellUndoAction_Impl* pUndoAction;
1994 //if the undo edit mode is active then undo all changes until the UNDO_EDIT_MODE action has been found
1997 pUndoAction = static_cast<SpellUndoAction_Impl*>(rUndoMgr.GetUndoAction());
1998 rUndoMgr.Undo();
1999 }while(bSaveUndoEdit && SPELLUNDO_UNDO_EDIT_MODE != pUndoAction->GetId() && GetUndoActionCount());
2001 if(bSaveUndoEdit || SPELLUNDO_CHANGE_GROUP == pUndoAction->GetId())
2002 GetSpellDialog()->UpdateBoxes_Impl();
2005 void SentenceEditWindow_Impl::ResetUndo()
2007 SfxUndoManager& rUndo = m_xEditEngine->GetUndoManager();
2008 rUndo.Clear();
2011 void SentenceEditWindow_Impl::AddUndoAction( std::unique_ptr<SfxUndoAction> pAction )
2013 SfxUndoManager& rUndoMgr = m_xEditEngine->GetUndoManager();
2014 rUndoMgr.AddUndoAction(std::move(pAction));
2015 GetSpellDialog()->m_xUndoPB->set_sensitive(true);
2018 size_t SentenceEditWindow_Impl::GetUndoActionCount() const
2020 return m_xEditEngine->GetUndoManager().GetUndoActionCount();
2023 void SentenceEditWindow_Impl::UndoActionStart( sal_uInt16 nId )
2025 m_xEditEngine->UndoActionStart(nId);
2028 void SentenceEditWindow_Impl::UndoActionEnd()
2030 m_xEditEngine->UndoActionEnd();
2033 void SentenceEditWindow_Impl::MoveErrorEnd(long nOffset)
2035 // Shouldn't we always add the real signed value instead???
2036 if(nOffset > 0)
2037 m_nErrorEnd = m_nErrorEnd - static_cast<sal_Int32>(nOffset);
2038 else
2039 m_nErrorEnd = m_nErrorEnd - static_cast<sal_Int32>(-nOffset);
2043 void SentenceEditWindow_Impl::SetUndoEditMode(bool bSet)
2045 DBG_ASSERT(!bSet || m_bIsUndoEditMode != bSet, "SetUndoEditMode with equal values?");
2046 m_bIsUndoEditMode = bSet;
2047 //disable all buttons except the Change
2048 SpellDialog* pSpellDialog = GetSpellDialog();
2049 weld::Widget* aControls[] =
2051 pSpellDialog->m_xChangeAllPB.get(),
2052 pSpellDialog->m_xExplainFT.get(),
2053 pSpellDialog->m_xIgnoreAllPB.get(),
2054 pSpellDialog->m_xIgnoreRulePB.get(),
2055 pSpellDialog->m_xIgnorePB.get(),
2056 pSpellDialog->m_xSuggestionLB.get(),
2057 pSpellDialog->m_xSuggestionFT.get(),
2058 pSpellDialog->m_xLanguageFT.get(),
2059 pSpellDialog->m_xLanguageLB->get_widget(),
2060 pSpellDialog->m_xAddToDictMB.get(),
2061 pSpellDialog->m_xAddToDictPB.get(),
2062 pSpellDialog->m_xAutoCorrPB.get(),
2063 nullptr
2065 sal_Int32 nIdx = 0;
2068 aControls[nIdx]->set_sensitive(false);
2070 while(aControls[++nIdx]);
2072 //remove error marks
2073 ESelection aAll(0, 0, 0, EE_TEXTPOS_ALL);
2074 m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_COLOR);
2075 m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT);
2076 m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT_CJK);
2077 m_xEditEngine->RemoveAttribs(aAll, false, EE_CHAR_WEIGHT_CTL);
2078 Invalidate();
2080 //put the appropriate action on the Undo-stack
2081 AddUndoAction( std::make_unique<SpellUndoAction_Impl>(
2082 SPELLUNDO_UNDO_EDIT_MODE, GetSpellDialog()->aDialogUndoLink) );
2083 pSpellDialog->m_xChangePB->set_sensitive(true);
2086 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */