TextBox easy control (rapid text editing)

Started by Myster, 19 August 2015, 09:09:36

Myster

Hello. Sorry if I overlooked a similar theme. And sorry for my english.
I was very annoyed with control in the TextBox (0.7-dev). Is it possible to make the control of it more universal (compulsory) as in many other soft? To be exact:

Minimal universal hotkeys when editing text in the TextBox:

  • Pressed Shift - keeps the end of the selection, if the caret moves to the left, or keeps the beginning of the selection, if the caret moves to the right
  • Pressed Control - makes it possible to move the caret to the first symbol of next word (or space or any given delimiter)
  • Double-click - selects the nearest word to the caret (from space to space or any given delimiter)
  • Home - puts the caret to the first position of the current line
  • End - puts the caret to the last position of the current line
  • Ctrl+Home - puts the caret to the text beginning
  • Ctrl+End - puts the caret to the text ending
  • PageUp - puts the cursor to the first position of the visible text
  • PageDown - puts the cursor to the last position of the visible text

I think this is the most basic. More I do not remember :)

And is it possible in the future (or already have)?

  • "furl" the last character in the line to 0-1px-size if it is space character?  (to ensure that a space did not appear in the first position of the next line)
  • And is it bug may be? When caret stays in the last position of the current line, and when I press Right Cursor key, the caret moves to the first position of the same CURRENT line, but NOT of the NEXT line (as it needs).

Or I did something wrong? Or did not do %)

PS: TGUI - it is the single most easy (unambiguously for me, about like a newbie) and flexible library, thank you for it. I am lost without it. It really gives a scope for ideas and without unnecessary frills.

texus

QuoteAnd is it bug may be? When caret stays in the last position of the current line, and when I press Right Cursor key, the caret moves to the first position of the same CURRENT line, but NOT of the NEXT line (as it needs).
This sounds like a bug. I'll look into it.

The other things can't be done with tgui. Both the double click one and the furl where on my todo list but they are not that easy to do.
I don't have time now, so I look at it this evening whether there is a way that you could do these things from inside your own code.

Myster

No problem, I understand. I'll try to change the caret control from my code. But I necessarily had to feel out the situation. I suppose if it was fixed or it will be)

And... I apologize for the poorly studied subject) But as I understood from docs I am free to try to correct the source code of Tgui's TextBox for my app? (on emergency) Cause that the class contains protected members like m_selStart, m_selEnd, etc, and I am afraid of some possible conflicts after inheriting of a class under my needs. Although maybe I'm wrong (I'm not prof-coder as I said) :)

Thanks for your answers)

texus

QuoteI understood from docs I am free to try to correct the source code of Tgui's TextBox for my app?
Sure, you can perfectly inherit from it and override some functions.
You are even allowed to make changes in the tgui source directly, but that would make it harder to ship the program or to update tgui of course.

Changing the protected members directly can indeed cause the text box to malfunction, you would basically have to know how things work internally and things might break on future changes. Public functions should be used as much as possible because they have a guarantee to work correctly (if they don't then the code is badly designed or it is a bug in tgui).

But you can also read the variables without problems so it should be possible to implement the changes. When inheriting from TextBox there are only a few members that you need to access. The m_lines variable contains the lines of text in the text box, m_selStart and m_selEnd are (x,y) pairs containing the amount of letters on the line before the caret and the line nr. The m_topLine and m_visibleLines can be used to find out which part is visible. You would only have to set the m_selStart and m_selEnd to the place where you want the caret to be and then call either rearrangeText or updateSelectionTexts to let my code fix the other variables. But you might have to experiment a bit because even I don't know exactly what every function or variable does.

The rearrangeText and updateSelectionTexts are still private, but by the time you read this I will have made them protected so if you update the the latest v0.7-dev version then you should be able to call these functions from a derived class.

Except for the double-click and the furl the changes should be relatively easy. The furl is actually already partly implemented in the code. There are two blocks commented out, the one at line 1276 being the problematic part. I doubt that you would be willing to rewrite the rearrangeText, but technically that is the place where the change has to be made to support it.

If you implement the things that you want then feel free to send me the code so that I can perhaps add it to tgui. I simply don't have the time to write everything myself anymore.

texus

QuoteWhen caret stays in the last position of the current line, and when I press Right Cursor key, the caret moves to the first position of the same CURRENT line, but NOT of the NEXT line (as it needs).
Could you show some code that reproduces this issue? It may be specific to the combination of your text and textbox size because I can't reproduce this.

Myster

#5
Big thanks for useful brief) I'll try it, but not sure that it will be soon (for me it may be long). But I'll try certainly, and if it will be some productive, I'll send my results.

