Warning, file /plasma/systemsettings/app/ToolTips/tooltipmanager.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002  *   SPDX-FileCopyrightText: 2008 Konstantin Heil <konst.heil@stud.uni-heidelberg.de>
0003  *
0004  *   SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "tooltipmanager.h"
0008 
0009 #include "MenuItem.h"
0010 
0011 #include <QAbstractItemView>
0012 #include <QApplication>
0013 #include <QGridLayout>
0014 #include <QLabel>
0015 #include <QRect>
0016 #include <QScrollBar>
0017 #include <QStyle>
0018 #include <QTimer>
0019 
0020 #include <KColorScheme>
0021 #include <KIconLoader>
0022 #include <KLocalizedString>
0023 #include <KToolTipWidget>
0024 
0025 class IconLoaderSingleton
0026 {
0027 public:
0028     IconLoaderSingleton() = default;
0029 
0030     KIconLoader self;
0031 };
0032 
0033 Q_GLOBAL_STATIC(IconLoaderSingleton, privateIconLoaderSelf)
0034 
0035 class ToolTipManager::Private
0036 {
0037 public:
0038     Private()
0039         : tooltip(nullptr)
0040         , view(nullptr)
0041         , timer(nullptr)
0042         , delay(QApplication::style()->styleHint(QStyle::SH_ToolTip_WakeUpDelay))
0043     {
0044     }
0045 
0046     KToolTipWidget *tooltip = nullptr;
0047     QAbstractItemView *view = nullptr;
0048     QTimer *timer = nullptr;
0049     QModelIndex item;
0050     QRect itemRect;
0051     int delay;
0052 };
0053 
0054 ToolTipManager::ToolTipManager(QAbstractItemView *parent)
0055     : QObject(parent)
0056     , d(new ToolTipManager::Private)
0057 {
0058     d->view = parent;
0059     d->tooltip = new KToolTipWidget(d->view);
0060     d->tooltip->setHideDelay(0);
0061 
0062     connect(parent, &QAbstractItemView::viewportEntered, this, &ToolTipManager::hideToolTip);
0063     connect(parent, &QAbstractItemView::entered, this, &ToolTipManager::requestToolTip);
0064 
0065     d->timer = new QTimer(this);
0066     d->timer->setSingleShot(true);
0067     connect(d->timer, &QTimer::timeout, this, &ToolTipManager::prepareToolTip);
0068 
0069     // When the mousewheel is used, the items don't get a hovered indication
0070     // (Qt-issue #200665). To assure that the tooltip still gets hidden,
0071     // the scrollbars are observed.
0072     connect(parent->horizontalScrollBar(), &QAbstractSlider::valueChanged, this, &ToolTipManager::hideToolTip);
0073     connect(parent->verticalScrollBar(), &QAbstractSlider::valueChanged, this, &ToolTipManager::hideToolTip);
0074 
0075     d->view->viewport()->installEventFilter(this);
0076 }
0077 
0078 ToolTipManager::~ToolTipManager()
0079 {
0080     delete d;
0081 }
0082 
0083 bool ToolTipManager::eventFilter(QObject *watched, QEvent *event)
0084 {
0085     if (watched == d->view->viewport()) {
0086         switch (event->type()) {
0087         case QEvent::Leave:
0088         case QEvent::MouseButtonPress:
0089             hideToolTip();
0090             break;
0091         default:
0092             break;
0093         }
0094     }
0095 
0096     return QObject::eventFilter(watched, event);
0097 }
0098 
0099 void ToolTipManager::requestToolTip(const QModelIndex &index)
0100 {
0101     // only request a tooltip for the name column and when no selection or
0102     // drag & drop operation is done (indicated by the left mouse button)
0103     if (!(QApplication::mouseButtons() & Qt::LeftButton)) {
0104         d->tooltip->hide();
0105 
0106         d->itemRect = d->view->visualRect(index);
0107         const QPoint pos = d->view->viewport()->mapToGlobal(d->itemRect.topLeft());
0108         d->itemRect.moveTo(pos);
0109         d->item = index;
0110         d->timer->start(d->delay);
0111     } else {
0112         hideToolTip();
0113     }
0114 }
0115 
0116 void ToolTipManager::hideToolTip()
0117 {
0118     d->timer->stop();
0119     d->tooltip->hideLater();
0120 }
0121 
0122 void ToolTipManager::prepareToolTip()
0123 {
0124     showToolTip(d->item);
0125 }
0126 
0127 void ToolTipManager::showToolTip(const QModelIndex &menuItem)
0128 {
0129     if (QApplication::mouseButtons() & Qt::LeftButton) {
0130         return;
0131     }
0132 
0133     QWidget *tip = createTipContent(menuItem);
0134 
0135     d->tooltip->showBelow(d->itemRect, tip, d->view->nativeParentWidget()->windowHandle());
0136 
0137     connect(d->tooltip, &KToolTipWidget::hidden, tip, &QObject::deleteLater);
0138 }
0139 
0140 QWidget *ToolTipManager::createTipContent(QModelIndex item)
0141 {
0142     auto loader = KIconLoader::global();
0143     const QSize dialogIconSize = QSize(loader->currentSize(KIconLoader::Dialog), loader->currentSize(KIconLoader::Dialog));
0144     const QSize toolbarIconSize = QSize(loader->currentSize(KIconLoader::MainToolbar), loader->currentSize(KIconLoader::MainToolbar));
0145 
0146     QWidget *tipContent = new QWidget();
0147     QGridLayout *tipLayout = new QGridLayout();
0148     tipLayout->setAlignment(Qt::AlignLeft);
0149 
0150     QLayout *primaryLine = generateToolTipLine(&item, tipContent, dialogIconSize, true);
0151     primaryLine->setAlignment(Qt::AlignLeft);
0152     tipLayout->addLayout(primaryLine, 0, 0, Qt::AlignLeft);
0153 
0154     for (int done = 0; d->view->model()->rowCount(item) > done; done = 1 + done) {
0155         QModelIndex childItem = d->view->model()->index(done, 0, item);
0156         QLayout *subLine = generateToolTipLine(&childItem, tipContent, toolbarIconSize, false);
0157         subLine->setAlignment(Qt::AlignLeft);
0158         tipLayout->addLayout(subLine, done + 2, 0, Qt::AlignLeft);
0159     }
0160 
0161     tipLayout->setVerticalSpacing(tipContent->fontMetrics().height() / 3);
0162     tipContent->setLayout(tipLayout);
0163 
0164     if (d->view->model()->rowCount(item) > 0) {
0165         QFrame *separatorLine = new QFrame(tipContent);
0166         separatorLine->setFrameStyle(QFrame::HLine);
0167         tipLayout->addWidget(separatorLine, 1, 0);
0168     }
0169 
0170     return tipContent;
0171 }
0172 
0173 QLayout *ToolTipManager::generateToolTipLine(QModelIndex *item, QWidget *toolTip, QSize iconSize, bool comment)
0174 {
0175     // Get MenuItem
0176     MenuItem *menuItem = d->view->model()->data(*item, Qt::UserRole).value<MenuItem *>();
0177 
0178     QString text = menuItem->name();
0179     if (comment) {
0180         text = QStringLiteral("<b>%1</b>").arg(menuItem->name());
0181 
0182         // Generate text
0183         text += QStringLiteral("<br />");
0184         if (!menuItem->comment().isEmpty()) {
0185             text += menuItem->comment();
0186         } else {
0187             int childCount = d->view->model()->rowCount(*item);
0188             text += i18np("Contains 1 item", "Contains %1 items", childCount);
0189         }
0190     }
0191     QLabel *textLabel = new QLabel(toolTip);
0192     textLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
0193     textLabel->setForegroundRole(QPalette::ToolTipText);
0194     textLabel->setText(text);
0195 
0196     // Get icon
0197     QPalette pal = textLabel->palette();
0198     for (auto state : {QPalette::Active, QPalette::Inactive, QPalette::Disabled}) {
0199         pal.setBrush(state, QPalette::WindowText, pal.toolTipText());
0200         pal.setBrush(state, QPalette::Window, pal.toolTipBase());
0201     }
0202 
0203     privateIconLoaderSelf->self.setCustomPalette(pal);
0204 
0205     QIcon icon = KDE::icon(menuItem->iconName(), &privateIconLoaderSelf->self);
0206     QLabel *iconLabel = new QLabel(toolTip);
0207     iconLabel->setPixmap(icon.pixmap(iconSize));
0208     iconLabel->setMaximumSize(iconSize);
0209 
0210     // Generate layout
0211     QHBoxLayout *layout = new QHBoxLayout();
0212     layout->setSpacing(textLabel->fontMetrics().height() / 3);
0213     layout->setAlignment(Qt::AlignLeft);
0214     layout->addWidget(iconLabel, Qt::AlignLeft);
0215     layout->addWidget(textLabel, Qt::AlignLeft);
0216 
0217     return layout;
0218 }