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: |
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 () 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 *; |
114 | QLabel *imageLabel; |
115 | QLabel *textLabel; |
116 | QLabel *; |
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 | |
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 | |
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(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 | |
189 | KTitleWidget::~KTitleWidget() = default; |
190 | |
191 | bool 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 | |
205 | void KTitleWidget::setWidget(QWidget *widget) |
206 | { |
207 | d->headerLayout->addWidget(widget, row: 2, column: 0, rowSpan: 1, columnSpan: 2); |
208 | } |
209 | |
210 | QString KTitleWidget::text() const |
211 | { |
212 | return d->textLabel->text(); |
213 | } |
214 | |
215 | QString KTitleWidget::() const |
216 | { |
217 | return d->commentLabel->text(); |
218 | } |
219 | |
220 | QIcon KTitleWidget::icon() const |
221 | { |
222 | return d->icon; |
223 | } |
224 | |
225 | QSize 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 | |
234 | void KTitleWidget::setBuddy(QWidget *buddy) |
235 | { |
236 | d->textLabel->setBuddy(buddy); |
237 | } |
238 | |
239 | void 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 | |
254 | void 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 | |
267 | void 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 | |
278 | int KTitleWidget::level() |
279 | { |
280 | return d->level; |
281 | } |
282 | |
283 | void KTitleWidget::setText(const QString &text, MessageType type) |
284 | { |
285 | setIcon(type); |
286 | setText(text); |
287 | } |
288 | |
289 | void KTitleWidget::(const QString &, 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 | |
300 | void 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 | |
311 | void 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 | |
326 | void KTitleWidget::setIcon(MessageType type, ImageAlignment alignment) |
327 | { |
328 | setIcon(icon: QIcon::fromTheme(name: d->iconTypeToIconName(type)), alignment); |
329 | } |
330 | |
331 | int KTitleWidget::autoHideTimeout() const |
332 | { |
333 | return d->autoHideTimeout; |
334 | } |
335 | |
336 | void 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 | |
347 | void 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 | |