With the no-return problem I tested a little more, and I can add a few words about the strange signs in the behavior of the caret:
1) It only happens at the end of a paragraph (I think, where the returning is going to take place... and yes, I forgot to say that I'm still working and compile on Win7. My first thoughts about the problem with a difference return characters like "\r\n" on Windows, but maybe I'm wrong)
2) I'll describe a little easier the bug algorithm that I saw:
* Cursor at the end of a paragraph
* Pressing the right arrow
* The cursor moves to the beginning of the CURRENT line
* Pressing the right arrow again
* Now the cursor moves to the NEXT line

Of course, the code is already big, so I will show clippings where my textbox is used (I think everything is pretty trivial):

in Layout.h

class Layout
{
private:
// ...
tgui::TextBox::Ptr mainTextBox;
// ...
};


in Layout.cpp
// ...
extern sf::RenderWindow window;
extern tgui::Gui gui;
// ...

Layout::Layout(unsigned _scr_w, unsigned _scr_h)
{
// ...
mainTextBox = tgui::TextBox::create(path_guiconf);
mainTextBox->setTextSize(text_size);
gui.add(mainTextBox);
// ...
}

void Layout::UpdateSizes(unsigned _scr_w, unsigned _scr_h)
{
// ...
mainTextBox->setPosition(col_text_pos, row1_pos_top + button_height*2.0f + spacing_border*2.0f);
mainTextBox->setSize(col_text_width, scr_h - button_height*3.0f - spacing_border*5.0f);
// ...
}

void Layout::Update()
{
if( win_mode == MODAL ){
mainTextBox->disable();
// ...
}else{
mainTextBox->enable();
// ...
}
}

void Layout::SceneLoad()
{
// ...
mainTextBox->setText( project[proj_pos-1].SceneGetText(item_pos) );
// ...
}

void Layout::SceneSave()
{
// ...
sf::String tstr( mainTextBox->getText() );
// ...
}


Actually my app quite raw, and some bug with the textbox already had, which was associated with the continuous sizes updating. Due to the continuous use of the resize command, a scroll bar did not give to see the text, if the view range of the textbox was without caret. It was necessary first to move the caret closer to opposite side (top or bottom), then use the scroll bar again (by mouse I mean). I corrected that visual bug when started to use resize-command only when window-resize-event happens, not every frame. It was my stupid mistake. And I wouldn't surprise it may be again) Though... all size bugs already fixed %) And moreover, this problem occurs in any created textbox (without any resize, and even inside the child windows)... I did not leave think about "\r\n" on Windows OS.

texus

The bug is now fixed, knowing that it happened at the end of a paragraph was enough to reproduce it here and thus quickly find the issue.

Inside tgui only '\n' is used so this was not related to windows. The problem happened because the contents of the line contains '\n' at the end. While you see e.g. 5 characters on the screen, the line will contain 6 characters because it also contains the newline. The caret is at position 5 and when pressing the right arrow key the code just checked if it was at the end of the line (which it isn't technically) and move it forward. The problem was solved by skipping the '\n' at the end of the line.

Myster

Understood. Excellent! One terminated))
Thanks. Now I'll try to be useful with other questions, if I can. With some success I'll keep up to post.

Myster

#8
I did something, but a pair of things with bugs (details below).
From my list of the first post I implemented the following (maybe I don't know about any potential problems, but so far I didn't notice a lot of bugs at work. Only a pair as I said):


  • Pressed Shift (yes) (*old bug with the '\n' and RightKey I suppose, only at the end of the current line, and it's buggy only with Shift selecting, details below)
  • Pressed Control (yes)
  • Double-click (yes)
  • Home (yes)
  • End (yes)
  • Ctrl+Home (yes)
  • Ctrl+End (yes)
  • PageUp (yes) (*may be bug again with the Shift-selection, in special situations my app throws, I think problem not with the PageUp key, but with the Shift. I didn't even know at what point might throw, but I'll try to understand)
  • PageDown (yes)

Easier to see the code.

All problems abut to the return from the current line to the next. All that is needed is to miss the symbol '\n' wherever it appears on the way of 'm_selEnd'.
The bug algorithm that I saw (now when Shift pressed):
>> Cursor at the end of a paragraph
>> Pressing the right arrow
>> The cursor moves to the beginning of the NEXT line (it's ok), but the NEXT line moves to the end of the CURRENT line! The CURRENT and NEXT line are joined now when '\n' selected! :o  :o  :o
>> Pressing the right arrow again
>> Now the cursor is moved to the second character of the next line, and the next line gets to its rightful place)

In the source TextBox.cpp:

1) in the "leftMousePressed" function only the following lines was replaced with new word-selection code:

