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"