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)

texus

#15
That PageDown issue wasn't very hard, the case where there are less lines than fill in the text box was just not checked.
I did find another rare crash with PageDown when there was a lot of text though, one that was much harder to reproduce, but I managed to fix it eventually. I just hope there aren't any other bugs like that one left.

Code (cpp) Select
            case sf::Keyboard::PageDown:
            {
                // Move to the bottom line when not there already
                if (m_topLine + m_visibleLines > m_lines.size())
                    m_selEnd.y = m_lines.size() - 1;
                else 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_selEnd.y + 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;
            }

Myster

Yes. It works good now)

Now I tried to make a selection the word by double clicking, based on your LeftKey and RightKey events (because I really better to select words that exceed the length of the line). But to be honest, I confused them.
There is a problem: if the caret is at the beginning of the word, it captures the word on the left too. If the cursor at the beginning of the line (i.e. when x = 0), it selects all the text before (from {0, 0} to word). And with the right button is nearly the same, but to the opposite side (to {0, linesize}).
To select a word by double-clicking "skippedWhitespace" bother me enough. :o Maybe it doesn't need to do this?
There is almost nothing has changed, except for an additional variable m_selClicked, and changes in the first part to m_selStart

Code (cpp) Select
            // Check if this is a double click
            if ((m_possibleDoubleClick) && (m_selStart == m_selEnd) && (caretPosition == m_selEnd))
            {
                // The next click is going to be a normal one again
                m_possibleDoubleClick = false;
               
                if( m_lines[m_selStart.y][m_selStart.x] == ' ' )
                {
                    // Select the space
                    m_selEnd.x = m_selStart.x + 1;
                }
                else
                {
                    // Select the word
                    sf::Vector2<std::size_t> m_selClicked = m_selStart;
                   
                    // Move to the beginning of the word
                    bool skippedWhitespace = false;
                    bool done = false;
                    for (unsigned int j = m_selClicked.y + 1; j > 0; --j)
                    {
                        for (unsigned int i = m_selClicked.x; i > 0; --i)
                        {
                            if (skippedWhitespace)
                            {
                                if (isWhitespace(m_lines[m_selStart.y][i-1]))
                                {
                                    m_selStart.x = i;
                                    done = true;
                                    break;
                                }
                            }
                            else
                            {
                                if (!isWhitespace(m_lines[m_selStart.y][i-1]))
                                    skippedWhitespace = true;
                            }
                        }

                        if (!done)
                        {
                            if (m_selStart.y > 0)
                            {
                                m_selStart.y--;
                                if (!m_lines[m_selStart.y].isEmpty() && m_lines[m_selStart.y][m_lines[m_selStart.y].getSize()-1] == '\n')
                                {
                                    if (!skippedWhitespace)
                                        m_selStart.x = m_lines[m_selStart.y].getSize()-1;
                                    else
                                    {
                                        m_selStart.x = 0;
                                        m_selStart.y++;
                                        break;
                                    }
                                }
                                else
                                    m_selStart.x = m_lines[m_selStart.y].getSize();
                            }
                            else
                            {
                                m_selStart.x = 0;
                                m_selStart.y = 0;
                            }
                        }
                        else
                            break;
                    }
                   
                    // Move to the end of the word
                    skippedWhitespace = false;
                    done = false;
                    for (unsigned int j = m_selClicked.y; j < m_lines.size(); ++j)
                    {
                        for (unsigned int i = m_selClicked.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;
                    }
                }
            }...

Myster

Ignore my last post please. I rewrote double-click from scratch. But some things have coincided with your code. Although the double-clicking algorithm really was a bit different than Keys' events. And I really didn't use "skippedWhitespace" (it seems superfluous for that event).

There is little bug (if it acknowledges as a bug, it doesn't cause the crash :D ) - when selects the '\n' at the end of line (at the end of the paragraph especially), it selects word on next line or itself '\n' at the current (but visually the caret moves to beginning of the line)

I'll try to find time to finish, so that it will be selected left word or left space, but not the '\n'

But for now this result:

Code (cpp) Select
...
            // Check if this is a double click
            if ((m_possibleDoubleClick) && (m_selStart == m_selEnd) && (caretPosition == m_selEnd))
            {
                // The next click is going to be a normal one again
                m_possibleDoubleClick = false;
               
                if (isWhitespace(m_lines[m_selStart.y][m_selStart.x]))
                {
                    // Select the space
                    m_selEnd.x = m_selStart.x + 1;
                }
                else
                {
                    // Move start pointer to the beginning of the word
                    bool done = false;
                    for (unsigned int j = m_selStart.y + 1; j > 0; --j)
                    {
                        for (unsigned int i = m_selStart.x; i > 0; --i)
                        {
                            if (isWhitespace(m_lines[m_selStart.y][i-1]))
                            {
                                m_selStart.x = i;
                                done = true;
                                break;
                            }
                            else
                                m_selStart.x = 0;
                        }
                       
                        if (!done)
                        {
                            if (m_selStart.x == 0)
                            {
                                if (m_selStart.y > 0)
                                {
                                    m_selStart.y--;
                                    m_selStart.x = m_lines[m_selStart.y].getSize();
                                }
                                else
                                {
                                    done = true;
                                    break;
                                }
                            }
                        }
                        else
                        {
                            if (m_selStart.x == m_lines[m_selStart.y].getSize())
                            {
                                m_selStart.y++;
                                m_selStart.x = 0;
                            }
                            break;
                        }
                    }
                   
                    // Move start pointer to the end of the word
                    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 (isWhitespace(m_lines[m_selEnd.y][i]))
                            {
                                m_selEnd.x = i;
                                done = true;
                                break;
                            }
                            else
                                m_selEnd.x = m_lines[m_selEnd.y].getSize();
                        }
                       
                        if (!done)
                        {
                            if (m_selEnd.x == m_lines[m_selEnd.y].getSize())
                            {
                                if ((m_selEnd.y + 1) < m_lines.size())
                                {
                                    m_selEnd.y++;
                                    m_selEnd.x = 0;
                                }
                                else
                                {
                                    done = true;
                                    break;
                                }
                            }
                        }
                        else
                        {
                            if (m_selEnd.x == m_lines[m_selEnd.y].getSize())
                            {
                                m_selEnd.y--;
                                m_selEnd.x = m_lines[m_selEnd.y].getSize();
                            }
                            break;
                        }
                    }
                }
            }
            else // No double clicking
