kdeui: rework KMessageWidget

using the selection color for information messages when the text is
selectable was questionable atleast, overriding QFrame action setters,
using QFrame as base class when none of its features were used..

Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
This commit is contained in:
Ivailo Monev 2024-04-16 03:24:12 +03:00
parent 4857f6cd17
commit 1c84eaf43b
3 changed files with 172 additions and 271 deletions

View file

@ -102,8 +102,7 @@ class KTEXTEDITOR_EXPORT Message : public QObject
* For simple notifications either use Positive or Information. * For simple notifications either use Positive or Information.
*/ */
enum MessageType { enum MessageType {
Positive = 0, ///< positive information message Information = 0, ///< information message type
Information, ///< information message type
Warning, ///< warning message type Warning, ///< warning message type
Error ///< error message type Error ///< error message type
}; };

View file

@ -19,19 +19,53 @@
*/ */
#include "kmessagewidget.h" #include "kmessagewidget.h"
#include "kaction.h" #include "kaction.h"
#include "kcolorscheme.h"
#include "kicon.h" #include "kicon.h"
#include "kiconloader.h" #include "kiconloader.h"
#include "kcolorscheme.h"
#include "kstandardaction.h" #include "kstandardaction.h"
#include "kpixmapwidget.h" #include "kpixmapwidget.h"
#include "kdebug.h" #include "kdebug.h"
#include <QEvent>
#include <QGridLayout>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLabel> #include <QLabel>
#include <QToolButton> #include <QToolButton>
#include <QStyle> #include <QPainter>
static const qreal s_roundness = 4.0;
static const qreal s_bordersize = 0.5;
static const qreal s_margin = 5;
class KMessageLabel : public QLabel
{
Q_OBJECT
public:
KMessageLabel(QWidget *parent);
QColor bg;
QColor border;
protected:
void paintEvent(QPaintEvent *event) final;
};
KMessageLabel::KMessageLabel(QWidget *parent)
: QLabel(parent)
{
setContentsMargins(s_margin, s_margin, s_margin, s_margin);
}
void KMessageLabel::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setBrush(border);
QRectF widgetrect = rect();
painter.drawRoundedRect(widgetrect, s_roundness, s_roundness);
painter.setBrush(bg);
widgetrect = widgetrect.adjusted(s_bordersize, s_bordersize, -s_bordersize, -s_bordersize);
painter.drawRoundedRect(widgetrect, s_roundness, s_roundness);
QLabel::paintEvent(event);
}
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// KMessageWidgetPrivate // KMessageWidgetPrivate
@ -39,118 +73,106 @@
class KMessageWidgetPrivate class KMessageWidgetPrivate
{ {
public: public:
void init(KMessageWidget *q_ptr); KMessageWidgetPrivate();
~KMessageWidgetPrivate();
KMessageWidget* q; void updateColors();
KPixmapWidget* iconWidget;
QLabel* textLabel; QVBoxLayout* mainlayout;
QToolButton* closeButton; QHBoxLayout* messagelayout;
KPixmapWidget* iconwidget;
KMessageLabel* textlabel;
QToolButton* closebutton;
QIcon icon; QIcon icon;
KMessageWidget::MessageType messageType; KMessageWidget::MessageType messagetype;
QHBoxLayout* buttonslayout;
QList<QToolButton*> buttons; QList<QToolButton*> buttons;
void updateLayout();
}; };
void KMessageWidgetPrivate::init(KMessageWidget *q_ptr) KMessageWidgetPrivate::KMessageWidgetPrivate()
: mainlayout(nullptr),
messagelayout(nullptr),
iconwidget(nullptr),
textlabel(nullptr),
closebutton(nullptr),
buttonslayout(nullptr),
messagetype(KMessageWidget::Information)
{ {
q = q_ptr;
q->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
iconWidget = new KPixmapWidget(q);
iconWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
iconWidget->hide();
textLabel = new QLabel(q);
textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
QObject::connect(textLabel, SIGNAL(linkActivated(QString)), q, SIGNAL(linkActivated(QString)));
QObject::connect(textLabel, SIGNAL(linkHovered(QString)), q, SIGNAL(linkHovered(QString)));
KAction* closeAction = KStandardAction::close(q, SLOT(animatedHide()), q);
// The default shortcut assigned by KStandardAction is Ctrl+W,
// which might conflict with application-specific shortcuts.
closeAction->setShortcut(QKeySequence());
closeButton = new QToolButton(q);
closeButton->setAutoRaise(true);
closeButton->setDefaultAction(closeAction);
q->setMessageType(KMessageWidget::Information);
} }
void KMessageWidgetPrivate::updateLayout() KMessageWidgetPrivate::~KMessageWidgetPrivate()
{ {
if (q->layout()) {
delete q->layout();
}
qDeleteAll(buttons); qDeleteAll(buttons);
buttons.clear(); buttons.clear();
delete buttonslayout;
Q_FOREACH(QAction* action, q->actions()) { delete messagelayout;
QToolButton* button = new QToolButton(q);
button->setDefaultAction(action);
button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
buttons.append(button);
}
// AutoRaise reduces visual clutter, but we don't want to turn it on if
// there are other buttons, otherwise the close button will look different
// from the others.
closeButton->setAutoRaise(buttons.isEmpty());
if (textLabel->wordWrap()) {
QGridLayout* layout = new QGridLayout(q);
// Set alignment to make sure icon does not move down if text wraps
layout->addWidget(iconWidget, 0, 0, 1, 1, Qt::AlignHCenter | Qt::AlignTop);
layout->addWidget(textLabel, 0, 1);
QHBoxLayout* buttonLayout = new QHBoxLayout();
buttonLayout->addStretch();
Q_FOREACH(QToolButton* button, buttons) {
// For some reason, calling show() is necessary if wordwrap is true,
// otherwise the buttons do not show up. It is not needed if
// wordwrap is false.
button->show();
buttonLayout->addWidget(button);
}
buttonLayout->addWidget(closeButton);
buttonLayout->addStretch();
layout->addItem(buttonLayout, 1, 0, 1, 2);
} else {
QHBoxLayout* layout = new QHBoxLayout(q);
layout->addWidget(iconWidget);
layout->addWidget(textLabel);
Q_FOREACH(QToolButton* button, buttons) {
layout->addWidget(button);
}
layout->addWidget(closeButton);
};
q->updateGeometry();
} }
void KMessageWidgetPrivate::updateColors()
{
const KColorScheme scheme(QPalette::Active, KColorScheme::Window);
switch (messagetype) {
case KMessageWidget::Information: {
// even tho the selection color may be more suitable for that it cannot be used because
// the text is selectable
textlabel->bg = scheme.background(KColorScheme::PositiveBackground).color();
break;
}
case KMessageWidget::Warning: {
textlabel->bg = scheme.background(KColorScheme::NeutralBackground).color();
break;
}
case KMessageWidget::Error: {
textlabel->bg = scheme.background(KColorScheme::NegativeBackground).color();
break;
}
}
// textlabel->bg = textlabel->bg.darker(60);
textlabel->border = KColorScheme::shade(textlabel->bg, KColorScheme::DarkShade);
}
//--------------------------------------------------------------------- //---------------------------------------------------------------------
// KMessageWidget // KMessageWidget
//--------------------------------------------------------------------- //---------------------------------------------------------------------
KMessageWidget::KMessageWidget(QWidget *parent) KMessageWidget::KMessageWidget(QWidget *parent)
: QFrame(parent), : QWidget(parent),
d(new KMessageWidgetPrivate()) d(new KMessageWidgetPrivate())
{ {
d->init(this); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
}
KMessageWidget::KMessageWidget(const QString &text, QWidget *parent) d->mainlayout = new QVBoxLayout(this);
: QFrame(parent), setLayout(d->mainlayout);
d(new KMessageWidgetPrivate())
{ d->messagelayout = new QHBoxLayout();
d->init(this);
setText(text); d->iconwidget = new KPixmapWidget(this);
d->iconwidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
d->iconwidget->hide();
d->messagelayout->addWidget(d->iconwidget);
d->textlabel = new KMessageLabel(this);
d->textlabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
d->textlabel->setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::LinksAccessibleByMouse);
d->textlabel->setAlignment(Qt::AlignCenter);
connect(d->textlabel, SIGNAL(linkActivated(QString)), this, SIGNAL(linkActivated(QString)));
connect(d->textlabel, SIGNAL(linkHovered(QString)), this, SIGNAL(linkHovered(QString)));
d->messagelayout->addWidget(d->textlabel);
KAction* closeAction = KStandardAction::close(this, SLOT(animatedHide()), this);
// The default shortcut assigned by KStandardAction is Ctrl+W,
// which might conflict with application-specific shortcuts.
closeAction->setShortcut(QKeySequence());
d->closebutton = new QToolButton(this);
d->closebutton->setAutoRaise(true);
d->closebutton->setDefaultAction(closeAction);
d->messagelayout->addWidget(d->closebutton);
d->mainlayout->addLayout(d->messagelayout);
d->buttonslayout = new QHBoxLayout();
d->mainlayout->addLayout(d->buttonslayout);
d->updateColors();
} }
KMessageWidget::~KMessageWidget() KMessageWidget::~KMessageWidget()
@ -160,133 +182,49 @@ KMessageWidget::~KMessageWidget()
QString KMessageWidget::text() const QString KMessageWidget::text() const
{ {
return d->textLabel->text(); return d->textlabel->text();
} }
void KMessageWidget::setText(const QString& text) void KMessageWidget::setText(const QString& text)
{ {
d->textLabel->setText(text); d->textlabel->setText(text);
updateGeometry(); updateGeometry();
} }
KMessageWidget::MessageType KMessageWidget::messageType() const KMessageWidget::MessageType KMessageWidget::messageType() const
{ {
return d->messageType; return d->messagetype;
}
static void getColorsFromColorScheme(KColorScheme::BackgroundRole bgRole, QColor* bg, QColor* fg)
{
KColorScheme scheme(QPalette::Active, KColorScheme::Window);
*bg = scheme.background(bgRole).color();
*fg = scheme.foreground().color();
} }
void KMessageWidget::setMessageType(KMessageWidget::MessageType type) void KMessageWidget::setMessageType(KMessageWidget::MessageType type)
{ {
d->messageType = type; d->messagetype = type;
QColor bg0, bg1, bg2, border, fg; d->updateColors();
switch (type) { update();
case Positive:
getColorsFromColorScheme(KColorScheme::PositiveBackground, &bg1, &fg);
break;
case Information:
// There is no "information" background role in KColorScheme, use the
// colors of highlighted items instead
bg1 = palette().highlight().color();
fg = palette().highlightedText().color();
break;
case Warning:
getColorsFromColorScheme(KColorScheme::NeutralBackground, &bg1, &fg);
break;
case Error:
getColorsFromColorScheme(KColorScheme::NegativeBackground, &bg1, &fg);
break;
}
// Colors
bg0 = bg1.lighter(110);
bg2 = bg1.darker(110);
border = KColorScheme::shade(bg1, KColorScheme::DarkShade);
setStyleSheet(
QString("QLabel {"
"background-color: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,"
" stop: 0 %1,"
" stop: 0.1 %2,"
" stop: 1.0 %3);"
"border-radius: 5px;"
"border: 1px solid %4;"
"color: %5;"
"}"
)
.arg(bg0.name())
.arg(bg1.name())
.arg(bg2.name())
.arg(border.name())
.arg(fg.name())
);
}
QSize KMessageWidget::sizeHint() const
{
ensurePolished();
return QFrame::sizeHint();
}
QSize KMessageWidget::minimumSizeHint() const
{
ensurePolished();
return QFrame::minimumSizeHint();
}
bool KMessageWidget::event(QEvent* event)
{
if (event->type() == QEvent::Polish && !layout()) {
d->updateLayout();
}
return QFrame::event(event);
}
int KMessageWidget::heightForWidth(int width) const
{
ensurePolished();
return QFrame::heightForWidth(width);
} }
bool KMessageWidget::wordWrap() const bool KMessageWidget::wordWrap() const
{ {
return d->textLabel->wordWrap(); return d->textlabel->wordWrap();
} }
void KMessageWidget::setWordWrap(bool wordWrap) void KMessageWidget::setWordWrap(bool wordWrap)
{ {
d->textLabel->setWordWrap(wordWrap); d->textlabel->setWordWrap(wordWrap);
d->updateLayout(); adjustSize();
} }
bool KMessageWidget::isCloseButtonVisible() const bool KMessageWidget::isCloseButtonVisible() const
{ {
return d->closeButton->isVisible(); return d->closebutton->isVisible();
} }
void KMessageWidget::setCloseButtonVisible(bool show) void KMessageWidget::setCloseButtonVisible(bool show)
{ {
d->closeButton->setVisible(show); d->closebutton->setVisible(show);
updateGeometry(); updateGeometry();
} }
void KMessageWidget::addAction(QAction* action)
{
QFrame::addAction(action);
d->updateLayout();
}
void KMessageWidget::removeAction(QAction* action)
{
QFrame::removeAction(action);
d->updateLayout();
}
void KMessageWidget::animatedShow() void KMessageWidget::animatedShow()
{ {
if (isVisible()) { if (isVisible()) {
@ -295,7 +233,7 @@ void KMessageWidget::animatedShow()
// yep, no animation. changing the geometry for 500ms looks exactly the same as showing the // yep, no animation. changing the geometry for 500ms looks exactly the same as showing the
// widget without doing so // widget without doing so
QFrame::show(); QWidget::show();
} }
void KMessageWidget::animatedHide() void KMessageWidget::animatedHide()
@ -304,7 +242,7 @@ void KMessageWidget::animatedHide()
return; return;
} }
QFrame::hide(); QWidget::hide();
} }
QIcon KMessageWidget::icon() const QIcon KMessageWidget::icon() const
@ -316,12 +254,45 @@ void KMessageWidget::setIcon(const QIcon& icon)
{ {
d->icon = icon; d->icon = icon;
if (d->icon.isNull()) { if (d->icon.isNull()) {
d->iconWidget->hide(); d->iconwidget->hide();
} else { } else {
const int size = KIconLoader::global()->currentSize(KIconLoader::MainToolbar); const int size = KIconLoader::global()->currentSize(KIconLoader::MainToolbar);
d->iconWidget->setPixmap(d->icon.pixmap(size)); d->iconwidget->setPixmap(d->icon.pixmap(size));
d->iconWidget->show(); d->iconwidget->show();
} }
} }
bool KMessageWidget::event(QEvent *event)
{
const bool result = QWidget::event(event);
switch (event->type()) {
case QEvent::PaletteChange: {
d->updateColors();
update();
break;
}
case QEvent::ActionChanged:
case QEvent::ActionAdded:
case QEvent::ActionRemoved: {
qDeleteAll(d->buttons);
d->buttons.clear();
delete d->buttonslayout;
d->buttonslayout = new QHBoxLayout();
d->mainlayout->addLayout(d->buttonslayout);
d->buttonslayout->addStretch();
foreach (QAction* action, actions()) {
QToolButton* button = new QToolButton(this);
button->setDefaultAction(action);
button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
d->buttons.append(button);
d->buttonslayout->addWidget(button, 1, Qt::AlignCenter);
}
d->buttonslayout->addStretch();
break;
}
}
return result;
}
#include "moc_kmessagewidget.cpp" #include "moc_kmessagewidget.cpp"
#include "kmessagewidget.moc"

