Home > other >  How can I detect whether a tooltip is visible at a given moment?
How can I detect whether a tooltip is visible at a given moment?

Time:02-10

I'm looking for a way to detect whether a Qt widget's ToolTip is visible at the moment when a particular key combination is pressed. If it is, I want to copy the ToolTip's text to the clipboard.

Specifically, I have a QListView containing abbreviated strings, which is set up (via the Qt::ToolTipRole of the associated model) to show the full string of the appropriate list item when the mouse is hovered over it. The behaviour I'm looking for is that if the user presses CTRL-C (as detected by a QShortcut) while the tooltip is visible, then the tooltip text is copied to the clipboard.

My original idea was to use the children() method of the QListView widget to see if there was a tooltip preset among them:

// Inisde the slot connected to QShortcut::activated...
auto children = _ui -> myListView -> children();
QString selectionText;
for (const auto & child : children)
{
    if (qobject_cast<QToolTip *>(child))
    {
        selectionText = qobject_cast<QToolTip *>(child) -> text();
        break;
    }
}

...but this failed because it turns out that QToolTip does not inherit from QObject.

I've also thought of screening for QEvent::QToolTip events in the ListView's main event handler, and while I could probably get this to work it seems excessively low-level; I'd need to use screen co-ordinates to determine which item in the list was being hovered over and look for the widget's timeout to check that the tooltip hadn't disappeared again by the time that the QShortcut was fired. I'd be disappointed if there weren't a simpler way.

Is there an obvious way forward that I've failed to see?

CodePudding user response:

There are probably several possible solutions, but I am afraid none of them is simple. What I would do is to use the implementation detail that the tooltip actual widget is called QTipLabel. See https://code.woboq.org/qt5/qtbase/src/widgets/kernel/qtooltip.cpp.html#QTipLabel and it inherits from QLabel so you can easily get the text from it.

I am afraid the following solution is just a savage hack. I have not tested it, but it should work.

  1. I would override the data model for your view, specifically override method data() which would call the data() method of the original model class but cache the last value which was returned when this method is called with role == Qt::ToolTipRole.
  2. Then you need to catch the shortcut you are interested in. After it is caught, you get all qApp->topLevelWidgets() https://doc.qt.io/qt-5/qapplication.html#topLevelWidgets` and go through them and check if any of them has class name equal to QTipLabel (use QMetaObject::className()) and is visible, i.e. isVisible() == true.
  3. If you get this visible QTipLabel widget (you hold it via QWidget*), qobject_cast it to QLabel* (you cannot cast it to QTipLabel beause you do not have access to the definition of QTipLabel class because it is in private Qt source file) and get the text with QLabel::text(). If the text is the same as the text which you stored in step 1, then yes, this is the text you are looking for and you can copy it to clipboard or do whatever yo want with it.

Nasty, isn't it? But it is the simplest what I can think of.

PS: I believe that step 1 can be implemented also by catching QEvent::QToolTip for your view and then do some magic to get the text, but I think that overriding data() for model can be a bit easier.

PPS: One obvious drawback is that Qt can rename QTipLabel class in the future. But I would not be worry about it. That won't happen becaus ethey do not change QtWidgets module any more. And if it happens, then you just rename the class in your code. No problem.

PPPS: Another potential corner-case is that some other widget (whose tooltip you do NOT want to capture with that shortcut) actually has the same tooltip text as any of the items in your list view (which you DO want to capture). Then if you display tooltip for your list item, then you move your mouse over to that other widget and hover so that its tooltip gets shown (but you do NOT want to capture it) and then you press that shortcut... But I guess that in reality this will not be your case. I doubt there will be this unlikely clash of tooltips.

CodePudding user response:

With thanks to @V.K., this is what worked:

auto candidates = qApp->topLevelWidgets();
QString selectionText;
for (const auto & candidate : candidates)
{
    if (strcmp(candidate->metaObject()->className(), "QTipLabel") == 0)
    {
        QLabel * label = qobject_cast<QLabel *>(candidate);
        if (label->isVisible())
        {
            selectionText = label -> text();
            break;
        }
    }
}
if (!selectionText.isEmpty())
    QGuiApplication::clipboard() -> setText(selectionText);
  • Related