Как я могу определить, видна ли всплывающая подсказка в данный момент?

Я ищу способ определить, отображается ли всплывающая подсказка виджета Qt в момент нажатия определенной комбинации клавиш. Если это так, я хочу скопировать текст всплывающей подсказки в буфер обмена.

В частности, у меня есть QListView, содержащий сокращенные строки, который настроен (через Qt::ToolTipRole связанной модели) для отображения полной строки соответствующего элемента списка при наведении на него указателя мыши. Поведение, которое я ищу, заключается в том, что если пользователь нажимает CTRL-C (как обнаружено с помощью QShortcut), когда всплывающая подсказка видна, текст всплывающей подсказки копируется в буфер обмена.

Моя первоначальная идея состояла в том, чтобы использовать метод children() виджета QListView, чтобы увидеть, есть ли среди них предустановленная всплывающая подсказка:

      // 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;
    }
}

... но это не удалось, потому что оказалось, что QToolTip не наследуется от QObject.

Я также подумал о проверке событий QEvent::QToolTip в главном обработчике событий ListView, и хотя я, вероятно, мог бы заставить это работать, это кажется чрезмерно низкоуровневым; Мне нужно было бы использовать координаты экрана, чтобы определить, над каким элементом в списке происходит наведение курсора, и искать время ожидания виджета, чтобы убедиться, что всплывающая подсказка не исчезла снова к моменту запуска QShortcut. Я был бы разочарован, если бы не было более простого способа.

Есть ли очевидный путь вперед, который я не увидел?

2 ответа

Вероятно, есть несколько возможных решений, но я боюсь, что ни одно из них не является простым. Что бы я сделал, так это использовал бы деталь реализации, которая называется фактическим виджетом всплывающей подсказки. См. https://code.woboq.org/qt5/qtbase/src/widgets/kernel/qtooltip.cpp.html#QTipLabel , и он наследуется от QLabelтак что вы можете легко получить текст из него.

Я боюсь, что следующее решение - просто дикий взлом. Я не тестировал его, но он должен работать.

  1. Я бы переопределил модель данных для вашего представления, в частности переопределил метод, который вызывал бы метод исходного класса модели, но кэшировал последнее значение, которое было возвращено, когда этот метод вызывается с помощью role == Qt::ToolTipRole.
  2. Затем вам нужно поймать интересующий вас ярлык. После того, как он будет пойман, вы получите все qApp->topLevelWidgets() https://doc.qt.io/qt-5/qapplication.html#topLevelWidgets` и просмотрите их и проверьте, имеет ли какое-либо из них имя класса, равное (используйте QMetaObject::className()) и виден, т.е. isVisible() == true.
  3. Если вы получаете этот видимый виджет (вы держите его через QWidget*), qobject_castэто к QLabel*(вы не можете использовать его, потому что у вас нет доступа к определению класса, потому что он находится в частном исходном файле Qt) и получить текст с помощью QLabel::text(). Если текст совпадает с текстом, который вы сохранили на шаге 1, то да, это именно тот текст, который вы ищете, и вы можете скопировать его в буфер обмена или делать с ним все, что хотите.

Отвратительно, не так ли? Но это самое простое, что я могу придумать.

PS: я считаю, что шаг 1 можно реализовать также путем отлова QEvent::QToolTipдля вашего просмотра, а затем сделать какое-то волшебство, чтобы получить текст, но я думаю, что переопределение data()для модели может быть немного проще.

PPS: один очевидный недостаток заключается в том, что Qt может переименовывать QTipLabelкласс в будущем. Но я бы не беспокоился об этом. Этого не произойдет, потому что они больше не меняют модуль QtWidgets. И если это произойдет, то вы просто переименуете класс в своем коде. Без проблем.

PPPS: Другим потенциальным краеугольным камнем является то, что какой-то другой виджет (чью всплывающую подсказку вы НЕ хотите захватывать с помощью этого ярлыка) на самом деле имеет тот же текст всплывающей подсказки, что и любой из элементов в вашем представлении списка (который вы ДЕЙСТВИТЕЛЬНО хотите захватить). Затем, если вы отображаете всплывающую подсказку для своего элемента списка, затем вы перемещаете указатель мыши на этот другой виджет и наводите курсор, чтобы отображалась его всплывающая подсказка (но вы НЕ хотите ее захватывать), а затем нажимаете этот ярлык... Но я думаю что на самом деле это будет не ваш случай. Я сомневаюсь, что будет это маловероятное столкновение всплывающих подсказок.

Благодаря @VK получилось вот что:

      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);
Другие вопросы по тегам