include/svx/rubydialog.hxx | 2 svx/source/dialog/rubydialog.cxx | 150 ++++++++++++++++++++- svx/uiconfig/ui/asianphoneticguidedialog.ui | 14 ++ sw/qa/core/uwriter.cxx | 195 ++++++++++++++++++++++++++-- sw/source/core/doc/docruby.cxx | 120 ++++++++--------- 5 files changed, 401 insertions(+), 80 deletions(-)
New commits: commit 5a45f7925b8c88baeb23ee253491888bfa6233cc Author: Jonathan Clark <jonat...@libreoffice.org> AuthorDate: Thu Sep 12 06:47:53 2024 -0600 Commit: Jonathan Clark <jonat...@libreoffice.org> CommitDate: Thu Sep 12 21:51:39 2024 +0200 tdf#156543 sw: Added base text mono feature to Asian Phonetic Guide This change adds a new button, Mono, to the Asian Phonetic Guide. Clicking on this button will automatically separate each base text character into its own base text run. Change-Id: I973e2c3259918db59e46dc7b89cb7e8ee4f45469 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/173276 Tested-by: Jenkins Reviewed-by: Jonathan Clark <jonat...@libreoffice.org> diff --git a/include/svx/rubydialog.hxx b/include/svx/rubydialog.hxx index 30b284ee0fbd..9b996527a391 100644 --- a/include/svx/rubydialog.hxx +++ b/include/svx/rubydialog.hxx @@ -84,6 +84,7 @@ class SvxRubyDialog final : public SfxModelessDialogController std::unique_ptr<weld::Button> m_xStylistPB; std::unique_ptr<weld::Button> m_xSelectionGroupPB; + std::unique_ptr<weld::Button> m_xSelectionMonoPB; std::unique_ptr<weld::Button> m_xApplyPB; std::unique_ptr<weld::Button> m_xClosePB; @@ -95,6 +96,7 @@ class SvxRubyDialog final : public SfxModelessDialogController std::unique_ptr<weld::CustomWeld> m_xPreview; DECL_LINK(SelectionGroup_Impl, weld::Button&, void); + DECL_LINK(SelectionMono_Impl, weld::Button&, void); DECL_LINK(ApplyHdl_Impl, weld::Button&, void); DECL_LINK(CloseHdl_Impl, weld::Button&, void); DECL_LINK(StylistHdl_Impl, weld::Button&, void); diff --git a/svx/source/dialog/rubydialog.cxx b/svx/source/dialog/rubydialog.cxx index f85a395029b0..8f19b1598a6f 100644 --- a/svx/source/dialog/rubydialog.cxx +++ b/svx/source/dialog/rubydialog.cxx @@ -20,6 +20,7 @@ #include <sal/config.h> #include <tools/debug.hxx> #include <comphelper/diagnose_ex.hxx> +#include <comphelper/processfactory.hxx> #include <svx/rubydialog.hxx> #include <sfx2/dispatch.hxx> @@ -38,6 +39,8 @@ #include <com/sun/star/text/RubyAdjust.hpp> #include <com/sun/star/view/XSelectionChangeListener.hpp> #include <com/sun/star/view/XSelectionSupplier.hpp> +#include <com/sun/star/i18n/BreakIterator.hpp> +#include <com/sun/star/i18n/CharacterIteratorMode.hpp> #include <cppuhelper/implbase.hxx> #include <svtools/colorcfg.hxx> #include <vcl/event.hxx> @@ -80,6 +83,7 @@ SfxChildWinInfo SvxRubyChildWindow::GetInfo() const { return SfxChildWindow::Get class SvxRubyData_Impl : public cppu::WeakImplHelper<css::view::XSelectionChangeListener> { + Reference<css::i18n::XBreakIterator> xBreak; Reference<XModel> xModel; Reference<XRubySelection> xSelection; Sequence<PropertyValues> aRubyValues; @@ -132,14 +136,14 @@ public: OUString sBaseTmp; OUStringBuffer aBaseString; - for (const PropertyValues& pVals : aRubyValues) + for (const PropertyValues& rVals : aRubyValues) { sBaseTmp.clear(); - for (const PropertyValue& pVal : pVals) + for (const PropertyValue& rVal : rVals) { - if (pVal.Name == cRubyBaseText) + if (rVal.Name == cRubyBaseText) { - pVal.Value >>= sBaseTmp; + rVal.Value >>= sBaseTmp; } } @@ -151,16 +155,16 @@ public: // Copy some reasonable style values from the previous ruby array pNewRubyValues[0] = aRubyValues[0]; - for (const PropertyValues& pVals : aRubyValues) + for (const PropertyValues& rVals : aRubyValues) { - for (const PropertyValue& pVal : pVals) + for (const PropertyValue& rVal : rVals) { - if (pVal.Name == cRubyText) + if (rVal.Name == cRubyText) { - pVal.Value >>= sBaseTmp; + rVal.Value >>= sBaseTmp; if (!sBaseTmp.isEmpty()) { - pNewRubyValues[0] = pVals; + pNewRubyValues[0] = rVals; break; } } @@ -184,12 +188,131 @@ public: aRubyValues = std::move(aNewRubyValues); } + + bool IsSelectionMono() + { + if (!xBreak.is()) + { + // Cannot continue if BreakIterator is not available + // Disable the button + return true; + } + + // Locale does not matter in this case; default ICU BreakIterator is sufficient + Locale aLocale; + + OUString sBaseTmp; + return std::all_of( + aRubyValues.begin(), aRubyValues.end(), [&](const PropertyValues& rVals) { + return !std::any_of(rVals.begin(), rVals.end(), [&](const PropertyValue& rVal) { + if (rVal.Name == cRubyBaseText) + { + rVal.Value >>= sBaseTmp; + sal_Int32 nDone = 0; + auto nPos = xBreak->nextCharacters( + sBaseTmp, 0, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 1, + nDone); + return nPos < sBaseTmp.getLength(); + } + + return false; + }); + }); + } + + void MakeSelectionMono() + { + if (!xBreak.is()) + { + // Cannot continue if BreakIterator is not available + return; + } + + // Locale does not matter in this case; default ICU BreakIterator is sufficient + Locale aLocale; + + OUString sBaseTmp; + + // Count the grapheme clusters + sal_Int32 nTotalGraphemeClusters = 0; + for (const PropertyValues& rVals : aRubyValues) + { + for (const PropertyValue& rVal : rVals) + { + if (rVal.Name == cRubyBaseText) + { + rVal.Value >>= sBaseTmp; + + sal_Int32 nPos = 0; + while (nPos < sBaseTmp.getLength()) + { + sal_Int32 nDone = 0; + nPos = xBreak->nextCharacters(sBaseTmp, nPos, aLocale, + css::i18n::CharacterIteratorMode::SKIPCELL, 1, + nDone); + ++nTotalGraphemeClusters; + } + } + } + } + + // Put each grapheme cluster in its own entry + Sequence<PropertyValues> aNewRubyValues{ nTotalGraphemeClusters }; + PropertyValues* pNewRubyValues = aNewRubyValues.getArray(); + + sal_Int32 nCurrGraphemeCluster = 0; + for (const PropertyValues& rVals : aRubyValues) + { + for (const PropertyValue& rVal : rVals) + { + if (rVal.Name == cRubyBaseText) + { + rVal.Value >>= sBaseTmp; + + sal_Int32 nPos = 0; + while (nPos < sBaseTmp.getLength()) + { + sal_Int32 nDone = 0; + auto nNextPos = xBreak->nextCharacters( + sBaseTmp, nPos, aLocale, css::i18n::CharacterIteratorMode::SKIPCELL, 1, + nDone); + + PropertyValues& rNewVals = pNewRubyValues[nCurrGraphemeCluster++]; + + // Initialize new property values with values from current run + rNewVals = rVals; + + PropertyValue* aNewVals = rNewVals.getArray(); + for (sal_Int32 i = 0; i < rNewVals.getLength(); ++i) + { + PropertyValue& rNewVal = aNewVals[i]; + + if (rNewVal.Name == cRubyText) + { + rNewVal.Value <<= OUString{}; + } + else if (rNewVal.Name == cRubyBaseText) + { + rNewVal.Value <<= sBaseTmp.copy(nPos, nNextPos - nPos); + } + } + + nPos = nNextPos; + } + } + } + } + + aRubyValues = std::move(aNewRubyValues); + } }; SvxRubyData_Impl::SvxRubyData_Impl() : bHasSelectionChanged(false) , bDisposing(false) { + Reference<XComponentContext> xContext = ::comphelper::getProcessComponentContext(); + xBreak = css::i18n::BreakIterator::create(xContext); } SvxRubyData_Impl::~SvxRubyData_Impl() {} @@ -273,6 +396,7 @@ SvxRubyDialog::SvxRubyDialog(SfxBindings* pBind, SfxChildWindow* pCW, weld::Wind , m_xCharStyleLB(m_xBuilder->weld_combo_box(u"stylelb"_ustr)) , m_xStylistPB(m_xBuilder->weld_button(u"styles"_ustr)) , m_xSelectionGroupPB(m_xBuilder->weld_button(u"selection-group"_ustr)) + , m_xSelectionMonoPB(m_xBuilder->weld_button(u"selection-mono"_ustr)) , m_xApplyPB(m_xBuilder->weld_button(u"ok"_ustr)) , m_xClosePB(m_xBuilder->weld_button(u"close"_ustr)) , m_xContentArea(m_xDialog->weld_content_area()) @@ -295,6 +419,7 @@ SvxRubyDialog::SvxRubyDialog(SfxBindings* pBind, SfxChildWindow* pCW, weld::Wind aEditArr[7] = m_xRight4ED.get(); m_xSelectionGroupPB->connect_clicked(LINK(this, SvxRubyDialog, SelectionGroup_Impl)); + m_xSelectionMonoPB->connect_clicked(LINK(this, SvxRubyDialog, SelectionMono_Impl)); m_xApplyPB->connect_clicked(LINK(this, SvxRubyDialog, ApplyHdl_Impl)); m_xClosePB->connect_clicked(LINK(this, SvxRubyDialog, CloseHdl_Impl)); m_xStylistPB->connect_clicked(LINK(this, SvxRubyDialog, StylistHdl_Impl)); @@ -474,6 +599,7 @@ void SvxRubyDialog::Update() { // Only enable selection grouping options when they can be applied m_xSelectionGroupPB->set_sensitive(!m_pImpl->IsSelectionGrouped()); + m_xSelectionMonoPB->set_sensitive(!m_pImpl->IsSelectionMono()); const Sequence<PropertyValues>& aRubyValues = m_pImpl->GetRubyValues(); sal_Int32 nLen = aRubyValues.getLength(); @@ -581,6 +707,12 @@ IMPL_LINK_NOARG(SvxRubyDialog, SelectionGroup_Impl, weld::Button&, void) Update(); } +IMPL_LINK_NOARG(SvxRubyDialog, SelectionMono_Impl, weld::Button&, void) +{ + m_pImpl->MakeSelectionMono(); + Update(); +} + IMPL_LINK_NOARG(SvxRubyDialog, ApplyHdl_Impl, weld::Button&, void) { const Sequence<PropertyValues>& aRubyValues = m_pImpl->GetRubyValues(); diff --git a/svx/uiconfig/ui/asianphoneticguidedialog.ui b/svx/uiconfig/ui/asianphoneticguidedialog.ui index 66a045d26ba7..a0fd8453f69e 100644 --- a/svx/uiconfig/ui/asianphoneticguidedialog.ui +++ b/svx/uiconfig/ui/asianphoneticguidedialog.ui @@ -520,6 +520,20 @@ <property name="position">0</property> </packing> </child> + <child> + <object class="GtkButton" id="selection-mono"> + <property name="label" translatable="yes" context="asianphoneticguidedialog|selectionmono">_Mono</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="use-underline">True</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> </object> <packing> <property name="left-attach">1</property> diff --git a/sw/qa/core/uwriter.cxx b/sw/qa/core/uwriter.cxx index 2e10ae43aa1c..abfce2412516 100644 --- a/sw/qa/core/uwriter.cxx +++ b/sw/qa/core/uwriter.cxx @@ -2023,10 +2023,10 @@ void SwDocTest::testFillRubyList() rList->push_back(std::move(pEnt)); }; - auto fnGetCombinedString = [&] + auto fnGetCombinedString = [](SwPaM& rPaM) { SwRubyList aRubies; - SwDoc::FillRubyList(aPaM, aRubies); + SwDoc::FillRubyList(rPaM, aRubies); OUStringBuffer aTemp; @@ -2041,13 +2041,13 @@ void SwDocTest::testFillRubyList() // Single word without existing rubies { fnAppendJapanese(u"学校"_ustr); - CPPUNIT_ASSERT_EQUAL(u"学校[]"_ustr, fnGetCombinedString()); + CPPUNIT_ASSERT_EQUAL(u"学校[]"_ustr, fnGetCombinedString(aPaM)); } // Compound word without existing rubies { fnAppendJapanese(u"自動販売機"_ustr); - CPPUNIT_ASSERT_EQUAL(u"自動[]販売[]機[]"_ustr, fnGetCombinedString()); + CPPUNIT_ASSERT_EQUAL(u"自動[]販売[]機[]"_ustr, fnGetCombinedString(aPaM)); } // Single word with existing rubies @@ -2059,7 +2059,7 @@ void SwDocTest::testFillRubyList() m_pDoc->SetRubyList(aPaM, rList); - CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]"_ustr, fnGetCombinedString()); + CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]"_ustr, fnGetCombinedString(aPaM)); } // Compound word with existing rubies @@ -2073,7 +2073,7 @@ void SwDocTest::testFillRubyList() m_pDoc->SetRubyList(aPaM, rList); - CPPUNIT_ASSERT_EQUAL(u"自動[じどう]販売[はんばい]機[き]"_ustr, fnGetCombinedString()); + CPPUNIT_ASSERT_EQUAL(u"自動[じどう]販売[はんばい]機[き]"_ustr, fnGetCombinedString(aPaM)); } // Compound word with existing rubies treated as a single word @@ -2087,7 +2087,7 @@ void SwDocTest::testFillRubyList() m_pDoc->SetRubyList(aPaM, rList); - CPPUNIT_ASSERT_EQUAL(u"自動販売機[じどうはんばいき]"_ustr, fnGetCombinedString()); + CPPUNIT_ASSERT_EQUAL(u"自動販売機[じどうはんばいき]"_ustr, fnGetCombinedString(aPaM)); } // tdf#141466: Characteristic test from bug @@ -2103,7 +2103,8 @@ void SwDocTest::testFillRubyList() m_pDoc->SetRubyList(aPaM, rList); - CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]に[]行[い]き[]ます[]。[]"_ustr, fnGetCombinedString()); + CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]に[]行[い]き[]ます[]。[]"_ustr, + fnGetCombinedString(aPaM)); } // tdf#107184: Characteristic test for ruby group mode editing @@ -2115,7 +2116,26 @@ void SwDocTest::testFillRubyList() m_pDoc->SetRubyList(aPaM, rList); - CPPUNIT_ASSERT_EQUAL(u"学校に行きます[がっこうにいきます]"_ustr, fnGetCombinedString()); + CPPUNIT_ASSERT_EQUAL(u"学校に行きます[がっこうにいきます]"_ustr, fnGetCombinedString(aPaM)); + } + + // tdf#156543: Characteristic test for ruby mono mode editing + { + fnAppendJapanese(u"学校に行きます"_ustr); + + SwRubyList rList; + fnAppendRuby(&rList, u"学"_ustr, u"がっ"_ustr); + fnAppendRuby(&rList, u"校"_ustr, u"こう"_ustr); + fnAppendRuby(&rList, u"に"_ustr, u""_ustr); + fnAppendRuby(&rList, u"行"_ustr, u"い"_ustr); + fnAppendRuby(&rList, u"き"_ustr, u""_ustr); + fnAppendRuby(&rList, u"ま"_ustr, u""_ustr); + fnAppendRuby(&rList, u"す"_ustr, u""_ustr); + + m_pDoc->SetRubyList(aPaM, rList); + + CPPUNIT_ASSERT_EQUAL(u"学[がっ]校[こう]に[]行[い]き[]ます[]"_ustr, + fnGetCombinedString(aPaM)); } // tdf#156543: Characteristic test for ruby mono mode editing @@ -2133,7 +2153,19 @@ void SwDocTest::testFillRubyList() m_pDoc->SetRubyList(aPaM, rList); - CPPUNIT_ASSERT_EQUAL(u"学[がっ]校[こう]に[]行[い]き[]ます[]"_ustr, fnGetCombinedString()); + CPPUNIT_ASSERT_EQUAL(u"学[がっ]校[こう]に[]行[い]き[]ます[]"_ustr, + fnGetCombinedString(aPaM)); + } + + // Partial PaM + { + fnAppendJapanese(u"学校に行こう。"_ustr); + + SwPaM aAdjPaM{ *aPaM.GetPoint(), *aPaM.GetMark() }; + aAdjPaM.Normalize(); + aAdjPaM.GetMark()->AdjustContent(-1); + + CPPUNIT_ASSERT_EQUAL(u"学校[]に[]行[]こう[]"_ustr, fnGetCombinedString(aAdjPaM)); } // Empty PaM @@ -2142,7 +2174,7 @@ void SwDocTest::testFillRubyList() aPaM.DeleteMark(); - CPPUNIT_ASSERT_EQUAL(u"学校[]"_ustr, fnGetCombinedString()); + CPPUNIT_ASSERT_EQUAL(u"学校[]"_ustr, fnGetCombinedString(aPaM)); } } @@ -2225,6 +2257,15 @@ void SwDocTest::testSetRubyList() CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aPaM.GetMark()->GetContentIndex()); CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]"_ustr, fnGetCombinedString()); + + // Operation should be idempotent + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aPaM.GetMark()->GetContentIndex()); + m_pDoc->SetRubyList(aPaM, rList); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(2), aPaM.GetMark()->GetContentIndex()); + + CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]"_ustr, fnGetCombinedString()); } // tdf#141466: Characteristic test from bug @@ -2245,6 +2286,48 @@ void SwDocTest::testSetRubyList() CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aPaM.GetMark()->GetContentIndex()); CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]に行[い]きます。"_ustr, fnGetCombinedString()); + + // Operation should be idempotent + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aPaM.GetMark()->GetContentIndex()); + m_pDoc->SetRubyList(aPaM, rList); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(8), aPaM.GetMark()->GetContentIndex()); + + CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]に行[い]きます。"_ustr, fnGetCombinedString()); + } + + // Base text merging/deletion at end of selection + { + fnAppendJapanese(u"学校に行こう。"_ustr); + + SwPaM aAdjPaM{ *aPaM.GetPoint(), *aPaM.GetMark() }; + aAdjPaM.GetPoint()->AdjustContent(-1); + + SwRubyList rList; + fnAppendRuby(&rList, u"学校"_ustr, u"がっこう"_ustr); + fnAppendRuby(&rList, u"に"_ustr, u""_ustr); + fnAppendRuby(&rList, u"行こう"_ustr, u"いこう"_ustr); + fnAppendRuby(&rList, u""_ustr, u""_ustr); + fnAppendRuby(&rList, u""_ustr, u"this ruby should not appear"_ustr); + fnAppendRuby(&rList, u""_ustr, u""_ustr); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(6), aAdjPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aAdjPaM.GetMark()->GetContentIndex()); + m_pDoc->SetRubyList(aAdjPaM, rList); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aAdjPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(6), aAdjPaM.GetMark()->GetContentIndex()); + + CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]に行こう[いこう]。"_ustr, fnGetCombinedString()); + + // Operation should be idempotent + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aAdjPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(6), aAdjPaM.GetMark()->GetContentIndex()); + m_pDoc->SetRubyList(aAdjPaM, rList); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aAdjPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(6), aAdjPaM.GetMark()->GetContentIndex()); + + CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]に行こう[いこう]。"_ustr, fnGetCombinedString()); } // Base text deletion @@ -2281,6 +2364,15 @@ void SwDocTest::testSetRubyList() CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex()); CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]に来[き]ます。"_ustr, fnGetCombinedString()); + + // Operation should be idempotent + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex()); + m_pDoc->SetRubyList(aPaM, rList2); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex()); + + CPPUNIT_ASSERT_EQUAL(u"学校[がっこう]に来[き]ます。"_ustr, fnGetCombinedString()); } // tdf#107184: Characteristic test for ruby group mode editing @@ -2297,6 +2389,15 @@ void SwDocTest::testSetRubyList() CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex()); CPPUNIT_ASSERT_EQUAL(u"学校に行きます[がっこうにいきます]"_ustr, fnGetCombinedString()); + + // Operation should be idempotent + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex()); + m_pDoc->SetRubyList(aPaM, rList); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex()); + + CPPUNIT_ASSERT_EQUAL(u"学校に行きます[がっこうにいきます]"_ustr, fnGetCombinedString()); } // tdf#107184: Delete ruby in group mode after populating @@ -2324,6 +2425,15 @@ void SwDocTest::testSetRubyList() CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex()); CPPUNIT_ASSERT_EQUAL(u"学校に行きます"_ustr, fnGetCombinedString()); + + // Operation should be idempotent + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex()); + m_pDoc->SetRubyList(aPaM, rList2); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex()); + + CPPUNIT_ASSERT_EQUAL(u"学校に行きます"_ustr, fnGetCombinedString()); } // tdf#156543: Characteristic test for ruby mono mode editing @@ -2346,6 +2456,15 @@ void SwDocTest::testSetRubyList() CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex()); CPPUNIT_ASSERT_EQUAL(u"学[がっ]校[こう]に行[い]きます[す]"_ustr, fnGetCombinedString()); + + // Operation should be idempotent + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex()); + m_pDoc->SetRubyList(aPaM, rList); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aPaM.GetMark()->GetContentIndex()); + + CPPUNIT_ASSERT_EQUAL(u"学[がっ]校[こう]に行[い]きます[す]"_ustr, fnGetCombinedString()); } // Offset PaM - Combination of insert and replace @@ -2371,10 +2490,54 @@ void SwDocTest::testSetRubyList() CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aEmptyPaM.GetPoint()->GetContentIndex()); CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aEmptyPaM.GetMark()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(u"学森林[しんりん]海上[かいじょう]地面[じめん]員"_ustr, + fnGetCombinedString()); + + // Operation should be idempotent + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aEmptyPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aEmptyPaM.GetMark()->GetContentIndex()); + m_pDoc->SetRubyList(aEmptyPaM, rList); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aEmptyPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aEmptyPaM.GetMark()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(u"学森林[しんりん]海上[かいじょう]地面[じめん]員"_ustr, fnGetCombinedString()); } + // Partial PaM with mono replacement + { + fnAppendJapanese(u"学校に行こう。"_ustr); + + SwPaM aAdjPaM{ *aPaM.GetPoint(), *aPaM.GetMark() }; + aAdjPaM.Normalize(); + aAdjPaM.GetMark()->AdjustContent(-1); + + SwRubyList rList; + fnAppendRuby(&rList, u"学"_ustr, u"がっ"_ustr); + fnAppendRuby(&rList, u"校"_ustr, u"こう"_ustr); + fnAppendRuby(&rList, u"に"_ustr, u""_ustr); + fnAppendRuby(&rList, u"行"_ustr, u"い"_ustr); + fnAppendRuby(&rList, u"こ"_ustr, u""_ustr); + fnAppendRuby(&rList, u"う"_ustr, u""_ustr); + + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aAdjPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(6), aAdjPaM.GetMark()->GetContentIndex()); + m_pDoc->SetRubyList(aAdjPaM, rList); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aAdjPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(6), aAdjPaM.GetMark()->GetContentIndex()); + + CPPUNIT_ASSERT_EQUAL(u"学[がっ]校[こう]に行[い]こう。"_ustr, fnGetCombinedString()); + + // Operation should be idempotent + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aAdjPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(6), aAdjPaM.GetMark()->GetContentIndex()); + m_pDoc->SetRubyList(aAdjPaM, rList); + CPPUNIT_ASSERT_EQUAL(sal_Int32(0), aAdjPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(6), aAdjPaM.GetMark()->GetContentIndex()); + + CPPUNIT_ASSERT_EQUAL(u"学[がっ]校[こう]に行[い]こう。"_ustr, fnGetCombinedString()); + } + // Empty PaM - Should insert { fnAppendJapanese(u"学校"_ustr); @@ -2397,6 +2560,16 @@ void SwDocTest::testSetRubyList() CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aEmptyPaM.GetPoint()->GetContentIndex()); CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aEmptyPaM.GetMark()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(u"学森林[しんりん]海上[かいじょう]地面[じめん]校"_ustr, + fnGetCombinedString()); + + // Operation should be idempotent + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aEmptyPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aEmptyPaM.GetMark()->GetContentIndex()); + m_pDoc->SetRubyList(aEmptyPaM, rList); + CPPUNIT_ASSERT_EQUAL(sal_Int32(1), aEmptyPaM.GetPoint()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(7), aEmptyPaM.GetMark()->GetContentIndex()); + CPPUNIT_ASSERT_EQUAL(u"学森林[しんりん]海上[かいじょう]地面[じめん]校"_ustr, fnGetCombinedString()); } diff --git a/sw/source/core/doc/docruby.cxx b/sw/source/core/doc/docruby.cxx index 15629c70325e..954870d56776 100644 --- a/sw/source/core/doc/docruby.cxx +++ b/sw/source/core/doc/docruby.cxx @@ -117,53 +117,57 @@ void SwDoc::SetRubyList(SwPaM& rPam, const SwRubyList& rList) } SwRubyListEntry aCheckEntry; - if (!SelectNextRubyChars(aPam, aCheckEntry)) + auto bSelected = SelectNextRubyChars(aPam, aCheckEntry); + + if (bSelected) { - // goto next paragraph - aPam.DeleteMark(); - aPam.Move(fnMoveForward, GoInNode); + ++nCurrBaseTexts; - if (*aPam.GetPoint() >= *pEnd) + // Existing ruby text was located. Apply the new attributes. + const SwRubyListEntry* pEntry = rList[nListEntry++].get(); + if (aCheckEntry.GetRubyAttr() != pEntry->GetRubyAttr()) { - break; + // set/reset the attribute + if (!pEntry->GetRubyAttr().GetText().isEmpty()) + { + getIDocumentContentOperations().InsertPoolItem(aPam, pEntry->GetRubyAttr()); + } + else + { + ResetAttrs(aPam, true, aDelArr); + } } - continue; - } + if (aCheckEntry.GetText() != pEntry->GetText()) + { + if (pEntry->GetText().isEmpty()) + { + ResetAttrs(aPam, true, aDelArr); + } - ++nCurrBaseTexts; + // text is changed, so replace the original + getIDocumentContentOperations().ReplaceRange(aPam, pEntry->GetText(), false); + aPam.Exchange(); + } - const SwRubyListEntry* pEntry = rList[nListEntry++].get(); - if (aCheckEntry.GetRubyAttr() != pEntry->GetRubyAttr()) + aPam.DeleteMark(); + } + else { - // set/reset the attribute - if (!pEntry->GetRubyAttr().GetText().isEmpty()) - { - getIDocumentContentOperations().InsertPoolItem(aPam, pEntry->GetRubyAttr()); - } - else - { - ResetAttrs(aPam, true, aDelArr); - } + // No existing ruby text located. Advance to next paragraph. + aPam.DeleteMark(); + aPam.Move(fnMoveForward, GoInNode); } - if (aCheckEntry.GetText() != pEntry->GetText()) + // Stop substituting when the cursor advances to the end of the selection. + if (*aPam.GetPoint() >= *pEnd) { - if (pEntry->GetText().isEmpty()) - { - ResetAttrs(aPam, true, aDelArr); - } - - // text is changed, so replace the original - getIDocumentContentOperations().ReplaceRange(aPam, pEntry->GetText(), false); - aPam.Exchange(); + break; } - - aPam.DeleteMark(); } // Delete any spans past the end of the ruby list - while (nListEntry == rList.size() && nCurrBaseTexts < nMaxBaseTexts) + while (nListEntry == rList.size() && nCurrBaseTexts < nMaxBaseTexts && *aPam.GetPoint() < *pEnd) { if (pEnd != pStt) { @@ -172,27 +176,24 @@ void SwDoc::SetRubyList(SwPaM& rPam, const SwRubyList& rList) } SwRubyListEntry aCheckEntry; - if (!SelectNextRubyChars(aPam, aCheckEntry)) + auto bSelected = SelectNextRubyChars(aPam, aCheckEntry); + + if (bSelected) { - // goto next paragraph - aPam.DeleteMark(); - aPam.Move(fnMoveForward, GoInNode); + ++nCurrBaseTexts; - if (*aPam.GetPoint() >= *pEnd) - { - break; - } + ResetAttrs(aPam, true, aDelArr); + getIDocumentContentOperations().DeleteRange(aPam); + aPam.Exchange(); - continue; + aPam.DeleteMark(); + } + else + { + // No existing ruby text located. Advance to next paragraph. + aPam.DeleteMark(); + aPam.Move(fnMoveForward, GoInNode); } - - ++nCurrBaseTexts; - - ResetAttrs(aPam, true, aDelArr); - getIDocumentContentOperations().DeleteRange(aPam); - aPam.Exchange(); - - aPam.DeleteMark(); } // Insert any spans past the end of the base text list @@ -201,22 +202,21 @@ void SwDoc::SetRubyList(SwPaM& rPam, const SwRubyList& rList) { const SwRubyListEntry* pEntry = rList[nListEntry++].get(); - if (pEnd != pStt) + if (!pEntry->GetText().isEmpty()) { aPam.SetMark(); - *aPam.GetMark() = *pEnd; - } + getIDocumentContentOperations().InsertString(aPam, pEntry->GetText()); + aPam.GetMark()->AdjustContent(-pEntry->GetText().getLength()); + + if (!pEntry->GetRubyAttr().GetText().isEmpty()) + { + getIDocumentContentOperations().InsertPoolItem(aPam, pEntry->GetRubyAttr()); + } - aPam.SetMark(); - getIDocumentContentOperations().InsertString(aPam, pEntry->GetText()); - nTotalContentGrowth += pEntry->GetText().getLength(); + aPam.DeleteMark(); - if (!pEntry->GetRubyAttr().GetText().isEmpty()) - { - getIDocumentContentOperations().InsertPoolItem(aPam, pEntry->GetRubyAttr()); + nTotalContentGrowth += pEntry->GetText().getLength(); } - - aPam.DeleteMark(); } // Expand selection to account for insertion