1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
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/editund2.hxx>
30 #include <editeng/colritem.hxx>
31 #include <editeng/eeitem.hxx>
32 #include <editeng/langitem.hxx>
33 #include <editeng/splwrap.hxx>
34 #include <editeng/unolingu.hxx>
35 #include <editeng/wghtitem.hxx>
36 #include <linguistic/misc.hxx>
37 #include <com/sun/star/lang/XServiceInfo.hpp>
38 #include <com/sun/star/frame/XStorable.hpp>
39 #include <com/sun/star/linguistic2/XDictionary.hpp>
40 #include <com/sun/star/linguistic2/XSpellAlternatives.hpp>
41 #include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
42 #include <com/sun/star/linguistic2/XSpellChecker1.hpp>
43 #include <sfx2/app.hxx>
44 #include <rtl/ustrbuf.hxx>
45 #include <vcl/specialchars.hxx>
46 #include <vcl/event.hxx>
47 #include <vcl/svapp.hxx>
48 #include <vcl/texteng.hxx>
49 #include <vcl/weld.hxx>
50 #include <svx/SpellDialogChildWindow.hxx>
51 #include <SpellDialog.hxx>
52 #include <optlingu.hxx>
53 #include <treeopt.hxx>
54 #include <svtools/colorcfg.hxx>
55 #include <svtools/langtab.hxx>
56 #include <sal/log.hxx>
57 #include <i18nlangtag/languagetag.hxx>
58 #include <comphelper/lok.hxx>
60 using namespace ::com::sun::star
;
61 using namespace ::com::sun::star::uno
;
62 using namespace ::com::sun::star::beans
;
63 using namespace ::com::sun::star::linguistic2
;
64 using namespace linguistic
;
67 // struct SpellDialog_Impl ---------------------------------------------
69 struct SpellDialog_Impl
71 Sequence
< Reference
< XDictionary
> > aDics
;
75 #define SPELLUNDO_START 200
77 #define SPELLUNDO_CHANGE_LANGUAGE (SPELLUNDO_START + 1)
78 #define SPELLUNDO_CHANGE_TEXTENGINE (SPELLUNDO_START + 2)
79 #define SPELLUNDO_CHANGE_NEXTERROR (SPELLUNDO_START + 3)
80 #define SPELLUNDO_CHANGE_ADD_TO_DICTIONARY (SPELLUNDO_START + 4)
81 #define SPELLUNDO_CHANGE_GROUP (SPELLUNDO_START + 5) //undo list
82 #define SPELLUNDO_MOVE_ERROREND (SPELLUNDO_START + 6)
83 #define SPELLUNDO_UNDO_EDIT_MODE (SPELLUNDO_START + 7)
84 #define SPELLUNDO_ADD_IGNORE_RULE (SPELLUNDO_START + 8)
87 class SpellUndoAction_Impl
: public SfxUndoAction
90 const Link
<SpellUndoAction_Impl
&,void>& m_rActionLink
;
91 //undo of button enabling
92 bool m_bEnableChangePB
;
93 bool m_bEnableChangeAllPB
;
94 //undo of MarkNextError - used in change and change all, ignore and ignore all
95 tools::Long m_nOldErrorStart
;
96 tools::Long m_nOldErrorEnd
;
97 bool m_bIsErrorLanguageSelected
;
98 //undo of AddToDictionary
99 Reference
<XDictionary
> m_xDictionary
;
100 OUString m_sAddedWord
;
101 //move end of error - ::ChangeMarkedWord()
102 tools::Long m_nOffset
;
105 SpellUndoAction_Impl(sal_uInt16 nId
, const Link
<SpellUndoAction_Impl
&,void>& rActionLink
) :
107 m_rActionLink( rActionLink
),
108 m_bEnableChangePB(false),
109 m_bEnableChangeAllPB(false),
110 m_nOldErrorStart(-1),
112 m_bIsErrorLanguageSelected(false),
116 virtual void Undo() override
;
117 sal_uInt16
GetId() const;
119 void SetEnableChangePB(){m_bEnableChangePB
= true;}
120 bool IsEnableChangePB() const {return m_bEnableChangePB
;}
122 void SetEnableChangeAllPB(){m_bEnableChangeAllPB
= true;}
123 bool IsEnableChangeAllPB() const {return m_bEnableChangeAllPB
;}
125 void SetErrorMove(tools::Long nOldStart
, tools::Long nOldEnd
)
127 m_nOldErrorStart
= nOldStart
;
128 m_nOldErrorEnd
= nOldEnd
;
130 tools::Long
GetOldErrorStart() const { return m_nOldErrorStart
;}
131 tools::Long
GetOldErrorEnd() const { return m_nOldErrorEnd
;}
133 void SetErrorLanguageSelected(bool bSet
){ m_bIsErrorLanguageSelected
= bSet
;}
134 bool IsErrorLanguageSelected() const {return m_bIsErrorLanguageSelected
;}
136 void SetDictionary(const Reference
<XDictionary
>& xDict
) { m_xDictionary
= xDict
; }
137 const Reference
<XDictionary
>& GetDictionary() const { return m_xDictionary
; }
138 void SetAddedWord(const OUString
& rWord
) {m_sAddedWord
= rWord
;}
139 const OUString
& GetAddedWord() const { return m_sAddedWord
;}
141 void SetOffset(tools::Long nSet
) {m_nOffset
= nSet
;}
142 tools::Long
GetOffset() const {return m_nOffset
;}
145 using namespace ::svx
;
147 void SpellUndoAction_Impl::Undo()
149 m_rActionLink
.Call(*this);
153 sal_uInt16
SpellUndoAction_Impl::GetId()const
158 // class SvxSpellCheckDialog ---------------------------------------------
160 SpellDialog::SpellDialog(SpellDialogChildWindow
* pChildWindow
,
161 weld::Window
* pParent
, SfxBindings
* _pBindings
)
162 : SfxModelessDialogController (_pBindings
, pChildWindow
,
163 pParent
, u
"cui/ui/spellingdialog.ui"_ustr
, u
"SpellingDialog"_ustr
)
164 , aDialogUndoLink(LINK (this, SpellDialog
, DialogUndoHdl
))
165 , m_pInitHdlEvent(nullptr)
167 , rParent(*pChildWindow
)
168 , pImpl( new SpellDialog_Impl
)
169 , m_xAltTitle(m_xBuilder
->weld_label(u
"alttitleft"_ustr
))
170 , m_xResumeFT(m_xBuilder
->weld_label(u
"resumeft"_ustr
))
171 , m_xNoSuggestionsFT(m_xBuilder
->weld_label(u
"nosuggestionsft"_ustr
))
172 , m_xLanguageFT(m_xBuilder
->weld_label(u
"languageft"_ustr
))
173 , m_xLanguageLB(new SvxLanguageBox(m_xBuilder
->weld_combo_box(u
"languagelb"_ustr
)))
174 , m_xExplainFT(m_xBuilder
->weld_label(u
"explain"_ustr
))
175 , m_xExplainLink(m_xBuilder
->weld_link_button(u
"explainlink"_ustr
))
176 , m_xNotInDictFT(m_xBuilder
->weld_label(u
"notindictft"_ustr
))
177 , m_xSentenceED(new SentenceEditWindow_Impl(m_xBuilder
->weld_scrolled_window(u
"scrolledwindow"_ustr
, true)))
178 , m_xSuggestionFT(m_xBuilder
->weld_label(u
"suggestionsft"_ustr
))
179 , m_xSuggestionLB(m_xBuilder
->weld_tree_view(u
"suggestionslb"_ustr
))
180 , m_xIgnorePB(m_xBuilder
->weld_button(u
"ignore"_ustr
))
181 , m_xIgnoreAllPB(m_xBuilder
->weld_button(u
"ignoreall"_ustr
))
182 , m_xIgnoreRulePB(m_xBuilder
->weld_button(u
"ignorerule"_ustr
))
183 , m_xAddToDictPB(m_xBuilder
->weld_button(u
"add"_ustr
))
184 , m_xAddToDictMB(m_xBuilder
->weld_menu_button(u
"addmb"_ustr
))
185 , m_xChangePB(m_xBuilder
->weld_button(u
"change"_ustr
))
186 , m_xChangeAllPB(m_xBuilder
->weld_button(u
"changeall"_ustr
))
187 , m_xAutoCorrPB(m_xBuilder
->weld_button(u
"autocorrect"_ustr
))
188 , m_xCheckGrammarCB(m_xBuilder
->weld_check_button(u
"checkgrammar"_ustr
))
189 , m_xOptionsPB(m_xBuilder
->weld_button(u
"options"_ustr
))
190 , m_xUndoPB(m_xBuilder
->weld_button(u
"undo"_ustr
))
191 , m_xClosePB(m_xBuilder
->weld_button(u
"close"_ustr
))
192 , m_xToolbar(m_xBuilder
->weld_toolbar(u
"toolbar"_ustr
))
193 , m_xSentenceEDWeld(new weld::CustomWeld(*m_xBuilder
, u
"errorsentence"_ustr
, *m_xSentenceED
))
195 m_xSentenceED
->SetSpellDialog(this);
196 m_xSentenceED
->Init(m_xToolbar
.get());
198 m_sTitleSpellingGrammar
= m_xDialog
->get_title();
199 m_sTitleSpelling
= m_xAltTitle
->get_label();
201 // fdo#68794 set initial title for cases where no text has been processed
202 // yet to show its language attributes
203 OUString sTitle
= rParent
.HasGrammarChecking() ? m_sTitleSpellingGrammar
: m_sTitleSpelling
;
204 m_xDialog
->set_title(m_xDialog
->strip_mnemonic(sTitle
.replaceFirst("$LANGUAGE ($LOCATION)", "")));
206 m_sResumeST
= m_xResumeFT
->get_label();
207 m_sNoSuggestionsST
= m_xNoSuggestionsFT
->strip_mnemonic(m_xNoSuggestionsFT
->get_label());
209 Size
aEdSize(m_xSuggestionLB
->get_approximate_digit_width() * 60,
210 m_xSuggestionLB
->get_height_rows(6));
211 m_xSuggestionLB
->set_size_request(aEdSize
.Width(), -1);
212 m_sIgnoreOnceST
= m_xIgnorePB
->get_label();
213 m_xAddToDictMB
->set_help_id(m_xAddToDictPB
->get_help_id());
214 xSpell
= LinguMgr::GetSpellChecker();
218 // disable controls if service is missing
219 m_xDialog
->set_sensitive(xSpell
.is());
221 //InitHdl wants to use virtual methods, so it
222 //can't be called during the ctor, so init
223 //it on next event cycle post-ctor
224 m_pInitHdlEvent
= Application::PostUserEvent(LINK(this, SpellDialog
, InitHdl
));
227 SpellDialog::~SpellDialog()
231 m_xOptionsDlg
->response(RET_CANCEL
);
232 m_xOptionsDlg
.reset();
236 Application::RemoveUserEvent(m_pInitHdlEvent
);
239 // save possibly modified user-dictionaries
240 Reference
< XSearchableDictionaryList
> xDicList( LinguMgr::GetDictionaryList() );
242 SaveDictionaries( xDicList
);
248 void SpellDialog::Init_Impl()
250 // initialize handler
251 m_xClosePB
->connect_clicked(LINK( this, SpellDialog
, CancelHdl
) );
252 m_xChangePB
->connect_clicked(LINK( this, SpellDialog
, ChangeHdl
) );
253 m_xChangeAllPB
->connect_clicked(LINK( this, SpellDialog
, ChangeAllHdl
) );
254 m_xIgnorePB
->connect_clicked(LINK( this, SpellDialog
, IgnoreHdl
) );
255 m_xIgnoreAllPB
->connect_clicked(LINK( this, SpellDialog
, IgnoreAllHdl
) );
256 m_xIgnoreRulePB
->connect_clicked(LINK( this, SpellDialog
, IgnoreAllHdl
) );
257 m_xUndoPB
->connect_clicked(LINK( this, SpellDialog
, UndoHdl
) );
259 m_xAutoCorrPB
->connect_clicked( LINK( this, SpellDialog
, ExtClickHdl
) );
260 m_xCheckGrammarCB
->connect_toggled( LINK( this, SpellDialog
, CheckGrammarHdl
));
261 m_xOptionsPB
->connect_clicked( LINK( this, SpellDialog
, ExtClickHdl
) );
263 m_xSuggestionLB
->connect_row_activated( LINK( this, SpellDialog
, DoubleClickChangeHdl
) );
265 m_xSentenceED
->SetModifyHdl(LINK ( this, SpellDialog
, ModifyHdl
) );
267 m_xAddToDictMB
->connect_selected(LINK ( this, SpellDialog
, AddToDictSelectHdl
) );
268 m_xAddToDictPB
->connect_clicked(LINK ( this, SpellDialog
, AddToDictClickHdl
) );
270 m_xLanguageLB
->connect_changed(LINK( this, SpellDialog
, LanguageSelectHdl
) );
272 // initialize language ListBox
273 m_xLanguageLB
->SetLanguageList(SvxLanguageListFlags::SPELL_USED
, false, false, true);
275 m_xSentenceED
->ClearModifyFlag();
276 LinguMgr::GetChangeAllList()->clear();
279 void SpellDialog::UpdateBoxes_Impl(bool bCallFromSelectHdl
)
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
);
298 if (bSpellErrorDescription
&& !aSpellErrorDescription
.sDialogTitle
.isEmpty())
299 m_xDialog
->set_title(m_xDialog
->strip_mnemonic(aSpellErrorDescription
.sDialogTitle
));
301 SetTitle_Impl( nAltLanguage
);
302 if( !bCallFromSelectHdl
)
303 m_xLanguageLB
->set_active_id( nAltLanguage
);
304 int nDicts
= InitUserDicts();
306 // enter alternatives
307 for (auto& aTmp
: aNewWords
)
309 if (m_xSuggestionLB
->find_text(aTmp
) == -1)
310 m_xSuggestionLB
->append_text(aTmp
);
312 m_xSuggestionLB
->set_sensitive(aNewWords
.hasElements());
313 if (aNewWords
.hasElements())
314 m_xSuggestionLB
->select(0);
316 m_xSuggestionLB
->append_text(m_sNoSuggestionsST
);
317 m_xAutoCorrPB
->set_sensitive(aNewWords
.hasElements());
318 m_xSuggestionFT
->set_sensitive(aNewWords
.hasElements());
319 m_xChangePB
->set_sensitive(aNewWords
.hasElements());
320 m_xChangeAllPB
->set_sensitive(aNewWords
.hasElements());
321 bool bShowChangeAll
= !bIsGrammarError
;
322 m_xChangeAllPB
->set_visible( bShowChangeAll
);
323 m_xExplainFT
->set_visible( !bShowChangeAll
);
324 m_xLanguageLB
->set_sensitive( bShowChangeAll
);
325 m_xIgnoreAllPB
->set_visible( bShowChangeAll
);
327 m_xAddToDictMB
->set_visible( bShowChangeAll
&& nDicts
> 1 && !comphelper::LibreOfficeKit::isActive());
328 m_xAddToDictPB
->set_visible( bShowChangeAll
&& nDicts
<= 1 && !comphelper::LibreOfficeKit::isActive());
329 m_xIgnoreRulePB
->set_visible( !bShowChangeAll
);
330 m_xIgnoreRulePB
->set_sensitive(bSpellErrorDescription
&& !aSpellErrorDescription
.sRuleId
.isEmpty());
331 m_xAutoCorrPB
->set_visible( bShowChangeAll
&& rParent
.HasAutoCorrection() );
333 bool bOldShowGrammar
= m_xCheckGrammarCB
->get_visible();
334 bool bOldShowExplain
= m_xExplainLink
->get_visible();
336 m_xCheckGrammarCB
->set_visible(rParent
.HasGrammarChecking());
337 m_xExplainLink
->set_visible(!m_xExplainLink
->get_uri().isEmpty());
338 if (m_xExplainFT
->get_label().isEmpty())
340 m_xExplainFT
->hide();
341 m_xExplainLink
->hide();
344 if (bOldShowExplain
!= m_xExplainLink
->get_visible() || bOldShowGrammar
!= m_xCheckGrammarCB
->get_visible())
345 m_xDialog
->resize_to_request();
348 void SpellDialog::SpellContinue_Impl(std::unique_ptr
<UndoChangeGroupGuard
>* pGuard
, bool bUseSavedSentence
, bool bIgnoreCurrentError
)
350 //initially or after the last error of a sentence MarkNextError will fail
351 //then GetNextSentence() has to be called followed again by MarkNextError()
352 //MarkNextError is not initially called if the UndoEdit mode is active
353 bool bNextSentence
= false;
359 if(!((!m_xSentenceED
->IsUndoEditMode() && m_xSentenceED
->MarkNextError( bIgnoreCurrentError
, xSpell
)) ||
360 ( bNextSentence
= GetNextSentence_Impl(pGuard
, bUseSavedSentence
, m_xSentenceED
->IsUndoEditMode()) && m_xSentenceED
->MarkNextError( false, xSpell
))))
363 SpellErrorDescription aSpellErrorDescription
;
364 bool bSpellErrorDescription
= m_xSentenceED
->GetAlternatives(aSpellErrorDescription
);
365 if (bSpellErrorDescription
)
368 weld::Widget
* aControls
[] =
370 m_xNotInDictFT
.get(),
371 m_xSentenceED
->GetDrawingArea(),
374 for (weld::Widget
* pWidget
: aControls
)
375 pWidget
->set_sensitive(true);
379 //remove undo if a new sentence is active
380 m_xSentenceED
->ResetUndo();
381 m_xUndoPB
->set_sensitive(false);
384 /* Initialize, asynchronous to prevent virtual calls
387 IMPL_LINK_NOARG( SpellDialog
, InitHdl
, void*, void)
389 m_pInitHdlEvent
= nullptr;
391 //show or hide AutoCorrect depending on the modules abilities
392 m_xAutoCorrPB
->set_visible(rParent
.HasAutoCorrection());
393 SpellContinue_Impl(nullptr);
394 m_xSentenceED
->ResetUndo();
395 m_xUndoPB
->set_sensitive(false);
397 // get current language
400 // fill dictionary PopupMenu
403 LockFocusChanges(true);
404 if(m_xSentenceED
->IsEnabled())
405 m_xSentenceED
->GrabFocus();
406 else if( m_xChangePB
->get_sensitive() )
407 m_xChangePB
->grab_focus();
408 else if( m_xIgnorePB
->get_sensitive() )
409 m_xIgnorePB
->grab_focus();
410 else if( m_xClosePB
->get_sensitive() )
411 m_xClosePB
->grab_focus();
412 LockFocusChanges(false);
413 //show grammar CheckBox depending on the modules abilities
414 m_xCheckGrammarCB
->set_active(rParent
.IsGrammarChecking());
418 IMPL_LINK( SpellDialog
, ExtClickHdl
, weld::Button
&, rBtn
, void )
420 if (m_xOptionsPB
.get() == &rBtn
)
421 StartSpellOptDlg_Impl();
422 else if (m_xAutoCorrPB
.get() == &rBtn
)
424 //get the currently selected wrong word
425 OUString sCurrentErrorText
= m_xSentenceED
->GetErrorText();
426 //get the wrong word from the XSpellAlternative
427 SpellErrorDescription aSpellErrorDescription
;
428 bool bSpellErrorDescription
= m_xSentenceED
->GetAlternatives(aSpellErrorDescription
);
429 if (bSpellErrorDescription
)
431 OUString
sWrong(aSpellErrorDescription
.sErrorText
);
432 //if the word has not been edited in the MultiLineEdit then
433 //the current suggestion should be used
434 //if it's not the 'no suggestions' entry
435 if(sWrong
== sCurrentErrorText
&&
436 m_xSuggestionLB
->get_sensitive() && m_xSuggestionLB
->get_selected_index() != -1 &&
437 m_sNoSuggestionsST
!= m_xSuggestionLB
->get_selected_text())
439 sCurrentErrorText
= m_xSuggestionLB
->get_selected_text();
441 if(sWrong
!= sCurrentErrorText
)
443 SvxPrepareAutoCorrect( sWrong
, sCurrentErrorText
);
444 LanguageType eLang
= GetSelectedLang_Impl();
445 rParent
.AddAutoCorrection( sWrong
, sCurrentErrorText
, eLang
);
446 //correct the word immediately
447 ChangeHdl(*m_xAutoCorrPB
);
453 IMPL_LINK_NOARG(SpellDialog
, CheckGrammarHdl
, weld::Toggleable
&, void)
455 rParent
.SetGrammarChecking(m_xCheckGrammarCB
->get_active());
459 void SpellDialog::StartSpellOptDlg_Impl()
461 auto xSet
= std::make_shared
<SfxItemSetFixed
<SID_AUTOSPELL_CHECK
,SID_AUTOSPELL_CHECK
>>( SfxGetpApp()->GetPool() );
462 m_xOptionsDlg
= std::make_shared
<SfxSingleTabDialogController
>(
463 m_xDialog
.get(), xSet
.get(), "content", "cui/ui/spelloptionsdialog.ui", "SpellOptionsDialog");
465 std::unique_ptr
<SfxTabPage
> xPage
= SvxLinguTabPage::Create(m_xOptionsDlg
->get_content_area(), m_xOptionsDlg
.get(), xSet
.get());
466 static_cast<SvxLinguTabPage
*>(xPage
.get())->HideGroups( GROUP_MODULES
);
467 m_xOptionsDlg
->SetTabPage(std::move(xPage
));
468 weld::GenericDialogController::runAsync(m_xOptionsDlg
, [this, xSet
=std::move(xSet
)] (sal_uInt32 nResult
) {
469 if (RET_OK
== nResult
)
472 const SfxItemSet
* pOutSet
= m_xOptionsDlg
->GetOutputItemSet();
474 OfaTreeOptionsDialog::ApplyLanguageOptions(*pOutSet
);
481 OUString
getDotReplacementString(const OUString
&rErrorText
, const OUString
&rSuggestedReplacement
)
483 OUString aString
= rErrorText
;
485 //dots are sometimes part of the spelled word but they are not necessarily part of the replacement
486 bool bDot
= aString
.endsWith(".");
488 aString
= rSuggestedReplacement
;
490 if(bDot
&& (aString
.isEmpty() || !aString
.endsWith(".")))
497 OUString
SpellDialog::getReplacementString() const
499 OUString sOrigString
= m_xSentenceED
->GetErrorText();
501 OUString
sReplacement(sOrigString
);
503 if(m_xSuggestionLB
->get_sensitive() &&
504 m_xSuggestionLB
->get_selected_index() != -1 &&
505 m_sNoSuggestionsST
!= m_xSuggestionLB
->get_selected_text())
506 sReplacement
= m_xSuggestionLB
->get_selected_text();
508 return getDotReplacementString(sOrigString
, sReplacement
);
511 IMPL_LINK_NOARG(SpellDialog
, DoubleClickChangeHdl
, weld::TreeView
&, bool)
513 ChangeHdl(*m_xChangePB
);
517 /* tdf#132822 start an undo group in ctor and close it in the dtor. This can
518 then be passed to SpellContinue_Impl which can delete it in advance of its
519 natural scope to force closing the undo group if SpellContinue_Impl needs to
520 fetch a new paragraph and discard all undo information which can only be
521 done properly if there are no open undo groups */
522 class UndoChangeGroupGuard
525 SentenceEditWindow_Impl
& m_rSentenceED
;
527 UndoChangeGroupGuard(SentenceEditWindow_Impl
& rSentenceED
)
528 : m_rSentenceED(rSentenceED
)
530 m_rSentenceED
.UndoActionStart(SPELLUNDO_CHANGE_GROUP
);
532 ~UndoChangeGroupGuard()
534 m_rSentenceED
.UndoActionEnd();
538 IMPL_LINK_NOARG(SpellDialog
, ChangeHdl
, weld::Button
&, void)
540 if (m_xSentenceED
->IsUndoEditMode())
542 SpellContinue_Impl();
546 auto xGuard(std::make_unique
<UndoChangeGroupGuard
>(*m_xSentenceED
));
547 OUString aString
= getReplacementString();
548 m_xSentenceED
->ChangeMarkedWord(aString
, GetSelectedLang_Impl());
549 SpellContinue_Impl(&xGuard
);
551 if(!m_xChangePB
->get_sensitive())
552 m_xIgnorePB
->grab_focus();
555 IMPL_LINK_NOARG(SpellDialog
, ChangeAllHdl
, weld::Button
&, void)
557 auto xGuard(std::make_unique
<UndoChangeGroupGuard
>(*m_xSentenceED
));
558 OUString aString
= getReplacementString();
559 LanguageType eLang
= GetSelectedLang_Impl();
561 // add new word to ChangeAll list
562 OUString
aOldWord( m_xSentenceED
->GetErrorText() );
563 SvxPrepareAutoCorrect( aOldWord
, aString
);
564 Reference
<XDictionary
> aXDictionary
= LinguMgr::GetChangeAllList();
565 DictionaryError nAdded
= AddEntryToDic( aXDictionary
,
569 if(nAdded
== DictionaryError::NONE
)
571 std::unique_ptr
<SpellUndoAction_Impl
> pAction(new SpellUndoAction_Impl(
572 SPELLUNDO_CHANGE_ADD_TO_DICTIONARY
, aDialogUndoLink
));
573 pAction
->SetDictionary(aXDictionary
);
574 pAction
->SetAddedWord(aOldWord
);
575 m_xSentenceED
->AddUndoAction(std::move(pAction
));
578 m_xSentenceED
->ChangeMarkedWord(aString
, eLang
);
579 SpellContinue_Impl(&xGuard
);
582 IMPL_LINK( SpellDialog
, IgnoreAllHdl
, weld::Button
&, rButton
, void )
584 auto xGuard(std::make_unique
<UndoChangeGroupGuard
>(*m_xSentenceED
));
585 // add word to IgnoreAll list
586 Reference
< XDictionary
> aXDictionary
= LinguMgr::GetIgnoreAllList();
587 //in case the error has been changed manually it has to be restored
588 m_xSentenceED
->RestoreCurrentError();
589 if (&rButton
== m_xIgnoreRulePB
.get())
591 SpellErrorDescription aSpellErrorDescription
;
592 bool bSpellErrorDescription
= m_xSentenceED
->GetAlternatives(aSpellErrorDescription
);
595 if( bSpellErrorDescription
&& aSpellErrorDescription
.xGrammarChecker
.is() )
597 aSpellErrorDescription
.xGrammarChecker
->ignoreRule(aSpellErrorDescription
.sRuleId
,
598 aSpellErrorDescription
.aLocale
);
599 // refresh the layout (workaround to launch a dictionary event)
600 aXDictionary
->setActive(false);
601 aXDictionary
->setActive(true);
604 catch( const uno::Exception
& )
610 OUString
sErrorText(m_xSentenceED
->GetErrorText());
611 DictionaryError nAdded
= AddEntryToDic( aXDictionary
,
614 if (nAdded
== DictionaryError::NONE
)
616 std::unique_ptr
<SpellUndoAction_Impl
> pAction(new SpellUndoAction_Impl(
617 SPELLUNDO_CHANGE_ADD_TO_DICTIONARY
, aDialogUndoLink
));
618 pAction
->SetDictionary(aXDictionary
);
619 pAction
->SetAddedWord(sErrorText
);
620 m_xSentenceED
->AddUndoAction(std::move(pAction
));
624 SpellContinue_Impl(&xGuard
);
627 IMPL_LINK_NOARG(SpellDialog
, UndoHdl
, weld::Button
&, void)
629 m_xSentenceED
->Undo();
630 if(!m_xSentenceED
->GetUndoActionCount())
631 m_xUndoPB
->set_sensitive(false);
635 IMPL_LINK( SpellDialog
, DialogUndoHdl
, SpellUndoAction_Impl
&, rAction
, void )
637 switch(rAction
.GetId())
639 case SPELLUNDO_CHANGE_TEXTENGINE
:
641 if(rAction
.IsEnableChangePB())
642 m_xChangePB
->set_sensitive(false);
643 if(rAction
.IsEnableChangeAllPB())
644 m_xChangeAllPB
->set_sensitive(false);
647 case SPELLUNDO_CHANGE_NEXTERROR
:
649 m_xSentenceED
->MoveErrorMarkTo(static_cast<sal_Int32
>(rAction
.GetOldErrorStart()),
650 static_cast<sal_Int32
>(rAction
.GetOldErrorEnd()),
652 if(rAction
.IsErrorLanguageSelected())
658 case SPELLUNDO_CHANGE_ADD_TO_DICTIONARY
:
660 if(rAction
.GetDictionary().is())
661 rAction
.GetDictionary()->remove(rAction
.GetAddedWord());
664 case SPELLUNDO_MOVE_ERROREND
:
666 if(rAction
.GetOffset() != 0)
667 m_xSentenceED
->MoveErrorEnd(rAction
.GetOffset());
670 case SPELLUNDO_UNDO_EDIT_MODE
:
672 //refill the dialog with the currently spelled sentence - throw away all changes
673 SpellContinue_Impl(nullptr, true);
676 case SPELLUNDO_ADD_IGNORE_RULE
:
677 //undo of ignored rules is not supported
682 void SpellDialog::Impl_Restore(bool bUseSavedSentence
)
684 //clear the "ChangeAllList"
685 LinguMgr::GetChangeAllList()->clear();
687 m_xSentenceED
->SetText(OUString());
688 m_xSentenceED
->ResetModified();
689 //Resolves: fdo#39348 refill the dialog with the currently spelled sentence
690 SpellContinue_Impl(nullptr, bUseSavedSentence
);
691 m_xIgnorePB
->set_label(m_sIgnoreOnceST
);
694 IMPL_LINK_NOARG(SpellDialog
, IgnoreHdl
, weld::Button
&, void)
696 if (m_sResumeST
== m_xIgnorePB
->get_label())
702 //in case the error has been changed manually it has to be restored,
703 // since the users choice now was to ignore the error
704 m_xSentenceED
->RestoreCurrentError();
706 // the word is being ignored
707 SpellContinue_Impl(nullptr, false, true);
711 void SpellDialog::Close()
716 // We have to call ToggleChildWindow directly; calling SfxDispatcher's
717 // Execute() does not work here when we are in a document with protected
718 // section - in that case, the cursor can move from the editable field to
719 // the protected area, and the slots get disabled because of
720 // SfxDisableFlags::SwOnProtectedCursor (see FN_SPELL_GRAMMAR_DIALOG in .sdi).
721 if (SfxViewFrame
* pViewFrame
= SfxViewFrame::Current())
722 pViewFrame
->ToggleChildWindow(rParent
.GetType());
725 LanguageType
SpellDialog::GetSelectedLang_Impl() const
727 LanguageType nLang
= m_xLanguageLB
->get_active_id();
731 IMPL_LINK_NOARG(SpellDialog
, LanguageSelectHdl
, weld::ComboBox
&, void)
733 //If selected language changes, then add->list should be regenerated to
737 //if currently an error is selected then search for alternatives for
738 //this word and fill the alternatives ListBox accordingly
739 OUString sError
= m_xSentenceED
->GetErrorText();
740 m_xSuggestionLB
->clear();
741 if (!sError
.isEmpty())
743 LanguageType eLanguage
= m_xLanguageLB
->get_active_id();
744 Reference
<XSpellAlternatives
> xAlt
= xSpell
->spell( sError
, static_cast<sal_uInt16
>(eLanguage
),
745 Sequence
< PropertyValue
>() );
747 m_xSentenceED
->SetAlternatives( xAlt
);
750 m_xSentenceED
->ChangeMarkedWord( sError
, eLanguage
);
751 SpellContinue_Impl();
754 m_xSentenceED
->AddUndoAction(std::make_unique
<SpellUndoAction_Impl
>(SPELLUNDO_CHANGE_LANGUAGE
, aDialogUndoLink
));
756 SpellDialog::UpdateBoxes_Impl(true);
759 void SpellDialog::SetTitle_Impl(LanguageType nLang
)
761 OUString sTitle
= rParent
.HasGrammarChecking() ? m_sTitleSpellingGrammar
: m_sTitleSpelling
;
762 sTitle
= sTitle
.replaceFirst( "$LANGUAGE ($LOCATION)", SvtLanguageTable::GetLanguageString(nLang
) );
763 m_xDialog
->set_title(m_xDialog
->strip_mnemonic(sTitle
));
766 int SpellDialog::InitUserDicts()
768 const LanguageType nLang
= m_xLanguageLB
->get_active_id();
770 // get list of dictionaries
771 Reference
< XSearchableDictionaryList
> xDicList( LinguMgr::GetDictionaryList() );
774 // add active, positive dictionary to dic-list (if not already done).
775 // This is to ensure that there is at least on dictionary to which
776 // words could be added.
777 Reference
< XDictionary
> xDic( LinguMgr::GetStandardDic() );
779 xDic
->setActive( true );
781 pImpl
->aDics
= xDicList
->getDictionaries();
786 // list suitable dictionaries
787 bool bEnable
= false;
788 m_xAddToDictMB
->clear();
789 sal_uInt16 nItemId
= 1; // menu items should be enumerated from 1 and not 0
790 for (auto& xDicTmp
: pImpl
->aDics
)
792 if (!xDicTmp
.is() || LinguMgr::GetIgnoreAllList() == xDicTmp
)
795 uno::Reference
< frame::XStorable
> xStor( xDicTmp
, uno::UNO_QUERY
);
796 LanguageType nActLanguage
= LanguageTag( xDicTmp
->getLocale() ).getLanguageType();
797 if( xDicTmp
->isActive()
798 && xDicTmp
->getDictionaryType() != linguistic2::DictionaryType_NEGATIVE
799 && (nLang
== nActLanguage
|| LANGUAGE_NONE
== nActLanguage
)
800 && (!xStor
.is() || !xStor
->isReadonly()) )
804 OUString aDictionaryImageUrl
;
805 uno::Reference
< lang::XServiceInfo
> xSvcInfo( xDicTmp
, uno::UNO_QUERY
);
808 aDictionaryImageUrl
= aCfg
.GetSpellAndGrammarContextDictionaryImage(
809 xSvcInfo
->getImplementationName());
812 m_xAddToDictMB
->append_item(OUString::number(nItemId
), xDicTmp
->getName(), aDictionaryImageUrl
);
817 m_xAddToDictMB
->set_sensitive( bEnable
);
818 m_xAddToDictPB
->set_sensitive( bEnable
);
820 int nDicts
= nItemId
-1;
822 m_xAddToDictMB
->set_visible(nDicts
> 1 && !comphelper::LibreOfficeKit::isActive());
823 m_xAddToDictPB
->set_visible(nDicts
<= 1 && !comphelper::LibreOfficeKit::isActive());
828 IMPL_LINK_NOARG(SpellDialog
, AddToDictClickHdl
, weld::Button
&, void)
830 AddToDictionaryExecute(OUString::number(1));
833 IMPL_LINK(SpellDialog
, AddToDictSelectHdl
, const OUString
&, rIdent
, void)
835 AddToDictionaryExecute(rIdent
);
838 void SpellDialog::AddToDictionaryExecute(const OUString
& rItemId
)
840 auto xGuard(std::make_unique
<UndoChangeGroupGuard
>(*m_xSentenceED
));
842 //GetErrorText() returns the current error even if the text is already
844 const OUString aNewWord
= m_xSentenceED
->GetErrorText();
846 OUString
aDicName(m_xAddToDictMB
->get_item_label(rItemId
));
848 uno::Reference
< linguistic2::XDictionary
> xDic
;
849 uno::Reference
< linguistic2::XSearchableDictionaryList
> xDicList( LinguMgr::GetDictionaryList() );
851 xDic
= xDicList
->getDictionaryByName( aDicName
);
853 DictionaryError nAddRes
= DictionaryError::UNKNOWN
;
856 nAddRes
= AddEntryToDic( xDic
, aNewWord
, false, OUString() );
857 // save modified user-dictionary if it is persistent
858 uno::Reference
< frame::XStorable
> xSavDic( xDic
, uno::UNO_QUERY
);
862 if (nAddRes
== DictionaryError::NONE
)
864 std::unique_ptr
<SpellUndoAction_Impl
> pAction(new SpellUndoAction_Impl(
865 SPELLUNDO_CHANGE_ADD_TO_DICTIONARY
, aDialogUndoLink
));
866 pAction
->SetDictionary( xDic
);
867 pAction
->SetAddedWord( aNewWord
);
868 m_xSentenceED
->AddUndoAction( std::move(pAction
) );
870 // failed because there is already an entry?
871 if (DictionaryError::NONE
!= nAddRes
&& xDic
->getEntry( aNewWord
).is())
872 nAddRes
= DictionaryError::NONE
;
874 if (DictionaryError::NONE
!= nAddRes
)
876 SvxDicError(m_xDialog
.get(), nAddRes
);
877 return; // don't continue
881 SpellContinue_Impl(&xGuard
);
884 IMPL_LINK_NOARG(SpellDialog
, ModifyHdl
, LinkParamNone
*, void)
886 m_xSuggestionLB
->unselect_all();
887 m_xSuggestionLB
->set_sensitive(false);
888 m_xAutoCorrPB
->set_sensitive(false);
889 std::unique_ptr
<SpellUndoAction_Impl
> pSpellAction(new SpellUndoAction_Impl(SPELLUNDO_CHANGE_TEXTENGINE
, aDialogUndoLink
));
890 if(!m_xChangeAllPB
->get_sensitive())
892 m_xChangeAllPB
->set_sensitive(true);
893 pSpellAction
->SetEnableChangeAllPB();
895 if(!m_xChangePB
->get_sensitive())
897 m_xChangePB
->set_sensitive(true);
898 pSpellAction
->SetEnableChangePB();
900 m_xSentenceED
->AddUndoAction(std::move(pSpellAction
));
903 IMPL_LINK_NOARG(SpellDialog
, CancelHdl
, weld::Button
&, void)
905 //apply changes and ignored text parts first - if there are any
906 if (m_xSentenceED
->IsModified())
908 rParent
.ApplyChangedSentence(m_xSentenceED
->CreateSpellPortions(), false);
913 void SpellDialog::ToplevelFocusChanged()
916 * FIXME: LoseFocus and GetFocus are signals from vcl that
917 * a window actually got/lost the focus, it never should be
918 * forwarded from another window, that is simply wrong.
919 * FIXME: overriding the virtual methods GetFocus and LoseFocus
920 * in SpellDialogChildWindow by making them pure is at least questionable.
921 * The only sensible thing would be to call the new Method differently,
922 * e.g. DialogGot/LostFocus or so.
924 if (!m_xDialog
->get_visible() || bFocusLocked
)
927 if (m_xDialog
->has_toplevel_focus())
929 //notify the child window of the focus change
934 //notify the child window of the focus change
939 void SpellDialog::Activate()
941 SfxModelessDialogController::Activate();
942 ToplevelFocusChanged();
945 void SpellDialog::Deactivate()
947 SfxModelessDialogController::Deactivate();
948 ToplevelFocusChanged();
951 void SpellDialog::InvalidateDialog()
955 m_xIgnorePB
->set_label(m_sResumeST
);
956 weld::Widget
* aDisableArr
[] =
958 m_xNotInDictFT
.get(),
959 m_xSentenceED
->GetDrawingArea(),
960 m_xSuggestionFT
.get(),
961 m_xSuggestionLB
.get(),
963 m_xLanguageLB
->get_widget(),
964 m_xIgnoreAllPB
.get(),
965 m_xIgnoreRulePB
.get(),
966 m_xAddToDictMB
.get(),
967 m_xAddToDictPB
.get(),
969 m_xChangeAllPB
.get(),
973 for (weld::Widget
* pWidget
: aDisableArr
)
974 pWidget
->set_sensitive(false);
976 SfxModelessDialogController::Deactivate();
979 bool SpellDialog::GetNextSentence_Impl(std::unique_ptr
<UndoChangeGroupGuard
>* pGuard
, bool bUseSavedSentence
, bool bRecheck
)
982 if(!bUseSavedSentence
)
984 //apply changes and ignored text parts
985 rParent
.ApplyChangedSentence(m_xSentenceED
->CreateSpellPortions(), bRecheck
);
987 m_xSentenceED
->ResetIgnoreErrorsAt();
988 m_xSentenceED
->ResetModified();
989 SpellPortions aSentence
= bUseSavedSentence
? m_aSavedSentence
: rParent
.GetNextWrongSentence( bRecheck
);
990 if(!bUseSavedSentence
)
991 m_aSavedSentence
= aSentence
;
992 bool bHasReplaced
= false;
993 while(!aSentence
.empty())
995 //apply all changes that are already part of the "ChangeAllList"
996 //returns true if the list still contains errors after the changes have been applied
998 if(!ApplyChangeAllList_Impl(aSentence
, bHasReplaced
))
1000 rParent
.ApplyChangedSentence(aSentence
, bRecheck
);
1001 aSentence
= rParent
.GetNextWrongSentence( bRecheck
);
1007 if(!aSentence
.empty())
1009 OUStringBuffer sText
;
1010 for (auto const& elem
: aSentence
)
1012 // hidden text has to be ignored
1014 sText
.append(elem
.sText
);
1016 // tdf#132822 fire undo-stack UndoActionEnd to close undo stack because we're about to throw away the paragraph entirely
1019 m_xSentenceED
->SetText(sText
.makeStringAndClear());
1020 sal_Int32 nStartPosition
= 0;
1021 sal_Int32 nEndPosition
= 0;
1023 for (auto const& elem
: aSentence
)
1025 // hidden text has to be ignored
1028 nEndPosition
+= elem
.sText
.getLength();
1029 if(elem
.xAlternatives
.is())
1031 SpellErrorDescription
aDesc( false, elem
.xAlternatives
->getWord(),
1032 elem
.xAlternatives
->getLocale(), elem
.xAlternatives
->getAlternatives(), nullptr);
1033 SfxGrabBagItem
aSpellErrorDescription(EE_CHAR_GRABBAG
,
1034 std::map
<OUString
, css::uno::Any
>{{ u
"SpellErrorDescription"_ustr
, uno::Any(aDesc
.toSequence()) }});
1035 m_xSentenceED
->SetAttrib(aSpellErrorDescription
, nStartPosition
, nEndPosition
);
1037 else if(elem
.bIsGrammarError
)
1039 beans::PropertyValues aProperties
= elem
.aGrammarError
.aProperties
;
1040 OUString sFullCommentURL
;
1042 while ( sFullCommentURL
.isEmpty() && i
< aProperties
.getLength() )
1044 if ( aProperties
[i
].Name
== "FullCommentURL" )
1046 uno::Any aValue
= aProperties
[i
].Value
;
1047 aValue
>>= sFullCommentURL
;
1052 SpellErrorDescription
aDesc( true,
1054 LanguageTag::convertToLocale( elem
.eLanguage
),
1055 elem
.aGrammarError
.aSuggestions
,
1056 elem
.xGrammarChecker
,
1058 &elem
.aGrammarError
.aFullComment
,
1059 &elem
.aGrammarError
.aRuleIdentifier
,
1062 SfxGrabBagItem
aSpellErrorDescriptionItem(EE_CHAR_GRABBAG
,
1063 std::map
<OUString
, css::uno::Any
>{{u
"SpellErrorDescription"_ustr
, uno::Any(aDesc
.toSequence())}});
1064 m_xSentenceED
->SetAttrib(aSpellErrorDescriptionItem
, nStartPosition
, nEndPosition
);
1068 m_xSentenceED
->SetAttrib(SvxColorItem(COL_LIGHTGRAY
, EE_CHAR_BKGCOLOR
), nStartPosition
, nEndPosition
);
1069 m_xSentenceED
->SetAttrib(SvxLanguageItem(elem
.eLanguage
, EE_CHAR_LANGUAGE
), nStartPosition
, nEndPosition
);
1070 nStartPosition
= nEndPosition
;
1073 //the edit field needs to be modified to apply the change from the ApplyChangeAllList
1075 m_xSentenceED
->ClearModifyFlag();
1076 m_xSentenceED
->ResetUndo();
1077 m_xUndoPB
->set_sensitive(false);
1078 bRet
= nStartPosition
> 0;
1082 /*-------------------------------------------------------------------------
1083 replace errors that have a replacement in the ChangeAllList
1084 returns false if the result doesn't contain errors after the replacement
1085 -----------------------------------------------------------------------*/
1086 bool SpellDialog::ApplyChangeAllList_Impl(SpellPortions
& rSentence
, bool &bHasReplaced
)
1088 bHasReplaced
= false;
1090 Reference
<XDictionary
> xChangeAll
= LinguMgr::GetChangeAllList();
1091 if(!xChangeAll
->getCount())
1094 for (auto & elem
: rSentence
)
1096 if(elem
.xAlternatives
.is())
1098 const OUString
&rString
= elem
.sText
;
1100 Reference
<XDictionaryEntry
> xEntry
= xChangeAll
->getEntry(rString
);
1104 elem
.sText
= getDotReplacementString(rString
, xEntry
->getReplacementText());
1105 elem
.xAlternatives
= nullptr;
1106 bHasReplaced
= true;
1111 else if( elem
.bIsGrammarError
)
1117 SentenceEditWindow_Impl::SentenceEditWindow_Impl(std::unique_ptr
<weld::ScrolledWindow
> xScrolledWindow
)
1118 : m_xScrolledWindow(std::move(xScrolledWindow
))
1119 , m_pSpellDialog(nullptr)
1120 , m_pToolbar(nullptr)
1123 , m_bIsUndoEditMode(false)
1125 m_xScrolledWindow
->connect_vadjustment_changed(LINK(this, SentenceEditWindow_Impl
, ScrollHdl
));
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();
1137 m_xEditEngine
->SetStatusEventHdl(LINK(this, SentenceEditWindow_Impl
, EditStatusHdl
));
1139 SetDocumentColor(pDrawingArea
);
1142 void SentenceEditWindow_Impl::SetDocumentColor(weld::DrawingArea
* pDrawingArea
)
1144 if (!pDrawingArea
|| !m_xEditView
|| !m_xEditEngine
)
1146 // tdf#142631 use document background color in this widget
1147 Color aBgColor
= svtools::ColorConfig().GetColorValue(svtools::DOCCOLOR
).nColor
;
1148 OutputDevice
& rDevice
= pDrawingArea
->get_ref_device();
1149 rDevice
.SetBackground(aBgColor
);
1150 m_xEditView
->SetBackgroundColor(aBgColor
);
1151 m_xEditEngine
->SetBackgroundColor(aBgColor
);
1154 void SentenceEditWindow_Impl::StyleUpdated()
1156 SetDocumentColor(GetDrawingArea());
1157 WeldEditView::StyleUpdated();
1160 IMPL_LINK_NOARG(SentenceEditWindow_Impl
, EditStatusHdl
, EditStatus
&, void)
1162 SetScrollBarRange();
1166 IMPL_LINK_NOARG(SentenceEditWindow_Impl
, ScrollHdl
, weld::ScrolledWindow
&, void)
1171 void SentenceEditWindow_Impl::DoScroll()
1175 auto currentDocPos
= m_xEditView
->GetVisArea().Top();
1176 auto nDiff
= currentDocPos
- m_xScrolledWindow
->vadjustment_get_value();
1177 // we expect SetScrollBarRange callback to be triggered by Scroll
1178 // to set where we ended up
1179 m_xEditView
->Scroll(0, nDiff
);
1183 void SentenceEditWindow_Impl::EditViewScrollStateChange()
1185 // editengine height has changed or editview scroll pos has changed
1186 SetScrollBarRange();
1189 void SentenceEditWindow_Impl::SetScrollBarRange()
1191 EditEngine
*pEditEngine
= GetEditEngine();
1194 if (!m_xScrolledWindow
)
1196 EditView
* pEditView
= GetEditView();
1200 int nVUpper
= pEditEngine
->GetTextHeight();
1201 int nVCurrentDocPos
= pEditView
->GetVisArea().Top();
1202 const Size
aOut(pEditView
->GetOutputArea().GetSize());
1203 int nVStepIncrement
= aOut
.Height() * 2 / 10;
1204 int nVPageIncrement
= aOut
.Height() * 8 / 10;
1205 int nVPageSize
= aOut
.Height();
1207 /* limit the page size to below nUpper because gtk's gtk_scrolled_window_start_deceleration has
1210 lower = gtk_adjustment_get_lower
1211 upper = gtk_adjustment_get_upper - gtk_adjustment_get_page_size
1213 and requires that upper > lower or the deceleration animation never ends
1215 nVPageSize
= std::min(nVPageSize
, nVUpper
);
1217 m_xScrolledWindow
->vadjustment_configure(nVCurrentDocPos
, 0, nVUpper
,
1218 nVStepIncrement
, nVPageIncrement
, nVPageSize
);
1219 m_xScrolledWindow
->set_vpolicy(nVUpper
> nVPageSize
? VclPolicyType::ALWAYS
: VclPolicyType::NEVER
);
1222 SentenceEditWindow_Impl::~SentenceEditWindow_Impl()
1228 const EECharAttrib
* FindCharAttrib(int nPosition
, sal_uInt16 nWhich
, std::vector
<EECharAttrib
>& rAttribList
)
1230 for (auto it
= rAttribList
.rbegin(); it
!= rAttribList
.rend(); ++it
)
1232 const auto& rTextAtr
= *it
;
1233 if (rTextAtr
.pAttr
->Which() != nWhich
)
1235 if (rTextAtr
.nStart
<= nPosition
&& rTextAtr
.nEnd
>= nPosition
)
1244 void ExtractErrorDescription(const EECharAttrib
& rEECharAttrib
, SpellErrorDescription
& rSpellErrorDescription
)
1246 css::uno::Sequence
<css::uno::Any
> aSequence
;
1247 const auto& rGrabBag
= static_cast<const SfxGrabBagItem
*>(rEECharAttrib
.pAttr
)->GetGrabBag();
1248 const auto iter
= rGrabBag
.find(u
"SpellErrorDescription"_ustr
);
1249 assert(iter
!= rGrabBag
.end());
1250 iter
->second
>>= aSequence
;
1251 rSpellErrorDescription
.fromSequence(aSequence
);
1255 /*-------------------------------------------------------------------------
1256 The selection before inputting a key may have a range or not
1257 and it may be inside or outside of field or error attributes.
1258 A range may include the attribute partially, completely or together
1259 with surrounding text. It may also contain more than one attribute
1260 or no attribute at all.
1261 Depending on this starting conditions some actions are necessary:
1262 Attempts to delete a field are only allowed if the selection is the same
1263 as the field's selection. Otherwise the field has to be selected and the key
1264 input action has to be skipped.
1265 Input of text at the start of the field requires the field attribute to be
1266 corrected - it is not allowed to grow.
1268 In case of errors the appending of text should grow the error attribute because
1269 that is what the user usually wants to do.
1271 Backspace at the start of the attribute requires to find out if a field ends
1272 directly in front of the cursor position. In case of a field this attribute has to be
1273 selected otherwise the key input method is allowed.
1275 All changes outside of the error attributes switch the dialog mode to a "Undo edit" state that
1276 removes all visible attributes and switches off further attribute checks.
1277 Undo in this restarts the dialog with a current sentence newly presented.
1278 All changes to the sentence are undone including the ones before the "Undo edit state" has been reached
1280 We end up with 9 types of selection
1281 1 (LEFT_NO) - no range, start of attribute - can also be 3 at the same time
1282 2 (INSIDE_NO) - no range, inside of attribute
1283 3 (RIGHT_NO) - no range, end of attribute - can also be 1 at the same time
1284 4 (FULL) - range, same as attribute
1285 5 (INSIDE_YES) - range, inside of the attribute
1286 6 (BRACE)- range, from outside of the attribute to the inside or
1287 including the complete attribute and something outside,
1288 maybe more than one attribute
1289 7 (OUTSIDE_NO) - no range, not at an attribute
1290 8 (OUTSIDE_YES) - range, completely outside of all attributes
1292 What has to be done depending on the attribute type involved
1293 possible actions: UE - Undo edit mode
1294 CO - Continue, no additional action is required
1295 FS - Field has to be completely selected
1296 EX - The attribute has to be expanded to include the added text
1298 1 - backspace delete any other
1299 UE on field FS on error CO on field FS on error CO
1301 2 - on field FS on error C
1302 3 - backspace delete any other
1303 on field FS on error CO UE on field UE on error EX
1305 if 1 and 3 happen to apply both then backspace and other handling is 1 delete is 3
1307 4 - on field UE and on error CO
1308 5 - on field FS and on error CO
1309 6 - on field FS and on error UE
1312 -----------------------------------------------------------------------*/
1318 #define INSIDE_YES 5
1320 #define OUTSIDE_NO 7
1321 #define OUTSIDE_YES 8
1323 #define ACTION_UNDOEDIT 0
1324 #define ACTION_CONTINUE 1
1325 #define ACTION_SELECTFIELD 2
1326 #define ACTION_EXPAND 3
1328 bool SentenceEditWindow_Impl::KeyInput(const KeyEvent
& rKeyEvt
)
1330 if (rKeyEvt
.GetKeyCode().GetCode() == KEY_TAB
)
1333 bool bConsumed
= false;
1335 bool bChange
= TextEngine::DoesKeyChangeText( rKeyEvt
);
1336 if (bChange
&& !IsUndoEditMode())
1340 ESelection
aCurrentSelection(m_xEditView
->GetSelection());
1341 aCurrentSelection
.Adjust();
1343 //determine if the selection contains a field
1344 bool bHasFieldLeft
= false;
1345 bool bHasErrorLeft
= false;
1347 bool bHasRange
= aCurrentSelection
.HasRange();
1348 sal_uInt8 nSelectionType
= 0; // invalid type!
1350 std::vector
<EECharAttrib
> aAttribList
;
1351 m_xEditEngine
->GetCharAttribs(0, aAttribList
);
1353 auto nCursor
= aCurrentSelection
.start
.nIndex
;
1354 const EECharAttrib
* pBackAttr
= FindCharAttrib(nCursor
, EE_CHAR_BKGCOLOR
, aAttribList
);
1355 const EECharAttrib
* pErrorAttr
= FindCharAttrib(nCursor
, EE_CHAR_GRABBAG
, aAttribList
);
1356 const EECharAttrib
* pBackAttrLeft
= nullptr;
1357 const EECharAttrib
* pErrorAttrLeft
= nullptr;
1359 bool bHasField
= pBackAttr
!= nullptr && (bHasRange
|| pBackAttr
->nEnd
> nCursor
);
1360 bool bHasError
= pErrorAttr
!= nullptr && (bHasRange
|| pErrorAttr
->nEnd
> nCursor
);
1364 pBackAttr
->nStart
== aCurrentSelection
.start
.nIndex
&&
1365 pBackAttr
->nEnd
== aCurrentSelection
.end
.nIndex
)
1367 nSelectionType
= FULL
;
1369 else if (pErrorAttr
&&
1370 pErrorAttr
->nStart
<= aCurrentSelection
.start
.nIndex
&&
1371 pErrorAttr
->nEnd
>= aCurrentSelection
.end
.nIndex
)
1373 nSelectionType
= INSIDE_YES
;
1377 nSelectionType
= bHasField
||bHasError
? BRACE
: OUTSIDE_NO
;
1378 while (nCursor
< aCurrentSelection
.end
.nIndex
)
1381 const EECharAttrib
* pIntBackAttr
= FindCharAttrib(nCursor
, EE_CHAR_BKGCOLOR
, aAttribList
);
1382 const EECharAttrib
* pIntErrorAttr
= FindCharAttrib(nCursor
, EE_CHAR_GRABBAG
, aAttribList
);
1383 //if any attr has been found then BRACE
1384 if (pIntBackAttr
|| pIntErrorAttr
)
1385 nSelectionType
= BRACE
;
1386 //the field has to be selected
1387 if (pIntBackAttr
&& !pBackAttr
)
1388 pBackAttr
= pIntBackAttr
;
1389 bHasField
|= pIntBackAttr
!= nullptr;
1395 //no range selection: then 1 2 3 and 8 are possible
1396 const EECharAttrib
* pCurAttr
= pBackAttr
? pBackAttr
: pErrorAttr
;
1399 nSelectionType
= pCurAttr
->nStart
== aCurrentSelection
.start
.nIndex
?
1400 LEFT_NO
: pCurAttr
->nEnd
== aCurrentSelection
.end
.nIndex
? RIGHT_NO
: INSIDE_NO
;
1403 nSelectionType
= OUTSIDE_NO
;
1405 bHasFieldLeft
= pBackAttr
&& pBackAttr
->nEnd
== nCursor
;
1408 pBackAttrLeft
= pBackAttr
;
1409 pBackAttr
= nullptr;
1411 bHasErrorLeft
= pErrorAttr
&& pErrorAttr
->nEnd
== nCursor
;
1414 pErrorAttrLeft
= pErrorAttr
;
1415 pErrorAttr
= nullptr;
1418 //check previous position if this exists
1419 //that is a redundant in the case the attribute found above already is on the left cursor side
1420 //but it's o.k. for two errors/fields side by side
1424 pBackAttrLeft
= FindCharAttrib(nCursor
, EE_CHAR_BKGCOLOR
, aAttribList
);
1425 pErrorAttrLeft
= FindCharAttrib(nCursor
, EE_CHAR_GRABBAG
, aAttribList
);
1426 bHasFieldLeft
= pBackAttrLeft
!=nullptr;
1427 bHasErrorLeft
= pErrorAttrLeft
!= nullptr;
1431 //Here we have to determine if the error found is the one currently active
1432 bool bIsErrorActive
= (pErrorAttr
&& pErrorAttr
->nStart
== m_nErrorStart
) ||
1433 (pErrorAttrLeft
&& pErrorAttrLeft
->nStart
== m_nErrorStart
);
1436 nSelectionType
== INVALID
, "cui.dialogs",
1437 "selection type not set");
1439 const vcl::KeyCode
& rKeyCode
= rKeyEvt
.GetKeyCode();
1440 bool bDelete
= rKeyCode
.GetCode() == KEY_DELETE
;
1441 bool bBackspace
= rKeyCode
.GetCode() == KEY_BACKSPACE
;
1443 sal_Int8 nAction
= ACTION_CONTINUE
;
1444 switch(nSelectionType
)
1446 // 1 - backspace delete any other
1447 // UE on field FS on error CO on field FS on error CO
1451 nAction
= bHasFieldLeft
? ACTION_SELECTFIELD
: ACTION_UNDOEDIT
;
1452 //to force the use of pBackAttrLeft
1453 pBackAttr
= nullptr;
1456 nAction
= bHasField
? ACTION_SELECTFIELD
: ACTION_CONTINUE
;
1458 nAction
= bHasError
&& !nCursor
? ACTION_CONTINUE
:
1459 bHasError
? ACTION_EXPAND
: bHasErrorLeft
? ACTION_CONTINUE
: ACTION_UNDOEDIT
;
1461 // 2 - on field FS on error C
1463 nAction
= bHasField
? ACTION_SELECTFIELD
:
1464 bIsErrorActive
? ACTION_CONTINUE
: ACTION_UNDOEDIT
;
1466 // 3 - backspace delete any other
1467 // on field FS on error CO UE on field UE on error EX
1470 nAction
= bHasFieldLeft
? ACTION_SELECTFIELD
: ACTION_CONTINUE
;
1472 nAction
= bHasFieldLeft
&& bHasError
? ACTION_CONTINUE
: ACTION_UNDOEDIT
;
1474 nAction
= bHasFieldLeft
&& bHasError
? ACTION_EXPAND
:
1475 bHasError
? ACTION_CONTINUE
: bHasErrorLeft
? ACTION_EXPAND
:ACTION_UNDOEDIT
;
1477 // 4 - on field UE and on error CO
1479 nAction
= ACTION_UNDOEDIT
;
1481 // 5 - on field FS and on error CO
1483 nAction
= bHasField
? ACTION_SELECTFIELD
: ACTION_CONTINUE
;
1485 // 6 - on field FS and on error UE
1487 nAction
= bHasField
? ACTION_SELECTFIELD
: ACTION_UNDOEDIT
;
1493 nAction
= ACTION_UNDOEDIT
;
1496 //save the current paragraph
1497 sal_Int32 nCurrentLen
= m_xEditEngine
->GetText().getLength();
1498 if (nAction
!= ACTION_SELECTFIELD
)
1500 m_xEditView
->PostKeyEvent(rKeyEvt
);
1504 const EECharAttrib
* pCharAttr
= pBackAttr
? pBackAttr
: pBackAttrLeft
;
1506 m_xEditView
->SetSelection(ESelection(0, pCharAttr
->nStart
, 0, pCharAttr
->nEnd
));
1508 if(nAction
== ACTION_EXPAND
)
1510 DBG_ASSERT(pErrorAttrLeft
|| pErrorAttr
, "where is the error");
1511 //text has been added on the right and only the 'error attribute has to be corrected
1514 SpellErrorDescription aSpellErrorDescription
;
1515 ExtractErrorDescription(*pErrorAttrLeft
, aSpellErrorDescription
);
1517 std::unique_ptr
<SfxPoolItem
> xNewError(pErrorAttrLeft
->pAttr
->Clone());
1518 sal_Int32 nStart
= pErrorAttrLeft
->nStart
;
1519 sal_Int32 nEnd
= pErrorAttrLeft
->nEnd
+ 1;
1520 m_xEditEngine
->RemoveAttribs(ESelection(0, nStart
, 0, nEnd
), false, EE_CHAR_GRABBAG
);
1521 SetAttrib(*xNewError
, nStart
, nEnd
);
1522 //only active errors move the mark
1525 bool bGrammar
= aSpellErrorDescription
.bIsGrammarError
;
1526 MoveErrorMarkTo(nStart
, nEnd
, bGrammar
);
1529 //text has been added on the left then the error attribute has to be expanded and the
1530 //field attribute on the right - if any - has to be contracted
1531 else if (pErrorAttr
)
1533 SpellErrorDescription aSpellErrorDescription
;
1534 ExtractErrorDescription(*pErrorAttr
, aSpellErrorDescription
);
1536 //determine the change
1537 sal_Int32 nAddedChars
= m_xEditEngine
->GetText().getLength() - nCurrentLen
;
1539 std::unique_ptr
<SfxPoolItem
> xNewError(pErrorAttr
->pAttr
->Clone());
1540 sal_Int32 nStart
= pErrorAttr
->nStart
+ nAddedChars
;
1541 sal_Int32 nEnd
= pErrorAttr
->nEnd
+ nAddedChars
;
1542 m_xEditEngine
->RemoveAttribs(ESelection(0, nStart
, 0, nEnd
), false, EE_CHAR_GRABBAG
);
1543 nStart
= pErrorAttr
->nStart
;
1544 SetAttrib(*xNewError
, nStart
, nEnd
);
1545 //only if the error is active the mark is moved here
1548 bool bGrammar
= aSpellErrorDescription
.bIsGrammarError
;
1549 MoveErrorMarkTo(nStart
, nEnd
, bGrammar
);
1555 std::unique_ptr
<SfxPoolItem
> xNewBack(pBackAttrLeft
->pAttr
->Clone());
1556 sal_Int32 _nStart
= pBackAttrLeft
->nStart
+ nAddedChars
;
1557 sal_Int32 _nEnd
= pBackAttrLeft
->nEnd
+ nAddedChars
;
1558 m_xEditEngine
->RemoveAttribs(ESelection(0, _nStart
, 0, _nEnd
), false, EE_CHAR_BKGCOLOR
);
1559 _nStart
= pBackAttrLeft
->nStart
;
1560 SetAttrib(*xNewBack
, _nStart
, _nEnd
);
1564 else if(nAction
== ACTION_UNDOEDIT
)
1566 SetUndoEditMode(true);
1568 //make sure the error positions are correct after text changes
1569 //the old attribute may have been deleted
1570 //all changes inside of the current error leave the error attribute at the current
1572 if (!IsUndoEditMode() && bIsErrorActive
)
1574 aAttribList
.clear();
1575 m_xEditEngine
->GetCharAttribs(0, aAttribList
);
1576 const EECharAttrib
* pFontColor
= FindCharAttrib(nCursor
, EE_CHAR_COLOR
, aAttribList
);
1577 const EECharAttrib
* pErrorAttrib
= FindCharAttrib(m_nErrorStart
, EE_CHAR_GRABBAG
, aAttribList
);
1578 if (pFontColor
&& pErrorAttrib
)
1580 m_nErrorStart
= pFontColor
->nStart
;
1581 m_nErrorEnd
= pFontColor
->nEnd
;
1582 if (pErrorAttrib
->nStart
!= m_nErrorStart
|| pErrorAttrib
->nEnd
!= m_nErrorEnd
)
1584 std::unique_ptr
<SfxPoolItem
> xNewError(pErrorAttrib
->pAttr
->Clone());
1586 m_xEditEngine
->RemoveAttribs(ESelection(0, pErrorAttr
->nStart
, 0, pErrorAttr
->nEnd
), false, EE_CHAR_GRABBAG
);
1587 SetAttrib(*xNewError
, m_nErrorStart
, m_nErrorEnd
);
1591 //this is not a modification anymore
1592 if(nAction
!= ACTION_SELECTFIELD
&& !m_bIsUndoEditMode
)
1596 bConsumed
= m_xEditView
->PostKeyEvent(rKeyEvt
);
1601 void SentenceEditWindow_Impl::Init(weld::Toolbar
* pToolbar
)
1603 m_pToolbar
= pToolbar
;
1604 m_pToolbar
->connect_clicked(LINK(this,SentenceEditWindow_Impl
,ToolbarHdl
));
1607 IMPL_LINK(SentenceEditWindow_Impl
, ToolbarHdl
, const OUString
&, rCurItemId
, void)
1609 if (rCurItemId
== "paste")
1611 m_xEditView
->Paste();
1614 else if (rCurItemId
== "insert")
1616 if (auto pImplFncGetSpecialChars
= vcl::GetGetSpecialCharsFunction())
1618 OUString aChars
= pImplFncGetSpecialChars(GetDrawingArea(), m_xEditEngine
->GetStandardFont(0));
1619 if (!aChars
.isEmpty())
1621 ESelection
aCurrentSelection(m_xEditView
->GetSelection());
1622 m_xEditEngine
->QuickInsertText(aChars
, aCurrentSelection
);
1629 bool SentenceEditWindow_Impl::MarkNextError( bool bIgnoreCurrentError
, const css::uno::Reference
<css::linguistic2::XSpellChecker1
>& xSpell
)
1631 if (bIgnoreCurrentError
)
1632 m_aIgnoreErrorsAt
.insert( m_nErrorStart
);
1634 const sal_Int32 nTextLen
= m_xEditEngine
->GetTextLen(0);
1636 if (m_nErrorEnd
>= nTextLen
- 1)
1638 //if it's not already modified the modified flag has to be reset at the end of the marking
1639 bool bModified
= IsModified();
1641 const sal_Int32 nOldErrorStart
= m_nErrorStart
;
1642 const sal_Int32 nOldErrorEnd
= m_nErrorEnd
;
1644 //create a cursor behind the end of the last error
1645 //- or at 0 at the start of the sentence
1646 sal_Int32
nCursor(m_nErrorEnd
? m_nErrorEnd
+ 1 : 0);
1648 //search for SpellErrorDescription
1649 SpellErrorDescription aSpellErrorDescription
;
1651 std::vector
<EECharAttrib
> aAttribList
;
1652 m_xEditEngine
->GetCharAttribs(0, aAttribList
);
1654 //iterate over the text and search for the next error that maybe has
1655 //to be replace by a ChangeAllList replacement
1656 bool bGrammarError
= false;
1657 while (nCursor
< nTextLen
)
1659 const SpellErrorDescription
* pSpellErrorDescription
= nullptr;
1660 const EECharAttrib
* pEECharAttrib
= nullptr;
1662 sal_Int32 nMinPos
= nTextLen
+ 1;
1663 for (const auto& rTextAtr
: aAttribList
)
1665 if (rTextAtr
.pAttr
->Which() != EE_CHAR_GRABBAG
)
1667 if (rTextAtr
.nEnd
> nCursor
&& rTextAtr
.nStart
< nMinPos
)
1669 nMinPos
= rTextAtr
.nStart
;
1670 pEECharAttrib
= &rTextAtr
;
1676 ExtractErrorDescription(*pEECharAttrib
, aSpellErrorDescription
);
1678 bGrammarError
= aSpellErrorDescription
.bIsGrammarError
;
1679 m_nErrorStart
= pEECharAttrib
->nStart
;
1680 m_nErrorEnd
= pEECharAttrib
->nEnd
;
1682 pSpellErrorDescription
= &aSpellErrorDescription
;
1685 nCursor
= std::max(nCursor
, nMinPos
); // move forward if possible
1687 // maybe the error found here is already in the ChangeAllList and has to be replaced
1688 Reference
<XDictionary
> xChangeAll
= LinguMgr::GetChangeAllList();
1689 Reference
<XDictionaryEntry
> xEntry
;
1691 if (xChangeAll
->getCount() && pSpellErrorDescription
&&
1692 (xEntry
= xChangeAll
->getEntry( pSpellErrorDescription
->sErrorText
)).is())
1694 OUString
sReplacement(getDotReplacementString(GetErrorText(), xEntry
->getReplacementText()));
1696 int nLenChange
= ChangeMarkedWord(sReplacement
, LanguageTag::convertToLanguageType(pSpellErrorDescription
->aLocale
));
1698 nCursor
+= sReplacement
.getLength();
1701 m_xEditEngine
->GetCharAttribs(0, aAttribList
);
1702 // maybe the error found here is already added to the dictionary and has to be ignored
1704 else if(pSpellErrorDescription
&& !bGrammarError
&&
1705 xSpell
->isValid(GetErrorText(),
1706 static_cast<sal_uInt16
>(LanguageTag::convertToLanguageType( pSpellErrorDescription
->aLocale
)),
1707 Sequence
< PropertyValue
>() ))
1715 //if an attrib has been found search for the end of the error string
1716 if (nCursor
< nTextLen
)
1718 MoveErrorMarkTo(nCursor
, m_nErrorEnd
, bGrammarError
);
1720 //add an undo action
1721 std::unique_ptr
<SpellUndoAction_Impl
> pAction(new SpellUndoAction_Impl(
1722 SPELLUNDO_CHANGE_NEXTERROR
, GetSpellDialog()->aDialogUndoLink
));
1723 pAction
->SetErrorMove(nOldErrorStart
, nOldErrorEnd
);
1725 if (GetErrorDescription(aSpellErrorDescription
, nOldErrorStart
))
1727 pAction
->SetErrorLanguageSelected(aSpellErrorDescription
.aSuggestions
.hasElements() &&
1728 LanguageTag(aSpellErrorDescription
.aLocale
).getLanguageType() == GetSpellDialog()->m_xLanguageLB
->get_active_id());
1731 pAction
->SetErrorLanguageSelected(false);
1733 AddUndoAction(std::move(pAction
));
1736 m_nErrorStart
= m_nErrorEnd
= nTextLen
;
1739 SpellDialog
* pSpellDialog
= GetSpellDialog();
1740 pSpellDialog
->m_xIgnorePB
->set_sensitive(bRet
);
1741 pSpellDialog
->m_xIgnoreAllPB
->set_sensitive(bRet
);
1742 pSpellDialog
->m_xAutoCorrPB
->set_sensitive(bRet
);
1743 pSpellDialog
->m_xAddToDictMB
->set_sensitive(bRet
);
1744 pSpellDialog
->m_xAddToDictPB
->set_sensitive(bRet
);
1748 void SentenceEditWindow_Impl::MoveErrorMarkTo(sal_Int32 nStart
, sal_Int32 nEnd
, bool bGrammarError
)
1750 ESelection
aAll(m_xEditEngine
->NormalizeESelection(ESelection::All()));
1751 m_xEditEngine
->RemoveAttribs(aAll
, false, EE_CHAR_COLOR
);
1752 m_xEditEngine
->RemoveAttribs(aAll
, false, EE_CHAR_WEIGHT
);
1753 m_xEditEngine
->RemoveAttribs(aAll
, false, EE_CHAR_WEIGHT_CJK
);
1754 m_xEditEngine
->RemoveAttribs(aAll
, false, EE_CHAR_WEIGHT_CTL
);
1756 // tdf#116566 Use color defined in the current Color Scheme
1757 Color aSpellErrorCollor
= svtools::ColorConfig().GetColorValue(svtools::SPELL
).nColor
;
1758 Color aGrammarErrorCollor
= svtools::ColorConfig().GetColorValue(svtools::GRAMMAR
).nColor
;
1760 SfxItemSet
aSet(m_xEditEngine
->GetEmptyItemSet());
1761 aSet
.Put(SvxColorItem(bGrammarError
? aGrammarErrorCollor
: aSpellErrorCollor
, EE_CHAR_COLOR
));
1762 aSet
.Put(SvxWeightItem(WEIGHT_BOLD
, EE_CHAR_WEIGHT
));
1763 aSet
.Put(SvxWeightItem(WEIGHT_BOLD
, EE_CHAR_WEIGHT_CJK
));
1764 aSet
.Put(SvxWeightItem(WEIGHT_BOLD
, EE_CHAR_WEIGHT_CTL
));
1766 m_xEditEngine
->QuickSetAttribs(aSet
, ESelection(0, nStart
, 0, nEnd
));
1768 // Set the selection so the editview will autoscroll to make this visible
1769 // unless (tdf#133958) the selection already overlaps this range
1770 ESelection aCurrentSelection
= m_xEditView
->GetSelection();
1771 aCurrentSelection
.Adjust();
1772 bool bCurrentSelectionInRange
= nStart
<= aCurrentSelection
.end
.nIndex
&& aCurrentSelection
.start
.nIndex
<= nEnd
;
1773 if (!bCurrentSelectionInRange
)
1775 m_xEditView
->SetSelection(ESelection(0, nStart
));
1776 // tdf#157148 ensure current location is auto-scrolled to be visible
1777 m_xEditView
->ShowCursor();
1782 m_nErrorStart
= nStart
;
1786 int SentenceEditWindow_Impl::ChangeMarkedWord(const OUString
& rNewWord
, LanguageType eLanguage
)
1788 std::vector
<EECharAttrib
> aAttribList
;
1789 m_xEditEngine
->GetCharAttribs(0, aAttribList
);
1791 //calculate length changes
1792 auto nDiffLen
= rNewWord
.getLength() - m_nErrorEnd
+ m_nErrorStart
;
1793 //Remove spell error attribute
1794 m_xEditEngine
->UndoActionStart(SPELLUNDO_MOVE_ERROREND
);
1795 const EECharAttrib
* pErrorAttrib
= FindCharAttrib(m_nErrorStart
, EE_CHAR_GRABBAG
, aAttribList
);
1796 DBG_ASSERT(pErrorAttrib
, "no error attribute found");
1797 bool bSpellErrorDescription
= false;
1798 SpellErrorDescription aSpellErrorDescription
;
1801 ExtractErrorDescription(*pErrorAttrib
, aSpellErrorDescription
);
1802 m_xEditEngine
->RemoveAttribs(ESelection(0, pErrorAttrib
->nStart
, 0, pErrorAttrib
->nEnd
), false, EE_CHAR_GRABBAG
);
1803 bSpellErrorDescription
= true;
1806 const EECharAttrib
* pBackAttrib
= FindCharAttrib(m_nErrorStart
, EE_CHAR_BKGCOLOR
, aAttribList
);
1808 ESelection
aSel(0, m_nErrorStart
, 0, m_nErrorEnd
);
1809 m_xEditEngine
->QuickInsertText(rNewWord
, aSel
);
1811 const sal_Int32 nTextLen
= m_xEditEngine
->GetTextLen(0);
1814 m_xEditEngine
->GetCharAttribs(0, aAttribList
);
1818 //attributes following an error at the start of the text are not moved but expanded from the
1819 //text engine - this is done to keep full-paragraph-attributes
1820 //in the current case that handling is not desired
1821 const EECharAttrib
* pLangAttrib
= FindCharAttrib(m_nErrorEnd
, EE_CHAR_LANGUAGE
, aAttribList
);
1823 if (pLangAttrib
&& !pLangAttrib
->nStart
&& pLangAttrib
->nEnd
== nTextLen
)
1825 LanguageType eNewLanguage
= static_cast<const SvxLanguageItem
*>(pLangAttrib
->pAttr
)->GetLanguage();
1826 m_xEditEngine
->RemoveAttribs(ESelection(0, pLangAttrib
->nStart
, 0, pLangAttrib
->nEnd
), false, EE_CHAR_LANGUAGE
);
1827 SetAttrib(SvxLanguageItem(eNewLanguage
, EE_CHAR_LANGUAGE
), m_nErrorEnd
+ nDiffLen
, nTextLen
);
1831 // undo expanded attributes!
1832 if (pBackAttrib
&& pBackAttrib
->nStart
< m_nErrorStart
&& pBackAttrib
->nEnd
== m_nErrorEnd
+ nDiffLen
)
1834 std::unique_ptr
<SfxPoolItem
> xNewBackground(pBackAttrib
->pAttr
->Clone());
1835 const sal_Int32 nStart
= pBackAttrib
->nStart
;
1837 m_xEditEngine
->RemoveAttribs(ESelection(0, pBackAttrib
->nStart
, 0, pBackAttrib
->nEnd
), false, EE_CHAR_BKGCOLOR
);
1839 SetAttrib(*xNewBackground
, nStart
, m_nErrorStart
);
1841 m_xEditEngine
->SetModified();
1843 //adjust end position
1844 tools::Long nEndTemp
= m_nErrorEnd
;
1845 nEndTemp
+= nDiffLen
;
1846 m_nErrorEnd
= static_cast<sal_Int32
>(nEndTemp
);
1848 std::unique_ptr
<SpellUndoAction_Impl
> pAction(new SpellUndoAction_Impl(
1849 SPELLUNDO_MOVE_ERROREND
, GetSpellDialog()->aDialogUndoLink
));
1850 pAction
->SetOffset(nDiffLen
);
1851 AddUndoAction(std::move(pAction
));
1852 if (bSpellErrorDescription
)
1854 SfxGrabBagItem
aSpellErrorDescriptionItem(EE_CHAR_GRABBAG
,
1855 std::map
<OUString
, css::uno::Any
>{{u
"SpellErrorDescription"_ustr
, uno::Any(aSpellErrorDescription
.toSequence())}});
1856 SetAttrib(aSpellErrorDescriptionItem
, m_nErrorStart
, m_nErrorEnd
);
1858 SetAttrib(SvxLanguageItem(eLanguage
, EE_CHAR_LANGUAGE
), m_nErrorStart
, m_nErrorEnd
);
1859 m_xEditEngine
->UndoActionEnd();
1866 OUString
SentenceEditWindow_Impl::GetErrorText() const
1868 return m_xEditEngine
->GetText(ESelection(0, m_nErrorStart
, 0, m_nErrorEnd
));
1871 bool SentenceEditWindow_Impl::GetErrorDescription(SpellErrorDescription
& rSpellErrorDescription
, sal_Int32 nPosition
)
1873 std::vector
<EECharAttrib
> aAttribList
;
1874 m_xEditEngine
->GetCharAttribs(0, aAttribList
);
1876 if (const EECharAttrib
* pEECharAttrib
= FindCharAttrib(nPosition
, EE_CHAR_GRABBAG
, aAttribList
))
1878 ExtractErrorDescription(*pEECharAttrib
, rSpellErrorDescription
);
1885 bool SentenceEditWindow_Impl::GetAlternatives(SpellErrorDescription
& rSpellErrorDescription
)
1887 return GetErrorDescription(rSpellErrorDescription
, m_nErrorStart
);
1890 void SentenceEditWindow_Impl::RestoreCurrentError()
1892 SpellErrorDescription aSpellErrorDescription
;
1893 if (GetErrorDescription(aSpellErrorDescription
, m_nErrorStart
))
1895 if (aSpellErrorDescription
.sErrorText
!= GetErrorText() )
1896 ChangeMarkedWord(aSpellErrorDescription
.sErrorText
, LanguageTag::convertToLanguageType(aSpellErrorDescription
.aLocale
));
1900 void SentenceEditWindow_Impl::SetAlternatives( const Reference
< XSpellAlternatives
>& xAlt
)
1903 lang::Locale aLocale
;
1904 uno::Sequence
< OUString
> aAlts
;
1907 aWord
= xAlt
->getWord();
1908 aLocale
= xAlt
->getLocale();
1909 aAlts
= xAlt
->getAlternatives();
1911 SpellErrorDescription
aDesc( false, aWord
, std::move(aLocale
), aAlts
, nullptr);
1912 SfxGrabBagItem
aSpellErrorDescription(EE_CHAR_GRABBAG
,
1913 std::map
<OUString
, css::uno::Any
>{{u
"SpellErrorDescription"_ustr
, uno::Any(aDesc
.toSequence())}});
1914 SetAttrib(aSpellErrorDescription
, m_nErrorStart
, m_nErrorEnd
);
1917 void SentenceEditWindow_Impl::SetAttrib(const SfxPoolItem
& rItem
, sal_Int32 nStart
, sal_Int32 nEnd
)
1919 SfxItemSet
aSet(m_xEditEngine
->GetEmptyItemSet());
1921 m_xEditEngine
->QuickSetAttribs(aSet
, ESelection(0, nStart
, 0, nEnd
));
1925 void SentenceEditWindow_Impl::SetText( const OUString
& rStr
)
1927 m_nErrorStart
= m_nErrorEnd
= 0;
1928 m_xEditEngine
->SetText(rStr
);
1933 struct LanguagePosition_Impl
1935 sal_Int32 nPosition
;
1936 LanguageType eLanguage
;
1938 LanguagePosition_Impl(sal_Int32 nPos
, LanguageType eLang
) :
1946 typedef std::vector
<LanguagePosition_Impl
> LanguagePositions_Impl
;
1948 static void lcl_InsertBreakPosition_Impl(
1949 LanguagePositions_Impl
& rBreakPositions
, sal_Int32 nInsert
, LanguageType eLanguage
)
1951 LanguagePositions_Impl::iterator aStart
= rBreakPositions
.begin();
1952 while(aStart
!= rBreakPositions
.end())
1954 if(aStart
->nPosition
== nInsert
)
1956 //the language of following starts has to overwrite
1957 //the one of previous ends
1958 aStart
->eLanguage
= eLanguage
;
1961 else if(aStart
->nPosition
> nInsert
)
1964 rBreakPositions
.insert(aStart
, LanguagePosition_Impl(nInsert
, eLanguage
));
1970 rBreakPositions
.emplace_back(nInsert
, eLanguage
);
1973 /*-------------------------------------------------------------------------
1974 Returns the text in spell portions. Each portion contains text with an
1975 equal language and attribute. The spell alternatives are empty.
1976 -----------------------------------------------------------------------*/
1977 svx::SpellPortions
SentenceEditWindow_Impl::CreateSpellPortions() const
1979 svx::SpellPortions aRet
;
1981 const sal_Int32 nTextLen
= m_xEditEngine
->GetTextLen(0);
1983 std::vector
<EECharAttrib
> aAttribList
;
1984 m_xEditEngine
->GetCharAttribs(0, aAttribList
);
1989 LanguagePositions_Impl aBreakPositions
;
1990 const EECharAttrib
* pLastLang
= nullptr;
1991 const EECharAttrib
* pLastError
= nullptr;
1992 LanguageType eLang
= LANGUAGE_DONTKNOW
;
1993 const EECharAttrib
* pError
= nullptr;
1994 while (nCursor
< nTextLen
)
1996 const EECharAttrib
* pLang
= FindCharAttrib(nCursor
, EE_CHAR_LANGUAGE
, aAttribList
);
1997 if(pLang
&& pLang
!= pLastLang
)
1999 eLang
= static_cast<const SvxLanguageItem
*>(pLang
->pAttr
)->GetLanguage();
2000 lcl_InsertBreakPosition_Impl(aBreakPositions
, pLang
->nStart
, eLang
);
2001 lcl_InsertBreakPosition_Impl(aBreakPositions
, pLang
->nEnd
, eLang
);
2004 pError
= FindCharAttrib(nCursor
, EE_CHAR_GRABBAG
, aAttribList
);
2005 if (pError
&& pLastError
!= pError
)
2007 lcl_InsertBreakPosition_Impl(aBreakPositions
, pError
->nStart
, eLang
);
2008 lcl_InsertBreakPosition_Impl(aBreakPositions
, pError
->nEnd
, eLang
);
2009 pLastError
= pError
;
2015 if (aBreakPositions
.empty())
2017 //if all content has been overwritten the attributes may have been removed, too
2018 svx::SpellPortion aPortion1
;
2019 aPortion1
.eLanguage
= GetSpellDialog()->GetSelectedLang_Impl();
2021 aPortion1
.sText
= m_xEditEngine
->GetText(ESelection(0, 0, 0, nTextLen
));
2023 aRet
.push_back(aPortion1
);
2027 LanguagePositions_Impl::iterator aStart
= aBreakPositions
.begin();
2028 //start should always be Null
2029 eLang
= aStart
->eLanguage
;
2030 sal_Int32 nStart
= aStart
->nPosition
;
2031 DBG_ASSERT(!nStart
, "invalid start position - language attribute missing?");
2034 while(aStart
!= aBreakPositions
.end())
2036 svx::SpellPortion aPortion1
;
2037 aPortion1
.eLanguage
= eLang
;
2039 aPortion1
.sText
= m_xEditEngine
->GetText(ESelection(0, nStart
, 0, aStart
->nPosition
));
2040 bool bIsIgnoreError
= m_aIgnoreErrorsAt
.find( nStart
) != m_aIgnoreErrorsAt
.end();
2041 if( bIsIgnoreError
)
2043 aPortion1
.bIgnoreThisError
= true;
2045 aRet
.push_back(aPortion1
);
2046 nStart
= aStart
->nPosition
;
2047 eLang
= aStart
->eLanguage
;
2052 // quick partly fix of #i71318. Correct fix needs to patch the EditEngine itself...
2053 // this one will only prevent text from disappearing. It may to not have the
2054 // correct language and will probably not spell checked...
2055 const sal_uInt32 nPara
= m_xEditEngine
->GetParagraphCount();
2058 OUStringBuffer aLeftOverText
;
2059 for (sal_uInt32 i
= 1; i
< nPara
; ++i
)
2061 aLeftOverText
.append("\x0a"); // the manual line break...
2062 aLeftOverText
.append(m_xEditEngine
->GetText(i
));
2065 { // we need to add a new portion containing the left-over text
2066 svx::SpellPortion aPortion2
;
2067 aPortion2
.eLanguage
= eLang
;
2068 aPortion2
.sText
= aLeftOverText
.makeStringAndClear();
2069 aRet
.push_back( aPortion2
);
2071 else if (!aLeftOverText
.isEmpty() && !aRet
.empty())
2072 { // we just need to append the left-over text to the last portion (which had no errors)
2073 aRet
[ aRet
.size() - 1 ].sText
+= aLeftOverText
;
2081 void SentenceEditWindow_Impl::Undo()
2083 EditUndoManager
& rUndoMgr
= m_xEditEngine
->GetUndoManager();
2084 DBG_ASSERT(GetUndoActionCount(), "no undo actions available" );
2085 if(!GetUndoActionCount())
2087 bool bSaveUndoEdit
= IsUndoEditMode();
2088 SpellUndoAction_Impl
* pUndoAction
;
2089 //if the undo edit mode is active then undo all changes until the UNDO_EDIT_MODE action has been found
2092 pUndoAction
= static_cast<SpellUndoAction_Impl
*>(rUndoMgr
.GetUndoAction());
2094 }while(bSaveUndoEdit
&& SPELLUNDO_UNDO_EDIT_MODE
!= pUndoAction
->GetId() && GetUndoActionCount());
2096 if(bSaveUndoEdit
|| SPELLUNDO_CHANGE_GROUP
== pUndoAction
->GetId())
2097 GetSpellDialog()->UpdateBoxes_Impl();
2100 void SentenceEditWindow_Impl::ResetUndo()
2102 EditUndoManager
& rUndo
= m_xEditEngine
->GetUndoManager();
2106 void SentenceEditWindow_Impl::AddUndoAction( std::unique_ptr
<SfxUndoAction
> pAction
)
2108 EditUndoManager
& rUndoMgr
= m_xEditEngine
->GetUndoManager();
2109 rUndoMgr
.AddUndoAction(std::move(pAction
));
2110 GetSpellDialog()->m_xUndoPB
->set_sensitive(true);
2113 size_t SentenceEditWindow_Impl::GetUndoActionCount() const
2115 return m_xEditEngine
->GetUndoManager().GetUndoActionCount();
2118 void SentenceEditWindow_Impl::UndoActionStart( sal_uInt16 nId
)
2120 m_xEditEngine
->UndoActionStart(nId
);
2123 void SentenceEditWindow_Impl::UndoActionEnd()
2125 m_xEditEngine
->UndoActionEnd();
2128 void SentenceEditWindow_Impl::MoveErrorEnd(tools::Long nOffset
)
2130 // Shouldn't we always add the real signed value instead???
2132 m_nErrorEnd
= m_nErrorEnd
- static_cast<sal_Int32
>(nOffset
);
2134 m_nErrorEnd
= m_nErrorEnd
- static_cast<sal_Int32
>(-nOffset
);
2138 void SentenceEditWindow_Impl::SetUndoEditMode(bool bSet
)
2140 DBG_ASSERT(!bSet
|| m_bIsUndoEditMode
!= bSet
, "SetUndoEditMode with equal values?");
2141 m_bIsUndoEditMode
= bSet
;
2142 //disable all buttons except the Change
2143 SpellDialog
* pSpellDialog
= GetSpellDialog();
2144 weld::Widget
* aControls
[] =
2146 pSpellDialog
->m_xChangeAllPB
.get(),
2147 pSpellDialog
->m_xExplainFT
.get(),
2148 pSpellDialog
->m_xIgnoreAllPB
.get(),
2149 pSpellDialog
->m_xIgnoreRulePB
.get(),
2150 pSpellDialog
->m_xIgnorePB
.get(),
2151 pSpellDialog
->m_xSuggestionLB
.get(),
2152 pSpellDialog
->m_xSuggestionFT
.get(),
2153 pSpellDialog
->m_xLanguageFT
.get(),
2154 pSpellDialog
->m_xLanguageLB
->get_widget(),
2155 pSpellDialog
->m_xAddToDictMB
.get(),
2156 pSpellDialog
->m_xAddToDictPB
.get(),
2157 pSpellDialog
->m_xAutoCorrPB
.get()
2159 for (weld::Widget
* pWidget
: aControls
)
2160 pWidget
->set_sensitive(false);
2162 //remove error marks
2163 ESelection
aAll(m_xEditEngine
->NormalizeESelection(ESelection::All()));
2164 m_xEditEngine
->RemoveAttribs(aAll
, false, EE_CHAR_COLOR
);
2165 m_xEditEngine
->RemoveAttribs(aAll
, false, EE_CHAR_WEIGHT
);
2166 m_xEditEngine
->RemoveAttribs(aAll
, false, EE_CHAR_WEIGHT_CJK
);
2167 m_xEditEngine
->RemoveAttribs(aAll
, false, EE_CHAR_WEIGHT_CTL
);
2170 //put the appropriate action on the Undo-stack
2171 AddUndoAction( std::make_unique
<SpellUndoAction_Impl
>(
2172 SPELLUNDO_UNDO_EDIT_MODE
, GetSpellDialog()->aDialogUndoLink
) );
2173 pSpellDialog
->m_xChangePB
->set_sensitive(true);
2176 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */