editeng/qa/unit/core-test.cxx                           |  112 ++++++++++
 editeng/source/editeng/impedit4.cxx                     |   14 +
 sw/qa/extras/uiwriter/uiwriter3.cxx                     |    8 
 sw/qa/extras/uiwriter/uiwriter4.cxx                     |  163 ++++++++++++++++
 sw/source/core/doc/DocumentContentOperationsManager.cxx |   26 +-
 sw/source/core/txtnode/txtedt.cxx                       |   22 +-
 6 files changed, 326 insertions(+), 19 deletions(-)

New commits:
commit cb699fd9c749d5fe621406918fa6458896c09239
Author:     Michael Warner <michael.warner.ut+libreoff...@gmail.com>
AuthorDate: Fri Aug 27 17:37:28 2021 -0400
Commit:     Heiko Tietze <heiko.tie...@documentfoundation.org>
CommitDate: Fri Nov 26 16:40:55 2021 +0100

    tdf#49033 Honor Selection When Applying Sentence Case Format
    
    Prevents Sentence Case formatting from occuring outside of a user's
    selection.
    
    This commit does not prevent Title Case formatting from
    adjusting the first letter of a word, even if the selection does
    not include that letter.
    
    Change-Id: Id3f1a5b0da973d24ef73f1e640306a65e1b4c561
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/121966
    Tested-by: Jenkins
    Reviewed-by: Heiko Tietze <heiko.tie...@documentfoundation.org>

diff --git a/editeng/qa/unit/core-test.cxx b/editeng/qa/unit/core-test.cxx
index 8215c451118a..8bdd43d926f1 100644
--- a/editeng/qa/unit/core-test.cxx
+++ b/editeng/qa/unit/core-test.cxx
@@ -31,6 +31,8 @@
 #include <svl/srchitem.hxx>
 #include <editeng/fontitem.hxx>
 #include <editeng/fhgtitem.hxx>
+#include <unotools/transliterationwrapper.hxx>
+#include <comphelper/processfactory.hxx>
 
 #include <com/sun/star/text/textfield/Type.hpp>
 
@@ -1745,15 +1747,117 @@ void Test::testLargeParaCopyPaste()
     CPPUNIT_ASSERT_EQUAL( aTenthPara, rDoc.GetParaAsString(sal_Int32(11)) );
 }
 
+OUString lcl_translitTest(EditEngine& aEditEngine, const OUString& text, const 
ESelection& esel, const TransliterationFlags nType)
+{
+    aEditEngine.SetText(text);
+    aEditEngine.TransliterateText(esel, nType);
+    return aEditEngine.GetText();
+}
+
+
 void Test::testTransliterate()
 {
     // Create EditEngine's instance
-    EditEngine aEditEngine( mpItemPool.get() );
+    EditEngine editEng( mpItemPool.get() );
 
     OUString sText("one (two) three");
-    aEditEngine.SetText(sText);
-    aEditEngine.TransliterateText(ESelection(0, 0, 0, sText.getLength()), 
TransliterationFlags::TITLE_CASE);
-    CPPUNIT_ASSERT_EQUAL(OUString("One (Two) Three"), aEditEngine.GetText());
+    editEng.SetText(sText);
+    editEng.TransliterateText(ESelection(0, 0, 0, sText.getLength()), 
TransliterationFlags::TITLE_CASE);
+    CPPUNIT_ASSERT_EQUAL(OUString("One (Two) Three"), editEng.GetText());
+
+    using TF = TransliterationFlags;
+    const OUString sText2 = "Mary Jones met joe Smith. Time Passed.";
+    int selStart = 12;
+    int selEnd = 12;
+    ESelection esel(0, selStart, 0, selEnd);
+
+    /* DocumentContentOperationsManager checks if the cursor is inside of a 
word before transliterating,
+     * but Edit Engine has no such check. Therefore, behavior is different 
between these two when the
+     * cursor is on a word boundary. */
+
+    /* No selection tests. Cursor between the ' ' and 'm' before 'met'. */
+    CPPUNIT_ASSERT_EQUAL(OUString(""), editEng.GetText(esel));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary jones met joe smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones Met joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones MET joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE));
+
+    /* No selection tests. Cursor between the 't' and the ' ' after 'met'. */
+    selStart = 14;
+    selEnd = 14;
+    esel = ESelection(0, selStart, 0, selEnd);
+    CPPUNIT_ASSERT_EQUAL(OUString(""), editEng.GetText(esel));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary jones met joe smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE));
+
+    /* No selection tests. Cursor between the 'h' and the '.' after 'Smith'. */
+    selStart = 24;
+    selEnd = 24;
+    esel = ESelection(0, selStart, 0, selEnd);
+    CPPUNIT_ASSERT_EQUAL(OUString(""), editEng.GetText(esel));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary jones met joe smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE));
+
+    /* No selection tests. Cursor between the 'm' and 'e' in 'met'. */
+    selStart = 12;
+    selEnd = 12;
+    esel = ESelection(0, selStart, 0, selEnd);
+    CPPUNIT_ASSERT_EQUAL(OUString(""), editEng.GetText(esel));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary jones met joe smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones Met joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones MET joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE));
+
+    /* Test behavior when there is a selection that does not cross a word 
boundary: "met" */
+    selStart = 11;
+    selEnd = 14;
+    esel = ESelection(0, selStart, 0, selEnd);
+    CPPUNIT_ASSERT_EQUAL(OUString("met"), editEng.GetText(esel));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones Met joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones Met joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones MET joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE));
+
+    /* Test behavior when there is a selection that crosses a word boundary: 
"nes met joe Sm" */
+    selStart = 7;
+    selEnd = 21;
+    esel = ESelection(0, selStart, 0, selEnd);
+    CPPUNIT_ASSERT_EQUAL(OUString("nes met joe Sm"), editEng.GetText(esel));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary JoNes met joe smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones Met Joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary JoNES MET JOE SMith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE));
+
+    /* Test behavior when there is a selection that crosses a sentence 
boundary: "joe Smith. Time Passed." */
+    selStart = 15;
+    selEnd = 38;
+    esel = ESelection(0, selStart, 0, selEnd);
+    editEng.SetText(sText2);
+    CPPUNIT_ASSERT_EQUAL(OUString("joe Smith. Time Passed."), 
editEng.GetText(esel));
+
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met Joe smith. Time passed."), 
lcl_translitTest(editEng, sText2, esel, TF::SENTENCE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met Joe Smith. Time Passed."), 
lcl_translitTest(editEng, sText2, esel, TF::TITLE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met JOE SMITH. TIME PASSED."), 
lcl_translitTest(editEng, sText2, esel, TF::LOWERCASE_UPPERCASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe smith. time passed."), 
lcl_translitTest(editEng, sText2, esel, TF::UPPERCASE_LOWERCASE));
+
+    /* Test behavior when sentence ends with a capital that is not selected: 
"CURRENT IS EQUAL TO 10 A" */
+    selStart = 0;
+    selEnd = 19;
+    esel = ESelection(0, selStart, 0, selEnd);
+    const OUString sText3("CURRENT IS EQUAL TO 10 A");
+    editEng.SetText(sText3);
+    CPPUNIT_ASSERT_EQUAL(OUString("CURRENT IS EQUAL TO"), 
editEng.GetText(esel));
+
+    CPPUNIT_ASSERT_EQUAL(OUString("Current is equal to 10 A"), 
lcl_translitTest(editEng, sText3, esel, TF::SENTENCE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Current Is Equal To 10 A"), 
lcl_translitTest(editEng, sText3, esel, TF::TITLE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("CURRENT IS EQUAL TO 10 A"), 
lcl_translitTest(editEng, sText3, esel, TF::LOWERCASE_UPPERCASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("current is equal to 10 A"), 
lcl_translitTest(editEng, sText3, esel, TF::UPPERCASE_LOWERCASE));
+
+
 }
 
 CPPUNIT_TEST_SUITE_REGISTRATION(Test);
diff --git a/editeng/source/editeng/impedit4.cxx 
b/editeng/source/editeng/impedit4.cxx
index dd3f13cf0f47..e160f3896f37 100644
--- a/editeng/source/editeng/impedit4.cxx
+++ b/editeng/source/editeng/impedit4.cxx
@@ -2661,7 +2661,13 @@ EditSelection ImpEditEngine::TransliterateText( const 
EditSelection& rSelection,
     aSel.Adjust( aEditDoc );
 
     if ( !aSel.HasRange() )
-        aSel = SelectWord( aSel );
+    {
+        /* Cursor is inside of a word */
+        if (nTransliterationMode == TransliterationFlags::SENTENCE_CASE)
+            aSel = SelectSentence( aSel );
+        else
+            aSel = SelectWord( aSel );
+    }
 
     // tdf#107176: if there's still no range, just return aSel
     if ( !aSel.HasRange() )
@@ -2826,6 +2832,12 @@ EditSelection ImpEditEngine::TransliterateText( const 
EditSelection& rSelection,
                     nCurrentEnd = nLastEnd;
             }
 
+            // prevent making any change outside of the user's selection
+            nCurrentStart = std::max(aSel.Min().GetIndex(), nCurrentStart);
+            nCurrentEnd = std::min(aSel.Max().GetIndex(), nCurrentEnd);
+            nLastStart = std::max(aSel.Min().GetIndex(), nLastStart);
+            nLastEnd = std::min(aSel.Max().GetIndex(), nLastEnd);
+
             while (nCurrentStart < nLastEnd)
             {
                 const sal_Int32 nLen = nCurrentEnd - nCurrentStart;
diff --git a/sw/qa/extras/uiwriter/uiwriter3.cxx 
b/sw/qa/extras/uiwriter/uiwriter3.cxx
index 80874d041267..7214b63e5811 100644
--- a/sw/qa/extras/uiwriter/uiwriter3.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter3.cxx
@@ -1883,24 +1883,28 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest3, testTdf116315)
         pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_SHIFT | KEY_F3);
         Scheduler::ProcessEventsToIdle();
 
+        // Title Case
         CPPUNIT_ASSERT_EQUAL(OUString("This is a Test"), 
getParagraph(1)->getString());
 
         pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_SHIFT | KEY_F3);
         Scheduler::ProcessEventsToIdle();
 
+        // Sentence Case
         // Without the fix in place, this test would have failed with
-        // - Expected: This is a test
+        // - Expected: This is a Test
         // - Actual  : This is a TEST
-        CPPUNIT_ASSERT_EQUAL(OUString("This is a test"), 
getParagraph(1)->getString());
+        CPPUNIT_ASSERT_EQUAL(OUString("This is a Test"), 
getParagraph(1)->getString());
 
         pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_SHIFT | KEY_F3);
         Scheduler::ProcessEventsToIdle();
 
+        // Upper Case
         CPPUNIT_ASSERT_EQUAL(OUString("This is a TEST"), 
getParagraph(1)->getString());
 
         pTextDoc->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, KEY_SHIFT | KEY_F3);
         Scheduler::ProcessEventsToIdle();
 
+        // Lower Case
         CPPUNIT_ASSERT_EQUAL(OUString("This is a test"), 
getParagraph(1)->getString());
     }
 }
