cui/source/dialogs/SpellDialog.cxx | 72 ++++++++++----------------- cui/source/factory/dlgfact.cxx | 1 cui/source/inc/SpellDialog.hxx | 8 +-- include/sfx2/childwin.hxx | 3 + include/svx/SpellDialogChildWindow.hxx | 2 include/svx/svxdlg.hxx | 1 sfx2/source/appl/childwin.cxx | 2 svx/source/dialog/SpellDialogChildWindow.cxx | 5 + 8 files changed, 45 insertions(+), 49 deletions(-)
New commits: commit 94442bd94e504b067efd0b992525b53fc660f114 Author: Michael Weghorn <m.wegh...@posteo.de> AuthorDate: Tue Aug 5 11:07:25 2025 +0200 Commit: Michael Weghorn <m.wegh...@posteo.de> CommitDate: Tue Aug 5 23:47:16 2025 +0200 tdf#163802 a11y: Fully initialize SpellDialog before showing SpellDialog can't do all of its initialization in the ctor because it needs to call virtual methods on the SpellDialogChildWindow* that gets passed as a param, but that one calls the SpellDialog from its ctor, i.e. it hasn't been fully initialized yet. For that reason, the part of the initialization done in InitHdl was delayed by using a user event (posted via Application::PostUserEvent). This also includes setting the dialog title based on the selected text language. As a consequence of this, the title, and therefore the accessible name of the dialog would change after it was shown, which would result in Orca speech output for the dialog getting interrupted (see tdf#163802). Avoid the problem by not depending on the next user event getting processed, but calling the method that does the rest of the initialization manually after the SpellDialogChildWindow constructor has finished. In order to do that, add a virtual SfxChildWindow::Initialize (that does nothing) and override it in the SpellDialogChildWindow subclass. Since SpellDialog::Initialize (renamed from SpellDialog::InitHdl) is now guaranteed to be called before the dialog shows and that one always sets a title (by calling UpdateBoxes_Impl), drop the no longer needed // fdo#68794 set initial title for cases where no text has been processed // yet to show its language attributes OUString sTitle = rParent.HasGrammarChecking() ? m_sTitleSpellingGrammar : m_sTitleSpelling; m_xDialog->set_title(m_xDialog->strip_mnemonic(sTitle.replaceFirst("$LANGUAGE ($LOCATION)", ""))); from the ctor. Additional fix for GTK 3 to avoid incorrect object:property-change:accessible-name events from getting sent when the title didn't actually change [1]: commit 0cc9e603f4635c31d2a8a5f602869474d329e728 Author: Michael Weghorn <m.wegh...@posteo.de> Date: Mon Aug 4 15:31:06 2025 +0200 a11y: Only notify when window title changed In gtk_window_set_title_internal, return early when called with the same title as is already set. This also ensures that notifying of a change of PROP_TITLE only happens when the property actually changed, which also prevents invalid object:property-change:accessible-name AT-SPI events from getting emitted on the accessibility layer when the title hasn't actually changed. Fixes: #7690 Part-of: <https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/8814> Output of the pyatspi listener script from tdf#163802, attachment 197494 for gtk3 before this LO commit (note the object:property-change:accessible-name event after the object:state-changed:showing event): event type: object:property-change:accessible-name, event source: Spelling: , states: ['ENABLED', 'SENSITIVE', 'SHOWING', 'VISIBLE'] object:property-change:accessible-name(0, 0, Spelling: ) source: [dialog | Spelling: ] host_application: [application | soffice] sender: [application | soffice] event type: object:state-changed:showing, event source: Spelling: , states: ['ENABLED', 'SENSITIVE', 'SHOWING', 'VISIBLE'] object:state-changed:showing(1, 0, 0) source: [dialog | Spelling: ] host_application: [application | soffice] sender: [application | soffice] event type: object:property-change:accessible-name, event source: Spelling: English (UK), states: ['ENABLED', 'SENSITIVE', 'SHOWING', 'VISIBLE'] object:property-change:accessible-name(0, 0, Spelling: English (UK)) source: [dialog | Spelling: English (UK)] host_application: [application | soffice] sender: [application | soffice] Output with this change in place: event type: object:property-change:accessible-name, event source: Spelling: English (UK), states: ['ENABLED', 'SENSITIVE', 'SHOWING', 'VISIBLE'] object:property-change:accessible-name(0, 0, Spelling: English (UK)) source: [dialog | Spelling: English (UK)] host_application: [application | soffice] sender: [application | soffice] event type: object:state-changed:showing, event source: Spelling: English (UK), states: ['ENABLED', 'SENSITIVE', 'SHOWING', 'VISIBLE'] object:state-changed:showing(1, 0, 0) source: [dialog | Spelling: English (UK)] host_application: [application | soffice] sender: [application | soffice] [1] https://gitlab.gnome.org/GNOME/gtk/-/commit/0cc9e603f4635c31d2a8a5f602869474d329e728 Change-Id: I6cdfe8443ae435e1e644b78942cf63e9df1d400f Reviewed-on: https://gerrit.libreoffice.org/c/core/+/188937 Reviewed-by: Michael Weghorn <m.wegh...@posteo.de> Tested-by: Jenkins diff --git a/cui/source/dialogs/SpellDialog.cxx b/cui/source/dialogs/SpellDialog.cxx index 4a43b37ca9a0..2986c60af2d1 100644 --- a/cui/source/dialogs/SpellDialog.cxx +++ b/cui/source/dialogs/SpellDialog.cxx @@ -162,7 +162,6 @@ SpellDialog::SpellDialog(SpellDialogChildWindow* pChildWindow, : SfxModelessDialogController (_pBindings, pChildWindow, pParent, u"cui/ui/spellingdialog.ui"_ustr, u"SpellingDialog"_ustr) , aDialogUndoLink(LINK (this, SpellDialog, DialogUndoHdl)) - , m_pInitHdlEvent(nullptr) , bFocusLocked(true) , rParent(*pChildWindow) , pImpl( new SpellDialog_Impl ) @@ -198,11 +197,6 @@ SpellDialog::SpellDialog(SpellDialogChildWindow* pChildWindow, m_sTitleSpellingGrammar = m_xDialog->get_title(); m_sTitleSpelling = m_xAltTitle->get_label(); - // fdo#68794 set initial title for cases where no text has been processed - // yet to show its language attributes - OUString sTitle = rParent.HasGrammarChecking() ? m_sTitleSpellingGrammar : m_sTitleSpelling; - m_xDialog->set_title(m_xDialog->strip_mnemonic(sTitle.replaceFirst("$LANGUAGE ($LOCATION)", ""))); - m_sResumeST = m_xResumeFT->get_label(); m_sNoSuggestionsST = m_xNoSuggestionsFT->strip_mnemonic(m_xNoSuggestionsFT->get_label()); @@ -244,10 +238,8 @@ SpellDialog::SpellDialog(SpellDialogChildWindow* pChildWindow, // disable controls if service is missing m_xDialog->set_sensitive(xSpell.is()); - //InitHdl wants to use virtual methods, so it - //can't be called during the ctor, so init - //it on next event cycle post-ctor - m_pInitHdlEvent = Application::PostUserEvent(LINK(this, SpellDialog, InitHdl)); + // further initialization happens in Initialize() to prevent virtual + // calls from the constructor } SpellDialog::~SpellDialog() @@ -258,8 +250,6 @@ SpellDialog::~SpellDialog() m_xOptionsDlg.reset(); } - if (m_pInitHdlEvent) - Application::RemoveUserEvent(m_pInitHdlEvent); if (pImpl) { // save possibly modified user-dictionaries @@ -376,12 +366,9 @@ void SpellDialog::SpellContinue_Impl(std::unique_ptr<UndoChangeGroupGuard>* pGua m_xUndoPB->set_sensitive(false); } } -/* Initialize, asynchronous to prevent virtual calls - from a constructor - */ -IMPL_LINK_NOARG( SpellDialog, InitHdl, void*, void) + +void SpellDialog::Initialize() { - m_pInitHdlEvent = nullptr; m_xDialog->freeze(); //show or hide AutoCorrect depending on the modules abilities m_xAutoCorrPB->set_visible(rParent.HasAutoCorrection()); diff --git a/cui/source/factory/dlgfact.cxx b/cui/source/factory/dlgfact.cxx index 58ee73e78163..d7e58ebac577 100644 --- a/cui/source/factory/dlgfact.cxx +++ b/cui/source/factory/dlgfact.cxx @@ -450,6 +450,7 @@ class AbstractSpellDialog_Impl final { public: using AbstractDialogImpl_BASE::AbstractDialogImpl_BASE; + void Initialize() override { m_pDlg->Initialize(); } void InvalidateDialog() override { m_pDlg->InvalidateDialog(); } std::shared_ptr<SfxDialogController> GetController() override { return m_pDlg; } SfxBindings& GetBindings() override { return m_pDlg->GetBindings(); } diff --git a/cui/source/inc/SpellDialog.hxx b/cui/source/inc/SpellDialog.hxx index 9f43d2e774fc..f868f42a70cb 100644 --- a/cui/source/inc/SpellDialog.hxx +++ b/cui/source/inc/SpellDialog.hxx @@ -139,7 +139,6 @@ private: OUString m_sTitleSpellingGrammar; Link<SpellUndoAction_Impl&,void> aDialogUndoLink; - ImplSVEvent * m_pInitHdlEvent; bool bFocusLocked; svx::SpellDialogChildWindow& rParent; @@ -193,8 +192,6 @@ private: DECL_LINK(LanguageSelectHdl, weld::ComboBox&, void); DECL_LINK(DialogUndoHdl, SpellUndoAction_Impl&, void); - DECL_LINK(InitHdl, void*, void); - void AddToDictionaryExecute(const OUString& rItemId); void StartSpellOptDlg_Impl(); int InitUserDicts(); @@ -225,6 +222,10 @@ public: SfxBindings* pBindings); virtual ~SpellDialog() override; + /* Initialize, to be called after the constructor to prevent virtual calls + from a constructor. */ + void Initialize(); + virtual void Activate() override; virtual void Deactivate() override; diff --git a/include/sfx2/childwin.hxx b/include/sfx2/childwin.hxx index c52a1258b700..86cfe0aefdab 100644 --- a/include/sfx2/childwin.hxx +++ b/include/sfx2/childwin.hxx @@ -113,6 +113,9 @@ protected: public: virtual ~SfxChildWindow(); + // subclasses can override this in case any initialization needs to happen after + // the constructor was called (e.g. to avoid calling virtual methods in the constructor) + virtual void Initialize() {} void Destroy(); vcl::Window* GetWindow() const { return pWindow; } diff --git a/include/svx/SpellDialogChildWindow.hxx b/include/svx/SpellDialogChildWindow.hxx index 0e6ed66616b0..9b50e5c8fc71 100644 --- a/include/svx/SpellDialogChildWindow.hxx +++ b/include/svx/SpellDialogChildWindow.hxx @@ -49,6 +49,8 @@ public: SpellDialogChildWindow(vcl::Window* pParent, sal_uInt16 nId, SfxBindings* pBindings); virtual ~SpellDialogChildWindow() override; + virtual void Initialize() override; + protected: /** This abstract method has to be defined by a derived class. It returns the next wrong sentence. diff --git a/include/svx/svxdlg.hxx b/include/svx/svxdlg.hxx index d3d844977493..5fe7b1eaf70e 100644 --- a/include/svx/svxdlg.hxx +++ b/include/svx/svxdlg.hxx @@ -75,6 +75,7 @@ class AbstractSpellDialog : public VclAbstractDialog protected: virtual ~AbstractSpellDialog() override = default; public: + virtual void Initialize() = 0; virtual void InvalidateDialog() = 0; virtual std::shared_ptr<SfxDialogController> GetController() = 0; virtual SfxBindings& GetBindings() = 0; diff --git a/sfx2/source/appl/childwin.cxx b/sfx2/source/appl/childwin.cxx index 82dc3f3a66fc..fb35a06acf63 100644 --- a/sfx2/source/appl/childwin.cxx +++ b/sfx2/source/appl/childwin.cxx @@ -219,6 +219,7 @@ std::unique_ptr<SfxChildWindow> SfxChildWindow::CreateChildWindow( sal_uInt16 nI SfxChildWinInfo aInfo = rInfo; Application::SetSystemWindowMode( SystemWindowFlags::NOAUTOMODE ); pChild = pFact->pCtor( pParent, nId, pBindings, &aInfo ); + pChild->Initialize(); Application::SetSystemWindowMode( nOldMode ); if ( pBindings ) pBindings->LEAVEREGISTRATIONS(); @@ -240,6 +241,7 @@ std::unique_ptr<SfxChildWindow> SfxChildWindow::CreateChildWindow( sal_uInt16 nI SfxChildWinInfo aInfo = rInfo; Application::SetSystemWindowMode( SystemWindowFlags::NOAUTOMODE ); pChild = pFact->pCtor( pParent, nId, pBindings, &aInfo ); + pChild->Initialize(); rInfo.nFlags |= aInfo.nFlags; Application::SetSystemWindowMode( nOldMode ); if ( pBindings ) diff --git a/svx/source/dialog/SpellDialogChildWindow.cxx b/svx/source/dialog/SpellDialogChildWindow.cxx index ae82769dfb9b..76f0803a2b63 100644 --- a/svx/source/dialog/SpellDialogChildWindow.cxx +++ b/svx/source/dialog/SpellDialogChildWindow.cxx @@ -44,6 +44,11 @@ SpellDialogChildWindow::~SpellDialogChildWindow() m_xAbstractSpellDialog.disposeAndClear(); } +void SpellDialogChildWindow::Initialize() +{ + m_xAbstractSpellDialog->Initialize(); +} + SfxBindings& SpellDialogChildWindow::GetBindings() const { assert(m_xAbstractSpellDialog); commit 82422565eb8f0e2cab33c82fd8feba60249a0d32 Author: Michael Weghorn <m.wegh...@posteo.de> AuthorDate: Tue Aug 5 10:34:48 2025 +0200 Commit: Michael Weghorn <m.wegh...@posteo.de> CommitDate: Tue Aug 5 23:47:09 2025 +0200 tdf#163802 Merge SpellDialog::Init_Impl into ctor This is also to make a bit more obvious what part of the initialization happens in the ctor and what only happens later: The (similarly named) SpellDialog::InitHdl only gets called later. (And the logic for the latter will change and it will be renamed to SpellDialog::Initialize in an upcoming commit.) Change-Id: Ia2647eddb15163f92c10b0ae9396b54895a38192 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/188936 Tested-by: Jenkins Reviewed-by: Michael Weghorn <m.wegh...@posteo.de> diff --git a/cui/source/dialogs/SpellDialog.cxx b/cui/source/dialogs/SpellDialog.cxx index e03eb313427c..4a43b37ca9a0 100644 --- a/cui/source/dialogs/SpellDialog.cxx +++ b/cui/source/dialogs/SpellDialog.cxx @@ -213,7 +213,33 @@ SpellDialog::SpellDialog(SpellDialogChildWindow* pChildWindow, m_xAddToDictMB->set_help_id(m_xAddToDictPB->get_help_id()); xSpell = LinguMgr::GetSpellChecker(); - Init_Impl(); + // initialize handler + m_xClosePB->connect_clicked(LINK( this, SpellDialog, CancelHdl ) ); + m_xChangePB->connect_clicked(LINK( this, SpellDialog, ChangeHdl ) ); + m_xChangeAllPB->connect_clicked(LINK( this, SpellDialog, ChangeAllHdl ) ); + m_xIgnorePB->connect_clicked(LINK( this, SpellDialog, IgnoreHdl ) ); + m_xIgnoreAllPB->connect_clicked(LINK( this, SpellDialog, IgnoreAllHdl ) ); + m_xIgnoreRulePB->connect_clicked(LINK( this, SpellDialog, IgnoreAllHdl ) ); + m_xUndoPB->connect_clicked(LINK( this, SpellDialog, UndoHdl ) ); + + m_xAutoCorrPB->connect_clicked( LINK( this, SpellDialog, ExtClickHdl ) ); + m_xCheckGrammarCB->connect_toggled( LINK( this, SpellDialog, CheckGrammarHdl )); + m_xOptionsPB->connect_clicked( LINK( this, SpellDialog, ExtClickHdl ) ); + + m_xSuggestionLB->connect_row_activated( LINK( this, SpellDialog, DoubleClickChangeHdl ) ); + + m_xSentenceED->SetModifyHdl(LINK ( this, SpellDialog, ModifyHdl) ); + + m_xAddToDictMB->connect_selected(LINK ( this, SpellDialog, AddToDictSelectHdl ) ); + m_xAddToDictPB->connect_clicked(LINK ( this, SpellDialog, AddToDictClickHdl ) ); + + m_xLanguageLB->connect_changed(LINK( this, SpellDialog, LanguageSelectHdl ) ); + + // initialize language ListBox + m_xLanguageLB->SetLanguageList(SvxLanguageListFlags::SPELL_USED, false, false, true); + + m_xSentenceED->ClearModifyFlag(); + LinguMgr::GetChangeAllList()->clear(); // disable controls if service is missing m_xDialog->set_sensitive(xSpell.is()); @@ -245,37 +271,6 @@ SpellDialog::~SpellDialog() } } -void SpellDialog::Init_Impl() -{ - // initialize handler - m_xClosePB->connect_clicked(LINK( this, SpellDialog, CancelHdl ) ); - m_xChangePB->connect_clicked(LINK( this, SpellDialog, ChangeHdl ) ); - m_xChangeAllPB->connect_clicked(LINK( this, SpellDialog, ChangeAllHdl ) ); - m_xIgnorePB->connect_clicked(LINK( this, SpellDialog, IgnoreHdl ) ); - m_xIgnoreAllPB->connect_clicked(LINK( this, SpellDialog, IgnoreAllHdl ) ); - m_xIgnoreRulePB->connect_clicked(LINK( this, SpellDialog, IgnoreAllHdl ) ); - m_xUndoPB->connect_clicked(LINK( this, SpellDialog, UndoHdl ) ); - - m_xAutoCorrPB->connect_clicked( LINK( this, SpellDialog, ExtClickHdl ) ); - m_xCheckGrammarCB->connect_toggled( LINK( this, SpellDialog, CheckGrammarHdl )); - m_xOptionsPB->connect_clicked( LINK( this, SpellDialog, ExtClickHdl ) ); - - m_xSuggestionLB->connect_row_activated( LINK( this, SpellDialog, DoubleClickChangeHdl ) ); - - m_xSentenceED->SetModifyHdl(LINK ( this, SpellDialog, ModifyHdl) ); - - m_xAddToDictMB->connect_selected(LINK ( this, SpellDialog, AddToDictSelectHdl ) ); - m_xAddToDictPB->connect_clicked(LINK ( this, SpellDialog, AddToDictClickHdl ) ); - - m_xLanguageLB->connect_changed(LINK( this, SpellDialog, LanguageSelectHdl ) ); - - // initialize language ListBox - m_xLanguageLB->SetLanguageList(SvxLanguageListFlags::SPELL_USED, false, false, true); - - m_xSentenceED->ClearModifyFlag(); - LinguMgr::GetChangeAllList()->clear(); -} - void SpellDialog::UpdateBoxes_Impl(bool bCallFromSelectHdl) { m_xSuggestionLB->clear(); diff --git a/cui/source/inc/SpellDialog.hxx b/cui/source/inc/SpellDialog.hxx index d09e813e4bbc..9f43d2e774fc 100644 --- a/cui/source/inc/SpellDialog.hxx +++ b/cui/source/inc/SpellDialog.hxx @@ -199,7 +199,6 @@ private: void StartSpellOptDlg_Impl(); int InitUserDicts(); void UpdateBoxes_Impl(bool bCallFromSelectHdl = false); - void Init_Impl(); void SpellContinue_Impl(std::unique_ptr<UndoChangeGroupGuard>* pGuard = nullptr, bool UseSavedSentence = false, bool bIgnoreCurrentError = false ); void LockFocusChanges( bool bLock ) {bFocusLocked = bLock;} void ToplevelFocusChanged();