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
21 class KTitleWidgetPrivate
22 {
23 public:
KTitleWidgetPrivate(KTitleWidget * parent)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
textStyleSheet() const33 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(QString::number(fontSize), q->palette().color(QPalette::WindowText).name());
54 }
55
commentStyleSheet() const56 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(q->palette().color(QPalette::HighlightedText).name(), q->palette().color(QPalette::Highlight).name());
69 break;
70 case KTitleWidget::PlainMessage:
71 default:
72 break;
73 }
74 return styleSheet;
75 }
76
updateIconAlignment(KTitleWidget::ImageAlignment newIconAlignment)77 void updateIconAlignment(KTitleWidget::ImageAlignment newIconAlignment)
78 {
79 if (iconAlignment == newIconAlignment) {
80 return;
81 }
82
83 iconAlignment = newIconAlignment;
84
85 headerLayout->removeWidget(textLabel);
86 headerLayout->removeWidget(commentLabel);
87 headerLayout->removeWidget(imageLabel);
88
89 if (iconAlignment == KTitleWidget::ImageLeft) {
90 // swap the text and image labels around
91 headerLayout->addWidget(imageLabel, 0, 0, 2, 1);
92 headerLayout->addWidget(textLabel, 0, 1);
93 headerLayout->addWidget(commentLabel, 1, 1);
94 headerLayout->setColumnStretch(0, 0);
95 headerLayout->setColumnStretch(1, 1);
96 } else {
97 headerLayout->addWidget(textLabel, 0, 0);
98 headerLayout->addWidget(commentLabel, 1, 0);
99 headerLayout->addWidget(imageLabel, 0, 1, 2, 1);
100 headerLayout->setColumnStretch(1, 0);
101 headerLayout->setColumnStretch(0, 1);
102 }
103 }
104
updatePixmap()105 void updatePixmap()
106 {
107 const QPixmap pixmap = icon.pixmap(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
timeoutFinished()130 void timeoutFinished()
131 {
132 q->setVisible(false);
133 }
134 };
135
iconTypeToIconName(KTitleWidget::MessageType type)136 QString 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
KTitleWidget(QWidget * parent)152 KTitleWidget::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(0, 0, 0, 0);
162
163 // default image / text part start
164 d->headerLayout = new QGridLayout(titleFrame);
165 d->headerLayout->setContentsMargins(0, 0, 0, 0);
166
167 d->textLabel = new QLabel(titleFrame);
168 d->textLabel->setVisible(false);
169 d->textLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
170
171 d->imageLabel = new QLabel(titleFrame);
172 d->imageLabel->setVisible(false);
173
174 d->commentLabel = new QLabel(titleFrame);
175 d->commentLabel->setVisible(false);
176 d->commentLabel->setOpenExternalLinks(true);
177 d->commentLabel->setWordWrap(true);
178 d->commentLabel->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
179
180 d->updateIconAlignment(ImageRight); // make sure d->iconAlignment is left, to trigger initial layout
181 // default image / text part end
182
183 QVBoxLayout *mainLayout = new QVBoxLayout(this);
184 mainLayout->addWidget(titleFrame);
185 mainLayout->setContentsMargins(0, 0, 0, 0);
186 }
187
188 KTitleWidget::~KTitleWidget() = default;
189
eventFilter(QObject * object,QEvent * event)190 bool KTitleWidget::eventFilter(QObject *object, QEvent *event)
191 {
192 // Hide message label on click
193 if (d->autoHideTimeout > 0 && event->type() == QEvent::MouseButtonPress) {
194 QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
195 if (mouseEvent && mouseEvent->button() == Qt::LeftButton) {
196 setVisible(false);
197 return true;
198 }
199 }
200
201 return QWidget::eventFilter(object, event);
202 }
203
setWidget(QWidget * widget)204 void KTitleWidget::setWidget(QWidget *widget)
205 {
206 d->headerLayout->addWidget(widget, 2, 0, 1, 2);
207 }
208
text() const209 QString KTitleWidget::text() const
210 {
211 return d->textLabel->text();
212 }
213
comment() const214 QString KTitleWidget::comment() const
215 {
216 return d->commentLabel->text();
217 }
218
219 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 72)
pixmap() const220 const QPixmap *KTitleWidget::pixmap() const
221 {
222 QT_WARNING_PUSH
223 QT_WARNING_DISABLE_CLANG("-Wdeprecated-declarations")
224 QT_WARNING_DISABLE_GCC("-Wdeprecated-declarations")
225 return d->imageLabel->pixmap();
226 QT_WARNING_POP
227 }
228 #endif
229
icon() const230 QIcon KTitleWidget::icon() const
231 {
232 return d->icon;
233 }
234
iconSize() const235 QSize KTitleWidget::iconSize() const
236 {
237 if (d->iconSize.isValid()) {
238 return d->iconSize;
239 }
240 const int iconSizeExtent = style()->pixelMetric(QStyle::PM_MessageBoxIconSize);
241 return QSize(iconSizeExtent, iconSizeExtent);
242 }
243
setBuddy(QWidget * buddy)244 void KTitleWidget::setBuddy(QWidget *buddy)
245 {
246 d->textLabel->setBuddy(buddy);
247 }
248
changeEvent(QEvent * e)249 void KTitleWidget::changeEvent(QEvent *e)
250 {
251 QWidget::changeEvent(e);
252 if (e->type() == QEvent::PaletteChange || e->type() == QEvent::FontChange || e->type() == QEvent::ApplicationFontChange) {
253 d->textLabel->setStyleSheet(d->textStyleSheet());
254 d->commentLabel->setStyleSheet(d->commentStyleSheet());
255 d->updatePixmap();
256 } else if (e->type() == QEvent::StyleChange) {
257 if (!d->iconSize.isValid()) {
258 // relies on style's PM_MessageBoxIconSize
259 d->updatePixmap();
260 }
261 }
262 }
263
setText(const QString & text,Qt::Alignment alignment)264 void KTitleWidget::setText(const QString &text, Qt::Alignment alignment)
265 {
266 d->textLabel->setVisible(!text.isNull());
267
268 if (!Qt::mightBeRichText(text)) {
269 d->textLabel->setStyleSheet(d->textStyleSheet());
270 }
271
272 d->textLabel->setText(text);
273 d->textLabel->setAlignment(alignment);
274 show();
275 }
276
setLevel(int level)277 void KTitleWidget::setLevel(int level)
278 {
279 if (d->level == level) {
280 return;
281 }
282
283 d->level = level;
284
285 d->textLabel->setStyleSheet(d->textStyleSheet());
286 }
287
level()288 int KTitleWidget::level()
289 {
290 return d->level;
291 }
292
setText(const QString & text,MessageType type)293 void KTitleWidget::setText(const QString &text, MessageType type)
294 {
295 setIcon(type);
296 setText(text);
297 }
298
setComment(const QString & comment,MessageType type)299 void KTitleWidget::setComment(const QString &comment, MessageType type)
300 {
301 d->commentLabel->setVisible(!comment.isNull());
302
303 // TODO: should we override the current icon with the corresponding MessageType icon?
304 d->messageType = type;
305 d->commentLabel->setStyleSheet(d->commentStyleSheet());
306 d->commentLabel->setText(comment);
307 show();
308 }
309
setIcon(const QIcon & icon,KTitleWidget::ImageAlignment alignment)310 void KTitleWidget::setIcon(const QIcon &icon, KTitleWidget::ImageAlignment alignment)
311 {
312 d->icon = icon;
313
314 d->imageLabel->setVisible(!icon.isNull());
315
316 d->updateIconAlignment(alignment);
317
318 d->updatePixmap();
319 }
320
setIconSize(const QSize & iconSize)321 void KTitleWidget::setIconSize(const QSize &iconSize)
322 {
323 if (d->iconSize == iconSize) {
324 return;
325 }
326
327 const QSize oldEffectiveIconSize = this->iconSize();
328
329 d->iconSize = iconSize;
330
331 if (oldEffectiveIconSize != this->iconSize()) {
332 d->updatePixmap();
333 }
334 }
335
336 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 72)
setPixmap(const QPixmap & pixmap,ImageAlignment alignment)337 void KTitleWidget::setPixmap(const QPixmap &pixmap, ImageAlignment alignment)
338 {
339 d->icon = QIcon(pixmap);
340 d->iconSize = pixmap.size();
341
342 d->imageLabel->setVisible(!d->icon.isNull());
343
344 d->updateIconAlignment(alignment);
345
346 d->updatePixmap();
347 }
348 #endif
349
350 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 63)
setPixmap(const QString & icon,ImageAlignment alignment)351 void KTitleWidget::setPixmap(const QString &icon, ImageAlignment alignment)
352 {
353 setIcon(QIcon::fromTheme(icon), alignment);
354 }
355 #endif
356
357 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 63)
setPixmap(const QIcon & icon,ImageAlignment alignment)358 void KTitleWidget::setPixmap(const QIcon &icon, ImageAlignment alignment)
359 {
360 setIcon(icon, alignment);
361 }
362 #endif
363
364 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 72)
setPixmap(MessageType type,ImageAlignment alignment)365 void KTitleWidget::setPixmap(MessageType type, ImageAlignment alignment)
366 {
367 setIcon(type, alignment);
368 }
369 #endif
370
setIcon(MessageType type,ImageAlignment alignment)371 void KTitleWidget::setIcon(MessageType type, ImageAlignment alignment)
372 {
373 setIcon(QIcon::fromTheme(d->iconTypeToIconName(type)), alignment);
374 }
375
autoHideTimeout() const376 int KTitleWidget::autoHideTimeout() const
377 {
378 return d->autoHideTimeout;
379 }
380
setAutoHideTimeout(int msecs)381 void KTitleWidget::setAutoHideTimeout(int msecs)
382 {
383 d->autoHideTimeout = msecs;
384
385 if (msecs > 0) {
386 installEventFilter(this);
387 } else {
388 removeEventFilter(this);
389 }
390 }
391
showEvent(QShowEvent * event)392 void KTitleWidget::showEvent(QShowEvent *event)
393 {
394 Q_UNUSED(event)
395 if (d->autoHideTimeout > 0) {
396 QTimer::singleShot(d->autoHideTimeout, this, [this] {
397 d->timeoutFinished();
398 });
399 }
400 }
401
402 #include "moc_ktitlewidget.cpp"
403