diff --git a/include/wx/stc/stc.h b/include/wx/stc/stc.h index b3fec1c00c4d..21855d2acc9b 100644 --- a/include/wx/stc/stc.h +++ b/include/wx/stc/stc.h @@ -5766,7 +5766,8 @@ class WXDLLIMPEXP_STC wxStyledTextCtrl : public wxControl, if ( pos == -1 ) return -1; - if ( x >= LineLength(y) ) + int len = LineLength((int)y); + if ( x >= len && !((int)y == GetLineCount() - 1 && x == len) ) return -1; pos += x; @@ -5780,7 +5781,8 @@ class WXDLLIMPEXP_STC wxStyledTextCtrl : public wxControl, return false; int lx = pos - PositionFromLine(l); - if ( lx >= LineLength(l) ) + int len = LineLength(l); + if ( lx >= len && !(l == GetLineCount() - 1 && lx == len) ) return false; if ( x ) diff --git a/src/stc/stc.h.in b/src/stc/stc.h.in index fffd20836d7b..ecb112180c73 100644 --- a/src/stc/stc.h.in +++ b/src/stc/stc.h.in @@ -510,7 +510,8 @@ public: if ( pos == -1 ) return -1; - if ( x >= LineLength(y) ) + int len = LineLength((int)y); + if ( x >= len && !((int)y == GetLineCount() - 1 && x == len) ) return -1; pos += x; @@ -524,7 +525,8 @@ public: return false; int lx = pos - PositionFromLine(l); - if ( lx >= LineLength(l) ) + int len = LineLength(l); + if ( lx >= len && !(l == GetLineCount() - 1 && lx == len) ) return false; if ( x ) diff --git a/tests/controls/styledtextctrltest.cpp b/tests/controls/styledtextctrltest.cpp index 0ac8a32391d7..0c7b4d731795 100644 --- a/tests/controls/styledtextctrltest.cpp +++ b/tests/controls/styledtextctrltest.cpp @@ -162,6 +162,459 @@ TEST_CASE_METHOD(StcPopupWindowsTestCase, #endif // !defined(__WXOSX_COCOA__) +static const int TEXT_HEIGHT = 200; + +#if defined(__WXMSW__) && !defined(__WXUNIVERSAL__) +#define wxHAS_2CHAR_NEWLINES 1 +#else +#define wxHAS_2CHAR_NEWLINES 0 +#endif + +// ---------------------------------------------------------------------------- +// test class +// ---------------------------------------------------------------------------- + +class StyledTextCtrlTestCase : public CppUnit::TestCase +{ +public: + StyledTextCtrlTestCase() { } + + virtual void setUp() override; + virtual void tearDown() override; + +private: + + CPPUNIT_TEST_SUITE( StyledTextCtrlTestCase ); + CPPUNIT_TEST( PositionToXYMultiLine ); + CPPUNIT_TEST( XYToPositionMultiLine ); + CPPUNIT_TEST_SUITE_END(); + + void PositionToXYMultiLine(); + void XYToPositionMultiLine(); + + void DoPositionToXYMultiLine(long style); + void DoXYToPositionMultiLine(long style); + + // Create the control with the following styles added to ms_style which may + // (or not) already contain wxTE_MULTILINE. + void CreateText(long extraStyles); + + wxStyledTextCtrl *m_text; + + static long ms_style; + + wxDECLARE_NO_COPY_CLASS(StyledTextCtrlTestCase); +}; + +// register in the unnamed registry so that these tests are run by default +CPPUNIT_TEST_SUITE_REGISTRATION( StyledTextCtrlTestCase ); + +// also include in its own registry so that these tests can be run alone +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION( StyledTextCtrlTestCase, "StyledTextCtrlTestCase" ); + +// ---------------------------------------------------------------------------- +// test initialization +// ---------------------------------------------------------------------------- + +// This is 0 initially and set to wxTE_MULTILINE later to allow running the +// same tests for both single and multi line controls. +long StyledTextCtrlTestCase::ms_style = 0; + +void StyledTextCtrlTestCase::CreateText(long extraStyles) +{ + const long style = ms_style | extraStyles; + const int h = (style & wxTE_MULTILINE) ? TEXT_HEIGHT : -1; + m_text = new wxStyledTextCtrl(wxTheApp->GetTopWindow(), wxID_ANY, + wxDefaultPosition, wxSize(400, h), + style); +} + +void StyledTextCtrlTestCase::setUp() +{ + CreateText(0); +} + +void StyledTextCtrlTestCase::tearDown() +{ + wxDELETE(m_text); +} + +// ---------------------------------------------------------------------------- +// tests themselves +// ---------------------------------------------------------------------------- + +void StyledTextCtrlTestCase::PositionToXYMultiLine() +{ + DoPositionToXYMultiLine(0); +} + +void StyledTextCtrlTestCase::DoPositionToXYMultiLine(long style) +{ + delete m_text; + CreateText(style|wxTE_MULTILINE|wxTE_DONTWRAP); + +#if wxHAS_2CHAR_NEWLINES + const bool isRichEdit = (style & (wxTE_RICH | wxTE_RICH2)) != 0; +#endif + + typedef struct { long x, y; } XYPos; + bool ok; + wxString text; + + // empty field + m_text->Clear(); + const long numChars_0 = 0; + wxASSERT(numChars_0 == text.length()); + XYPos coords_0[numChars_0+1] = + { { 0, 0 } }; + + CPPUNIT_ASSERT_EQUAL( numChars_0, m_text->GetLastPosition() ); + for ( long i = 0; i < (long)WXSIZEOF(coords_0); i++ ) + { + long x, y; + ok = m_text->PositionToXY(i, &x, &y); + CPPUNIT_ASSERT_EQUAL( true, ok ); + CPPUNIT_ASSERT_EQUAL( coords_0[i].x, x ); + CPPUNIT_ASSERT_EQUAL( coords_0[i].y, y ); + } + ok = m_text->PositionToXY(WXSIZEOF(coords_0), nullptr, nullptr); + CPPUNIT_ASSERT_EQUAL( false, ok ); + + // one line + text = wxS("1234"); + m_text->SetValue(text); + const long numChars_1 = 4; + wxASSERT( numChars_1 == text.length() ); + XYPos coords_1[numChars_1+1] = + { { 0, 0 }, { 1, 0 }, { 2, 0}, { 3, 0 }, { 4, 0 } }; + + CPPUNIT_ASSERT_EQUAL( numChars_1, m_text->GetLastPosition() ); + for ( long i = 0; i < (long)WXSIZEOF(coords_1); i++ ) + { + long x, y; + ok = m_text->PositionToXY(i, &x, &y); + CPPUNIT_ASSERT_EQUAL( true, ok ); + CPPUNIT_ASSERT_EQUAL( coords_1[i].x, x ); + CPPUNIT_ASSERT_EQUAL( coords_1[i].y, y ); + } + ok = m_text->PositionToXY(WXSIZEOF(coords_1), nullptr, nullptr); + CPPUNIT_ASSERT_EQUAL( false, ok ); + + // few lines + text = wxS("123\nab\nX"); + m_text->SetValue(text); + +#if wxHAS_2CHAR_NEWLINES + // Take into account that every new line mark occupies + // two characters, not one. + const long numChars_msw_2 = 8 + 2; + // Note: Two new line characters refer to the same X-Y position. + XYPos coords_2_msw[numChars_msw_2 + 1] = + { { 0, 0 },{ 1, 0 },{ 2, 0 },{ 3, 0 },{ 3, 0 }, + { 0, 1 },{ 1, 1 },{ 2, 1 },{ 2, 1 }, + { 0, 2 },{ 1, 2 } }; +#endif // WXMSW + + const long numChars_2 = 8; + wxASSERT(numChars_2 == text.length()); + XYPos coords_2[numChars_2 + 1] = + { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 }, + { 0, 1 }, { 1, 1 }, { 2, 1 }, + { 0, 2 }, { 1, 2 } }; + + const long &ref_numChars_2 = +#if wxHAS_2CHAR_NEWLINES + isRichEdit ? numChars_2 : numChars_msw_2; +#else + numChars_2; +#endif + + XYPos *ref_coords_2 = +#if wxHAS_2CHAR_NEWLINES + isRichEdit ? coords_2 : coords_2_msw; +#else + coords_2; +#endif + + CPPUNIT_ASSERT_EQUAL( ref_numChars_2, m_text->GetLastPosition() ); + for ( long i = 0; i < ref_numChars_2+1; i++ ) + { + long x, y; + ok = m_text->PositionToXY(i, &x, &y); + CPPUNIT_ASSERT_EQUAL( true, ok ); + CPPUNIT_ASSERT_EQUAL( ref_coords_2[i].x, x ); + CPPUNIT_ASSERT_EQUAL( ref_coords_2[i].y, y ); + } + ok = m_text->PositionToXY(ref_numChars_2 + 1, nullptr, nullptr); + CPPUNIT_ASSERT_EQUAL( false, ok ); + + // only empty lines + text = wxS("\n\n\n"); + m_text->SetValue(text); + +#if wxHAS_2CHAR_NEWLINES + // Take into account that every new line mark occupies + // two characters, not one. + const long numChars_msw_3 = 3 + 3; + // Note: Two new line characters refer to the same X-Y position. + XYPos coords_3_msw[numChars_msw_3 + 1] = + { { 0, 0 },{ 0, 0 }, + { 0, 1 },{ 0, 1 }, + { 0, 2 },{ 0, 2 }, + { 0, 3 } }; +#endif // WXMSW + + const long numChars_3 = 3; + wxASSERT(numChars_3 == text.length()); + XYPos coords_3[numChars_3+1] = + { { 0, 0 }, + { 0, 1 }, + { 0, 2 }, + { 0, 3 } }; + + const long &ref_numChars_3 = +#if wxHAS_2CHAR_NEWLINES + isRichEdit ? numChars_3 : numChars_msw_3; +#else + numChars_3; +#endif + + XYPos *ref_coords_3 = +#if wxHAS_2CHAR_NEWLINES + isRichEdit ? coords_3 : coords_3_msw; +#else + coords_3; +#endif + + CPPUNIT_ASSERT_EQUAL( ref_numChars_3, m_text->GetLastPosition() ); + for ( long i = 0; i < ref_numChars_3+1; i++ ) + { + long x, y; + ok = m_text->PositionToXY(i, &x, &y); + CPPUNIT_ASSERT_EQUAL( true, ok ); + CPPUNIT_ASSERT_EQUAL( ref_coords_3[i].x, x ); + CPPUNIT_ASSERT_EQUAL( ref_coords_3[i].y, y ); + } + ok = m_text->PositionToXY(ref_numChars_3 + 1, nullptr, nullptr); + CPPUNIT_ASSERT_EQUAL( false, ok ); + + // mixed empty/non-empty lines + text = wxS("123\na\n\nX\n\n"); + m_text->SetValue(text); + +#if wxHAS_2CHAR_NEWLINES + // Take into account that every new line mark occupies + // two characters, not one. + const long numChars_msw_4 = 10 + 5; + // Note: Two new line characters refer to the same X-Y position. + XYPos coords_4_msw[numChars_msw_4 + 1] = + { { 0, 0 },{ 1, 0 },{ 2, 0 },{ 3, 0 },{ 3, 0 }, + { 0, 1 },{ 1, 1 },{ 1, 1 }, + { 0, 2 },{ 0, 2 }, + { 0, 3 },{ 1, 3 },{ 1, 3 }, + { 0, 4 },{ 0, 4 }, + { 0, 5 } }; +#endif // WXMSW + + const long numChars_4 = 10; + wxASSERT(numChars_4 == text.length()); + XYPos coords_4[numChars_4+1] = + { { 0, 0 }, { 1, 0 }, { 2, 0 }, { 3, 0 }, + { 0, 1 }, { 1, 1 }, + { 0, 2 }, + { 0, 3 }, { 1, 3 }, + { 0, 4 }, + { 0, 5 } }; + + const long &ref_numChars_4 = +#if wxHAS_2CHAR_NEWLINES + isRichEdit ? numChars_4 : numChars_msw_4; +#else + numChars_4; +#endif + + XYPos *ref_coords_4 = +#if wxHAS_2CHAR_NEWLINES + isRichEdit ? coords_4 : coords_4_msw; +#else + coords_4; +#endif + + CPPUNIT_ASSERT_EQUAL( ref_numChars_4, m_text->GetLastPosition() ); + for ( long i = 0; i < ref_numChars_4+1; i++ ) + { + long x, y; + ok = m_text->PositionToXY(i, &x, &y); + CPPUNIT_ASSERT_EQUAL( true, ok ); + CPPUNIT_ASSERT_EQUAL( ref_coords_4[i].x, x ); + CPPUNIT_ASSERT_EQUAL( ref_coords_4[i].y, y ); + } + ok = m_text->PositionToXY(ref_numChars_4 + 1, nullptr, nullptr); + CPPUNIT_ASSERT_EQUAL( false, ok ); +} + +void StyledTextCtrlTestCase::XYToPositionMultiLine() +{ + DoXYToPositionMultiLine(0); +} + +void StyledTextCtrlTestCase::DoXYToPositionMultiLine(long style) +{ + delete m_text; + CreateText(style|wxTE_MULTILINE|wxTE_DONTWRAP); + +#if wxHAS_2CHAR_NEWLINES + const bool isRichEdit = (style & (wxTE_RICH | wxTE_RICH2)) != 0; +#endif + + wxString text; + // empty field + m_text->Clear(); + const long maxLineLength_0 = 0+1; + const long numLines_0 = 1; + CPPUNIT_ASSERT_EQUAL( numLines_0, m_text->GetNumberOfLines() ); + long pos_0[numLines_0+1][maxLineLength_0+1] = + { { 0, -1 }, + { -1, -1 } }; + for ( long y = 0; y < numLines_0+1; y++ ) + for( long x = 0; x < maxLineLength_0+1; x++ ) + { + long p = m_text->XYToPosition(x, y); + INFO("x=" << x << ", y=" << y); + CPPUNIT_ASSERT_EQUAL( pos_0[y][x], p ); + } + + // one line + text = wxS("1234"); + m_text->SetValue(text); + const long maxLineLength_1 = 4+1; + const long numLines_1 = 1; + CPPUNIT_ASSERT_EQUAL( numLines_1, m_text->GetNumberOfLines() ); + long pos_1[numLines_1+1][maxLineLength_1+1] = + { { 0, 1, 2, 3, 4, -1 }, + { -1, -1, -1, -1, -1, -1 } }; + for ( long y = 0; y < numLines_1+1; y++ ) + for( long x = 0; x < maxLineLength_1+1; x++ ) + { + long p = m_text->XYToPosition(x, y); + INFO("x=" << x << ", y=" << y); + CPPUNIT_ASSERT_EQUAL( pos_1[y][x], p ); + } + + // few lines + text = wxS("123\nab\nX"); + m_text->SetValue(text); + const long maxLineLength_2 = 4; + const long numLines_2 = 3; + CPPUNIT_ASSERT_EQUAL( numLines_2, m_text->GetNumberOfLines() ); +#if wxHAS_2CHAR_NEWLINES + // Note: New lines are occupied by two characters. + long pos_2_msw[numLines_2 + 1][maxLineLength_2 + 1] = + { { 0, 1, 2, 3, -1 }, // New line occupies positions 3, 4 + { 5, 6, 7, -1, -1 }, // New line occupies positions 7, 8 + { 9, 10, -1, -1, -1 }, + { -1, -1, -1, -1, -1 } }; +#endif // WXMSW + long pos_2[numLines_2+1][maxLineLength_2+1] = + { { 0, 1, 2, 3, -1 }, + { 4, 5, 6, -1, -1 }, + { 7, 8, -1, -1, -1 }, + { -1, -1, -1, -1, -1 } }; + + long (&ref_pos_2)[numLines_2 + 1][maxLineLength_2 + 1] = +#if wxHAS_2CHAR_NEWLINES + isRichEdit ? pos_2 : pos_2_msw; +#else + pos_2; +#endif + + for ( long y = 0; y < numLines_2+1; y++ ) + for( long x = 0; x < maxLineLength_2+1; x++ ) + { + long p = m_text->XYToPosition(x, y); + INFO("x=" << x << ", y=" << y); + CPPUNIT_ASSERT_EQUAL( ref_pos_2[y][x], p ); + } + + // only empty lines + text = wxS("\n\n\n"); + m_text->SetValue(text); + const long maxLineLength_3 = 1; + const long numLines_3 = 4; + CPPUNIT_ASSERT_EQUAL( numLines_3, m_text->GetNumberOfLines() ); +#if wxHAS_2CHAR_NEWLINES + // Note: New lines are occupied by two characters. + long pos_3_msw[numLines_3 + 1][maxLineLength_3 + 1] = + { { 0, -1 }, // New line occupies positions 0, 1 + { 2, -1 }, // New line occupies positions 2, 3 + { 4, -1 }, // New line occupies positions 4, 5 + { 6, -1 }, + { -1, -1 } }; +#endif // WXMSW + long pos_3[numLines_3+1][maxLineLength_3+1] = + { { 0, -1 }, + { 1, -1 }, + { 2, -1 }, + { 3, -1 }, + { -1, -1 } }; + + long (&ref_pos_3)[numLines_3 + 1][maxLineLength_3 + 1] = +#if wxHAS_2CHAR_NEWLINES + isRichEdit ? pos_3 : pos_3_msw; +#else + pos_3; +#endif + + for ( long y = 0; y < numLines_3+1; y++ ) + for( long x = 0; x < maxLineLength_3+1; x++ ) + { + long p = m_text->XYToPosition(x, y); + INFO("x=" << x << ", y=" << y); + CPPUNIT_ASSERT_EQUAL( ref_pos_3[y][x], p ); + } + + // mixed empty/non-empty lines + text = wxS("123\na\n\nX\n\n"); + m_text->SetValue(text); + const long maxLineLength_4 = 4; + const long numLines_4 = 6; + CPPUNIT_ASSERT_EQUAL( numLines_4, m_text->GetNumberOfLines() ); +#if wxHAS_2CHAR_NEWLINES + // Note: New lines are occupied by two characters. + long pos_4_msw[numLines_4 + 1][maxLineLength_4 + 1] = + { { 0, 1, 2, 3, -1 }, // New line occupies positions 3, 4 + { 5, 6, -1, -1, -1 }, // New line occupies positions 6, 7 + { 8, -1, -1, -1, -1 }, // New line occupies positions 8, 9 + { 10, 11, -1, -1, -1 }, // New line occupies positions 11, 12 + { 13, -1, -1, -1, -1 }, // New line occupies positions 13, 14 + { 15, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 } }; +#endif // WXMSW + long pos_4[numLines_4+1][maxLineLength_4+1] = + { { 0, 1, 2, 3, -1 }, + { 4, 5, -1, -1, -1 }, + { 6, -1, -1, -1, -1 }, + { 7, 8, -1, -1, -1 }, + { 9, -1, -1, -1, -1 }, + { 10, -1, -1, -1, -1 }, + { -1, -1, -1, -1, -1 } }; + + long (&ref_pos_4)[numLines_4 + 1][maxLineLength_4 + 1] = +#if wxHAS_2CHAR_NEWLINES + isRichEdit ? pos_4 : pos_4_msw; +#else + pos_4; +#endif + + for ( long y = 0; y < numLines_4+1; y++ ) + for( long x = 0; x < maxLineLength_4+1; x++ ) + { + long p = m_text->XYToPosition(x, y); + INFO("x=" << x << ", y=" << y); + CPPUNIT_ASSERT_EQUAL( ref_pos_4[y][x], p ); + } +} + #endif // defined(__WXOSX_COCOA__) || defined(__WXMSW__) || defined(__WXGTK__) #endif // WXUSINGDLL