3

I'm trying to show a tooltip when the cursor is over a keyword in a text editor using:

QTextCursor cursor = cursorForPosition(pos);
cursor.select(QTextCursor::WordUnderCursor);

This works well but the definition of a word does not fits my needs. For exemple the keyword \abcde is recognized as 2 words \ and abcde. Similarly the word a1:2 is recognized in three parts a1, : and 1. Basically what I'd like is to change the behavior such as a word is defined as a set of characters separated by space.

I tryied QTextCursor::BlockUnderCursor but it does the same than QTextCursor::LineUnderCursor and returns the entire line.

JRR
  • 3,024
  • 2
  • 13
  • 37

2 Answers2

0

Here's a way to select words separated by a space when a text cursor's position changes.

It basically works this way:

  • Check if the current cursor is selecting (QTextCursor::anchor and QTextCursor::position have different values), and set the custom cursor's position accordingly.
  • Go forward until you reach the end or a space.
  • Store the last position reached.
  • Go back until you reach the start or a space.
  • Select from where the cursor stopped until the position previously stored as the end.
  • The result is the fetched selection.
void Form::on_textEdit_cursorPositionChanged()
{
    //prevent infinite loop in case text is added manually at start
    if(textCursor().position()<0)
            return;

    QTextCursor cursor(ui->textEdit->textCursor());
    int end;

    //prevent cursor from being anchored when the normal cursor is selecting
    if(ui->textEdit->textCursor().anchor()!=ui->textEdit->textCursor().position())
    {
        cursor.setPosition(ui->textEdit->textCursor().position());
    }

    //I'm checking for new line using QChar(8233)
    //you might have to change that depending on your platform
    while(!cursor.atEnd() &&
          (ui->textEdit->document()->characterAt(cursor.position())!=' ' && ui->textEdit->document()->characterAt(cursor.position())!=QChar(8233)))
    {
        cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor);
    }

    end=cursor.position();

    while(!cursor.atStart() &&
          (ui->textEdit->document()->characterAt(cursor.position()-1)!=' ' && ui->textEdit->document()->characterAt(cursor.position()-1)!=QChar(8233)))
    {
        cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor);
    }

    cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor,end-cursor.position());

    QString result = cursor.selectedText();
}

Demonstration:

Custom cursor at work

  • 1
    @JRR i updated the break condition instead with the condition you provided, i also use linux and it works, must add it to both conditions too, as for windows, i checked and saw that windows uses '\r\n', see if this shortcuts your research. Good luck ```if(cursor->selectedText()==' ' || cursor->selectedText()==QChar(8233))``` – Abderrahmene Rayene Mihoub Feb 22 '23 at 14:08
0

Well, let's start with the real problem. Been a "bug" since Qt4. A small discussion can be found here. In true Qtc fashion the bug was closed with this comment:

This is easily done by users implementing their own selection based on their own criteria

And zero examples of how to do that, back in 2009. Since then Qt has adopted Unicode Word Boundaries. They have also doubled down on the reprehensible coding practice of putting everything anyone would want to change into internal private classes. Here is the snippet from QTextCursor

enter image description here

Some PyQt developers claim to have hacked around it here. That will do you little good because someone got the bright idea to make select() use an enum since then.

If you are using Qt6 you are beyond screwed or so it appears. I can find no official Qt6 version of qtextengine.cpp. Most people, including myself, stopped using Qt over the past two years due to things like this.

Your "solution" is to create a custom QTextEngine class and make your QTextCursor use it in the layout. You need to override this hard coded fiasco to always return false.

enter image description here

Your second "solution" is to install an eventFilter(). In it you must trap all events to the QTextEdit that might select/move a word, and handle them yourself.

Your third "solution" is to create a custom QTextEdit class and override the default event() handler. Again, you must trap every keypress/mouse/whatever event that might select or move a word.

DO NOT try to take a shortcut and map onto selectionChanged() signal. If someone tries to select 3 characters with mouse/keyboard you will force it to a full word.

When overriding events or filtering them be absolutely certain you replace the QTextCursor in the edit widget. enter image description here

Many people chose to create a custom QTextEngine over the years because it was one place to fix it all. Umm they were also building from source. They built a custom Qt library after changing the QTextEngine class. Most added a property like

bool enableUnicodeWords;

If true, the offensive method worked as-is. If false it always returned false. Customizing the Qt source requires you publish said customizations somewhere. No I didn't find any published with a quick search.

This has been a problem since at least 2009 and because it impacted only desktop applications, it was ignored. Qt spent the past decade plus focusing on phones and QML to the detriment of all else.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
user3450148
  • 851
  • 5
  • 13