// Select the whole text
m_selStart = {0, 0};
m_selEnd = sf::Vector2<std::size_t>(m_lines[m_lines.size()-1].getSize(), _lines.size()-1);


2) in the "keyPressed" function:

case sf::Keyboard::Up: (nearly all corrected)
case sf::Keyboard::Down: (nearly all corrected)
case sf::Keyboard::Left: (nearly all corrected)
case sf::Keyboard::Right: (nearly all corrected) (*but here lies the bug with selection of '\n')
case sf::Keyboard::Home: (nearly all corrected)
case sf::Keyboard::End: (nearly all corrected)
case sf::Keyboard::PageUp: (added)
case sf::Keyboard::PageDown: (added)


3) When I corrected I questioned the need for the following code:

if (m_selStart != m_selEnd)
{
if ((m_selStart.y > m_selEnd.y) || ((m_selStart.y == m_selEnd.y) && (m_selStart.x > m_selEnd.x)))
m_selStart = m_selEnd;
else
m_selEnd = m_selStart;
}
...


because the selection is still lost in the end of the keys' method execution. In any case, I haven't noticed significant differences. In my case, all selection-deselection problems decided a simple condition:

if (!event.key.shift) m_selStart = m_selEnd;

But may be I'm wrong.

Summary:
The bug with the Union "Shift-selection + jumping next line" is tolerable because app isn't throws.
But the bug with the Union "Shift-selection + PageUp" very unstable, it's seriously, than the jumping line.
I will still think with it.

texus

#9
I'll see if I can fix those things.

QuoteIf the caret at the space then the word by left side will be selected
Is there an editor that works like that or is this just a side-effect of the code? The text editor that I use (gedit) selects the whitespace itself when the cursor is on the left of the space or between multiple spaces.

Btw, I fixed your post. You needed to remove both the list tags in the beginning and their corresponding /list tags somewhere in the middle together.

Edit: Your code is also giving warnings on lines 504 and 722 since the unsigned int is always equal or bigger than 0. If the break isn't called then it will crash. I'll fix the one on 722 together with the other things that I have to fix in that function, but perhaps you could have a look at 504 if you also want to change the behavior of the double clicking on space? Of course if you don't want to then I will look into both.

Myster

QuoteIs there an editor that works like that or is this just a side-effect of the code? The text editor that I use (gedit) selects the whitespace itself when the cursor is on the left of the space or between multiple spaces.
Agree. I use Notepad++ and it's selects spaces too. Like many editors as I see.
Honestly I thought that select the spaces never need, but only words. But I was wrong (and didn't think properly). In some cases it needs of course. It's better to stick to general practice and select the spaces itself. There is no doubt.

Quotewarnings on lines 504 and 722
Oops, I overlooked. Hasty. Remainders from the past variants. "==" meant.

Quote... somewhere in the middle together.
Overlooked too :) Thanks

QuoteI'll see if I can fix those things.
I will be very grateful. Also I will look for bugs, but at times for some things I lack logical vision.


texus

