File indexing completed on 2025-04-27 03:58:28

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2008-12-10
0007  * Description : tool tip widget for iconview, thumbbar, and folderview items
0008  *
0009  * SPDX-FileCopyrightText: 2008-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0010  *
0011  * SPDX-License-Identifier: GPL-2.0-or-later
0012  *
0013  * ============================================================ */
0014 
0015 #include "ditemtooltip.h"
0016 
0017 // Qt includes
0018 
0019 #include <QApplication>
0020 #include <QDateTime>
0021 #include <QPainter>
0022 #include <QPixmap>
0023 #include <QStyle>
0024 #include <QStyleHintReturnMask>
0025 #include <QStyleOptionFrame>
0026 #include <QStylePainter>
0027 #include <QToolTip>
0028 #include <QVBoxLayout>
0029 #include <QTextDocument>
0030 #include <QByteArray>
0031 #include <QBuffer>
0032 #include <QScreen>
0033 #include <QWindow>
0034 
0035 // KDE includes
0036 
0037 #include <klocalizedstring.h>
0038 
0039 namespace Digikam
0040 {
0041 
0042 DToolTipStyleSheet::DToolTipStyleSheet(const QFont& font)
0043     : maxStringLength(30),
0044       unavailable    (i18n("unavailable"))
0045 {
0046     // Note: rich-text doc https://doc.qt.io/qt-5/richtext-html-subset.html
0047 
0048     // Fix layout following bug #376438
0049 
0050     QString dir = QLatin1String("dir=\"rtl\"");
0051 
0052     if (qApp->layoutDirection() == Qt::LeftToRight)
0053     {
0054         dir = QLatin1String("dir=\"ltr\"");
0055     }
0056 
0057     QString fontSize = (font.pointSize() == -1) ? QString::fromUtf8("font-size: %1px;").arg(font.pixelSize())
0058                                                 : QString::fromUtf8("font-size: %1pt;").arg(font.pointSize());
0059 
0060     tipHeader        = QLatin1String("<qt><table cellspacing=\"0\" cellpadding=\"0\" width=\"250\" border=\"0\">");
0061     tipFooter        = QLatin1String("</table></qt>");
0062 
0063     headBeg          = QString::fromLatin1("<tr bgcolor=\"%1\"><td colspan=\"2\">"
0064                                       "<nobr><p style=\"color:%2; font-family:%3; %4; %5\"><center><b>")
0065                        .arg(qApp->palette().color(QPalette::Base).name())
0066                        .arg(qApp->palette().color(QPalette::Text).name())
0067                        .arg(font.family())
0068                        .arg(fontSize)
0069                        .arg(dir);
0070 
0071     headEnd          = QLatin1String("</b></center></p></nobr></td></tr>");
0072 
0073     cellBeg          = QString::fromLatin1("<tr><td><nobr><p style=\"color:%1; font-family:%2; %3; %4\">")
0074                        .arg(qApp->palette().color(QPalette::ToolTipText).name())
0075                        .arg(font.family())
0076                        .arg(fontSize)
0077                        .arg(dir);
0078 
0079     cellMid          = QString::fromLatin1("</p></nobr></td><td><nobr><p style=\"color:%1; font-family:%2; %3; %4\">")
0080                        .arg(qApp->palette().color(QPalette::ToolTipText).name())
0081                        .arg(font.family())
0082                        .arg(fontSize)
0083                        .arg(dir);
0084 
0085     cellEnd          = QLatin1String("</p></nobr></td></tr>");
0086 
0087     cellSpecBeg      = QString::fromLatin1("<tr><td><nobr><p style=\"color:%1; font-family:%2; %3; %4\">")
0088                        .arg(qApp->palette().color(QPalette::ToolTipText).name())
0089                        .arg(font.family())
0090                        .arg(fontSize)
0091                        .arg(dir);
0092 
0093     cellSpecMid      = QString::fromLatin1("</p></nobr></td><td><nobr><p style=\"color:%1; font-family:%2; %3; %4\"><i>")
0094                        .arg(qApp->palette().color(QPalette::ToolTipText).name())
0095                        .arg(font.family())
0096                        .arg(fontSize)
0097                        .arg(dir);
0098 
0099     cellSpecEnd      = QLatin1String("</i></p></nobr></td></tr>");
0100 }
0101 
0102 QString DToolTipStyleSheet::breakString(const QString& input) const
0103 {
0104     QString str = input.simplified();
0105     str         = str.toHtmlEscaped();
0106 
0107     if (str.length() <= maxStringLength)
0108     {
0109         return str;
0110     }
0111 
0112     QString br;
0113 
0114     int i     = 0;
0115     int count = 0;
0116 
0117     while (i < str.length())
0118     {
0119         if ((count >= maxStringLength) && str.at(i).isSpace())
0120         {
0121             count = 0;
0122             br.append(QLatin1String("<br/>"));
0123         }
0124         else
0125         {
0126             br.append(str.at(i));
0127         }
0128 
0129         ++i;
0130         ++count;
0131     }
0132 
0133     return br;
0134 }
0135 
0136 QString DToolTipStyleSheet::elidedText(const QString& str, Qt::TextElideMode elideMode) const
0137 {
0138     if (str.length() <= maxStringLength)
0139     {
0140         return str;
0141     }
0142 
0143     switch (elideMode)
0144     {
0145         case Qt::ElideLeft:
0146         {
0147             return QLatin1String("...") + str.right(maxStringLength-3);
0148         }
0149 
0150         case Qt::ElideRight:
0151         {
0152             return str.left(maxStringLength-3) + QLatin1String("...");
0153         }
0154 
0155         case Qt::ElideMiddle:
0156         {
0157             return str.left(maxStringLength / 2 - 2) + QLatin1String("...") + str.right(maxStringLength / 2 - 1);
0158         }
0159 
0160         case Qt::ElideNone:
0161         {
0162             return str.left(maxStringLength);
0163         }
0164 
0165         default:
0166         {
0167             return str;
0168         }
0169     }
0170 }
0171 
0172 QString DToolTipStyleSheet::imageAsBase64(const QImage& img) const
0173 {
0174     QByteArray byteArray;
0175     QBuffer    buffer(&byteArray);
0176     img.save(&buffer, "PNG");
0177     QString    iconBase64 = QString::fromLatin1(byteArray.toBase64().data());
0178 
0179     return QString::fromLatin1("<img src=\"data:image/png;base64,%1\">").arg(iconBase64);
0180 }
0181 
0182 // --------------------------------------------------------------------------------------------------
0183 
0184 class Q_DECL_HIDDEN DItemToolTip::Private
0185 {
0186 public:
0187 
0188     explicit Private()
0189       : tipBorder(5),
0190         corner   (0)
0191     {
0192     }
0193 
0194     const int   tipBorder;
0195     int         corner;
0196     QPixmap     corners[4];
0197 };
0198 
0199 DItemToolTip::DItemToolTip(QWidget* const parent)
0200     : QLabel(parent, Qt::ToolTip),
0201       d     (new Private)
0202 {
0203     hide();
0204 
0205     setForegroundRole(QPalette::ToolTipText);
0206     setBackgroundRole(QPalette::ToolTipBase);
0207     ensurePolished();
0208     const int fwidth = qMax(d->tipBorder, 1 + style()->pixelMetric(QStyle::PM_ToolTipLabelFrameWidth, nullptr, this));
0209     setContentsMargins(fwidth, fwidth, fwidth, fwidth);
0210     setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, nullptr, this) / 255.0);
0211     setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
0212 
0213     setFrameStyle(QFrame::StyledPanel);
0214 /*
0215     Old-style box:
0216     setFrameStyle(QFrame::Plain | QFrame::Box);
0217     setLineWidth(1);
0218 */
0219     renderArrows();
0220 }
0221 
0222 DItemToolTip::~DItemToolTip()
0223 {
0224     delete d;
0225 }
0226 
0227 void DItemToolTip::updateToolTip()
0228 {
0229     renderArrows();
0230 
0231     QString contents = tipContents();
0232 /*
0233     setWordWrap(Qt::mightBeRichText(contents));
0234 */
0235     setText(contents);
0236     resize(sizeHint());
0237 }
0238 
0239 bool DItemToolTip::toolTipIsEmpty() const
0240 {
0241     return(text().isEmpty());
0242 }
0243 
0244 void DItemToolTip::reposition()
0245 {
0246     QRect rect = repositionRect();
0247 
0248     if (rect.isNull())
0249     {
0250         return;
0251     }
0252 
0253     QPoint pos = rect.center();
0254 
0255     // d->corner:
0256     // 0: upperleft
0257     // 1: upperright
0258     // 2: lowerleft
0259     // 3: lowerright
0260 
0261     d->corner = 0;
0262 
0263     // should the tooltip be shown to the left or to the right of the ivi ?
0264 
0265     QScreen* screen = qApp->primaryScreen();
0266 
0267     if (QWidget* const widget = nativeParentWidget())
0268     {
0269         if (QWindow* const window = widget->windowHandle())
0270         {
0271             screen = window->screen();
0272         }
0273     }
0274 
0275     QRect desk = screen->geometry();
0276 
0277     if ((rect.center().x() + width()) > desk.right())
0278     {
0279         // to the left
0280 
0281         if (pos.x() - width() < desk.left())
0282         {
0283             pos.setX(0);
0284             d->corner = 4;
0285         }
0286         else
0287         {
0288             pos.setX(pos.x() - width());
0289             d->corner = 1;
0290         }
0291     }
0292 
0293     // should the tooltip be shown above or below the ivi ?
0294 
0295     if (rect.bottom() + height() > desk.bottom())
0296     {
0297         // above
0298 
0299         int top = rect.top() - height() - 5;
0300 
0301         if (top < desk.top())
0302         {
0303             top = desk.top();
0304 
0305             // recalculate x
0306 
0307             if      (d->corner == 0)
0308             {
0309                 pos.setX(rect.right() + 5);
0310             }
0311             else if (d->corner == 1)
0312             {
0313                 pos.setX(rect.left() - width() - 5);
0314             }
0315         }
0316 
0317         pos.setY(top);
0318         d->corner += 2;
0319     }
0320     else
0321     {
0322         pos.setY(rect.bottom() + 5);
0323     }
0324 
0325     move(pos);
0326 }
0327 
0328 void DItemToolTip::renderArrows()
0329 {
0330     int w = d->tipBorder;
0331 
0332     // -- left top arrow -------------------------------------
0333 
0334     QPixmap& pix0 = d->corners[0];
0335     pix0          = QPixmap(w, w);
0336     pix0.fill(Qt::transparent);
0337 
0338     QPainter p0(&pix0);
0339     p0.setPen(QPen(qApp->palette().color(QPalette::Text), 1));
0340 
0341     for (int j = 0 ; j < w ; ++j)
0342     {
0343         p0.drawLine(0, j, w-j-1, j);
0344     }
0345 
0346     p0.end();
0347 
0348     // -- right top arrow ------------------------------------
0349 
0350     QPixmap& pix1 = d->corners[1];
0351     pix1          = QPixmap(w, w);
0352     pix1.fill(Qt::transparent);
0353 
0354     QPainter p1(&pix1);
0355     p1.setPen(QPen(qApp->palette().color(QPalette::Text), 1));
0356 
0357     for (int j = 0 ; j < w ; ++j)
0358     {
0359         p1.drawLine(j, j, w-1, j);
0360     }
0361 
0362     p1.end();
0363 
0364     // -- left bottom arrow ----------------------------------
0365 
0366     QPixmap& pix2 = d->corners[2];
0367     pix2          = QPixmap(w, w);
0368     pix2.fill(Qt::transparent);
0369 
0370     QPainter p2(&pix2);
0371     p2.setPen(QPen(qApp->palette().color(QPalette::Text), 1));
0372 
0373     for (int j = 0 ; j < w ; ++j)
0374     {
0375         p2.drawLine(0, j, j, j);
0376     }
0377 
0378     p2.end();
0379 
0380     // -- right bottom arrow ---------------------------------
0381 
0382     QPixmap& pix3 = d->corners[3];
0383     pix3          = QPixmap(w, w);
0384     pix3.fill(Qt::transparent);
0385 
0386     QPainter p3(&pix3);
0387     p3.setPen(QPen(qApp->palette().color(QPalette::Text), 1));
0388 
0389     for (int j = 0 ; j < w ; ++j)
0390     {
0391         p3.drawLine(w-j-1, j, w-1, j);
0392     }
0393 
0394     p3.end();
0395 }
0396 
0397 bool DItemToolTip::event(QEvent* e)
0398 {
0399     switch (e->type())
0400     {
0401         case QEvent::Leave:
0402         case QEvent::MouseButtonPress:
0403         case QEvent::MouseButtonRelease:
0404         case QEvent::FocusIn:
0405         case QEvent::FocusOut:
0406         case QEvent::Wheel:
0407         {
0408             hide();
0409             break;
0410         }
0411 
0412         default:
0413         {
0414             break;
0415         }
0416     }
0417 
0418     return QLabel::event(e);
0419 }
0420 
0421 void DItemToolTip::resizeEvent(QResizeEvent* e)
0422 {
0423     QStyleHintReturnMask frameMask;
0424     QStyleOption option;
0425     option.initFrom(this);
0426 
0427     if (style()->styleHint(QStyle::SH_ToolTip_Mask, &option, this, &frameMask))
0428     {
0429         setMask(frameMask.region);
0430     }
0431 
0432     update();
0433     QLabel::resizeEvent(e);
0434 }
0435 
0436 void DItemToolTip::paintEvent(QPaintEvent* e)
0437 {
0438     {
0439         QStylePainter p(this);
0440         QStyleOptionFrame opt;
0441         opt.initFrom(this);
0442         p.drawPrimitive(QStyle::PE_PanelTipLabel, opt);
0443     }
0444 
0445     QLabel::paintEvent(e);
0446 
0447     QPainter p(this);
0448 
0449     if (d->corner >= 4)
0450     {
0451         return;
0452     }
0453 
0454     QPixmap& pix = d->corners[d->corner];
0455 
0456     switch (d->corner)
0457     {
0458         case 0:
0459         {
0460             p.drawPixmap(3, 3, pix);
0461             break;
0462         }
0463 
0464         case 1:
0465         {
0466             p.drawPixmap(width() - pix.width() - 3, 3, pix);
0467             break;
0468         }
0469 
0470         case 2:
0471         {
0472             p.drawPixmap(3, height() - pix.height() - 3, pix);
0473             break;
0474         }
0475 
0476         case 3:
0477         {
0478             p.drawPixmap(width() - pix.width() - 3, height() - pix.height() - 3, pix);
0479             break;
0480         }
0481     }
0482 }
0483 
0484 } // namespace Digikam
0485 
0486 #include "moc_ditemtooltip.cpp"