File indexing completed on 2024-05-26 04:46:50

0001 /*
0002    SPDX-FileCopyrightText: 2014-2024 Laurent Montel <montel@kde.org>
0003 
0004    SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "textmessageindicator.h"
0008 
0009 #include <QAbstractScrollArea>
0010 #include <QApplication>
0011 #include <QPainter>
0012 #include <QResizeEvent>
0013 #include <QStyle>
0014 #include <QTimer>
0015 
0016 using namespace TextCustomEditor;
0017 TextMessageIndicator::TextMessageIndicator(QWidget *parent)
0018     : QWidget(parent)
0019 {
0020     setObjectName(QStringLiteral("TextMessageIndicator"));
0021     setFocusPolicy(Qt::NoFocus);
0022     QPalette pal = palette();
0023     pal.setColor(QPalette::Active, QPalette::Window, QApplication::palette().highlight().color());
0024     setPalette(pal);
0025     // if the layout is LtR, we can safely place it in the right position
0026     if (layoutDirection() == Qt::LeftToRight) {
0027         move(10, parentWidget()->height() - 10);
0028     }
0029     resize(0, 0);
0030     hide();
0031 }
0032 
0033 void TextMessageIndicator::display(const QString &message, const QString &details, Icon icon, int durationMs)
0034 {
0035     if (message.isEmpty()) {
0036         return;
0037     }
0038     // set text
0039     mMessage = message;
0040     mDetails = details;
0041     // reset vars
0042     mLineSpacing = 0;
0043     // load icon (if set)
0044     mSymbol = QPixmap();
0045     const auto iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize);
0046     if (icon != None) {
0047         switch (icon) {
0048         case Error:
0049             mSymbol = QIcon::fromTheme(QStringLiteral("dialog-error")).pixmap(iconSize);
0050             break;
0051         case Warning:
0052             mSymbol = QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(iconSize);
0053             break;
0054         default:
0055             mSymbol = QIcon::fromTheme(QStringLiteral("dialog-information")).pixmap(iconSize);
0056             break;
0057         }
0058     }
0059 
0060     computeSizeAndResize();
0061     // show widget and schedule a repaint
0062     show();
0063     update();
0064 
0065     // close the message window after given mS
0066     if (durationMs > 0) {
0067         if (!mTimer) {
0068             mTimer = new QTimer(this);
0069             mTimer->setSingleShot(true);
0070             connect(mTimer, &QTimer::timeout, this, &TextMessageIndicator::hide);
0071         }
0072         mTimer->start(durationMs);
0073     } else if (mTimer) {
0074         mTimer->stop();
0075     }
0076 
0077     qobject_cast<QAbstractScrollArea *>(parentWidget())->viewport()->installEventFilter(this);
0078 }
0079 
0080 QRect TextMessageIndicator::computeTextRect(const QString &message, int extra_width) const
0081 // Return the QRect which embeds the text
0082 {
0083     int charSize = fontMetrics().averageCharWidth();
0084     /* width of the viewport, minus 20 (~ size removed by further resizing),
0085        minus the extra size (usually the icon width), minus (a bit empirical)
0086        twice the mean width of a character to ensure that the bounding box is
0087        really smaller than the container.
0088      */
0089     const int boundingWidth =
0090         qobject_cast<QAbstractScrollArea *>(parentWidget())->viewport()->width() - 20 - (extra_width > 0 ? 2 + extra_width : 0) - 2 * charSize;
0091     QRect textRect = fontMetrics().boundingRect(0, 0, boundingWidth, 0, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, message);
0092     textRect.translate(-textRect.left(), -textRect.top());
0093     textRect.adjust(0, 0, 2, 2);
0094 
0095     return textRect;
0096 }
0097 
0098 void TextMessageIndicator::computeSizeAndResize()
0099 {
0100     // determine text rectangle
0101     const QRect textRect = computeTextRect(mMessage, mSymbol.width());
0102     int width = textRect.width();
0103     int height = textRect.height();
0104 
0105     if (!mDetails.isEmpty()) {
0106         // determine details text rectangle
0107         const QRect detailsRect = computeTextRect(mDetails, mSymbol.width());
0108         width = qMax(width, detailsRect.width());
0109         height += detailsRect.height();
0110 
0111         // plus add a ~60% line spacing
0112         mLineSpacing = static_cast<int>(fontMetrics().height() * 0.6);
0113         height += mLineSpacing;
0114     }
0115 
0116     // update geometry with icon information
0117     if (!mSymbol.isNull()) {
0118         width += 2 + mSymbol.width();
0119         height = qMax(height, mSymbol.height());
0120     }
0121 
0122     // resize widget
0123     resize(QRect(0, 0, width + 10, height + 8).size());
0124 
0125     // if the layout is RtL, we can move it to the right place only after we
0126     // know how much size it will take
0127     int posX = parentWidget()->width() - geometry().width() - 20 - 1;
0128     if (layoutDirection() == Qt::RightToLeft) {
0129         posX = 10;
0130     }
0131     move(posX, parentWidget()->height() - geometry().height() - 20);
0132 }
0133 
0134 bool TextMessageIndicator::eventFilter(QObject *obj, QEvent *event)
0135 {
0136     /* if the parent object (scroll area) resizes, the message should
0137        resize as well */
0138     if (event->type() == QEvent::Resize) {
0139         auto resizeEvent = static_cast<QResizeEvent *>(event);
0140         if (resizeEvent->oldSize() != resizeEvent->size()) {
0141             computeSizeAndResize();
0142         }
0143     }
0144     // standard event processing
0145     return QObject::eventFilter(obj, event);
0146 }
0147 
0148 void TextMessageIndicator::paintEvent(QPaintEvent * /* e */)
0149 {
0150     const QRect textRect = computeTextRect(mMessage, mSymbol.width());
0151 
0152     QRect detailsRect;
0153     if (!mDetails.isEmpty()) {
0154         detailsRect = computeTextRect(mDetails, mSymbol.width());
0155     }
0156 
0157     int textXOffset = 0;
0158     // add 2 to account for the reduced drawRoundRect later
0159     int textYOffset = (geometry().height() - textRect.height() - detailsRect.height() - mLineSpacing + 2) / 2;
0160     int iconXOffset = 0;
0161     int iconYOffset = !mSymbol.isNull() ? (geometry().height() - mSymbol.height()) / 2 : 0;
0162     int shadowOffset = 1;
0163 
0164     if (layoutDirection() == Qt::RightToLeft) {
0165         iconXOffset = 2 + textRect.width();
0166     } else {
0167         textXOffset = 2 + mSymbol.width();
0168     }
0169 
0170     // draw background
0171     QPainter painter(this);
0172     painter.setRenderHint(QPainter::Antialiasing, true);
0173     painter.setPen(Qt::black);
0174     painter.setBrush(palette().color(QPalette::Window));
0175     painter.translate(0.5, 0.5);
0176     painter.drawRoundedRect(1, 1, width() - 2, height() - 2, 1600 / width(), 1600 / height(), Qt::RelativeSize);
0177 
0178     // draw icon if present
0179     if (!mSymbol.isNull()) {
0180         painter.drawPixmap(5 + iconXOffset, iconYOffset, mSymbol, 0, 0, mSymbol.width(), mSymbol.height());
0181     }
0182 
0183     const int xStartPoint = 5 + textXOffset;
0184     const int yStartPoint = textYOffset;
0185     const int textDrawingFlags = Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap;
0186 
0187     // draw shadow and text
0188     painter.setPen(palette().color(QPalette::Window).darker(115));
0189     painter.drawText(xStartPoint + shadowOffset, yStartPoint + shadowOffset, textRect.width(), textRect.height(), textDrawingFlags, mMessage);
0190     if (!mDetails.isEmpty()) {
0191         painter.drawText(xStartPoint + shadowOffset,
0192                          yStartPoint + textRect.height() + mLineSpacing + shadowOffset,
0193                          textRect.width(),
0194                          detailsRect.height(),
0195                          textDrawingFlags,
0196                          mDetails);
0197     }
0198     painter.setPen(palette().color(QPalette::WindowText));
0199     painter.drawText(xStartPoint, yStartPoint, textRect.width(), textRect.height(), textDrawingFlags, mMessage);
0200     if (!mDetails.isEmpty()) {
0201         painter.drawText(xStartPoint + shadowOffset,
0202                          yStartPoint + textRect.height() + mLineSpacing,
0203                          textRect.width(),
0204                          detailsRect.height(),
0205                          textDrawingFlags,
0206                          mDetails);
0207     }
0208 }
0209 
0210 void TextMessageIndicator::mousePressEvent(QMouseEvent * /*e*/)
0211 {
0212     if (mTimer) {
0213         mTimer->stop();
0214     }
0215     hide();
0216 }
0217 
0218 #include "moc_textmessageindicator.cpp"