I rewrote some parts of the keyPressed function, you should check if your problems are gone and if everything still works as it should (I changed some details in behavior).
Code (cpp) Select
    void TextBox::keyPressed(const sf::Event::KeyEvent& event)
    {
        switch (event.code)
        {
            case sf::Keyboard::Up:
            {
                if (m_selEnd.y > 0)
                    m_selEnd = findCaretPosition({m_caretPosition.x, m_caretPosition.y - m_lineHeight});
                else
                    m_selEnd = {0, 0};

                if (!event.shift)
                    m_selStart = m_selEnd;

                updateSelectionTexts();
                break;
            }

            case sf::Keyboard::Down:
            {
                if (m_selEnd.y < m_lines.size()-1)
                    m_selEnd = findCaretPosition({m_caretPosition.x, m_caretPosition.y + m_lineHeight});
                else
                    m_selEnd = sf::Vector2<std::size_t>(m_lines[m_lines.size()-1].getSize(), m_lines.size()-1);

                if (!event.shift)
                    m_selStart = m_selEnd;

                updateSelectionTexts();
                break;
            }

            case sf::Keyboard::Left:
            {
                if (event.control)
                {
                    // Move to the beginning of the word (or to the previous word when already at the beginning)
                    bool skippedWhitespace = false;
                    bool done = false;
                    for (unsigned int j = m_selEnd.y + 1; j > 0; --j)
                    {
                        for (unsigned int i = m_selEnd.x; i > 0; --i)
                        {
                            if (skippedWhitespace)
                            {
                                if (isWhitespace(m_lines[m_selEnd.y][i-1]))
                                {
                                    m_selEnd.x = i;
                                    done = true;
                                    break;
                                }
                            }
                            else
                            {
                                if (!isWhitespace(m_lines[m_selEnd.y][i-1]))
                                    skippedWhitespace = true;
                            }
                        }

                        if (!done)
                        {
                            if (m_selEnd.y > 0)
                            {
                                m_selEnd.y--;
                                if (!m_lines[m_selEnd.y].isEmpty() && m_lines[m_selEnd.y][m_lines[m_selEnd.y].getSize()-1] == '\n')
                                {
                                    if (!skippedWhitespace)
                                        m_selEnd.x = m_lines[m_selEnd.y].getSize()-1;
                                    else
                                    {
                                        m_selEnd.x = 0;
                                        m_selEnd.y++;
                                        break;
                                    }
                                }
                                else
                                    m_selEnd.x = m_lines[m_selEnd.y].getSize();
                            }
                            else
                            {
                                m_selEnd.x = 0;
                                m_selEnd.y = 0;
                            }
                        }
                        else
                            break;
                    }
                }
                else
                {
                    if (m_selEnd.x > 0)
                        m_selEnd.x--;
                    else
                    {
                        // You are at the left side of a line so move up
                        if (m_selEnd.y > 0)
                        {
                            m_selEnd.y--;
                            m_selEnd.x = m_lines[m_selEnd.y].getSize() - 1;
                        }
                    }
                }

                if (!event.shift)
                    m_selStart = m_selEnd;

                updateSelectionTexts();
                break;
            }

            case sf::Keyboard::Right:
            {
                // You can still move to the right on this line
                if (event.control)
                {
                    // Move to the beginning of the word (or to the previous word when already at the beginning)
                    bool skippedWhitespace = false;
                    bool done = false;
                    for (unsigned int j = m_selEnd.y; j < m_lines.size(); ++j)
                    {
                        for (unsigned int i = m_selEnd.x; i < m_lines[m_selEnd.y].getSize(); ++i)
                        {
                            if (skippedWhitespace)
                            {
                                if (isWhitespace(m_lines[m_selEnd.y][i]))
                                {
                                    m_selEnd.x = i;
                                    done = true;
                                    break;
                                }
                            }
                            else
                            {
                                if (!isWhitespace(m_lines[m_selEnd.y][i]))
                                    skippedWhitespace = true;
                            }
                        }

                        if (!done)
                        {
                            if (!skippedWhitespace)
                            {
                                if (m_selEnd.y+1 < m_lines.size())
                                {
                                    m_selEnd.y++;
                                    m_selEnd.x = 0;
                                }
                            }
                            else
                            {
                                if (!m_lines[m_selEnd.y].isEmpty() && (m_lines[m_selEnd.y][m_lines[m_selEnd.y].getSize()-1] == '\n'))
                                    m_selEnd.x = m_lines[m_selEnd.y].getSize() - 1;
                                else
                                    m_selEnd.x = m_lines[m_selEnd.y].getSize();
                            }
                        }
                        else
                            break;
                    }
                }
                else
                {
                    // Move to the next line if you are at the end of the line
                    if ((m_selEnd.x == m_lines[m_selEnd.y].getSize()) || ((m_selEnd.x+1 == m_lines[m_selEnd.y].getSize()) && (m_lines[m_selEnd.y][m_selEnd.x] == '\n')))
                    {
                        if (m_selEnd.y < m_lines.size()-1)
                        {
                            m_selEnd.y++;
                            m_selEnd.x = 0;
                        }
                    }
                    else
                        m_selEnd.x++;
                }

                if (!event.shift)
                    m_selStart = m_selEnd;

                updateSelectionTexts();
                break;
            }

            case sf::Keyboard::Home:
            {
                if (event.control)
                    m_selEnd = {0, 0};
                else
                    m_selEnd.x = 0;

                if (!event.shift)
                    m_selStart = m_selEnd;

                updateSelectionTexts();
                break;
            }

            case sf::Keyboard::End:
            {
                if (event.control)
                    m_selEnd = {m_lines[m_lines.size()-1].getSize(), m_lines.size()-1};
                else
                {
                    if (!m_lines[m_selEnd.y].isEmpty() && (m_lines[m_selEnd.y][m_lines[m_selEnd.y].getSize()-1] == '\n'))
                        m_selEnd.x = m_lines[m_selEnd.y].getSize() - 1;
                    else
                        m_selEnd.x = m_lines[m_selEnd.y].getSize();
                }

                if (!event.shift)
                    m_selStart = m_selEnd;

                updateSelectionTexts();
                break;
            }

            case sf::Keyboard::PageUp:
            {
                // Move to the top line when not there already
                if (m_selEnd.y != m_topLine)
                    m_selEnd.y = m_topLine;
                else
                {
                    // Scroll up when we already where at the top line
                    Padding padding = getRenderer()->getScaledPadding();
                    auto visibleLines = static_cast<std::size_t>((getSize().y - padding.top - padding.bottom) / m_lineHeight);
                    if (m_topLine < visibleLines - 1)
                        m_selEnd.y = 0;
                    else
                        m_selEnd.y = m_topLine - visibleLines + 1;
                }

                m_selEnd.x = 0;

                if (!event.shift)
                    m_selStart = m_selEnd;

                updateSelectionTexts();
                break;
            }

            case sf::Keyboard::PageDown:
            {
                // Move to the bottom line when not there already
                if (m_selEnd.y != m_topLine + m_visibleLines - 1)
                    m_selEnd.y = m_topLine + m_visibleLines - 1;
                else
                {
                    // Scroll down when we already where at the bottom line
                    Padding padding = getRenderer()->getScaledPadding();
                    auto visibleLines = static_cast<std::size_t>((getSize().y - padding.top - padding.bottom) / m_lineHeight);
                    if (m_selEnd.y + visibleLines >= m_lines.size() + 2)
                        m_selEnd.y = m_lines.size() - 1;
                    else
                        m_selEnd.y = m_topLine + m_visibleLines + visibleLines - 2;
                }

                if (!m_lines[m_selEnd.y].isEmpty() && (m_lines[m_selEnd.y][m_lines[m_selEnd.y].getSize()-1] == '\n'))
                    m_selEnd.x = m_lines[m_selEnd.y].getSize() - 1;
                else
                    m_selEnd.x = m_lines[m_selEnd.y].getSize();

                if (!event.shift)
                    m_selStart = m_selEnd;

                updateSelectionTexts();
                break;
            }

            case sf::Keyboard::Return:
            {
                textEntered('\n');
                break;
            }

            case sf::Keyboard::BackSpace:
            {
                if (m_readOnly)
                    break;

                // Make sure that we did not select any characters
                if (m_selStart == m_selEnd)
                {
                    // Delete the previous character on this line
                    if (m_selEnd.x > 0)
                    {
                        m_selEnd.x--;
                    }
                    else // We are at the left side of the line
                    {
                        // Delete a character from the line above you
                        if (m_selEnd.y > 0)
                        {
                            m_selEnd.y--;
                            m_selEnd.x = m_lines[m_selEnd.y].getSize() - 1;
                        }
                        else // You are at the beginning of the text
                            break;
                    }

                    m_selStart = m_selEnd;
                    m_text.erase(findTextCaretPosition().second, 1);
                    rearrangeText(true);
                }
                else // When you did select some characters then delete them
                    deleteSelectedCharacters();

                // The caret should be visible again
                m_caretVisible = true;
                m_animationTimeElapsed = {};

                m_callback.text = m_text;
                sendSignal("TextChanged", m_text);
                break;
            }

            case sf::Keyboard::Delete:
            {
                if (m_readOnly)
                    break;

                // Make sure that no text is selected
                if (m_selStart == m_selEnd)
                {
                    // Delete the next character on this line
                    if (m_selEnd.x == m_lines[m_selEnd.y].getSize())
                    {
                        // Delete a character from the line below you
                        if (m_selEnd.y < m_lines.size()-1)
                        {
                            m_selEnd.y++;
                            m_selEnd.x = 0;

                            m_selStart = m_selEnd;
                        }
                        else // You are at the end of the text
                            break;
                    }

                    m_text.erase(findTextCaretPosition().second, 1);
                    rearrangeText(true);
                }
                else // You did select some characters, so remove them
                    deleteSelectedCharacters();

                m_callback.text = m_text;
                sendSignal("TextChanged", m_text);
                break;
            }

            case sf::Keyboard::A:
            {
                if (event.control)
                {
                    m_selStart = {0, 0};
                    m_selEnd = sf::Vector2<std::size_t>(m_lines[m_lines.size()-1].getSize(), m_lines.size()-1);
                    updateSelectionTexts();
                }

                break;
            }

            case sf::Keyboard::C:
            {
                if (event.control)
                    Clipboard::set(m_textSelection1.getString() + m_textSelection2.getString());

                break;
            }

            case sf::Keyboard::X:
            {
                if (event.control && !m_readOnly)
                {
                    Clipboard::set(m_textSelection1.getString() + m_textSelection2.getString());
                    deleteSelectedCharacters();
                }

                break;
            }

            case sf::Keyboard::V:
            {
                if (event.control && !m_readOnly)
                {
                    sf::String clipboardContents = Clipboard::get();

                    // Only continue pasting if you actually have to do something
                    if ((m_selStart != m_selEnd) || (clipboardContents != ""))
                    {
                        deleteSelectedCharacters();

                        m_text.insert(findTextCaretPosition().first, clipboardContents);
                        m_lines[m_selStart.y].insert(m_selStart.x, clipboardContents);

                        m_selStart.x += clipboardContents.getSize();
                        m_selEnd = m_selStart;
                        rearrangeText(true);

                        m_callback.text = m_text;
                        sendSignal("TextChanged", m_text);
                    }
                }

                break;
            }

            default:
                break;
        }

        // The caret should be visible again
        m_caretVisible = true;
        m_animationTimeElapsed = {};
    }

