cui/source/dialogs/SpellDialog.cxx |   74 +++++++++++++++++++++++++++++++++++--
 cui/source/inc/SpellDialog.hxx     |    9 ++++
 2 files changed, 78 insertions(+), 5 deletions(-)

New commits:
commit 506012898d0a41acbdefbe0a9b5a735df21c89ef
Author:     Justin Luth <[email protected]>
AuthorDate: Thu Jul 10 16:21:41 2025 -0400
Commit:     Justin Luth <[email protected]>
CommitDate: Mon Sep 29 13:53:51 2025 +0200

    tdf#126814 cui SpellDialog: don't clear ChangeAll dict unless ADD_ENTRY
    
    Coding was inspired/copy/pasted/adapted from mk-reviewed
    https://gerrit.libreoffice.org/c/core/+/160152
    
    When the spell checker loses focus,
    that might mean that a spell-check is run on another document.
    In that case, the session's ChangeAll dictionary
    might have added new words
    (which must not be allowed to affect our document)
    and thus the resume function was clearing the ChangeAll dict again.
    
    However, unnecessarily killing the CorrectAll words
    is VERY annoying to the user doing the spell checking.
    
    So try to avoid clearing upon resume if possible.
    One way is to listen for any ADD_ENTRY events to the dictionary,
    and if there are none, then it is safe to continue using it.
    
    NOTE: it is already possible for two simultaneous spell checks
    to affect each other's results. Not every focus change
    results in calling SpellDialog::InvalidateDialog...
    
    Change-Id: Id8d80f61501c1750121b58df75b3796ff391ca59
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/187667
    Tested-by: Jenkins
    Reviewed-by: Justin Luth <[email protected]>

diff --git a/cui/source/dialogs/SpellDialog.cxx 
b/cui/source/dialogs/SpellDialog.cxx
index 6071bf6abb74..1e915376aff1 100644
--- a/cui/source/dialogs/SpellDialog.cxx
+++ b/cui/source/dialogs/SpellDialog.cxx
@@ -36,6 +36,7 @@
 #include <linguistic/misc.hxx>
 #include <com/sun/star/lang/XServiceInfo.hpp>
 #include <com/sun/star/frame/XStorable.hpp>
+#include <com/sun/star/linguistic2/DictionaryEventFlags.hpp>
 #include <com/sun/star/linguistic2/XDictionary.hpp>
 #include <com/sun/star/linguistic2/XSpellAlternatives.hpp>
 #include <com/sun/star/linguistic2/XSearchableDictionaryList.hpp>
@@ -89,6 +90,52 @@ namespace
 
 
 namespace svx{
+
+class DictionaryEventListener : public 
cppu::WeakImplHelper<XDictionaryEventListener>
+{
+public:
+    DictionaryEventListener(uno::Reference<linguistic2::XDictionary> xDict, 
SpellDialog& rDialog);
+    void stopListening();
+
+    //  XEventListener
+    void SAL_CALL disposing(const css::lang::EventObject& rSource) override;
+
+    // XDictionaryEventListener
+    void SAL_CALL processDictionaryEvent(const DictionaryEvent& rDicEvt) 
override;
+
+private:
+    uno::Reference<linguistic2::XDictionary> m_xDict;
+    SpellDialog& m_rDialog;
+};
+
+DictionaryEventListener::DictionaryEventListener(uno::Reference<linguistic2::XDictionary>
 xDict,
+                                                 SpellDialog& rDialog)
+    : m_xDict(std::move(xDict))
+    , m_rDialog(rDialog)
+{
+    if (m_xDict)
+        m_xDict->addDictionaryEventListener(this);
+}
+
+void DictionaryEventListener::stopListening()
+{
+    if (m_xDict)
+        m_xDict->removeDictionaryEventListener(this);
+}
+
+void DictionaryEventListener::disposing(const css::lang::EventObject& rSource)
+{
+    if (rSource.Source == m_xDict)
+        m_xDict->removeDictionaryEventListener(this);
+}
+
+void DictionaryEventListener::processDictionaryEvent(const DictionaryEvent& 
rDicEvt)
+{
+    m_rDialog.processDictionaryEvent(rDicEvt);
+}
+
+// class SpellUndoAction_Impl
+
 class SpellUndoAction_Impl : public SfxUndoAction
 {
     SpellUndoAction m_nId;
@@ -243,6 +290,8 @@ SpellDialog::SpellDialog(SpellDialogChildWindow* 
pChildWindow,
     // disable controls if service is missing
     m_xDialog->set_sensitive(xSpell.is());
 
+    m_xChangeAllDictListener.set(new 
DictionaryEventListener(LinguMgr::GetChangeAllList(), *this));
+
     // further initialization happens in Initialize() to prevent virtual
     // calls from the constructor
 }
@@ -264,6 +313,9 @@ SpellDialog::~SpellDialog()
 
         pImpl.reset();
     }
+
+    m_xChangeAllDictListener->stopListening();
+    m_xChangeAllDictListener.clear();
 }
 
 void SpellDialog::UpdateBoxes_Impl(bool bCallFromSelectHdl)
