include/sfx2/objsh.hxx | 2 sc/source/ui/docshell/docsh.cxx | 2 sc/source/ui/inc/docsh.hxx | 2 sfx2/source/doc/objxtor.cxx | 2 sw/inc/docsh.hxx | 2 sw/qa/extras/tiledrendering/tiledrendering.cxx | 95 ++++++++++++++++++++++++- sw/source/uibase/app/docsh.cxx | 22 +++++ sw/source/uibase/uiview/viewstat.cxx | 10 ++ 8 files changed, 127 insertions(+), 10 deletions(-)
New commits: commit d17f6bef972a858165dd6773c74f828de3176045 Author: Miklos Vajna <vmik...@collabora.com> AuthorDate: Wed Mar 5 08:22:14 2025 +0100 Commit: Caolán McNamara <caolan.mcnam...@collabora.com> CommitDate: Wed Mar 5 09:41:41 2025 +0100 cool#11226 sw per-view redline on: state for the per-view and per-doc commands The UI for recording is one toolbar button (toggle) with a dropdown that has 2 radio buttons. The expectation is that once the toggle is on, one of the radio buttons report enabled state. So this is a tri-state: the record status is off, per-view or per-doc on the UI. At a code level, we have a per-doc flag and we have a boolean in each view. The requirements: 1) Compatibility: if the dropdown menu is not used, then clicking on the old toggle should enable recording for all views & once it's enabled (either per-view or per-doc), clicking on it should disable the recording. 2) If recording is on, the exactly one of per-view or per-doc radio button in the submenu should report enabled status. This leads to a not entirely symmetric behavior if you compare the enable, status and disable parts of this commands, and this is wanted to result in the least amount of surprise. Fix the problem by: 1) Enable: the toolbar button enables recording for all views (to stay compatible), the dropdown menu allows choosing between this view vs all views. 2) Status: the toolbar button reports enabled status if recording is on for this view for any reason; the "this view" command reports enabled status if recording is on in this view but not in all views; the "all views" command reports enabled status if recording is on in this view and no other view has it disabled. 3) Disable is almost the opposite of enabled, but the toolbar toggle can always disable recording, so a per-view -> off -> all-views transition is possible by clicking on the toggle twice. This required a single change to testTrackChangesPerViewEnableOne, because now going back to the no-record state is doable by clicking on the enabled toolbar button, i.e. by dispatching the .uno:TrackChanges command; dispatching .uno:TrackChangesInThisView always changes to the per-view recording. Change-Id: Iacc984f832b4c08e0e100a67774e1e559729d82a Reviewed-on: https://gerrit.libreoffice.org/c/core/+/182517 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Tested-by: Caolán McNamara <caolan.mcnam...@collabora.com> Reviewed-by: Caolán McNamara <caolan.mcnam...@collabora.com> diff --git a/include/sfx2/objsh.hxx b/include/sfx2/objsh.hxx index 4b245a63a057..4206e26dc8bb 100644 --- a/include/sfx2/objsh.hxx +++ b/include/sfx2/objsh.hxx @@ -688,7 +688,7 @@ public: // change recording and respective passwword protection for Writer and Calc // slots available for Writer: FN_REDLINE_ON, FN_REDLINE_ON // slots used for Calc: FID_CHG_RECORD, SID_CHG_PROTECT - virtual bool IsChangeRecording(SfxViewShell* pViewShell = nullptr) const; + virtual bool IsChangeRecording(SfxViewShell* pViewShell = nullptr, bool bRecordAllViews = true) const; virtual bool HasChangeRecordProtection() const; virtual void SetChangeRecording( bool bActivate, bool bLockAllViews = false, bool bRecordAllViews = true ); virtual void SetProtectionPassword( const OUString &rPassword ); diff --git a/sc/source/ui/docshell/docsh.cxx b/sc/source/ui/docshell/docsh.cxx index 3720fa2ee685..3e13572220dc 100644 --- a/sc/source/ui/docshell/docsh.cxx +++ b/sc/source/ui/docshell/docsh.cxx @@ -3368,7 +3368,7 @@ void ScDocShellModificator::SetDocumentModified() } } -bool ScDocShell::IsChangeRecording(SfxViewShell* /*pViewShell*/) const +bool ScDocShell::IsChangeRecording(SfxViewShell* /*pViewShell*/, bool /*bRecordAllViews*/) const { ScChangeTrack* pChangeTrack = m_pDocument->GetChangeTrack(); return pChangeTrack != nullptr; diff --git a/sc/source/ui/inc/docsh.hxx b/sc/source/ui/inc/docsh.hxx index 127700949474..515ee1e4fb0f 100644 --- a/sc/source/ui/inc/docsh.hxx +++ b/sc/source/ui/inc/docsh.hxx @@ -425,7 +425,7 @@ public: // password protection for Calc (derived from SfxObjectShell) // see also: FID_CHG_RECORD, SID_CHG_PROTECT - virtual bool IsChangeRecording(SfxViewShell* pViewShell = nullptr) const override; + virtual bool IsChangeRecording(SfxViewShell* pViewShell = nullptr, bool bRecordAllViews = true) const override; virtual bool HasChangeRecordProtection() const override; virtual void SetChangeRecording( bool bActivate, bool bLockAllViews = false, bool bRecordAllViews = true ) override; virtual void SetProtectionPassword( const OUString &rPassword ) override; diff --git a/sfx2/source/doc/objxtor.cxx b/sfx2/source/doc/objxtor.cxx index ec1441aad050..6be353114b0b 100644 --- a/sfx2/source/doc/objxtor.cxx +++ b/sfx2/source/doc/objxtor.cxx @@ -1092,7 +1092,7 @@ void SfxObjectShell::SetInitialized_Impl( const bool i_fromInitNew ) } -bool SfxObjectShell::IsChangeRecording(SfxViewShell* /*pViewShell*/) const +bool SfxObjectShell::IsChangeRecording(SfxViewShell* /*pViewShell*/, bool /*bRecordAllViews*/) const { // currently this function needs to be overwritten by Writer and Calc only SAL_WARN( "sfx.doc", "function not implemented" ); diff --git a/sw/inc/docsh.hxx b/sw/inc/docsh.hxx index 5140bd9daa99..bbebb8c2c386 100644 --- a/sw/inc/docsh.hxx +++ b/sw/inc/docsh.hxx @@ -321,7 +321,7 @@ public: /** passwword protection for Writer (derived from SfxObjectShell) see also: FN_REDLINE_ON, FN_REDLINE_ON */ - virtual bool IsChangeRecording(SfxViewShell* pViewShell = nullptr) const override; + virtual bool IsChangeRecording(SfxViewShell* pViewShell = nullptr, bool bRecordAllViews = true) const override; virtual bool HasChangeRecordProtection() const override; virtual void SetChangeRecording( bool bActivate, bool bLockAllViews = false, bool bRecordAllViews = true ) override; virtual void SetProtectionPassword( const OUString &rPassword ) override; diff --git a/sw/qa/extras/tiledrendering/tiledrendering.cxx b/sw/qa/extras/tiledrendering/tiledrendering.cxx index 030bb6a589a8..1046c9ea27fe 100644 --- a/sw/qa/extras/tiledrendering/tiledrendering.cxx +++ b/sw/qa/extras/tiledrendering/tiledrendering.cxx @@ -4860,7 +4860,7 @@ CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTrackChangesPerViewEnableOne) CPPUNIT_ASSERT(aRecord2.empty()); // And given a reset state (both view1 and view2 recording is disabled): - comphelper::dispatchCommand(".uno:TrackChangesInThisView", {}); + comphelper::dispatchCommand(".uno:TrackChanges", {}); // When recording changes in view2: SfxLokHelper::setView(nView2); @@ -5004,6 +5004,99 @@ CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTrackChangesPerDocInsert) CPPUNIT_ASSERT_EQUAL(static_cast<SwRedlineTable::size_type>(2), pWrtShell2->GetRedlineCount()); } +CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTrackChangesStates) +{ + // Given a document with 2 views: + SwXTextDocument* pXTextDocument = createDoc(); + CPPUNIT_ASSERT(pXTextDocument); + ViewCallback aView1; + int nView1 = SfxLokHelper::getView(); + SwView* pView1 = pXTextDocument->GetDocShell()->GetView(); + SfxLokHelper::createView(); + ViewCallback aView2; + SwView* pView2 = pXTextDocument->GetDocShell()->GetView(); + SfxLokHelper::setView(nView1); + + // When enabling recording in view1 only: + pView1->GetViewFrame().GetDispatcher()->Execute(FN_TRACK_CHANGES_IN_THIS_VIEW, + SfxCallMode::SYNCHRON); + + // Then make sure the "is record", "is record for this view on" and "is record for all views on" + // statuses are correct: + std::unique_ptr<SfxPoolItem> pItem; + pView1->GetViewFrame().GetBindings().QueryState(FN_REDLINE_ON, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_REDLINE_ON), pItem->Which()); + // Without the accompanying fix in place, this test would have failed, enabling recording for + // this view didn't enable the toolbar button in the same view, which looked confusing. + CPPUNIT_ASSERT(dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + pView1->GetViewFrame().GetBindings().QueryState(FN_TRACK_CHANGES_IN_THIS_VIEW, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_TRACK_CHANGES_IN_THIS_VIEW), pItem->Which()); + CPPUNIT_ASSERT(dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + pView1->GetViewFrame().GetBindings().QueryState(FN_TRACK_CHANGES_IN_ALL_VIEWS, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_TRACK_CHANGES_IN_ALL_VIEWS), pItem->Which()); + CPPUNIT_ASSERT(!dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + pView2->GetViewFrame().GetBindings().QueryState(FN_REDLINE_ON, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_REDLINE_ON), pItem->Which()); + CPPUNIT_ASSERT(!dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + pView2->GetViewFrame().GetBindings().QueryState(FN_TRACK_CHANGES_IN_THIS_VIEW, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_TRACK_CHANGES_IN_THIS_VIEW), pItem->Which()); + CPPUNIT_ASSERT(!dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + pView2->GetViewFrame().GetBindings().QueryState(FN_TRACK_CHANGES_IN_ALL_VIEWS, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_TRACK_CHANGES_IN_ALL_VIEWS), pItem->Which()); + CPPUNIT_ASSERT(!dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + + // When disabling recording: + SfxBoolItem aOn(FN_REDLINE_ON, false); + pView1->GetViewFrame().GetDispatcher()->ExecuteList(FN_REDLINE_ON, SfxCallMode::SYNCHRON, + { &aOn }); + + // Then make sure the "is record", "is record for this view on" and "is record for all views on" + // statuses are correct: + pView1->GetViewFrame().GetBindings().QueryState(FN_REDLINE_ON, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_REDLINE_ON), pItem->Which()); + CPPUNIT_ASSERT(!dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + pView1->GetViewFrame().GetBindings().QueryState(FN_TRACK_CHANGES_IN_THIS_VIEW, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_TRACK_CHANGES_IN_THIS_VIEW), pItem->Which()); + CPPUNIT_ASSERT(!dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + pView1->GetViewFrame().GetBindings().QueryState(FN_TRACK_CHANGES_IN_ALL_VIEWS, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_TRACK_CHANGES_IN_ALL_VIEWS), pItem->Which()); + CPPUNIT_ASSERT(!dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + pView2->GetViewFrame().GetBindings().QueryState(FN_REDLINE_ON, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_REDLINE_ON), pItem->Which()); + CPPUNIT_ASSERT(!dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + pView2->GetViewFrame().GetBindings().QueryState(FN_TRACK_CHANGES_IN_THIS_VIEW, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_TRACK_CHANGES_IN_THIS_VIEW), pItem->Which()); + CPPUNIT_ASSERT(!dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + pView2->GetViewFrame().GetBindings().QueryState(FN_TRACK_CHANGES_IN_ALL_VIEWS, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_TRACK_CHANGES_IN_ALL_VIEWS), pItem->Which()); + CPPUNIT_ASSERT(!dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + + // When enabling recording in all views: + pView1->GetViewFrame().GetDispatcher()->Execute(FN_TRACK_CHANGES_IN_ALL_VIEWS, + SfxCallMode::SYNCHRON); + + // Then make sure the "is record", "is record for this view on" and "is record for all views on" + // statuses are correct: + pView1->GetViewFrame().GetBindings().QueryState(FN_REDLINE_ON, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_REDLINE_ON), pItem->Which()); + CPPUNIT_ASSERT(dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + pView1->GetViewFrame().GetBindings().QueryState(FN_TRACK_CHANGES_IN_THIS_VIEW, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_TRACK_CHANGES_IN_THIS_VIEW), pItem->Which()); + CPPUNIT_ASSERT(!dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + pView1->GetViewFrame().GetBindings().QueryState(FN_TRACK_CHANGES_IN_ALL_VIEWS, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_TRACK_CHANGES_IN_ALL_VIEWS), pItem->Which()); + CPPUNIT_ASSERT(dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + pView2->GetViewFrame().GetBindings().QueryState(FN_REDLINE_ON, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_REDLINE_ON), pItem->Which()); + CPPUNIT_ASSERT(dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + pView2->GetViewFrame().GetBindings().QueryState(FN_TRACK_CHANGES_IN_THIS_VIEW, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_TRACK_CHANGES_IN_THIS_VIEW), pItem->Which()); + CPPUNIT_ASSERT(!dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); + pView2->GetViewFrame().GetBindings().QueryState(FN_TRACK_CHANGES_IN_ALL_VIEWS, pItem); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_uInt16>(FN_TRACK_CHANGES_IN_ALL_VIEWS), pItem->Which()); + CPPUNIT_ASSERT(dynamic_cast<SfxBoolItem*>(pItem.get())->GetValue()); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/source/uibase/app/docsh.cxx b/sw/source/uibase/app/docsh.cxx index f71093addeb4..5f20ca5b1825 100644 --- a/sw/source/uibase/app/docsh.cxx +++ b/sw/source/uibase/app/docsh.cxx @@ -1346,7 +1346,7 @@ const ::sfx2::IXmlIdRegistry* SwDocShell::GetXmlIdRegistry() const return m_xDoc ? &m_xDoc->GetXmlIdRegistry() : nullptr; } -bool SwDocShell::IsChangeRecording(SfxViewShell* pViewShell) const +bool SwDocShell::IsChangeRecording(SfxViewShell* pViewShell, bool bRecordAllViews) const { SwWrtShell* pWrtShell = nullptr; auto pView = dynamic_cast<SwView*>(pViewShell); @@ -1361,7 +1361,25 @@ bool SwDocShell::IsChangeRecording(SfxViewShell* pViewShell) const if (!pWrtShell) return false; - return bool(pWrtShell->GetRedlineFlags() & RedlineFlags::On); + + auto bOn = bool(pWrtShell->GetRedlineFlags() & RedlineFlags::On); + if (bOn) + { + if (bRecordAllViews) + { + for (SwViewShell& rSh : pWrtShell->GetRingContainer()) + { + if (!rSh.GetViewOptions()->IsRedlineRecordingOn()) + { + return false; + } + } + } + + return true; + } + + return false; } bool SwDocShell::HasChangeRecordProtection() const diff --git a/sw/source/uibase/uiview/viewstat.cxx b/sw/source/uibase/uiview/viewstat.cxx index fd568e6d7a1e..84d6b1ece4e6 100644 --- a/sw/source/uibase/uiview/viewstat.cxx +++ b/sw/source/uibase/uiview/viewstat.cxx @@ -325,7 +325,8 @@ void SwView::GetState(SfxItemSet &rSet) } break; case FN_REDLINE_ON: - rSet.Put( SfxBoolItem( nWhich, GetDocShell()->IsChangeRecording(this) ) ); + // Enabled at least in this view. + rSet.Put( SfxBoolItem( nWhich, GetDocShell()->IsChangeRecording(this, /*bRecordAllViews=*/false) ) ); // When the view is new (e.g. on load), show the Hidden Track Changes infobar // if Show Changes is disabled, but recording of changes is enabled // or hidden tracked changes are there already in the document. @@ -333,7 +334,7 @@ void SwView::GetState(SfxItemSet &rSet) // enabled, see in sfx2. if ( m_bForceChangesToolbar && m_pWrtShell->GetLayout()->IsHideRedlines() ) { - bool isRecording = GetDocShell()->IsChangeRecording(this); + bool isRecording = GetDocShell()->IsChangeRecording(this, /*bRecordAllViews=*/false); bool hasRecorded = m_pWrtShell->GetDoc()->getIDocumentRedlineAccess().GetRedlineTable().size(); if ( isRecording || hasRecorded ) @@ -352,10 +353,15 @@ void SwView::GetState(SfxItemSet &rSet) break; case FN_TRACK_CHANGES_IN_THIS_VIEW: { + // Enabled in this view, but not in all views. + bool bOn = GetDocShell()->IsChangeRecording(this, /*bRecordAllViews=*/false) && !GetDocShell()->IsChangeRecording(this); + rSet.Put(SfxBoolItem(nWhich, bOn)); } break; case FN_TRACK_CHANGES_IN_ALL_VIEWS: { + // Enabled in all views. + rSet.Put(SfxBoolItem(nWhich, GetDocShell()->IsChangeRecording(this))); } break; case FN_REDLINE_PROTECT :