KTextAddons

textmessageindicator.cpp
1/*
2 SPDX-FileCopyrightText: 2014-2025 Laurent Montel <montel@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "textmessageindicator.h"
8
9#include <QAbstractScrollArea>
10#include <QApplication>
11#include <QPainter>
12#include <QResizeEvent>
13#include <QStyle>
14#include <QTimer>
15
16using namespace TextCustomEditor;
17TextMessageIndicator::TextMessageIndicator(QWidget *parent)
18 : QWidget(parent)
19{
20 setObjectName(QStringLiteral("TextMessageIndicator"));
21 setFocusPolicy(Qt::NoFocus);
22 QPalette pal = palette();
24 setPalette(pal);
25 // if the layout is LTR, we can safely place it in the right position
26 if (layoutDirection() == Qt::LeftToRight) {
27 move(10, parentWidget()->height() - 10);
28 }
29 resize(0, 0);
30 hide();
31}
32
33void TextMessageIndicator::display(const QString &message, const QString &details, Icon icon, int durationMs)
34{
35 if (message.isEmpty()) {
36 return;
37 }
38 // set text
39 mMessage = message;
40 mDetails = details;
41 // reset vars
42 mLineSpacing = 0;
43 // load icon (if set)
44 mSymbol = QPixmap();
45 const auto iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize);
46 if (icon != None) {
47 switch (icon) {
48 case Error:
49 mSymbol = QIcon::fromTheme(QStringLiteral("dialog-error")).pixmap(iconSize);
50 break;
51 case Warning:
52 mSymbol = QIcon::fromTheme(QStringLiteral("dialog-warning")).pixmap(iconSize);
53 break;
54 default:
55 mSymbol = QIcon::fromTheme(QStringLiteral("dialog-information")).pixmap(iconSize);
56 break;
57 }
58 }
59
60 computeSizeAndResize();
61 // show widget and schedule a repaint
62 show();
63 update();
64
65 // close the message window after given mS
66 if (durationMs > 0) {
67 if (!mTimer) {
68 mTimer = new QTimer(this);
69 mTimer->setSingleShot(true);
71 }
72 mTimer->start(durationMs);
73 } else if (mTimer) {
74 mTimer->stop();
75 }
76
77 qobject_cast<QAbstractScrollArea *>(parentWidget())->viewport()->installEventFilter(this);
78}
79
80QRect TextMessageIndicator::computeTextRect(const QString &message, int extra_width) const
81// Return the QRect which embeds the text
82{
83 int charSize = fontMetrics().averageCharWidth();
84 /* width of the viewport, minus 20 (~ size removed by further resizing),
85 minus the extra size (usually the icon width), minus (a bit empirical)
86 twice the mean width of a character to ensure that the bounding box is
87 really smaller than the container.
88 */
89 const int boundingWidth =
90 qobject_cast<QAbstractScrollArea *>(parentWidget())->viewport()->width() - 20 - (extra_width > 0 ? 2 + extra_width : 0) - 2 * charSize;
91 QRect textRect = fontMetrics().boundingRect(0, 0, boundingWidth, 0, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, message);
92 textRect.translate(-textRect.left(), -textRect.top());
93 textRect.adjust(0, 0, 2, 2);
94
95 return textRect;
96}
97
98void TextMessageIndicator::computeSizeAndResize()
99{
100 // determine text rectangle
101 const QRect textRect = computeTextRect(mMessage, mSymbol.width());
102 int width = textRect.width();
103 int height = textRect.height();
104
105 if (!mDetails.isEmpty()) {
106 // determine details text rectangle
107 const QRect detailsRect = computeTextRect(mDetails, mSymbol.width());
108 width = qMax(width, detailsRect.width());
109 height += detailsRect.height();
110
111 // plus add a ~60% line spacing
112 mLineSpacing = static_cast<int>(fontMetrics().height() * 0.6);
113 height += mLineSpacing;
114 }
115
116 // update geometry with icon information
117 if (!mSymbol.isNull()) {
118 width += 2 + mSymbol.width();
119 height = qMax(height, mSymbol.height());
120 }
121
122 // resize widget
123 resize(QRect(0, 0, width + 10, height + 8).size());
124
125 // if the layout is RtL, we can move it to the right place only after we
126 // know how much size it will take
127 int posX = parentWidget()->width() - geometry().width() - 20 - 1;
129 posX = 10;
130 }
131 move(posX, parentWidget()->height() - geometry().height() - 20);
132}
133
134bool TextMessageIndicator::eventFilter(QObject *obj, QEvent *event)
135{
136 /* if the parent object (scroll area) resizes, the message should
137 resize as well */
138 if (event->type() == QEvent::Resize) {
139 auto resizeEvent = static_cast<QResizeEvent *>(event);
140 if (resizeEvent->oldSize() != resizeEvent->size()) {
141 computeSizeAndResize();
142 }
143 }
144 // standard event processing
145 return QObject::eventFilter(obj, event);
146}
147
148void TextMessageIndicator::paintEvent(QPaintEvent * /* e */)
149{
150 const QRect textRect = computeTextRect(mMessage, mSymbol.width());
151
152 QRect detailsRect;
153 if (!mDetails.isEmpty()) {
154 detailsRect = computeTextRect(mDetails, mSymbol.width());
155 }
156
157 int textXOffset = 0;
158 // add 2 to account for the reduced drawRoundRect later
159 int textYOffset = (geometry().height() - textRect.height() - detailsRect.height() - mLineSpacing + 2) / 2;
160 int iconXOffset = 0;
161 int iconYOffset = !mSymbol.isNull() ? (geometry().height() - mSymbol.height()) / 2 : 0;
162 int shadowOffset = 1;
163
165 iconXOffset = 2 + textRect.width();
166 } else {
167 textXOffset = 2 + mSymbol.width();
168 }
169
170 // draw background
171 QPainter painter(this);
172 painter.setRenderHint(QPainter::Antialiasing, true);
173 painter.setPen(Qt::black);
174 painter.setBrush(palette().color(QPalette::Window));
175 painter.translate(0.5, 0.5);
176 painter.drawRoundedRect(1, 1, width() - 2, height() - 2, 1600 / width(), 1600 / height(), Qt::RelativeSize);
177
178 // draw icon if present
179 if (!mSymbol.isNull()) {
180 painter.drawPixmap(5 + iconXOffset, iconYOffset, mSymbol, 0, 0, mSymbol.width(), mSymbol.height());
181 }
182
183 const int xStartPoint = 5 + textXOffset;
184 const int yStartPoint = textYOffset;
185 const int textDrawingFlags = Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap;
186
187 // draw shadow and text
188 painter.setPen(palette().color(QPalette::Window).darker(115));
189 painter.drawText(xStartPoint + shadowOffset, yStartPoint + shadowOffset, textRect.width(), textRect.height(), textDrawingFlags, mMessage);
190 if (!mDetails.isEmpty()) {
191 painter.drawText(xStartPoint + shadowOffset,
192 yStartPoint + textRect.height() + mLineSpacing + shadowOffset,
193 textRect.width(),
194 detailsRect.height(),
195 textDrawingFlags,
196 mDetails);
197 }
198 painter.setPen(palette().color(QPalette::WindowText));
199 painter.drawText(xStartPoint, yStartPoint, textRect.width(), textRect.height(), textDrawingFlags, mMessage);
200 if (!mDetails.isEmpty()) {
201 painter.drawText(xStartPoint + shadowOffset,
202 yStartPoint + textRect.height() + mLineSpacing,
203 textRect.width(),
204 detailsRect.height(),
205 textDrawingFlags,
206 mDetails);
207 }
208}
209
210void TextMessageIndicator::mousePressEvent(QMouseEvent * /*e*/)
211{
212 if (mTimer) {
213 mTimer->stop();
214 }
215 hide();
216}
217
218#include "moc_textmessageindicator.cpp"
KIOCORE_EXPORT CopyJob * move(const QList< QUrl > &src, const QUrl &dest, JobFlags flags=DefaultFlags)
QPalette palette(const QWidget *widget)
int height() const const
QPixmap pixmap(QWindow *window, const QSize &size, Mode mode, State state) const const
QIcon fromTheme(const QString &name)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool event(QEvent *e)
virtual bool eventFilter(QObject *watched, QEvent *event)
T qobject_cast(QObject *object)
void setColor(ColorGroup group, ColorRole role, const QColor &color)
void adjust(int dx1, int dy1, int dx2, int dy2)
int height() const const
bool isNull() const const
int left() const const
int top() const const
void translate(const QPoint &offset)
int width() const const
bool isEmpty() const const
PM_SmallIconSize
virtual int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const const=0
AlignLeft
LeftToRight
RelativeSize
TextWordWrap
void timeout()
QFontMetrics fontMetrics() const const
void hide()
QWidget * parentWidget() const const
void move(const QPoint &)
virtual void resizeEvent(QResizeEvent *event)
void show()
void resize(const QSize &)
QStyle * style() const const
void update()
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:49:24 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.