KWidgetsAddons

kmessagewidget.cpp
1/*
2 This file is part of the KDE libraries
3
4 SPDX-FileCopyrightText: 2011 Aurélien Gâteau <agateau@kde.org>
5 SPDX-FileCopyrightText: 2014 Dominik Haumann <dhaumann@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.1-or-later
8*/
9#include "kmessagewidget.h"
10
11#include <QAction>
12#include <QApplication>
13#include <QEvent>
14#include <QGridLayout>
15#include <QGuiApplication>
16#include <QHBoxLayout>
17#include <QLabel>
18#include <QPainter>
19#include <QShowEvent>
20#include <QStyle>
21#include <QStyleOption>
22#include <QTimeLine>
23#include <QToolButton>
24//---------------------------------------------------------------------
25// KMessageWidgetPrivate
26//---------------------------------------------------------------------
27
28constexpr int borderSize = 2;
29
30class KMessageWidgetPrivate
31{
32public:
33 void init(KMessageWidget *);
34
36 QLabel *iconLabel = nullptr;
37 QLabel *textLabel = nullptr;
38 QToolButton *closeButton = nullptr;
39 QTimeLine *timeLine = nullptr;
40 QIcon icon;
41 bool ignoreShowAndResizeEventDoingAnimatedShow = false;
44 bool wordWrap;
46 bool ignorePaletteChange = false;
47
48 void createLayout();
49 void setPalette();
50 void updateLayout();
51 void slotTimeLineChanged(qreal);
52 void slotTimeLineFinished();
53 int bestContentHeight() const;
54};
55
56void KMessageWidgetPrivate::init(KMessageWidget *q_ptr)
57{
58 q = q_ptr;
59 // Note: when changing the value 500, also update KMessageWidgetTest
60 timeLine = new QTimeLine(500, q);
61 QObject::connect(timeLine, &QTimeLine::valueChanged, q, [this](qreal value) {
62 slotTimeLineChanged(value);
63 });
64 QObject::connect(timeLine, &QTimeLine::finished, q, [this]() {
65 slotTimeLineFinished();
66 });
67
68 wordWrap = false;
69
70 iconLabel = new QLabel(q);
72 iconLabel->hide();
73
74 textLabel = new QLabel(q);
79
80 QAction *closeAction = new QAction(q);
81 closeAction->setText(KMessageWidget::tr("&Close", "@action:button"));
82 closeAction->setToolTip(KMessageWidget::tr("Close message", "@info:tooltip"));
84 opt.initFrom(q);
85 closeAction->setIcon(q->style()->standardIcon(QStyle::SP_DialogCloseButton, &opt, q));
86
88
89 closeButton = new QToolButton(q);
90 closeButton->setAutoRaise(true);
91 closeButton->setDefaultAction(closeAction);
92
94}
95
96void KMessageWidgetPrivate::createLayout()
97{
98 delete q->layout();
99
100 qDeleteAll(buttons);
101 buttons.clear();
102
103 const auto actions = q->actions();
104 buttons.reserve(actions.size());
105 for (QAction *action : actions) {
106 QToolButton *button = new QToolButton(q);
107 button->setDefaultAction(action);
109 auto previous = buttons.isEmpty() ? static_cast<QWidget *>(textLabel) : buttons.back();
110 QWidget::setTabOrder(previous, button);
111 buttons.append(button);
112 }
113
114 // AutoRaise reduces visual clutter, but we don't want to turn it on if
115 // there are other buttons, otherwise the close button will look different
116 // from the others.
117 closeButton->setAutoRaise(buttons.isEmpty());
118 if (wordWrap) {
119 QGridLayout *layout = new QGridLayout(q);
120 // Set alignment to make sure icon does not move down if text wraps
121 layout->addWidget(iconLabel, 0, 0, 1, 1, Qt::AlignCenter);
122 layout->addWidget(textLabel, 0, 1);
123
124 if (buttons.isEmpty()) {
125 // Use top-vertical alignment like the icon does.
126 layout->addWidget(closeButton, 0, 2, 1, 1, Qt::AlignHCenter | Qt::AlignTop);
127 } else {
128 // Use an additional layout in row 1 for the buttons.
129 QHBoxLayout *buttonLayout = new QHBoxLayout;
130 buttonLayout->addStretch();
131 for (QToolButton *button : std::as_const(buttons)) {
132 // For some reason, calling show() is necessary if wordwrap is true,
133 // otherwise the buttons do not show up. It is not needed if
134 // wordwrap is false.
135 button->show();
136 buttonLayout->addWidget(button);
137 }
138 buttonLayout->addWidget(closeButton);
139 layout->addItem(buttonLayout, 1, 0, 1, 2);
140 }
141 } else {
142 QHBoxLayout *layout = new QHBoxLayout(q);
143 layout->addWidget(iconLabel, 0, Qt::AlignVCenter);
144 layout->addWidget(textLabel);
145
146 for (QToolButton *button : std::as_const(buttons)) {
147 layout->addWidget(button, 0, Qt::AlignTop);
148 }
149 layout->addWidget(closeButton, 0, Qt::AlignTop);
150 };
151 // Add bordersize to the margin so it starts from the inner border and doesn't look too cramped
152 q->layout()->setContentsMargins(q->layout()->contentsMargins() + borderSize);
153 if (q->isVisible()) {
154 q->setFixedHeight(q->sizeHint().height());
155 }
156 q->updateGeometry();
157}
158
159void KMessageWidgetPrivate::setPalette()
160{
161 QColor bgBaseColor;
162
163 // We have to hardcode colors here because KWidgetsAddons is a tier 1 framework
164 // and therefore can't depend on any other KDE Frameworks
165 // The following RGB color values come from the "default" scheme in kcolorscheme.cpp
166 switch (messageType) {
168 bgBaseColor.setRgb(39, 174, 96); // Window: ForegroundPositive
169 break;
171 bgBaseColor.setRgb(61, 174, 233); // Window: ForegroundActive
172 break;
174 bgBaseColor.setRgb(246, 116, 0); // Window: ForegroundNeutral
175 break;
177 bgBaseColor.setRgb(218, 68, 83); // Window: ForegroundNegative
178 break;
179 }
180 QPalette palette = q->palette();
181 palette.setColor(QPalette::Window, bgBaseColor);
182 const QColor parentTextColor = (q->parentWidget() ? q->parentWidget()->palette() : qApp->palette()).color(QPalette::WindowText);
183 palette.setColor(QPalette::WindowText, parentTextColor);
184 // Explicitly set the palettes of the labels because some apps use stylesheets which break the
185 // palette propagation
186 ignorePaletteChange = true;
187 q->setPalette(palette);
188 ignorePaletteChange = false;
189 iconLabel->setPalette(palette);
190 textLabel->setPalette(palette);
191
192 // update the Icon in case it is recolorable
193 q->setIcon(icon);
194 q->update();
195}
196
197void KMessageWidgetPrivate::updateLayout()
198{
199 createLayout();
200}
201
202void KMessageWidgetPrivate::slotTimeLineChanged(qreal value)
203{
204 q->setFixedHeight(qMin(value * 2, qreal(1.0)) * bestContentHeight());
205 q->update();
206}
207
208void KMessageWidgetPrivate::slotTimeLineFinished()
209{
210 if (timeLine->direction() == QTimeLine::Forward) {
211 q->resize(q->width(), bestContentHeight());
212
213 // notify about finished animation
214 Q_EMIT q->showAnimationFinished();
215 } else {
216 // hide and notify about finished animation
217 q->hide();
218 Q_EMIT q->hideAnimationFinished();
219 }
220}
221
222int KMessageWidgetPrivate::bestContentHeight() const
223{
224 int height = q->heightForWidth(q->width());
225 if (height == -1) {
226 height = q->sizeHint().height();
227 }
228 return height;
229}
230
231//---------------------------------------------------------------------
232// KMessageWidget
233//---------------------------------------------------------------------
235 : QFrame(parent)
236 , d(new KMessageWidgetPrivate)
237{
238 d->init(this);
239}
240
242 : QFrame(parent)
243 , d(new KMessageWidgetPrivate)
244{
245 d->init(this);
246 setText(text);
247}
248
250
251QString KMessageWidget::text() const
252{
253 return d->textLabel->text();
254}
255
257{
258 d->textLabel->setText(text);
260}
261
262Qt::TextFormat KMessageWidget::textFormat() const
263{
264 return d->textLabel->textFormat();
265}
266
268{
269 d->textLabel->setTextFormat(textFormat);
270}
271
272KMessageWidget::MessageType KMessageWidget::messageType() const
273{
274 return d->messageType;
275}
276
278{
279 d->messageType = type;
280 d->setPalette();
281}
282
284{
286 return QFrame::sizeHint();
287}
288
294
295bool KMessageWidget::event(QEvent *event)
296{
297 if (event->type() == QEvent::Polish && !layout()) {
298 d->createLayout();
299 } else if ((event->type() == QEvent::Show && !d->ignoreShowAndResizeEventDoingAnimatedShow)
300 || (event->type() == QEvent::LayoutRequest && d->timeLine->state() == QTimeLine::NotRunning)) {
301 setFixedHeight(d->bestContentHeight());
302
303 // if we are displaying this when application first starts, there's
304 // a possibility that the layout is not properly updated with the
305 // rest of the application because the setFixedHeight call above has
306 // the same height that was set beforehand, when we lacked a parent
307 // and thus, the layout() geometry is bogus. so we pass a bogus
308 // value to it, just to trigger a recalculation, and revert to the
309 // best content height.
310 if (geometry().height() < layout()->geometry().height()) {
311 setFixedHeight(d->bestContentHeight() + 2); // this triggers a recalculation.
312 setFixedHeight(d->bestContentHeight()); // this actually sets the correct values.
313 }
314
315 } else if (event->type() == QEvent::ParentChange) {
316 d->setPalette();
317 } else if (event->type() == QEvent::PaletteChange) {
318 if (!d->ignorePaletteChange) {
319 d->setPalette();
320 }
321 }
322 return QFrame::event(event);
323}
324
325void KMessageWidget::resizeEvent(QResizeEvent *event)
326{
327 QFrame::resizeEvent(event);
328 if (d->timeLine->state() == QTimeLine::NotRunning && d->ignoreShowAndResizeEventDoingAnimatedShow) {
329 setFixedHeight(d->bestContentHeight());
330 }
331}
332
334{
337}
338
339void KMessageWidget::paintEvent(QPaintEvent *event)
340{
341 Q_UNUSED(event)
342 QPainter painter(this);
343 if (d->timeLine->state() == QTimeLine::Running) {
344 painter.setOpacity(d->timeLine->currentValue() * d->timeLine->currentValue());
345 }
346 constexpr float radius = 4 * 0.6;
347 const QRect innerRect = rect().marginsRemoved(QMargins() + borderSize / 2);
348 const QColor color = palette().color(QPalette::Window);
349 constexpr float alpha = 0.2;
350 const QColor parentWindowColor = (parentWidget() ? parentWidget()->palette() : qApp->palette()).color(QPalette::Window);
351 const int newRed = (color.red() * alpha) + (parentWindowColor.red() * (1 - alpha));
352 const int newGreen = (color.green() * alpha) + (parentWindowColor.green() * (1 - alpha));
353 const int newBlue = (color.blue() * alpha) + (parentWindowColor.blue() * (1 - alpha));
354
355 painter.setRenderHint(QPainter::Antialiasing);
356 painter.setBrush(QColor(newRed, newGreen, newBlue));
357 if (d->position == Position::Inline) {
358 painter.setPen(QPen(color, borderSize));
359 painter.drawRoundedRect(innerRect, radius, radius);
360 return;
361 }
362
363 painter.setPen(QPen(Qt::NoPen));
364 painter.drawRect(rect());
365
366 if (d->position == Position::Header) {
367 painter.setPen(QPen(color, 1));
368 painter.drawLine(0, rect().height(), rect().width(), rect().height());
369 } else {
370 painter.setPen(QPen(color, 1));
371 painter.drawLine(0, 0, rect().width(), 0);
372 }
373}
374
375bool KMessageWidget::wordWrap() const
376{
377 return d->wordWrap;
378}
379
381{
382 d->wordWrap = wordWrap;
383 d->textLabel->setWordWrap(wordWrap);
384 QSizePolicy policy = sizePolicy();
385 policy.setHeightForWidth(wordWrap);
386 setSizePolicy(policy);
387 d->updateLayout();
388 // Without this, when user does wordWrap -> !wordWrap -> wordWrap, a minimum
389 // height is set, causing the widget to be too high.
390 // Mostly visible in test programs.
391 if (wordWrap) {
393 }
394}
395
396KMessageWidget::Position KMessageWidget::position() const
397{
398 return d->position;
399}
400
402{
403 d->position = position;
405}
406
408{
409 return d->closeButton->isVisible();
410}
411
413{
414 d->closeButton->setVisible(show);
416}
417
419{
420 QFrame::addAction(action);
421 d->updateLayout();
422}
423
425{
426 QFrame::removeAction(action);
427 d->updateLayout();
428}
429
431{
432 const auto ourActions = actions();
433 for (auto *action : ourActions) {
434 removeAction(action);
435 }
436 d->updateLayout();
437}
438
440{
441 // Test before styleHint, as there might have been a style change while animation was running
443 d->timeLine->stop();
445 }
446
447 if (!style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this) || (parentWidget() && !parentWidget()->isVisible())) {
448 show();
450 return;
451 }
452
453 if (isVisible() && (d->timeLine->state() == QTimeLine::NotRunning) && (height() == d->bestContentHeight())) {
455 return;
456 }
457
458 d->ignoreShowAndResizeEventDoingAnimatedShow = true;
459 show();
460 d->ignoreShowAndResizeEventDoingAnimatedShow = false;
462
463 d->timeLine->setDirection(QTimeLine::Forward);
464 if (d->timeLine->state() == QTimeLine::NotRunning) {
465 d->timeLine->start();
466 }
467}
468
470{
471 // test this before isVisible, as animatedShow might have been called directly before,
472 // so the first timeline event is not yet done and the widget is still hidden
473 // And before styleHint, as there might have been a style change while animation was running
475 d->timeLine->stop();
477 }
478
479 if (!style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this)) {
480 hide();
482 return;
483 }
484
485 if (!isVisible()) {
486 // explicitly hide it, so it stays hidden in case it is only not visible due to the parents
487 hide();
489 return;
490 }
491
492 d->timeLine->setDirection(QTimeLine::Backward);
493 if (d->timeLine->state() == QTimeLine::NotRunning) {
494 d->timeLine->start();
495 }
496}
497
499{
500 return (d->timeLine->direction() == QTimeLine::Backward) && (d->timeLine->state() == QTimeLine::Running);
501}
502
504{
505 return (d->timeLine->direction() == QTimeLine::Forward) && (d->timeLine->state() == QTimeLine::Running);
506}
507
508QIcon KMessageWidget::icon() const
509{
510 return d->icon;
511}
512
514{
515 d->icon = icon;
516 if (d->icon.isNull()) {
517 d->iconLabel->hide();
518 } else {
520 d->iconLabel->setPixmap(d->icon.pixmap(size));
521 d->iconLabel->show();
522 }
523}
524
525#include "moc_kmessagewidget.cpp"
A widget to provide feedback or propose opportunistic interactions.
void removeAction(QAction *action)
Remove action from the message widget.
bool isShowAnimationRunning() const
Check whether the show animation started by calling animatedShow() is still running.
void setCloseButtonVisible(bool visible)
Set the visibility of the close button.
void animatedHide()
Hide the widget using an animation.
bool isHideAnimationRunning() const
Check whether the hide animation started by calling animatedHide() is still running.
void linkActivated(const QString &contents)
This signal is emitted when the user clicks a link in the text label.
void showAnimationFinished()
This signal is emitted when the show animation is finished, started by calling animatedShow().
void linkHovered(const QString &contents)
This signal is emitted when the user hovers over a link in the text label.
void animatedShow()
Show the widget using an animation.
void addAction(QAction *action)
Add action to the message widget.
void hideAnimationFinished()
This signal is emitted when the hide animation is finished, started by calling animatedHide().
~KMessageWidget() override
Destructor.
int heightForWidth(int width) const override
Returns the required height for width.
void setMessageType(KMessageWidget::MessageType type)
Set the message type to type.
void setTextFormat(Qt::TextFormat textFormat)
Set the text format of the message widget's label.
MessageType
Available message types.
@ Error
Error message type.
@ Information
Information message type.
@ Positive
Positive message type.
@ Warning
Warning message type.
void setIcon(const QIcon &icon)
Define an icon to be shown on the left of the text.
KMessageWidget(QWidget *parent=nullptr)
Constructs a KMessageWidget with the specified parent.
bool isCloseButtonVisible() const
Check whether the close button is visible.
QSize sizeHint() const override
Returns the preferred size of the message widget.
void setWordWrap(bool wordWrap)
Set word wrap to wordWrap.
void clearActions()
Clears all actions from the message widget.
void setPosition(Position position)
Set the position of this message.
QSize minimumSizeHint() const override
Returns the minimum size of the message widget.
Position
Position of the KMessageWidget.
@ Inline
The message widget is display inside the content.
@ Header
The message widget is displayed as header.
void setText(const QString &text)
Set the text of the message widget to text.
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
QAction * back(const QObject *recvr, const char *slot, QObject *parent)
void setIcon(const QIcon &icon)
void setText(const QString &text)
void setToolTip(const QString &tip)
void triggered(bool checked)
void addStretch(int stretch)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
int blue() const const
int green() const const
int red() const const
void setRgb(QRgb rgb)
virtual bool event(QEvent *e) override
virtual QSize sizeHint() const const override
virtual void addItem(QLayoutItem *item) override
void addWidget(QWidget *widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment)
void linkActivated(const QString &link)
void linkHovered(const QString &link)
void setTextInteractionFlags(Qt::TextInteractionFlags flags)
QMargins contentsMargins() const const
void setContentsMargins(const QMargins &margins)
void append(QList< T > &&value)
void clear()
bool isEmpty() const const
void reserve(qsizetype size)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QString tr(const char *sourceText, const char *disambiguation, int n)
void setColor(ColorGroup group, ColorRole role, const QColor &color)
QRect marginsRemoved(const QMargins &margins) const const
int height() const const
void setHeightForWidth(bool dependent)
PM_ToolBarIconSize
SP_DialogCloseButton
SH_Widget_Animate
virtual int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const const=0
virtual QIcon standardIcon(StandardPixmap standardIcon, const QStyleOption *option, const QWidget *widget) const const=0
void initFrom(const QWidget *widget)
AlignCenter
TextFormat
TextBrowserInteraction
ToolButtonTextBesideIcon
void finished()
void valueChanged(qreal value)
void setAutoRaise(bool enable)
void setDefaultAction(QAction *action)
void setToolButtonStyle(Qt::ToolButtonStyle style)
QList< QAction * > actions() const const
QAction * addAction(const QIcon &icon, const QString &text)
void ensurePolished() const const
virtual int heightForWidth(int w) const const
void hide()
QLayout * layout() const const
void setMinimumHeight(int minh)
QWidget * parentWidget() const const
void removeAction(QAction *action)
virtual void resizeEvent(QResizeEvent *event)
void setFixedHeight(int h)
void setTabOrder(QWidget *first, QWidget *second)
void show()
void resize(const QSize &)
void setSizePolicy(QSizePolicy)
QStyle * style() const const
void update()
void updateGeometry()
bool isVisible() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 19 2024 11:51:03 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.