@@ -440,7 +492,7 @@ IMPL_LINK( SpellDialog, ExtClickHdl, weld::Button&, rBtn, 
void )
 IMPL_LINK_NOARG(SpellDialog, CheckGrammarHdl, weld::Toggleable&, void)
 {
     rParent.SetGrammarChecking(m_xCheckGrammarCB->get_active());
-    Impl_Restore(true);
+    Impl_Restore(/*UseSavedSentence=*/true, /*ClearChangeAll=*/true);
 }
 
 void SpellDialog::StartSpellOptDlg_Impl()
@@ -495,6 +547,18 @@ OUString SpellDialog::getReplacementString() const
     return getDotReplacementString(sOrigString, sReplacement);
 }
 
+void SpellDialog::processDictionaryEvent(const DictionaryEvent& rDicEvt)
+{
+    if (rDicEvt.nEvent & DictionaryEventFlags::ADD_ENTRY)
+    {
+        // The ChangeAll dictionary is shared by all documents.
+        // If something changed it while this dialog did not have the focus,
+        // when spell checking resumes, it must be cleared and start adding 
words all over again.
+        if (!m_xDialog->has_toplevel_focus() && rDicEvt.Source == 
LinguMgr::GetChangeAllList())
+            m_bResumeClearsChangeAllDict = true;
+    }
+}
+
 IMPL_LINK_NOARG(SpellDialog, DoubleClickChangeHdl, weld::TreeView&, bool)
 {
     ChangeHdl(*m_xChangePB);
@@ -669,10 +733,11 @@ IMPL_LINK( SpellDialog, DialogUndoHdl, 
SpellUndoAction_Impl&, rAction, void )
     }
 }
 
-void SpellDialog::Impl_Restore(bool bUseSavedSentence)
+void SpellDialog::Impl_Restore(bool bUseSavedSentence, bool bClearChangeAll)
 {
     //clear the "ChangeAllList"
-    LinguMgr::GetChangeAllList()->clear();
+    if (bClearChangeAll)
+        LinguMgr::GetChangeAllList()->clear();
     //get a new sentence
     m_xSentenceED->SetText(OUString());
     m_xSentenceED->ResetModified();
@@ -685,7 +750,8 @@ IMPL_LINK_NOARG(SpellDialog, IgnoreHdl, weld::Button&, void)
 {
     if (m_sResumeST == m_xIgnorePB->get_label())
     {
-        Impl_Restore(false);
+        Impl_Restore(/*UseSavedSentence=*/false, m_bResumeClearsChangeAllDict);
+        m_bResumeClearsChangeAllDict = false;
     }
     else
     {
diff --git a/cui/source/inc/SpellDialog.hxx b/cui/source/inc/SpellDialog.hxx
index fae28d10ceac..c7f8c0ce722a 100644
--- a/cui/source/inc/SpellDialog.hxx
+++ b/cui/source/inc/SpellDialog.hxx
@@ -19,6 +19,7 @@
 #pragma once
 
 #include <sfx2/basedlgs.hxx>
+#include <com/sun/star/linguistic2/DictionaryEvent.hpp>
 #include <com/sun/star/uno/Reference.hxx>
 
 
@@ -127,10 +128,12 @@ public:
 };
 
 class SpellDialogChildWindow;
+class DictionaryEventListener;
 
 class SpellDialog : public SfxModelessDialogController
 {
     friend class SentenceEditWindow_Impl;
+    friend class DictionaryEventListener;
 private:
     OUString        m_sResumeST;
     OUString        m_sIgnoreOnceST;
@@ -149,6 +152,9 @@ private:
     css::uno::Reference<
         css::linguistic2::XSpellChecker1 >     xSpell;
 
+    rtl::Reference<DictionaryEventListener> m_xChangeAllDictListener;
+    bool m_bResumeClearsChangeAllDict = false; // clear if another instance 
changed this word list
+
     std::unordered_map<OUString, OUString> m_aDictIdToName;
 
     std::unique_ptr<weld::Label> m_xAltTitle;
@@ -200,7 +206,7 @@ private:
     void            SpellContinue_Impl(std::unique_ptr<UndoChangeGroupGuard>* 
pGuard = nullptr, bool UseSavedSentence = false, bool bIgnoreCurrentError = 
false );
     void            LockFocusChanges( bool bLock ) {bFocusLocked = bLock;}
     void            ToplevelFocusChanged();
-    void            Impl_Restore(bool bUseSavedSentence);
+    void            Impl_Restore(bool bUseSavedSentence, bool bClearChangeAll);
 
     LanguageType    GetSelectedLang_Impl() const;
 
@@ -215,6 +221,7 @@ private:
 protected:
 
     OUString getReplacementString() const;
+    void processDictionaryEvent(const css::linguistic2::DictionaryEvent& 
rDicEvt);
 
 public:
     SpellDialog(

Reply via email to