diff --git a/sw/qa/extras/uiwriter/uiwriter4.cxx 
b/sw/qa/extras/uiwriter/uiwriter4.cxx
index 0b76f34ec616..f9cb31778e16 100644
--- a/sw/qa/extras/uiwriter/uiwriter4.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter4.cxx
@@ -141,6 +141,7 @@
 #include <unotxdoc.hxx>
 #include <comphelper/processfactory.hxx>
 #include <rootfrm.hxx>
+#include <unotools/transliterationwrapper.hxx>
 
 namespace
 {
@@ -166,6 +167,7 @@ class SwUiWriterTest4 : public SwModelTestBase, public 
HtmlTestTools
 {
 public:
     void testTdf96515();
+    void testTdf49033();
     void testTdf96943();
     void testTdf96536();
     void testTdf96479();
@@ -287,6 +289,7 @@ public:
 
     CPPUNIT_TEST_SUITE(SwUiWriterTest4);
     CPPUNIT_TEST(testTdf96515);
+    CPPUNIT_TEST(testTdf49033);
     CPPUNIT_TEST(testTdf96943);
     CPPUNIT_TEST(testTdf96536);
     CPPUNIT_TEST(testTdf96479);
@@ -433,6 +436,166 @@ void SwUiWriterTest4::testTdf96515()
     CPPUNIT_ASSERT_EQUAL(1, getPages());
 }
 
+static OUString lcl_translitTest(SwDoc& rDoc, const SwPaM& rPaM, 
TransliterationFlags const nType)
+{
+    utl::TransliterationWrapper 
aTrans(::comphelper::getProcessComponentContext(), nType);
+    rDoc.getIDocumentContentOperations().TransliterateText(rPaM, aTrans);
+    //SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
+    return rPaM.GetNode(false).GetTextNode()->GetText();
+}
+
+void SwUiWriterTest4::testTdf49033()
+{
+    SwDoc* pDoc = createSwDoc();
+    SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell();
+
+    // Insert the test text at the end of the document.
+    pWrtShell->SttEndDoc(/*bStt=*/false);
+    pWrtShell->Insert("Mary Jones met joe Smith. Time Passed.");
+    pWrtShell->StartOfSection();
+    SwShellCursor* pCursor = pWrtShell->getShellCursor(false);
+
+    using TF = TransliterationFlags;
+
+    /* -- Test behavior when there is no selection -- */
+
+    /* Move cursor between the 't' and the ' ' after 'met', nothing should 
change */
+    for (int i = 0; i < 14; i++)
+        pCursor->Move(fnMoveForward);
+
+    CPPUNIT_ASSERT_EQUAL(false, pCursor->HasMark());
+    CPPUNIT_ASSERT_EQUAL(false, pWrtShell->IsSelection());
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, 
TF::LOWERCASE_UPPERCASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, 
TF::UPPERCASE_LOWERCASE));
+
+    /* Move cursor between the 'h' and the '.' after 'Smith', nothing should 
change */
+    for (int i = 0; i < 10; i++)
+        pCursor->Move(fnMoveForward);
+
+    CPPUNIT_ASSERT_EQUAL(false, pCursor->HasMark());
+    CPPUNIT_ASSERT_EQUAL(false, pWrtShell->IsSelection());
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, 
TF::LOWERCASE_UPPERCASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, 
TF::UPPERCASE_LOWERCASE));
+
+    /* Move cursor between the 'm' and the 'e' in 'met' */
+    for (int i = 0; i < 12; i++)
+        pCursor->Move(fnMoveBackward);
+
+    CPPUNIT_ASSERT_EQUAL(false, pCursor->HasMark());
+    CPPUNIT_ASSERT_EQUAL(false, pWrtShell->IsSelection());
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary jones met joe smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
+
+    /* Undo the sentence case change to reset for the following tests */
+    pDoc->GetIDocumentUndoRedo().Undo();
+
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones Met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones MET joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, 
TF::LOWERCASE_UPPERCASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, 
TF::UPPERCASE_LOWERCASE));
+
+    /* -- Test behavior when there is a selection that does not cross a word 
boundary -- */
+    pCursor->Move(fnMoveBackward);
+    pWrtShell->SelWrd();
+    CPPUNIT_ASSERT_EQUAL(true, pCursor->HasMark());
+    CPPUNIT_ASSERT_EQUAL(true, pWrtShell->IsSelection());
+
+    OUString currentSelectedText;
+    pWrtShell->GetSelectedText(currentSelectedText);
+    CPPUNIT_ASSERT_EQUAL(OUString("met"), currentSelectedText);
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones Met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones Met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones MET joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, 
TF::LOWERCASE_UPPERCASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, 
TF::UPPERCASE_LOWERCASE));
+
+    /* -- Test behavior when there is a selection that crosses a word boundary 
-- */
+    for (int i = 0; i < 7; i++)
+        pCursor->Move(fnMoveBackward);
+    pCursor->SetMark();
+    for (int i = 0; i < 14; i++)
+        pCursor->Move(fnMoveForward);
+
+    pWrtShell->GetSelectedText(currentSelectedText);
+    CPPUNIT_ASSERT_EQUAL(OUString("nes met joe Sm"), currentSelectedText);
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary JoNes met joe smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones Met Joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary JoNES MET JOE SMith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, 
TF::LOWERCASE_UPPERCASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, 
TF::UPPERCASE_LOWERCASE));
+
+    /* Reset the 's' to upper-case for the next test */
+    for (int i = 0; i < 2; i++)
+        pCursor->Move(fnMoveBackward);
+    pCursor->SetMark();
+    pCursor->Move(fnMoveForward);
+    pDoc->getIDocumentContentOperations().ReplaceRange(*pCursor, 
OUString('S'), false);
+
+    /* -- Test behavior when there is a selection that crosses a sentence 
boundary -- */
+    for (int i = 0; i < 4; i++)
+        pCursor->Move(fnMoveBackward);
+    pCursor->SetMark();
+    for (int i = 0; i < 22; i++)
+        pCursor->Move(fnMoveForward);
+    pWrtShell->GetSelectedText(currentSelectedText);
+    CPPUNIT_ASSERT_EQUAL(OUString("joe Smith. Time Passed"), 
currentSelectedText);
+
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met Joe smith. Time passed."),
+                         lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
+
+    /* Undo the sentence case change to reset for the following tests */
+    pDoc->GetIDocumentUndoRedo().Undo();
+
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met Joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met JOE SMITH. TIME PASSED."),
+                         lcl_translitTest(*pDoc, *pCursor, 
TF::LOWERCASE_UPPERCASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe smith. time passed."),
+                         lcl_translitTest(*pDoc, *pCursor, 
TF::UPPERCASE_LOWERCASE));
+
+    /* Undo the previous changes to reset for the following tests */
+    pDoc->GetIDocumentUndoRedo().Undo();
+    pDoc->GetIDocumentUndoRedo().Undo();
+    pDoc->GetIDocumentUndoRedo().Undo();
+
+    /* -- Test behavior when there is a selection that does not reach end of 
sentence -- */
+    for (int i = 0; i < 37; i++)
+        pCursor->Move(fnMoveBackward);
+    pCursor->SetMark();
+    for (int i = 0; i < 10; i++)
+        pCursor->Move(fnMoveForward);
+    pWrtShell->GetSelectedText(currentSelectedText);
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones"), currentSelectedText);
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary jones met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, TF::SENTENCE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("Mary Jones met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, TF::TITLE_CASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("MARY JONES met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, 
TF::LOWERCASE_UPPERCASE));
+    CPPUNIT_ASSERT_EQUAL(OUString("mary jones met joe Smith. Time Passed."),
+                         lcl_translitTest(*pDoc, *pCursor, 
TF::UPPERCASE_LOWERCASE));
+}
+
 void SwUiWriterTest4::testTdf96943()
 {
     // Enable hide whitespace mode.
diff --git a/sw/source/core/doc/DocumentContentOperationsManager.cxx 
b/sw/source/core/doc/DocumentContentOperationsManager.cxx
index 5311bfb5e3b8..ab3bd4ca9cd8 100644
--- a/sw/source/core/doc/DocumentContentOperationsManager.cxx
+++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx
@@ -2932,10 +2932,9 @@ void DocumentContentOperationsManager::TransliterateText(
     sal_Int32 nEndCnt = pEnd->nContent.GetIndex();
 
     SwTextNode* pTNd = pStt->nNode.GetNode().GetTextNode();
-    if( pStt == pEnd && pTNd )  // no selection?
+    if( (pStt == pEnd) && pTNd )  // no selection?
     {
-        // set current word as 'area of effect'
-
+        /* Check if cursor is inside of a word */
         assert(g_pBreakIt && g_pBreakIt->GetBreakIter().is());
         Boundary aBndry = g_pBreakIt->GetBreakIter()->getWordBoundary(
                             pTNd->GetText(), nSttCnt,
@@ -2945,8 +2944,23 @@ void DocumentContentOperationsManager::TransliterateText(
 
         if( aBndry.startPos < nSttCnt && nSttCnt < aBndry.endPos )
         {
-            nSttCnt = aBndry.startPos;
-            nEndCnt = aBndry.endPos;
+            /* Cursor is inside of a word */
+            if (rTrans.getType() == TransliterationFlags::SENTENCE_CASE) {
+                /* set current sentence as 'area of effect' */
+                nSttCnt = g_pBreakIt->GetBreakIter()->beginOfSentence(
+                            pTNd->GetText(), nSttCnt,
+                            g_pBreakIt->GetLocale( pTNd->GetLang( nSttCnt ) ) 
);
+                nEndCnt = g_pBreakIt->GetBreakIter()->endOfSentence(
+                            pTNd->GetText(), nEndCnt,
+                            g_pBreakIt->GetLocale( pTNd->GetLang( nEndCnt ) ) 
);
+            } else {
+                /* Set current word as 'area of effect' */
+                nSttCnt = aBndry.startPos;
+                nEndCnt = aBndry.endPos;
+            }
+        } else {
+            /* Cursor is not inside of a word. Nothing should happen. */
+            return;
         }
     }
 
@@ -2990,7 +3004,7 @@ void DocumentContentOperationsManager::TransliterateText(
 
     if( nSttNd != nEndNd )  // is more than one text node involved?
     {
-        // iterate over all effected text nodes, the first and the last one
+        // iterate over all affected text nodes, the first and the last one
         // may be incomplete because the selection starts and/or ends there
 
         SwNodeIndex aIdx( pStt->nNode );
diff --git a/sw/source/core/txtnode/txtedt.cxx 
b/sw/source/core/txtnode/txtedt.cxx
index 9fabb33aa6e1..f4d43feb3cdc 100644
--- a/sw/source/core/txtnode/txtedt.cxx
+++ b/sw/source/core/txtnode/txtedt.cxx
@@ -1699,6 +1699,9 @@ void SwTextNode::TransliterateText(
     if (nStt >= nEnd)
         return;
 
+    const sal_Int32 selStart = nStt;
+    const sal_Int32 selEnd = nEnd;
+
     // since we don't use Hiragana/Katakana or half-width/full-width 
transliterations here
     // it is fine to use ANYWORD_IGNOREWHITESPACES. (ANY_WORD btw is broken 
and will
     // occasionally miss words in consecutive sentences). Also with 
ANYWORD_IGNOREWHITESPACES
@@ -1781,8 +1784,9 @@ void SwTextNode::TransliterateText(
     }
     else if (rTrans.getType() == TransliterationFlags::SENTENCE_CASE)
     {
-        // for 'sentence case' we need to iterate sentence by sentence
-
+        // For 'sentence case' we need to iterate sentence by sentence.
+        // nLastStart and nLastEnd are the boundaries of the last sentence in
+        // the user's selection.
         sal_Int32 nLastStart = g_pBreakIt->GetBreakIter()->beginOfSentence(
                 GetText(), nEnd,
                 g_pBreakIt->GetLocale( GetLang( nEnd ) ) );
@@ -1790,10 +1794,10 @@ void SwTextNode::TransliterateText(
                 GetText(), nLastStart,
                 g_pBreakIt->GetLocale( GetLang( nLastStart ) ) );
 
-        // extend nStt, nEnd to the current sentence boundaries
-        sal_Int32 nCurrentStart = g_pBreakIt->GetBreakIter()->beginOfSentence(
-                GetText(), nStt,
-                g_pBreakIt->GetLocale( GetLang( nStt ) ) );
+        // Begin with the starting point of the user's selection (it may not be
+        // the beginning of a sentence)...
+        sal_Int32 nCurrentStart = nStt;
+        // ...And extend to the end of the first sentence
         sal_Int32 nCurrentEnd = g_pBreakIt->GetBreakIter()->endOfSentence(
                 GetText(), nCurrentStart,
                 g_pBreakIt->GetLocale( GetLang( nCurrentStart ) ) );
@@ -1836,6 +1840,11 @@ void SwTextNode::TransliterateText(
                 nCurrentEnd = nLastEnd;
         }
 
+        // Prevent going outside of the user's selection
+        nCurrentStart = std::max(selStart, nCurrentStart);
+        nCurrentEnd = std::min(selEnd, nCurrentEnd);
+        nLastEnd = std::min(selEnd, nLastEnd);
+
         while (nCurrentStart < nLastEnd)
         {
             sal_Int32 nLen = nCurrentEnd - nCurrentStart;
@@ -1924,6 +1933,7 @@ void SwTextNode::TransliterateText(
         // call to ReplaceTextOnly
         swTransliterationChgData & rData =
             aChanges[ aChanges.size() - 1 - i ];
+
         nSum += rData.sChanged.getLength() - rData.nLen;
         if (nSum > o3tl::make_unsigned(GetSpaceLeft()))
         {

Reply via email to