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