...

Myster

Now all bugs were terminated. I added a few lines before the scan starting.

Code (cpp) Select
                ...
                // The next click is going to be a normal one again
                m_possibleDoubleClick = false;
               
                // Correct the caret position if the click was to the right of the end of line
                if (m_lines[m_selStart.y].getSize() > 1 && (m_selStart.x == (m_lines[m_selStart.y].getSize()-1) || m_selStart.x == m_lines[m_selStart.y].getSize()))
                {
                    m_selStart.x--;
                    m_selEnd.x = m_selStart.x;
                }
               
                if (isWhitespace(m_lines[m_selStart.y][m_selStart.x]))
                {
                    // Select the space
                    ...


texus, will all of that were implemented in the future tgui's updates? ::) Cause it's very needful things, and I'd not like to do unnecessary inheritances or change sources every update ot other superfluous things. I think the other programmers too) ::)

And how about EditBox? Can I try to add to it similar events? Namely just three:
control
shift
double-click

texus

Quotewill all of that were implemented in the future tgui's updates?
As soon as we both confirm that the changes are working then the changes will be added in tgui.

Could you send the full TextBox.cpp file so that we have the exact same version?

QuoteAnd how about EditBox? Can I try to add to it similar events?
Sure, feel free to change it too.

Myster

#20
No problem, the file attached.
I meant in any future and any update and even with any other code, etc.  I understand that it even may take time testing) Just to be comfortable control that will not scare the users of future applications)

QuoteSure, feel free to change it too.
Ok, I try if tomorrow will be a time. I hope)

texus

#21
I made one more change to the code. You only selected a single space when double clicking, but if there are multiple whitespace next to each other then they should all be selected. I basically just reused the code that you wrote to select the entire word and made it do the inverse as well to select the whitespaces.

If you have a github account and know how to do it then you could send a pull request with the changes. Otherwise I will just commit the change myself but with your name on it. But I would need your name and email that you want on the git commit.

Quotetexus, will all of that were implemented in the future tgui's updates? ::) Cause it's very needful things, and I'd not like to do unnecessary inheritances or change sources every update ot other superfluous things. I think the other programmers too) ::)
QuoteI meant in any future and any update and even with any other code, etc.  I understand that it even may take time testing) Just to be comfortable control that will not scare the users of future applications)
I don't fully understand these sentences, so if there was still a question in it you should try to rephrase it.

Myster

#22
No, you understood correctly. And you've already answered my question) Thank you :)

Quoteif there are multiple whitespace next to each other then they should all be selected
I updated with your changes. You right, it's better. Just now I noticed that in my Notepad++ implemented multiple space selection.

No. Unfortunately, still I don't have an account on the github. In fact, I think I gave the basic idea, but you made a substantial part of it. I mean, not sure that my work here has the weight and whether it is worth to mention me.
But all at your discretion) I'm thankful for all.
My surname and name (with transliteration): Ilyin Mikhail
E-mail: ilyin_my @ mail.ru (without spaces, it's for spam :) )

And yet all the innovations are stable. I've not bumped into bugs or crashes. But I'll let you know if something will (I hope not). I will test my app as a user, and the continuous work will show itself.
Maybe some features I forgot (my feeling). But now they are not significant, if I can't remember)

Myster

#23
Yes! I remembered! Can you add this line, please?

Code (cpp) Select
            else // No double clicking
            {
                if (!(sf::Keyboard::isKeyPressed(sf::Keyboard::LShift) || sf::Keyboard::isKeyPressed(sf::Keyboard::RShift)))
                    m_selStart = caretPosition;
                m_selEnd = caretPosition;


In the "leftMousePressed" function are no events, but in order not to overload with event links it easier to make the line a bit longer?
In any case, I just remember that "Shift + LeftMouseClick" is comfortable too.  ::)

texus

#24
The changes (including the one you just asked to add) have been added to tgui.

The changes wouldn't be made at all if you didn't start them, and the code that I improved was always still based on what you wrote, so I think you do deserve the credit.