1 /*
2 SPDX-FileCopyrightText: 2006 Peter Penz <peter.penz@gmx.at>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "konqstatusbarmessagelabel.h"
8 #include <QStyle>
9 #include <QTextDocument>
10
11 #include <kcolorscheme.h>
12 #include <QIcon>
13 #include <KLocalizedString>
14 #include "konqdebug.h"
15
16 #include <QFontMetrics>
17 #include <QPainter>
18 #include <QPixmap>
19 #include <QToolButton>
20 #include <QTimer>
21
22 enum { GeometryTimeout = 100 };
23 enum { BorderGap = 2 };
24
25 class KonqStatusBarMessageLabel::Private
26 {
27 public:
Private()28 Private() :
29 m_type(Default),
30 m_state(DefaultState),
31 m_illumination(0),
32 m_minTextHeight(-1),
33 m_timer(nullptr),
34 m_closeButton(nullptr)
35 {}
36
isRichText() const37 bool isRichText() const
38 {
39 return m_text.startsWith(QLatin1String("<html>")) || m_text.startsWith(QLatin1String("<qt>"));
40 }
41
42 KonqStatusBarMessageLabel::Type m_type;
43 KonqStatusBarMessageLabel::State m_state;
44 int m_illumination;
45 int m_minTextHeight;
46 QTimer *m_timer;
47 QString m_text;
48 QString m_defaultText;
49 QTextDocument m_textDocument;
50 QList<QString> m_pendingMessages;
51 QPixmap m_pixmap;
52 QToolButton *m_closeButton;
53 };
54
KonqStatusBarMessageLabel(QWidget * parent)55 KonqStatusBarMessageLabel::KonqStatusBarMessageLabel(QWidget *parent) :
56 QWidget(parent), d(new Private)
57 {
58 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum /*the sizeHint is the max*/);
59
60 d->m_timer = new QTimer(this);
61 connect(d->m_timer, &QTimer::timeout,
62 this, &KonqStatusBarMessageLabel::timerDone);
63
64 d->m_closeButton = new QToolButton(this);
65 d->m_closeButton->setAutoRaise(true);
66 d->m_closeButton->setIcon(QIcon::fromTheme(QStringLiteral("dialog-close")));
67 d->m_closeButton->setToolTip(i18nc("@info", "Close"));
68 d->m_closeButton->setAccessibleName(i18n("Close"));
69 d->m_closeButton->hide();
70 connect(d->m_closeButton, &QToolButton::clicked,
71 this, &KonqStatusBarMessageLabel::closeErrorMessage);
72 }
73
~KonqStatusBarMessageLabel()74 KonqStatusBarMessageLabel::~KonqStatusBarMessageLabel()
75 {
76 delete d;
77 }
78
setMessage(const QString & text,Type type)79 void KonqStatusBarMessageLabel::setMessage(const QString &text,
80 Type type)
81 {
82 if ((text == d->m_text) && (type == d->m_type)) {
83 return;
84 }
85
86 if (d->m_type == Error) {
87 if (type == Error) {
88 d->m_pendingMessages.insert(0, d->m_text);
89 } else if ((d->m_state != DefaultState) || !d->m_pendingMessages.isEmpty()) {
90 // a non-error message should not be shown, as there
91 // are other pending error messages in the queue
92 return;
93 }
94 }
95
96 d->m_text = text;
97 d->m_type = type;
98
99 if (d->isRichText()) {
100 d->m_textDocument.setTextWidth(-1);
101 d->m_textDocument.setDefaultFont(font());
102 QString html = QStringLiteral("<html><font color=\"");
103 html += palette().windowText().color().name();
104 html += QLatin1String("\">");
105 html += d->m_text;
106 d->m_textDocument.setHtml(html);
107 }
108
109 d->m_timer->stop();
110 d->m_illumination = 0;
111 d->m_state = DefaultState;
112
113 const char *iconName = nullptr;
114 QPixmap pixmap;
115 switch (type) {
116 case OperationCompleted:
117 iconName = "dialog-ok";
118 // "ok" icon should probably be "dialog-success", but we don't have that icon in KDE 4.0
119 d->m_closeButton->hide();
120 break;
121
122 case Information:
123 iconName = "dialog-information";
124 d->m_closeButton->hide();
125 break;
126
127 case Error:
128 d->m_timer->start(100);
129 d->m_state = Illuminate;
130
131 updateCloseButtonPosition();
132 d->m_closeButton->show();
133 updateGeometry();
134 break;
135
136 case Default:
137 default:
138 d->m_closeButton->hide();
139 updateGeometry();
140 break;
141 }
142
143 d->m_pixmap = (iconName == nullptr) ? QPixmap() : QIcon::fromTheme(iconName).pixmap(style()->pixelMetric(QStyle::PM_SmallIconSize));
144 QTimer::singleShot(GeometryTimeout, this, SLOT(assureVisibleText()));
145
146 if (type == Error) {
147 setAccessibleName(i18n("Error: %1", text));
148 } else {
149 setAccessibleName(text);
150 }
151
152 update();
153 }
154
type() const155 KonqStatusBarMessageLabel::Type KonqStatusBarMessageLabel::type() const
156 {
157 return d->m_type;
158 }
159
text() const160 QString KonqStatusBarMessageLabel::text() const
161 {
162 return d->m_text;
163 }
164
setDefaultText(const QString & text)165 void KonqStatusBarMessageLabel::setDefaultText(const QString &text)
166 {
167 d->m_defaultText = text;
168 }
169
defaultText() const170 QString KonqStatusBarMessageLabel::defaultText() const
171 {
172 return d->m_defaultText;
173 }
174
setMinimumTextHeight(int min)175 void KonqStatusBarMessageLabel::setMinimumTextHeight(int min)
176 {
177 if (min != d->m_minTextHeight) {
178 d->m_minTextHeight = min;
179 setMinimumHeight(min);
180 if (d->m_closeButton->height() > min) {
181 d->m_closeButton->setFixedHeight(min);
182 }
183 }
184 }
185
minimumTextHeight() const186 int KonqStatusBarMessageLabel::minimumTextHeight() const
187 {
188 return d->m_minTextHeight;
189 }
190
paintEvent(QPaintEvent *)191 void KonqStatusBarMessageLabel::paintEvent(QPaintEvent * /* event */)
192 {
193 QPainter painter(this);
194
195 if (d->m_illumination > 0) {
196 // at this point, a: we are a second label being drawn over the already
197 // painted status area, so we can be translucent, and b: our palette's
198 // window color (bg only) seems to be wrong (always black)
199 KColorScheme scheme(palette().currentColorGroup(), KColorScheme::Window);
200 QColor backgroundColor = scheme.background(KColorScheme::NegativeBackground).color();
201 backgroundColor.setAlpha(qMin(255, d->m_illumination * 2));
202 painter.setBrush(backgroundColor);
203 painter.setPen(Qt::NoPen);
204 painter.drawRect(QRect(0, 0, width(), height()));
205 }
206
207 // draw pixmap
208 int x = BorderGap;
209 const int y = (d->m_minTextHeight - d->m_pixmap.height()) / 2;
210
211 if (!d->m_pixmap.isNull()) {
212 painter.drawPixmap(x, y, d->m_pixmap);
213 x += d->m_pixmap.width() + BorderGap;
214 }
215
216 // draw text
217
218 const QRect availTextRect(x, 0, availableTextWidth(), height());
219
220 if (d->isRichText()) {
221 const QSize sz = d->m_textDocument.size().toSize();
222
223 // Vertical centering
224 const QRect textRect = QStyle::alignedRect(Qt::LeftToRight, Qt::AlignLeft | Qt::AlignVCenter, sz, availTextRect);
225 //qCDebug(KONQUEROR_LOG) << d->m_text << " sz=" << sz << textRect;
226
227 // What about wordwrap here?
228
229 painter.translate(textRect.left(), textRect.top());
230 d->m_textDocument.drawContents(&painter);
231 } else {
232 // plain text
233 painter.setPen(palette().windowText().color());
234 int flags = Qt::AlignVCenter;
235 if (height() > d->m_minTextHeight) {
236 flags = flags | Qt::TextWordWrap;
237 }
238 painter.drawText(availTextRect, flags, d->m_text);
239 }
240 painter.end();
241 }
242
resizeEvent(QResizeEvent * event)243 void KonqStatusBarMessageLabel::resizeEvent(QResizeEvent *event)
244 {
245 QWidget::resizeEvent(event);
246 updateCloseButtonPosition();
247 QTimer::singleShot(GeometryTimeout, this, SLOT(assureVisibleText()));
248 }
249
timerDone()250 void KonqStatusBarMessageLabel::timerDone()
251 {
252 switch (d->m_state) {
253 case Illuminate: {
254 // increase the illumination
255 const int illumination_max = 128;
256 if (d->m_illumination < illumination_max) {
257 d->m_illumination += 32;
258 if (d->m_illumination > illumination_max) {
259 d->m_illumination = illumination_max;
260 }
261 update();
262 } else {
263 d->m_state = Illuminated;
264 d->m_timer->start(5000);
265 }
266 break;
267 }
268
269 case Illuminated: {
270 // start desaturation
271 d->m_state = Desaturate;
272 d->m_timer->start(100);
273 break;
274 }
275
276 case Desaturate: {
277 // desaturate
278 if (d->m_illumination > 0) {
279 d->m_illumination -= 5;
280 update();
281 } else {
282 d->m_state = DefaultState;
283 d->m_timer->stop();
284 }
285 break;
286 }
287
288 default:
289 break;
290 }
291 }
292
assureVisibleText()293 void KonqStatusBarMessageLabel::assureVisibleText()
294 {
295 if (d->m_text.isEmpty()) {
296 return;
297 }
298
299 int requiredHeight = d->m_minTextHeight;
300 if (d->m_type != Default) {
301 // Calculate the required height of the widget thats
302 // needed for having a fully visible text. Note that for the default
303 // statusbar type (e. g. hover information) increasing the text height
304 // is not wanted, as this might rearrange the layout of items.
305
306 QFontMetrics fontMetrics(font());
307 const QRect bounds(fontMetrics.boundingRect(0, 0, availableTextWidth(), height(),
308 Qt::AlignVCenter | Qt::TextWordWrap, d->m_text));
309 requiredHeight = bounds.height();
310 if (requiredHeight < d->m_minTextHeight) {
311 requiredHeight = d->m_minTextHeight;
312 }
313 }
314
315 // Increase/decrease the current height of the widget to the
316 // required height. The increasing/decreasing is done in several
317 // steps to have an animation if the height is modified
318 // (see KonqStatusBarMessageLabel::resizeEvent())
319 const int gap = d->m_minTextHeight / 2;
320 int minHeight = minimumHeight();
321 if (minHeight < requiredHeight) {
322 minHeight += gap;
323 if (minHeight > requiredHeight) {
324 minHeight = requiredHeight;
325 }
326 setMinimumHeight(minHeight);
327 updateGeometry();
328 } else if (minHeight > requiredHeight) {
329 minHeight -= gap;
330 if (minHeight < requiredHeight) {
331 minHeight = requiredHeight;
332 }
333 setMinimumHeight(minHeight);
334 updateGeometry();
335 }
336
337 updateCloseButtonPosition();
338 }
339
availableTextWidth() const340 int KonqStatusBarMessageLabel::availableTextWidth() const
341 {
342 const int buttonWidth = (d->m_type == Error) ?
343 d->m_closeButton->width() + BorderGap : 0;
344 return width() - d->m_pixmap.width() - (BorderGap * 4) - buttonWidth;
345 }
346
updateCloseButtonPosition()347 void KonqStatusBarMessageLabel::updateCloseButtonPosition()
348 {
349 const int x = width() - d->m_closeButton->width() - BorderGap;
350 d->m_closeButton->move(x, 0);
351 }
352
closeErrorMessage()353 void KonqStatusBarMessageLabel::closeErrorMessage()
354 {
355 if (!showPendingMessage()) {
356 d->m_state = DefaultState;
357 setMessage(d->m_defaultText, Default);
358 }
359 }
360
showPendingMessage()361 bool KonqStatusBarMessageLabel::showPendingMessage()
362 {
363 if (!d->m_pendingMessages.isEmpty()) {
364 reset();
365 setMessage(d->m_pendingMessages.takeFirst(), Error);
366 return true;
367 }
368 return false;
369 }
370
reset()371 void KonqStatusBarMessageLabel::reset()
372 {
373 d->m_text.clear();
374 d->m_type = Default;
375 }
376
sizeHint() const377 QSize KonqStatusBarMessageLabel::sizeHint() const
378 {
379 return minimumSizeHint();
380 }
381
minimumSizeHint() const382 QSize KonqStatusBarMessageLabel::minimumSizeHint() const
383 {
384 const int fontHeight = fontMetrics().height();
385 QSize sz(100, fontHeight);
386 if (d->m_closeButton->isVisible()) {
387 const QSize toolButtonSize = d->m_closeButton->sizeHint();
388 sz = toolButtonSize.expandedTo(sz);
389 }
390 return sz;
391 }
392