Myster

I'm surprised such work speed)) It's pretty much all that I needed.

* PageUp now works fine (as long as the problems are not seen, and the application does not throw)
* The Control behavior is now very competent (ends of words are taken into account, depending on the "caret movement" direction; and it seems the word that exceed the size of the line, fully selects; and Control key while pressing Shift now allows to select a word from a neighboring line)

Now it's more comfortable to edit text  :D

It remains an issues with: "jumping-joining next line", when Shift-selection (at the end of line) takes place without Control key; and with DoubleClick behaviour.
I'm still trying to fix double-click, but so far I have many distractions :o

Thanks a lot, texus. It's invaluable help)

texus

QuoteI'm surprised such work speed
I have summer holiday right now so I have nothing better to do than playing games and working on tgui :)
Once university starts again next month I will hardly have any time to work on tgui so I try to make up for that during the summer holiday.

QuoteIt remains an issues with: "jumping-joining next line"
It took me some time to figure out where it had to be fixed, either the setPosition or the updateSelectionTexts function had to be changed but after changing my mind ten times about which one was easier to change I ended up just choosing updateSelectionTexts. The fix is actually only 3 insertions, but I'll send the entire function so that you don't have to try to figure out where to make the change.
Code (cpp) Select
    void TextBox::updateSelectionTexts()
    {
        Padding padding = getRenderer()->getScaledPadding();

        float maxLineWidth = std::max(0.f, getSize().x - padding.left - padding.right);
        if (m_scroll && (!m_scroll->getAutoHide() || (m_scroll->getMaximum() > m_scroll->getLowValue())))
            maxLineWidth = std::max(0.f, maxLineWidth - m_scroll->getSize().x);

        // If there is no selection then just put the whole text in m_textBeforeSelection
        if (m_selStart == m_selEnd)
        {
            sf::String displayedText;
            for (std::size_t i = 0; i < m_lines.size(); ++i)
            {
                if (((m_lines[i] != "") && (m_lines[i][m_lines[i].getSize()-1] == '\n')) || (i == m_lines.size()-1))
                    displayedText += m_lines[i];
                else
                    displayedText += m_lines[i] + "\n";
            }

            m_textBeforeSelection.setString(displayedText);
            m_textSelection1.setString("");
            m_textSelection2.setString("");
            m_textAfterSelection1.setString("");
            m_textAfterSelection2.setString("");
        }
        else // Some text is selected
        {
            auto selectionStart = m_selStart;
            auto selectionEnd = m_selEnd;

            if ((m_selStart.y > m_selEnd.y) || ((m_selStart.y == m_selEnd.y) && (m_selStart.x > m_selEnd.x)))
                std::swap(selectionStart, selectionEnd);

            // Set the text before the selection
            if (selectionStart.y > 0)
            {
                sf::String string;
                for (std::size_t i = 0; i < selectionStart.y; ++i)
                {
                    if ((m_lines[i] != "") && (m_lines[i][m_lines[i].getSize()-1] == '\n'))
                        string += m_lines[i];
                    else
                        string += m_lines[i] + "\n";
                }

                string += m_lines[selectionStart.y].substring(0, selectionStart.x);
                m_textBeforeSelection.setString(string);
            }
            else
                m_textBeforeSelection.setString(m_lines[0].substring(0, selectionStart.x));

            // Set the selected text
            if (m_selStart.y == m_selEnd.y)
            {
                m_textSelection1.setString(m_lines[selectionStart.y].substring(selectionStart.x, selectionEnd.x - selectionStart.x));
                m_textSelection2.setString("");
            }
            else
            {
                if (!m_lines[selectionStart.y].isEmpty() && (m_lines[selectionStart.y][m_lines[selectionStart.y].getSize()-1] != '\n') && (selectionEnd.y > selectionStart.y))
                    m_textSelection1.setString(m_lines[selectionStart.y].substring(selectionStart.x, m_lines[selectionStart.y].getSize() - selectionStart.x) + '\n');
                else
                    m_textSelection1.setString(m_lines[selectionStart.y].substring(selectionStart.x, m_lines[selectionStart.y].getSize() - selectionStart.x));

                sf::String string;
                for (std::size_t i = selectionStart.y + 1; i < selectionEnd.y; ++i)
                {
                    if ((m_lines[i] != "") && (m_lines[i][m_lines[i].getSize()-1] == '\n'))
                        string += m_lines[i];
                    else
                        string += m_lines[i] + "\n";
                }

                string += m_lines[selectionEnd.y].substring(0, selectionEnd.x);

                m_textSelection2.setString(string);
            }

            // Set the text after the selection
            {
                m_textAfterSelection1.setString(m_lines[selectionEnd.y].substring(selectionEnd.x, m_lines[selectionEnd.y].getSize() - selectionEnd.x));

                sf::String string;
                for (std::size_t i = selectionEnd.y + 1; i < m_lines.size(); ++i)
                {
                    if (((m_lines[i] != "") && (m_lines[i][m_lines[i].getSize()-1] == '\n')) || (i == m_lines.size()-1))
                        string += m_lines[i];
                    else
                        string += m_lines[i] + "\n";
                }

                m_textAfterSelection2.setString(string);
            }
        }

        // Check if the caret is located above or below the view
        if (m_scroll != nullptr)
        {
            if (m_selEnd.y <= m_topLine)
                m_scroll->setValue(static_cast<unsigned int>(m_selEnd.y * m_lineHeight));
            else if (m_selEnd.y + 1 >= m_topLine + m_visibleLines)
                m_scroll->setValue(static_cast<unsigned int>(((m_selEnd.y + 1) * m_lineHeight) - m_scroll->getLowValue()));
        }

        updatePosition();
    }

Myster

I like when people can rest a bit) Especially those who do a lot for other people ;)
I'm still on vacation too, but next week go out to work) and I think I will not worry so much, but who knows me :)
I pester often, because I'm not even a programmer (hardly at least), I'm more a designer-artist-writer, even though I'm an accountant by profession ;D

And I am very sorry, because I want a bit spoil the rest ::)
With the "jumping line" it works fine now) Big thanks for code! And with the PageUp there is no problem now as I see, but now there are PageDown seriously disappointed me. :-[

But I think I've understood what the problem is:
if the TextBox never contained more lines than TextBox size, the PageDown will try to move to a non-existent line.

I attached a video where it's better to be seen than I will explain (at the beginning of the video, tested where was a lot of lines of text; at the end of the video - where the text is never typed - and it causes crash)