Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-6762

BUG with FIX: multiple problems with QTextEdit tooltip hit testing; affects tooltips on both text and images

    XMLWordPrintable

Details

    • Bug
    • Resolution: Unresolved
    • P3: Somewhat important
    • None
    • 4.5.3
    • GUI: Look'n'Feel
    • None
    • This issue is in code that affects all platforms that support tooltips.

    Description

      If you

      • use QTextEdit, QPlainTextEdit, or QTextBrowser
      • call insertText() OR insertImage() with a QTextCharFormat that has a tooltip

      Then when you mouse over the text/image that has the tooltip,

      • sometimes the tooltip fails to appear (depending on whether you're in the first or second half of the text/image, horizontally)

      And when you mouse to the area just AFTER the tooltip,

      • sometimes the tooltip appears erroneously.

      You may not always notice this problem with text because the region of failure is the first half of the first character of the text and the one space after the text, but with images it is hugely noticeable and it seems to the user as if there is no tooltip, or as if the tooltip takes forever to come up (before they get bored and move the mouse to the right half).

      This is due to a combination of two different bugs

      --------------------

      BUG 1: The function QTextControlPrivate::showToolTip() has the following code:

      void QTextControlPrivate::showToolTip(const QPoint &globalPos, const QPointF &pos, QWidget *contextWidget)
      {
          const QString toolTip = q_func()->cursorForPosition(pos).charFormat().toolTip();
          if (toolTip.isEmpty())
              return;
          QToolTip::showText(globalPos, toolTip, contextWidget);
      }
      

      This code assumes that if you call charFormat(), you get the format AFTER the cursor position.

      But you actually get the code BEFORE the cursor position, despite incorrect documentation in the intro of QTextCursor,
      which conflicts with the correct documentation of charFormat() (I will post a separate bug about this documentation error).

      Fix for this is below. It's not just as simple as adding one, because of BUG 2.

      --------------------

      BUG 2:

      QTextControl::cursorForPosition() and
      QTextEdit::cursorForPosition() and
      QPlainTextEdit::cursorForPosition()

      all do a certain kind of hit testing which is probably useful for mouse clicking, but incorrect for tooltips.

      Specifically, say you have a single wide image like this:

      XXXXXXXXXXX

      these functions return a different cursor depending on whether the mouse is on the left or right side.

      XXXXXXXXXXX
      000000
      11111

      they do this because
      QTextControl::cursorForPosition() calls
      QTextControl::hitText() with Qt::FuzzyHit which calls
      QTextDocumentLayoutPrivate::hitTest() with Qt::FuzzyHit which calls
      QTextLine::xToCursor() with QTextLine::CursorBetweenCharacters

      in the case of tooltips, we always want QTextLine::CursorOnCharacter instead, which (unintuitively) is connected with Qt::ExactHit and not Qt::FuzzyHit

      (side note: this Qt::ExactHit / Qt::FuzzyHit API is confusing and basically not documented in any meaningful way. the higher-level API calls should use enums called CursorBetweenCharacters and
      CursorOnCharacter instead, to be clear and predictable. failing that, we should at least clearly document that: Qt::FuzzyHit means QTextLine::CursorBetweenCharacters and Qt::ExactHit means QTextLine::CursorOnCharacter (even though this pairing makes no sense). It would be much much better to rename the enums for clarity, keeping the old ones only for compatibility).

      --------------------

      BUG FIX

      So the fix has several parts:

      1. QTextControlPrivate::showToolTip needs to add one to its cursor before calling charFormat

      2. QTextControl::cursorForPosition() , QTextEdit::cursorForPosition() , QPlainTextEdit::cursorForPosition() need to give the caller the ability to control the hit testing behavior. I include all 3 because if we're going to add this functionality internally, we might as well give it to Qt users as well.

      In the code below, I implemented fix #2 by adding an accuracy argument to the cursorForPosition() calls. However, this is not ideal because the Qt::ExactHit / Qt::FuzzyHit names are unclear and basically meaningless.

      Instead, for your real fix, it would be MUCH BETTER to come up with new enums with names like CursorBetweenCharacters and CursorOnCharacter, and use those instead.

      Relevant new code:

      QTextControl.cpp
      void QTextControlPrivate::showToolTip(const QPoint &globalPos, const QPointF &pos, QWidget *contextWidget)
      {
          // this gives us the cursor position before pos
          //
          QTextCursor curs = q_func()->cursorForPosition(pos, Qt::ExactHit);
      
          // note QTextCursor::charFormat() gives us the format BEFORE the cursor,
          // despite the incorrect documentation in the intro of QTextCursor.
          // we want the format at pos, and thus after the cursor.
          //
          curs.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, 1);
      
          const QString toolTip = curs.charFormat().toolTip();
          if (toolTip.isEmpty())
              return;
          QToolTip::showText(globalPos, toolTip, contextWidget);
      }
      

      And we need to add the accuracy argument to all of the cursorForPosition() calls (again it would be better NOT to do as I have here, but instead add new enums called CursorBetweenCharacters and CursorOnCharacter):

      QTextControl_p.h
          QTextCursor cursorForPosition(const QPointF &pos, Qt::HitTestAccuracy accuracy = Qt::FuzzyHit) const;
      
      QTextControl.cpp:
      
      QTextCursor QTextControl::cursorForPosition(const QPointF &pos, Qt::HitTestAccuracy accuracy) const
      {
          Q_D(const QTextControl);
          int cursorPos = hitTest(pos, accuracy);
          if (cursorPos == -1)
              cursorPos = 0;
          QTextCursor c(d->doc);
          c.setPosition(cursorPos);
          return c;
      }
      
      QTextEdit.h
          QTextCursor cursorForPosition(const QPoint &pos, Qt::HitTestAccuracy accuracy = Qt::FuzzyHit) const;
      
      QTextEdit.cpp
      
      QTextCursor QTextEdit::cursorForPosition(const QPoint &pos, Qt::HitTestAccuracy accuracy) const
      {
          Q_D(const QTextEdit);
          return d->control->cursorForPosition(d->mapToContents(pos), accuracy);
      }
      
      QPlainTextEdit.h
          QTextCursor cursorForPosition(const QPoint &pos, Qt::HitTestAccuracy accuracy = Qt::FuzzyHit) const;
      
      QPlainTextEdit.cpp
      QTextCursor QPlainTextEdit::cursorForPosition(const QPoint &pos, Qt::HitTestAccuracy accuracy) const
      {
          Q_D(const QPlainTextEdit);
          return d->control->cursorForPosition(d->mapToContents(pos), accuracy);
      }
      

      Attachments

        No reviews matched the request. Check your Options in the drop-down menu of this sections header.

        Activity

          People

            Unassigned Unassigned
            lsemprini Louis Semprini
            Votes:
            2 Vote for this issue
            Watchers:
            4 Start watching this issue

            Dates

              Created:
              Updated:

              Gerrit Reviews

                There are no open Gerrit changes