View file

@ -22,7 +22,8 @@
#include <kdeui_export.h> #include <kdeui_export.h>
#include <QFrame> #include <QEvent>
#include <QWidget>
class KMessageWidgetPrivate; class KMessageWidgetPrivate;
@ -36,60 +37,10 @@ class KMessageWidgetPrivate;
* to "OK Only" message boxes. If you do not need the modalness of KMessageBox, * to "OK Only" message boxes. If you do not need the modalness of KMessageBox,
* consider using KMessageWidget instead. * consider using KMessageWidget instead.
* *
* <b>Negative feedback</b>
*
* The KMessageWidget can be used as a secondary indicator of failure: the
* first indicator is usually the fact the action the user expected to happen
* did not happen.
*
* Example: User fills a form, clicks "Submit".
*
* @li Expected feedback: form closes
* @li First indicator of failure: form stays there
* @li Second indicator of failure: a KMessageWidget appears on top of the
* form, explaining the error condition
*
* When used to provide negative feedback, KMessageWidget should be placed
* close to its context. In the case of a form, it should appear on top of the
* form entries.
*
* KMessageWidget should get inserted in the existing layout. Space should not
* be reserved for it, otherwise it becomes "dead space", ignored by the user.
* KMessageWidget should also not appear as an overlay to prevent blocking
* access to elements the user needs to interact with to fix the failure.
*
* <b>Positive feedback</b>
*
* KMessageWidget can be used for positive feedback but it shouldn't be
* overused. It is often enough to provide feedback by simply showing the
* results of an action.
*
* Examples of acceptable uses:
*
* @li Confirm success of "critical" transactions
* @li Indicate completion of background tasks
*
* Example of inadapted uses:
*
* @li Indicate successful saving of a file
* @li Indicate a file has been successfully removed
*
* <b>Opportunistic interaction</b>
*
* Opportunistic interaction is the situation where the application suggests to
* the user an action he could be interested in perform, either based on an
* action the user just triggered or an event which the application noticed.
*
* Example of acceptable uses:
*
* @li A browser can propose remembering a recently entered password
* @li A music collection can propose ripping a CD which just got inserted
* @li A chat application may notify the user a "special friend" just connected
*
* @author Aurélien Gâteau <agateau@kde.org> * @author Aurélien Gâteau <agateau@kde.org>
* @since 4.7 * @since 4.7
*/ */
class KDEUI_EXPORT KMessageWidget : public QFrame class KDEUI_EXPORT KMessageWidget : public QWidget
{ {
Q_OBJECT Q_OBJECT
Q_ENUMS(MessageType) Q_ENUMS(MessageType)
@ -101,7 +52,6 @@ class KDEUI_EXPORT KMessageWidget : public QFrame
Q_PROPERTY(QIcon icon READ icon WRITE setIcon) Q_PROPERTY(QIcon icon READ icon WRITE setIcon)
public: public:
enum MessageType { enum MessageType {
Positive,
Information, Information,
Warning, Warning,
Error Error
@ -111,29 +61,13 @@ public:
* Constructs a KMessageWidget with the specified parent. * Constructs a KMessageWidget with the specified parent.
*/ */
explicit KMessageWidget(QWidget *parent = nullptr); explicit KMessageWidget(QWidget *parent = nullptr);
explicit KMessageWidget(const QString &text, QWidget *parent = nullptr);
~KMessageWidget(); ~KMessageWidget();
QString text() const; QString text() const;
bool wordWrap() const; bool wordWrap() const;
bool isCloseButtonVisible() const; bool isCloseButtonVisible() const;
MessageType messageType() const; MessageType messageType() const;
void addAction(QAction *action);
void removeAction(QAction *action);
QSize sizeHint() const;
QSize minimumSizeHint() const;
int heightForWidth(int width) const;
/** /**
* The icon shown on the left of the text. By default, no icon is shown. * The icon shown on the left of the text. By default, no icon is shown.
* @since 4.11 * @since 4.11
@ -142,11 +76,8 @@ public:
public Q_SLOTS: public Q_SLOTS:
void setText(const QString &text); void setText(const QString &text);
void setWordWrap(bool wordWrap); void setWordWrap(bool wordWrap);
void setCloseButtonVisible(bool visible); void setCloseButtonVisible(bool visible);
void setMessageType(KMessageWidget::MessageType type); void setMessageType(KMessageWidget::MessageType type);
/** /**