[KRichTextEdit] Always treat key press as single modification in undo stack
ClosedPublic

Authored by poboiko on Apr 14 2020, 11:10 AM.

Details

Summary

If my cursor is on a bullet list, and I press Enter to create a new element,
it registers as 5-7 actions in an undo stack, mostly margins adjustments. So to
rewind it, user has to press Ctrl+Z 5+ times, which is quite frustrating.

Instead this patch suggests to treat _any_ change that is a result of a single
key press as a single event, so user has to press Ctrl+Z just once to undo it

BUG: 256001

Test Plan
  1. Open KMail -> New Mail, enable Rich Text mode (or, say, open KJots)
  2. Create a bullet list with couple of elements
  3. Put cursor on the last element, press Enter to create a new element
  4. Try to undo this action with Ctrl+Z
  5. (without patch) One has to press Ctrl+Z 5-7 times to undo it.
  6. (with patch) A single Ctrl+Z is enough

Diff Detail

Repository
R310 KTextWidgets
Lint
Automatic diff as part of commit; lint not applicable.
Unit
Automatic diff as part of commit; unit tests not applicable.
poboiko created this revision.Apr 14 2020, 11:10 AM
Restricted Application added a project: Frameworks. · View Herald TranscriptApr 14 2020, 11:10 AM
poboiko requested review of this revision.Apr 14 2020, 11:10 AM
poboiko edited the summary of this revision. (Show Details)Apr 14 2020, 11:13 AM
ahmadsamir added a subscriber: ahmadsamir.

I tested this with the krichtexteditor test app from kxmlgui, and it works. (It's never too late to fix a bug, even if it's from 2010 :)).

dfaure accepted this revision.Apr 14 2020, 8:38 PM
This revision is now accepted and ready to land.Apr 14 2020, 8:38 PM
This revision was automatically updated to reflect the committed changes.
poboiko added a comment.EditedApr 16 2020, 5:45 PM

@dfaure There seem to be a regression caused by this patch: if I reach the end of current view, and then press Enter so it has to scroll down, it scrolls to the very beginning of the document instead.

This is the same issue as in ancient bug 195828, and it seems like it is Qt issue.

Here's the simplest MWE:

#include <QApplication>
#include <QMainWindow>
#include <QTextEdit>
#include <QPushButton>
#include <QVBoxLayout>

class CustomTextEdit : public QTextEdit
{
public:
    using QTextEdit::QTextEdit;
    void keyPressEvent(QKeyEvent *e) override
    {
        textCursor().beginEditBlock();
        QTextEdit::keyPressEvent(e);
        textCursor().endEditBlock();
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    auto w = new QWidget();
    auto edit = new CustomTextEdit();
    auto button = new QPushButton(QStringLiteral("Insert new line"));
    button->connect(button, &QPushButton::clicked, [edit](){
        auto cursor = edit->textCursor();
        cursor.beginEditBlock();
        cursor.insertBlock();
        edit->setTextCursor(cursor);
        cursor.endEditBlock();
    });
    auto layout = new QVBoxLayout(w);
    layout->addWidget(edit);
    layout->addWidget(button);
    w->show();
    return a.exec();
}

Seems like the following code in QTextCursorPrivate::setX causes the trouble:

if (priv->isInEditBlock() || priv->inContentsChange) {
    x = -1; // mark dirty
    return;
}

and seems like this "dirty" value is treated literally afterwards.

For this example, switching endEditBlock and setTextCursor lines fixes the issue (and same with Insert Rule).
I've also found a (dirty) workaround for the original patch: if I call setTextCursor(textCursor()) right after endEditBlock(), it works like a charm. It seems like this line (although semantically is a NOOP) triggers the update of this "dirty" value.

How should I proceed?

The *ideal* way of proceeding is to submit a Qt bug report with your testcase (after searching for an existing Qt bug report for the same issue), then turn it into a Qt autotest and make a gerrit merge request for Qt with both the test and a fix for it. And then, and only then, add a workaround in krichtextedit with a Qt version ifdef and a reference to the Qt bug report.

The less ideal way is to only do the Qt bug report and the workaround here...

poboiko added a comment.EditedApr 18 2020, 3:11 PM

The *ideal* way of proceeding is to submit a Qt bug report with your testcase (after searching for an existing Qt bug report for the same issue), then turn it into a Qt autotest and make a gerrit merge request for Qt with both the test and a fix for it. And then, and only then, add a workaround in krichtextedit with a Qt version ifdef and a reference to the Qt bug report.

The less ideal way is to only do the Qt bug report and the workaround here...

Thanks for your comment!
I already did the first part (that is, Qt bugreport).
I can play around Qt code too, and see if I can prepare a test & patch.
Although, to be honest, I'm a bit afraid to touch it. Most likely there was some reasoning behind this "dirty" value, which I can't yet guess by looking at the code (it lacks documentation / comments) :(


BTW, there is a less dirty workaround: there is a nice method ensureCursorVisible(), which does the trick without any black magic. Probably that's what setTextCursor(textCursor()) does internally.