1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2007 Urs Wolfer <uwolfer@kde.org>
4 SPDX-FileCopyrightText: 2007 Michaƫl Larouche <larouche@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-or-later
7*/
8
9#include "ktitlewidget.h"
10
11#include <QApplication>
12#include <QFrame>
13#include <QIcon>
14#include <QLabel>
15#include <QLayout>
16#include <QMouseEvent>
17#include <QStyle>
18#include <QTextDocument>
19#include <QTimer>
20
21class KTitleWidgetPrivate
22{
23public:
24 KTitleWidgetPrivate(KTitleWidget *parent)
25 : q(parent)
26 // use Left so updateIconAlignment(ImageRight) as called by constructor triggers the default layout
27 , iconAlignment(KTitleWidget::ImageLeft)
28 , autoHideTimeout(0)
29 , messageType(KTitleWidget::InfoMessage)
30 {
31 }
32
33 QString textStyleSheet() const
34 {
35 qreal factor;
36 switch (level) {
37 case 1:
38 factor = 1.35;
39 break;
40 case 2:
41 factor = 1.20;
42 break;
43 case 3:
44 factor = 1.15;
45 break;
46 case 4:
47 factor = 1.10;
48 break;
49 default:
50 factor = 1;
51 }
52 const double fontSize = QApplication::font().pointSize() * factor;
53 return QStringLiteral("QLabel { font-size: %1pt; color: %2 }").arg(args: QString::number(fontSize), args: q->palette().color(cr: QPalette::WindowText).name());
54 }
55
56 QString commentStyleSheet() const
57 {
58 QString styleSheet;
59 switch (messageType) {
60 // FIXME: we need the usability color styles to implement different
61 // yet palette appropriate colours for the different use cases!
62 // also .. should we include an icon here,
63 // perhaps using the imageLabel?
64 case KTitleWidget::InfoMessage:
65 case KTitleWidget::WarningMessage:
66 case KTitleWidget::ErrorMessage:
67 styleSheet = QStringLiteral("QLabel { color: palette(%1); background: palette(%2); }")
68 .arg(args: q->palette().color(cr: QPalette::HighlightedText).name(), args: q->palette().color(cr: QPalette::Highlight).name());
69 break;
70 case KTitleWidget::PlainMessage:
71 default:
72 break;
73 }
74 return styleSheet;
75 }
76
77 void updateIconAlignment(KTitleWidget::ImageAlignment newIconAlignment)
78 {
79 if (iconAlignment == newIconAlignment) {
80 return;
81 }
82
83 iconAlignment = newIconAlignment;
84
85 headerLayout->removeWidget(w: textLabel);
86 headerLayout->removeWidget(w: commentLabel);
87 headerLayout->removeWidget(w: imageLabel);
88
89 if (iconAlignment == KTitleWidget::ImageLeft) {
90 // swap the text and image labels around
91 headerLayout->addWidget(imageLabel, row: 0, column: 0, rowSpan: 2, columnSpan: 1);
92 headerLayout->addWidget(textLabel, row: 0, column: 1);
93 headerLayout->addWidget(commentLabel, row: 1, column: 1);
94 headerLayout->setColumnStretch(column: 0, stretch: 0);
95 headerLayout->setColumnStretch(column: 1, stretch: 1);
96 } else {
97 headerLayout->addWidget(textLabel, row: 0, column: 0);
98 headerLayout->addWidget(commentLabel, row: 1, column: 0);
99 headerLayout->addWidget(imageLabel, row: 0, column: 1, rowSpan: 2, columnSpan: 1);
100 headerLayout->setColumnStretch(column: 1, stretch: 0);
101 headerLayout->setColumnStretch(column: 0, stretch: 1);
102 }
103 }
104
105 void updatePixmap()
106 {
107 const QPixmap pixmap = icon.pixmap(size: q->iconSize());
108 imageLabel->setPixmap(pixmap);
109 }
110
111 int level = 1;
112 KTitleWidget *const q;
113 QGridLayout *headerLayout;
114 QLabel *imageLabel;
115 QLabel *textLabel;
116 QLabel *commentLabel;
117 QIcon icon;
118 QSize iconSize;
119 KTitleWidget::ImageAlignment iconAlignment;
120 int autoHideTimeout;
121 KTitleWidget::MessageType messageType;
122
123 /**
124 * @brief Get the icon name from the icon type
125 * @param type icon type from the enum
126 * @return named icon as QString
127 */
128 QString iconTypeToIconName(KTitleWidget::MessageType type);
129
130 void timeoutFinished()
131 {
132 q->setVisible(false);
133 }
134};
135
136QString KTitleWidgetPrivate::iconTypeToIconName(KTitleWidget::MessageType type)
137{
138 switch (type) {
139 case KTitleWidget::InfoMessage:
140 return QStringLiteral("dialog-information");
141 case KTitleWidget::ErrorMessage:
142 return QStringLiteral("dialog-error");
143 case KTitleWidget::WarningMessage:
144 return QStringLiteral("dialog-warning");
145 case KTitleWidget::PlainMessage:
146 break;
147 }
148
149 return QString();
150}
151
152KTitleWidget::KTitleWidget(QWidget *parent)
153 : QWidget(parent)
154 , d(new KTitleWidgetPrivate(this))
155{
156 QFrame *titleFrame = new QFrame(this);
157 titleFrame->setAutoFillBackground(true);
158 titleFrame->setFrameShape(QFrame::StyledPanel);
159 titleFrame->setFrameShadow(QFrame::Plain);
160 titleFrame->setBackgroundRole(QPalette::Window);
161 titleFrame->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
162
163 // default image / text part start
164 d->headerLayout = new QGridLayout(titleFrame);
165 d->headerLayout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
166 d->headerLayout->setSizeConstraint(QLayout::SetFixedSize);
167
168 d->textLabel = new QLabel(titleFrame);
169 d->textLabel->setVisible(false);
170 d->textLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
171
172 d->imageLabel = new QLabel(titleFrame);
173 d->imageLabel->setVisible(false);
174
175 d->commentLabel = new QLabel(titleFrame);
176 d->commentLabel->setVisible(false);
177 d->commentLabel->setOpenExternalLinks(true);
178 d->commentLabel->setWordWrap(true);
179 d->commentLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
180
181 d->updateIconAlignment(newIconAlignment: ImageRight); // make sure d->iconAlignment is left, to trigger initial layout
182 // default image / text part end
183
184 QVBoxLayout *mainLayout = new QVBoxLayout(this);
185 mainLayout->addWidget(titleFrame);
186 mainLayout->setContentsMargins(left: 0, top: 0, right: 0, bottom: 0);
187}
188
189KTitleWidget::~KTitleWidget() = default;
190
191bool KTitleWidget::eventFilter(QObject *object, QEvent *event)
192{
193 // Hide message label on click
194 if (d->autoHideTimeout > 0 && event->type() == QEvent::MouseButtonPress) {
195 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
196 if (mouseEvent && mouseEvent->button() == Qt::LeftButton) {
197 setVisible(false);
198 return true;
199 }
200 }
201
202 return QWidget::eventFilter(watched: object, event);
203}
204
205void KTitleWidget::setWidget(QWidget *widget)
206{
207 d->headerLayout->addWidget(widget, row: 2, column: 0, rowSpan: 1, columnSpan: 2);
208}
209
210QString KTitleWidget::text() const
211{
212 return d->textLabel->text();
213}
214
215QString KTitleWidget::comment() const
216{
217 return d->commentLabel->text();
218}
219
220QIcon KTitleWidget::icon() const
221{
222 return d->icon;
223}
224
225QSize KTitleWidget::iconSize() const
226{
227 if (d->iconSize.isValid()) {
228 return d->iconSize;
229 }
230 const int iconSizeExtent = style()->pixelMetric(metric: QStyle::PM_MessageBoxIconSize);
231 return QSize(iconSizeExtent, iconSizeExtent);
232}
233
234void KTitleWidget::setBuddy(QWidget *buddy)
235{
236 d->textLabel->setBuddy(buddy);
237}
238
239void KTitleWidget::changeEvent(QEvent *e)
240{
241 QWidget::changeEvent(e);
242 if (e->type() == QEvent::PaletteChange || e->type() == QEvent::FontChange || e->type() == QEvent::ApplicationFontChange) {
243 d->textLabel->setStyleSheet(d->textStyleSheet());
244 d->commentLabel->setStyleSheet(d->commentStyleSheet());
245 d->updatePixmap();
246 } else if (e->type() == QEvent::StyleChange) {
247 if (!d->iconSize.isValid()) {
248 // relies on style's PM_MessageBoxIconSize
249 d->updatePixmap();
250 }
251 }
252}
253
254void KTitleWidget::setText(const QString &text, Qt::Alignment alignment)
255{
256 d->textLabel->setVisible(!text.isNull());
257
258 if (!Qt::mightBeRichText(text)) {
259 d->textLabel->setStyleSheet(d->textStyleSheet());
260 }
261
262 d->textLabel->setText(text);
263 d->textLabel->setAlignment(alignment);
264 show();
265}
266
267void KTitleWidget::setLevel(int level)
268{
269 if (d->level == level) {
270 return;
271 }
272
273 d->level = level;
274
275 d->textLabel->setStyleSheet(d->textStyleSheet());
276}
277
278int KTitleWidget::level()
279{
280 return d->level;
281}
282
283void KTitleWidget::setText(const QString &text, MessageType type)
284{
285 setIcon(type);
286 setText(text);
287}
288
289void KTitleWidget::setComment(const QString &comment, MessageType type)
290{
291 d->commentLabel->setVisible(!comment.isNull());
292
293 // TODO: should we override the current icon with the corresponding MessageType icon?
294 d->messageType = type;
295 d->commentLabel->setStyleSheet(d->commentStyleSheet());
296 d->commentLabel->setText(comment);
297 show();
298}
299
300void KTitleWidget::setIcon(const QIcon &icon, KTitleWidget::ImageAlignment alignment)
301{
302 d->icon = icon;
303
304 d->imageLabel->setVisible(!icon.isNull());
305
306 d->updateIconAlignment(newIconAlignment: alignment);
307
308 d->updatePixmap();
309}
310
311void KTitleWidget::setIconSize(const QSize &iconSize)
312{
313 if (d->iconSize == iconSize) {
314 return;
315 }
316
317 const QSize oldEffectiveIconSize = this->iconSize();
318
319 d->iconSize = iconSize;
320
321 if (oldEffectiveIconSize != this->iconSize()) {
322 d->updatePixmap();
323 }
324}
325
326void KTitleWidget::setIcon(MessageType type, ImageAlignment alignment)
327{
328 setIcon(icon: QIcon::fromTheme(name: d->iconTypeToIconName(type)), alignment);
329}
330
331int KTitleWidget::autoHideTimeout() const
332{
333 return d->autoHideTimeout;
334}
335
336void KTitleWidget::setAutoHideTimeout(int msecs)
337{
338 d->autoHideTimeout = msecs;
339
340 if (msecs > 0) {
341 installEventFilter(filterObj: this);
342 } else {
343 removeEventFilter(obj: this);
344 }
345}
346
347void KTitleWidget::showEvent(QShowEvent *event)
348{
349 Q_UNUSED(event)
350 if (d->autoHideTimeout > 0) {
351 QTimer::singleShot(interval: d->autoHideTimeout, receiver: this, slot: [this] {
352 d->timeoutFinished();
353 });
354 }
355}
356
357#include "moc_ktitlewidget.cpp"
358

source code of kwidgetsaddons/src/ktitlewidget.cpp