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
47 void createLayout();
48 void setPalette();
49 void updateLayout();
50 void slotTimeLineChanged(qreal);
51 void slotTimeLineFinished();
52 int bestContentHeight() const;
53};
54
55void KMessageWidgetPrivate::init(KMessageWidget *q_ptr)
56{
57 q = q_ptr;
58 // Note: when changing the value 500, also update KMessageWidgetTest
59 timeLine = new QTimeLine(500, q);
60 QObject::connect(timeLine, &QTimeLine::valueChanged, q, [this](qreal value) {
61 slotTimeLineChanged(value);
62 });
63 QObject::connect(timeLine, &QTimeLine::finished, q, [this]() {
64 slotTimeLineFinished();
65 });
66
67 wordWrap = false;
68
69 iconLabel = new QLabel(q);
71 iconLabel->hide();
72
73 textLabel = new QLabel(q);
78
79 QAction *closeAction = new QAction(q);
80 closeAction->setText(KMessageWidget::tr("&Close", "@action:button"));
81 closeAction->setToolTip(KMessageWidget::tr("Close message", "@info:tooltip"));
83 opt.initFrom(q);
84 closeAction->setIcon(q->style()->standardIcon(QStyle::SP_DialogCloseButton, &opt, q));
85
87
88 closeButton = new QToolButton(q);
89 closeButton->setAutoRaise(true);
90 closeButton->setDefaultAction(closeAction);
91
93}
94
95void KMessageWidgetPrivate::createLayout()
96{
97 delete q->layout();
98
99 qDeleteAll(buttons);
100 buttons.clear();
101
102 const auto actions = q->actions();
103 buttons.reserve(actions.size());
104 for (QAction *action : actions) {
105 QToolButton *button = new QToolButton(q);
106 button->setDefaultAction(action);
108 auto previous = buttons.isEmpty() ? static_cast<QWidget *>(textLabel) : buttons.back();
109 QWidget::setTabOrder(previous, button);
110 buttons.append(button);
111 }
112
113 // AutoRaise reduces visual clutter, but we don't want to turn it on if
114 // there are other buttons, otherwise the close button will look different
115 // from the others.
116 closeButton->setAutoRaise(buttons.isEmpty());
117 if (wordWrap) {
118 QGridLayout *layout = new QGridLayout(q);
119 // Set alignment to make sure icon does not move down if text wraps
120 layout->addWidget(iconLabel, 0, 0, 1, 1, Qt::AlignHCenter | Qt::AlignTop);
121 layout->addWidget(textLabel, 0, 1);
122
123 if (buttons.isEmpty()) {
124 // Use top-vertical alignment like the icon does.
125 layout->addWidget(closeButton, 0, 2, 1, 1, Qt::AlignHCenter | Qt::AlignTop);
126 } else {
127 // Use an additional layout in row 1 for the buttons.
128 QHBoxLayout *buttonLayout = new QHBoxLayout;
129 buttonLayout->addStretch();
130 for (QToolButton *button : std::as_const(buttons)) {
131 // For some reason, calling show() is necessary if wordwrap is true,
132 // otherwise the buttons do not show up. It is not needed if
133 // wordwrap is false.
134 button->show();
135 buttonLayout->addWidget(button);
136 }
137 buttonLayout->addWidget(closeButton);
138 layout->addItem(buttonLayout, 1, 0, 1, 2);
139 }
140 } else {
141 QHBoxLayout *layout = new QHBoxLayout(q);
142 layout->addWidget(iconLabel, 0, Qt::AlignTop);
143 layout->addWidget(textLabel);
144
145 for (QToolButton *button : std::as_const(buttons)) {
146 layout->addWidget(button, 0, Qt::AlignTop);
147 }
148 layout->addWidget(closeButton, 0, Qt::AlignTop);
149 };
150 // Add bordersize to the margin so it starts from the inner border and doesn't look too cramped
151 q->layout()->setContentsMargins(q->layout()->contentsMargins() + borderSize);
152 if (q->isVisible()) {
153 q->setFixedHeight(q->sizeHint().height());
154 }
155 q->updateGeometry();
156}
157
158void KMessageWidgetPrivate::setPalette()
159{
160 QColor bgBaseColor;
161
162 // We have to hardcode colors here because KWidgetsAddons is a tier 1 framework
163 // and therefore can't depend on any other KDE Frameworks
164 // The following RGB color values come from the "default" scheme in kcolorscheme.cpp
165 switch (messageType) {
167 bgBaseColor.setRgb(39, 174, 96); // Window: ForegroundPositive
168 break;
170 bgBaseColor.setRgb(61, 174, 233); // Window: ForegroundActive
171 break;
173 bgBaseColor.setRgb(246, 116, 0); // Window: ForegroundNeutral
174 break;
176 bgBaseColor.setRgb(218, 68, 83); // Window: ForegroundNegative
177 break;
178 }
179 QPalette palette = q->palette();
180 palette.setColor(QPalette::Window, bgBaseColor);
181 const QColor parentTextColor = (q->parentWidget() ? q->parentWidget()->palette() : qApp->palette()).color(QPalette::WindowText);
182 palette.setColor(QPalette::WindowText, parentTextColor);
183 q->setPalette(palette);
184 // Explicitly set the palettes of the labels because some apps use stylesheets which break the
185 // palette propagation
186 iconLabel->setPalette(palette);
187 textLabel->setPalette(palette);
188 q->style()->polish(q);
189 // update the Icon in case it is recolorable
190 q->setIcon(icon);
191 q->update();
192}
193
194void KMessageWidgetPrivate::updateLayout()
195{
196 createLayout();
197}
198
199void KMessageWidgetPrivate::slotTimeLineChanged(qreal value)
200{
201 q->setFixedHeight(qMin(value * 2, qreal(1.0)) * bestContentHeight());
202 q->update();
203}
204
205void KMessageWidgetPrivate::slotTimeLineFinished()
206{
207 if (timeLine->direction() == QTimeLine::Forward) {
208 q->resize(q->width(), bestContentHeight());
209
210 // notify about finished animation
211 Q_EMIT q->showAnimationFinished();
212 } else {
213 // hide and notify about finished animation
214 q->hide();
215 Q_EMIT q->hideAnimationFinished();
216 }
217}
218
219int KMessageWidgetPrivate::bestContentHeight() const
220{
221 int height = q->heightForWidth(q->width());
222 if (height == -1) {
223 height = q->sizeHint().height();
224 }
225 return height;
226}
227
228//---------------------------------------------------------------------
229// KMessageWidget
230//---------------------------------------------------------------------
232 : QFrame(parent)
233 , d(new KMessageWidgetPrivate)
234{
235 d->init(this);
236}
237
239 : QFrame(parent)
240 , d(new KMessageWidgetPrivate)
241{
242 d->init(this);
243 setText(text);
244}
245
247
248QString KMessageWidget::text() const
249{
250 return d->textLabel->text();
251}
252
254{
255 d->textLabel->setText(text);
257}
258
259Qt::TextFormat KMessageWidget::textFormat() const
260{
261 return d->textLabel->textFormat();
262}
263
265{
266 d->textLabel->setTextFormat(textFormat);
267}
268
269KMessageWidget::MessageType KMessageWidget::messageType() const
270{
271 return d->messageType;
272}
273
275{
276 d->messageType = type;
277 d->setPalette();
278}
279
281{
283 return QFrame::sizeHint();
284}
285
291
292bool KMessageWidget::event(QEvent *event)
293{
294 if (event->type() == QEvent::Polish && !layout()) {
295 d->createLayout();
296 } else if ((event->type() == QEvent::Show && !d->ignoreShowAndResizeEventDoingAnimatedShow)
297 || (event->type() == QEvent::LayoutRequest && d->timeLine->state() == QTimeLine::NotRunning)) {
298 setFixedHeight(d->bestContentHeight());
299
300 // if we are displaying this when application first starts, there's
301 // a possibility that the layout is not properly updated with the
302 // rest of the application because the setFixedHeight call above has
303 // the same height that was set beforehand, when we lacked a parent
304 // and thus, the layout() geometry is bogus. so we pass a bogus
305 // value to it, just to trigger a recalculation, and revert to the
306 // best content height.
307 if (geometry().height() < layout()->geometry().height()) {
308 setFixedHeight(d->bestContentHeight() + 2); // this triggers a recalculation.
309 setFixedHeight(d->bestContentHeight()); // this actually sets the correct values.
310 }
311
312 } else if (event->type() == QEvent::ParentChange) {
313 d->setPalette();
314 } else if (event->type() == QEvent::ApplicationPaletteChange) {
315 d->setPalette();
316 }
317 return QFrame::event(event);
318}
319
320void KMessageWidget::resizeEvent(QResizeEvent *event)
321{
322 QFrame::resizeEvent(event);
323 if (d->timeLine->state() == QTimeLine::NotRunning && d->ignoreShowAndResizeEventDoingAnimatedShow) {
324 setFixedHeight(d->bestContentHeight());
325 }
326}
327
329{
332}
333
334void KMessageWidget::paintEvent(QPaintEvent *event)
335{
336 Q_UNUSED(event)
337 QPainter painter(this);
338 if (d->timeLine->state() == QTimeLine::Running) {
339 painter.setOpacity(d->timeLine->currentValue() * d->timeLine->currentValue());
340 }
341 constexpr float radius = 4 * 0.6;
342 const QRect innerRect = rect().marginsRemoved(QMargins() + borderSize / 2);
343 const QColor color = palette().color(QPalette::Window);
344 constexpr float alpha = 0.2;
345 const QColor parentWindowColor = (parentWidget() ? parentWidget()->palette() : qApp->palette()).color(QPalette::Window);
346 const int newRed = (color.red() * alpha) + (parentWindowColor.red() * (1 - alpha));
347 const int newGreen = (color.green() * alpha) + (parentWindowColor.green() * (1 - alpha));
348 const int newBlue = (color.blue() * alpha) + (parentWindowColor.blue() * (1 - alpha));
349
350 painter.setRenderHint(QPainter::Antialiasing);
351 painter.setBrush(QColor(newRed, newGreen, newBlue));
352 if (d->position == Position::Inline) {
353 painter.setPen(QPen(color, borderSize));
354 painter.drawRoundedRect(innerRect, radius, radius);
355 return;
356 }
357
358 painter.setPen(QPen(Qt::NoPen));
359 painter.drawRect(rect());
360
361 if (d->position == Position::Header) {
362 painter.setPen(QPen(color, 1));
363 painter.drawLine(0, rect().height(), rect().width(), rect().height());
364 } else {
365 painter.setPen(QPen(color, 1));
366 painter.drawLine(0, 0, rect().width(), 0);
367 }
368}
369
370bool KMessageWidget::wordWrap() const
371{
372 return d->wordWrap;
373}
374
376{
377 d->wordWrap = wordWrap;
378 d->textLabel->setWordWrap(wordWrap);
379 QSizePolicy policy = sizePolicy();
380 policy.setHeightForWidth(wordWrap);
381 setSizePolicy(policy);
382 d->updateLayout();
383 // Without this, when user does wordWrap -> !wordWrap -> wordWrap, a minimum
384 // height is set, causing the widget to be too high.
385 // Mostly visible in test programs.
386 if (wordWrap) {
388 }
389}
390
391KMessageWidget::Position KMessageWidget::position() const
392{
393 return d->position;
394}
395
397{
398 d->position = position;
400}
401
403{
404 return d->closeButton->isVisible();
405}
406
408{
409 d->closeButton->setVisible(show);
411}
412
414{
415 QFrame::addAction(action);
416 d->updateLayout();
417}
418
420{
421 QFrame::removeAction(action);
422 d->updateLayout();
423}
424
426{
427 const auto ourActions = actions();
428 for (auto *action : ourActions) {
429 removeAction(action);
430 }
431 d->updateLayout();
432}
433
435{
436 // Test before styleHint, as there might have been a style change while animation was running
438 d->timeLine->stop();
440 }
441
442 if (!style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this) || (parentWidget() && !parentWidget()->isVisible())) {
443 show();
445 return;
446 }
447
448 if (isVisible() && (d->timeLine->state() == QTimeLine::NotRunning) && (height() == d->bestContentHeight())) {
450 return;
451 }
452
453 d->ignoreShowAndResizeEventDoingAnimatedShow = true;
454 show();
455 d->ignoreShowAndResizeEventDoingAnimatedShow = false;
457
458 d->timeLine->setDirection(QTimeLine::Forward);
459 if (d->timeLine->state() == QTimeLine::NotRunning) {
460 d->timeLine->start();
461 }
462}
463
465{
466 // test this before isVisible, as animatedShow might have been called directly before,
467 // so the first timeline event is not yet done and the widget is still hidden
468 // And before styleHint, as there might have been a style change while animation was running
470 d->timeLine->stop();
472 }
473
474 if (!style()->styleHint(QStyle::SH_Widget_Animate, nullptr, this)) {
475 hide();
477 return;
478 }
479
480 if (!isVisible()) {
481 // explicitly hide it, so it stays hidden in case it is only not visible due to the parents
482 hide();
484 return;
485 }
486
487 d->timeLine->setDirection(QTimeLine::Backward);
488 if (d->timeLine->state() == QTimeLine::NotRunning) {
489 d->timeLine->start();
490 }
491}
492
494{
495 return (d->timeLine->direction() == QTimeLine::Backward) && (d->timeLine->state() == QTimeLine::Running);
496}
497
499{
500 return (d->timeLine->direction() == QTimeLine::Forward) && (d->timeLine->state() == QTimeLine::Running);
501}
502
503QIcon KMessageWidget::icon() const
504{
505 return d->icon;
506}
507
509{
510 d->icon = icon;
511 if (d->icon.isNull()) {
512 d->iconLabel->hide();
513 } else {
515 d->iconLabel->setPixmap(d->icon.pixmap(size));
516 d->iconLabel->show();
517 }
518}
519
520#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)
const QList< QKeySequence > & back()
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)
T qobject_cast(QObject *object)
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 void polish(QApplication *application)
virtual QIcon standardIcon(StandardPixmap standardIcon, const QStyleOption *option, const QWidget *widget) const const=0
void initFrom(const QWidget *widget)
AlignHCenter
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 Tue Mar 26 2024 11:14:43 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.