Manipulating selection with QTextCursor and QTextEdit [SOLVED]



  • I'm working with a subclass of QTextEdit - but because of my project's requirements, I only display one "screen" of text at a time. The very large complete text is stored elsewhere, and I have my own scrollbar to know which subset of it to display.

    Now I'm trying to achieve a very "natural" behavior that I lost because of the way I work with the QTextEdit - the user selects text, keeps the button pressed, then drags the mouse up (or down) outside the boundaries of the text box, which causes scrolling and the extension of selection.

    I got the scrolling part working (based on QTextEdit's source code) but I'm having a hard time with getting the right selection behavior. After my text is replaced, I want to update the selection to reflect the extended one.

    My specific question is how to achieve the following state in code: get to a selection "in progress" that starts (or ends) with a position I choose? I want to "fake" the selection as if the user momentarily let go of the mouse button, selected the text from the previous position and got back to where his mouse cursor was.

    I hope this is clear, if not I'll try to provide a screenshot. Appreciate any feedback!


  • Lifetime Qt Champion

    Hi,

    I think some images would indeed be a good thing to show what you want to achieve



  • I prepared some screenshots to show the wanted behavior, using a standard text box:

    Step 1 - user starts selecting some lines, dragging cursor up

    Step 2 - user continues to select all the way to the currently-top-displayed-line

    Step 3 - user continues to drag up, the text scrolled one line up, selection extended to include newly displayed line

    This (step 3) is the behavior I want to imitate. In my code, the scrolling up causes a replacement of the text. Once that happens, the previous selection is (naturally) forgotten. I want to write code that - immediately after replacement of text and while user is still with mouse button pressed - causes selection to restart from the position it was in before the scroll.

    I hope this is more clear with the screenshots.



  • If there are any ideas still I'll appreciate it.


  • Moderators

    I guess you need to track the selection yourself ie. on click calculate the "real" or "absolute" cursor position, similar on mouse move and select the "local" or "relative" block yourself.

    Sounded like a nice exercise, so here's my coffee-break try at this.
    I didn't bother to implement the scroll bar so use mouse scroll to move around.
    Pardon the rough edges and napkin quality code, but I hope you can extract the basic idea out of it:

    #include <QApplication>
    #include <QMouseEvent>
    #include <QWheelEvent>
    #include <QTextEdit>
    
    class TextEdit : public QTextEdit
    {
    public:
        TextEdit(QWidget* parent = nullptr) : QTextEdit(parent),
            startPos(0), endPos(0), absCursorStart(0), absCursorEnd(0) {}
    
        void mouseMoveEvent(QMouseEvent* evt) {
            if(!(evt->buttons() & Qt::LeftButton))
                return;
    
            auto cfp = cursorForPosition(evt->pos());
            absCursorEnd = cfp.position() + startPos * 11;
    
            if(evt->pos().y() < 0 && startPos > 0)
                --startPos;
            if(evt->pos().y() >= height() && endPos < textData.size())
                ++startPos;
    
            adjust();
        }
    
        void wheelEvent(QWheelEvent* evt) {
            auto lines = evt->delta() / 100;
            startPos = qBound(0, startPos - lines, textData.size());
            adjust();
        }
    
        void mousePressEvent(QMouseEvent* evt) {
            auto cfp = cursorForPosition(evt->pos());
            setTextCursor(cfp);
            absCursorStart = absCursorEnd = cfp.position() + startPos * 11;
        }
        void adjust() {
            auto numLines = height() / fontMetrics().height() - 1;
            auto size = qMin(textData.size(), numLines);
            auto subText = QStringList(textData.mid(startPos, size)).join("\n");
            setText(subText);
            endPos = startPos + numLines;
    
            auto numVisibleChars = (endPos - startPos) * 11 - 1;
    
            auto localCursorStart = qBound(0, absCursorStart - startPos * 11, numVisibleChars);
            auto localCursorEnd   = qBound(0, absCursorEnd - startPos * 11, numVisibleChars);
    
            auto tc = textCursor();
            tc.setPosition(localCursorEnd);
            tc.setPosition(localCursorStart, QTextCursor::KeepAnchor);
            setTextCursor(tc);
        }
    
        void resizeEvent(QResizeEvent* evt) {
            QTextEdit::resizeEvent(evt);
            adjust();
        }
    
        QStringList textData;
        int startPos;
        int endPos;
        int absCursorStart;
        int absCursorEnd;
    };
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        QStringList stuff;
        for(int i = 0; i < 100; ++i)
            stuff << QString(10, QChar('a' + (i % 30)));
    
        TextEdit te;
        te.textData = stuff;
        te.show();
    
        return a.exec();
    }
    

    Of course you need to be careful about copy/paste. You should copy the whole selected block manually, as the built in mechanism will only copy the visible portion of the selection this way.



  • Chris, thank you, that's really helpful. I admit I'm having trouble with the rough edges but basic idea extraction is definitely in progress.

    I already had something similar in my code, but I think there was something wrong about the way I reapplied my selection after updating the data.

    Again, huge thanks!



  • Applied to my code, marked as [SOLVED]. Thank you once again!


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.