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"