KWidgetsAddons

kmessagedialog.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2020 Ahmad Samir <a.samirh78@gmail.com>
4
5 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include "kmessagedialog.h"
9#include "kmessagebox_p.h"
10
11#include "loggingcategory.h"
12
13#include <QApplication>
14#include <QCheckBox>
15#include <QDebug>
16#include <QDialogButtonBox>
17#include <QHBoxLayout>
18#include <QLabel>
19#include <QListWidget>
20#include <QPushButton>
21#include <QScreen>
22#include <QScrollArea>
23#include <QScrollBar>
24#include <QStyle>
25#include <QStyleOption>
26#include <QTextBrowser>
27#include <QVBoxLayout>
28#include <QWindow>
29
30#include <KCollapsibleGroupBox>
31#include <KSqueezedTextLabel>
32
34
35// TODO KF6 remove QObject inheritance again
36class KMessageDialogPrivate : public QObject
37{
39
40public:
41 explicit KMessageDialogPrivate(KMessageDialog::Type type, KMessageDialog *qq)
42 : m_type(type)
43 , q(qq)
44 {
45 }
46
48 KMessageDialog *const q;
49
50 QVBoxLayout *m_topLayout = nullptr;
51 QWidget *m_mainWidget = nullptr;
52 QLabel *m_iconLabel = nullptr;
53 QLabel *m_messageLabel = nullptr;
54 QListWidget *m_listWidget = nullptr;
55 QLabel *m_detailsLabel = nullptr;
56 QTextBrowser *m_detailsTextEdit = nullptr;
57 KCollapsibleGroupBox *m_detailsGroup = nullptr;
58 QCheckBox *m_dontAskAgainCB = nullptr;
59 QDialogButtonBox *m_buttonBox = nullptr;
60 QMetaObject::Connection m_buttonBoxConnection;
61 bool m_notifyEnabled = true;
62};
63
65 : QDialog(parent)
66 , d(new KMessageDialogPrivate(type, this))
67{
68 // Dialog top-level layout
69 d->m_topLayout = new QVBoxLayout(this);
70 d->m_topLayout->setSizeConstraint(QLayout::SetFixedSize);
71
72 // Main widget
73 d->m_mainWidget = new QWidget(this);
74 d->m_topLayout->addWidget(d->m_mainWidget);
75
76 // Layout for the main widget
77 auto *mainLayout = new QVBoxLayout(d->m_mainWidget);
78 QStyle *widgetStyle = d->m_mainWidget->style();
79 // Provide extra spacing
80 mainLayout->setSpacing(widgetStyle->pixelMetric(QStyle::PM_LayoutVerticalSpacing) * 2);
81 mainLayout->setContentsMargins(0, 0, 0, 0);
82
83 auto *hLayout = new QHBoxLayout{};
84 mainLayout->addLayout(hLayout, 5);
85
86 // Icon
87 auto *iconLayout = new QVBoxLayout{};
88 hLayout->addLayout(iconLayout, 0);
89
90 d->m_iconLabel = new QLabel(d->m_mainWidget);
91 d->m_iconLabel->setVisible(false);
92 iconLayout->addWidget(d->m_iconLabel);
93 hLayout->addSpacing(widgetStyle->pixelMetric(QStyle::PM_LayoutHorizontalSpacing));
94
95 const QRect desktop = screen()->geometry();
96 const auto desktopWidth = desktop.width();
97 // Main message text
98 d->m_messageLabel = new QLabel(text, d->m_mainWidget);
99 if (d->m_messageLabel->sizeHint().width() > (desktopWidth * 0.5)) {
100 // Enable automatic wrapping of messages which are longer than 50% of screen width
101 d->m_messageLabel->setWordWrap(true);
102 // Use a squeezed label if text is still too wide
103 const bool usingSqueezedLabel = d->m_messageLabel->sizeHint().width() > (desktopWidth * 0.85);
104 if (usingSqueezedLabel) {
105 delete d->m_messageLabel;
106 d->m_messageLabel = new KSqueezedTextLabel(text, d->m_mainWidget);
107 }
108 }
109
110 d->m_messageLabel->setTextInteractionFlags(s_textFlags);
111
112 const bool usingScrollArea = (desktop.height() / 3) < d->m_messageLabel->sizeHint().height();
113 if (usingScrollArea) {
114 QScrollArea *messageScrollArea = new QScrollArea(d->m_mainWidget);
115 messageScrollArea->setWidget(d->m_messageLabel);
116 messageScrollArea->setFrameShape(QFrame::NoFrame);
117 messageScrollArea->setWidgetResizable(true);
118 hLayout->addWidget(messageScrollArea, 5);
119 } else {
120 hLayout->addWidget(d->m_messageLabel, 5);
121 }
122
123 // List widget, will be populated by setListWidgetItems()
124 d->m_listWidget = new QListWidget(d->m_mainWidget);
125 mainLayout->addWidget(d->m_listWidget, usingScrollArea ? 10 : 50);
126 d->m_listWidget->setVisible(false);
127
128 // DontAskAgain checkbox, will be set up by setDontAskAgainText()
129 d->m_dontAskAgainCB = new QCheckBox(d->m_mainWidget);
130 mainLayout->addWidget(d->m_dontAskAgainCB);
131 d->m_dontAskAgainCB->setVisible(false);
132
133 // Details widget, text will be added by setDetails()
134 auto *detailsHLayout = new QHBoxLayout{};
135 d->m_topLayout->addLayout(detailsHLayout);
136
137 d->m_detailsGroup = new KCollapsibleGroupBox();
138 d->m_detailsGroup->setVisible(false);
139 d->m_detailsGroup->setTitle(QApplication::translate("KMessageDialog", "Details"));
140 QVBoxLayout *detailsLayout = new QVBoxLayout(d->m_detailsGroup);
141
142 d->m_detailsLabel = new QLabel();
143 d->m_detailsLabel->setTextInteractionFlags(s_textFlags);
144 d->m_detailsLabel->setWordWrap(true);
145 detailsLayout->addWidget(d->m_detailsLabel);
146
147 d->m_detailsTextEdit = new QTextBrowser{};
148 d->m_detailsTextEdit->setMinimumHeight(d->m_detailsTextEdit->fontMetrics().lineSpacing() * 11);
149 detailsLayout->addWidget(d->m_detailsTextEdit, 50);
150
151 detailsHLayout->addWidget(d->m_detailsGroup);
152
153 // Button box
154 d->m_buttonBox = new QDialogButtonBox(this);
155 d->m_topLayout->addWidget(d->m_buttonBox);
156
157 // Default buttons
158 if ((d->m_type == KMessageDialog::Information) || (d->m_type != KMessageDialog::Error)) {
159 // set Ok button
160 setButtons();
161 } else if ((d->m_type == KMessageDialog::WarningContinueCancel)) {
162 // set Continue & Cancel buttons
164 }
165
166 setNotifyEnabled(true);
167
168 // If the dialog is rejected, e.g. by pressing Esc, done() signal connected to the button box
169 // won't be emitted
170 connect(this, &QDialog::rejected, this, [this]() {
172 });
173}
174
175// This method has been copied from KWindowSystem to avoid depending on it
176static void setMainWindow(QDialog *dialog, WId mainWindowId)
177{
178#ifdef Q_OS_OSX
179 if (!QWidget::find(mainWindowId)) {
180 return;
181 }
182#endif
183 // Set the WA_NativeWindow attribute to force the creation of the QWindow.
184 // Without this QWidget::windowHandle() returns 0.
185 dialog->setAttribute(Qt::WA_NativeWindow, true);
186 QWindow *subWindow = dialog->windowHandle();
187 Q_ASSERT(subWindow);
188
189 QWindow *mainWindow = QWindow::fromWinId(mainWindowId);
190 if (!mainWindow) {
191 // foreign windows not supported on all platforms
192 return;
193 }
194 // mainWindow is not the child of any object, so make sure it gets deleted at some point
196 subWindow->setTransientParent(mainWindow);
197}
198
200 : KMessageDialog(type, text)
201{
202 QWidget *parent = QWidget::find(parent_id);
204 if (!parent && parent_id) {
205 setMainWindow(this, parent_id);
206 }
207}
208
213
215{
216 if (!caption.isEmpty()) {
217 setWindowTitle(caption);
218 return;
219 }
220
221 QString title;
222 switch (d->m_type) { // Get a title based on the dialog Type
225 title = QApplication::translate("KMessageDialog", "Question");
226 break;
230 title = QApplication::translate("KMessageDialog", "Warning");
231 break;
233 title = QApplication::translate("KMessageDialog", "Information");
234 break;
236 title = QApplication::translate("KMessageDialog", "Error");
237 break;
238 }
239 default:
240 break;
241 }
242
243 setWindowTitle(title);
244}
245
247{
248 QIcon effectiveIcon(icon);
249 if (effectiveIcon.isNull()) { // Fallback to an icon based on the dialog Type
250 QStyle *style = this->style();
251 switch (d->m_type) {
254 effectiveIcon = style->standardIcon(QStyle::SP_MessageBoxQuestion, nullptr, this);
255 break;
259 effectiveIcon = style->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, this);
260 break;
262 effectiveIcon = style->standardIcon(QStyle::SP_MessageBoxInformation, nullptr, this);
263 break;
265 effectiveIcon = style->standardIcon(QStyle::SP_MessageBoxCritical, nullptr, this);
266 break;
267 default:
268 break;
269 }
270 }
271
272 if (effectiveIcon.isNull()) {
273 qCWarning(KWidgetsAddonsLog) << "Neither the requested icon nor a generic one based on the "
274 "dialog type could be found.";
275 return;
276 }
277
278 d->m_iconLabel->setVisible(true);
279
280 QStyleOption option;
281 option.initFrom(d->m_mainWidget);
282 QStyle *widgetStyle = d->m_mainWidget->style();
283 const int size = widgetStyle->pixelMetric(QStyle::PM_MessageBoxIconSize, &option, d->m_mainWidget);
284 d->m_iconLabel->setPixmap(effectiveIcon.pixmap(size));
285}
286
288{
289 const bool isEmpty = strlist.isEmpty();
290 d->m_listWidget->setVisible(!isEmpty);
291 if (isEmpty) {
292 return;
293 }
294
295 // Enable automatic wrapping since the listwidget already has a good initial width
296 d->m_messageLabel->setWordWrap(true);
297 d->m_listWidget->addItems(strlist);
298
299 QStyleOptionViewItem styleOption;
300 styleOption.initFrom(d->m_listWidget);
301 QFontMetrics fm(styleOption.font);
302 int listWidth = d->m_listWidget->width();
303 for (const QString &str : strlist) {
304 listWidth = qMax(listWidth, fm.boundingRect(str).width());
305 }
306 const int borderWidth = (d->m_listWidget->width() - d->m_listWidget->viewport()->width() //
307 + d->m_listWidget->verticalScrollBar()->height());
308 listWidth += borderWidth;
309 const auto deskWidthPortion = screen()->geometry().width() * 0.85;
310 if (listWidth > deskWidthPortion) { // Limit the list widget size to 85% of screen width
311 listWidth = qRound(deskWidthPortion);
312 }
313 d->m_listWidget->setMinimumWidth(listWidth);
314 d->m_listWidget->setSelectionMode(QListWidget::NoSelection);
315 d->m_messageLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
316}
317
319{
320 d->m_detailsGroup->setVisible(!details.isEmpty());
321
322 if (details.length() < 512) { // random number KMessageBox uses.
323 d->m_detailsLabel->setText(details);
324 d->m_detailsLabel->show();
325
326 d->m_detailsTextEdit->setText(QString());
327 d->m_detailsTextEdit->hide();
328 } else {
329 d->m_detailsLabel->setText(QString());
330 d->m_detailsLabel->hide();
331
332 d->m_detailsTextEdit->setText(details);
333 d->m_detailsTextEdit->show();
334 }
335}
336
337void KMessageDialog::setButtons(const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const KGuiItem &cancelAction)
338{
339 switch (d->m_type) {
341 d->m_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No);
342 auto *buttonYes = d->m_buttonBox->button(QDialogButtonBox::Yes);
343 KGuiItem::assign(buttonYes, primaryAction);
344 buttonYes->setFocus();
345 KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::No), secondaryAction);
346 break;
347 }
349 d->m_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel);
350 auto *buttonYes = d->m_buttonBox->button(QDialogButtonBox::Yes);
351 KGuiItem::assign(buttonYes, primaryAction);
352 buttonYes->setFocus();
353 KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::No), secondaryAction);
354 KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::Cancel), cancelAction);
355 break;
356 }
358 d->m_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No);
359 KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::Yes), primaryAction);
360
361 auto *noBtn = d->m_buttonBox->button(QDialogButtonBox::No);
362 KGuiItem::assign(noBtn, secondaryAction);
363 noBtn->setDefault(true);
364 noBtn->setFocus();
365 break;
366 }
368 d->m_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel);
369 KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::Yes), primaryAction);
370 KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::No), secondaryAction);
371
372 auto *cancelButton = d->m_buttonBox->button(QDialogButtonBox::Cancel);
373 KGuiItem::assign(cancelButton, cancelAction);
374 cancelButton->setDefault(true);
375 cancelButton->setFocus();
376 break;
377 }
379 d->m_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::Cancel);
380
381 KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::Yes), primaryAction);
382
383 auto *cancelButton = d->m_buttonBox->button(QDialogButtonBox::Cancel);
384 KGuiItem::assign(cancelButton, cancelAction);
385 cancelButton->setDefault(true);
386 cancelButton->setFocus();
387 break;
388 }
391 d->m_buttonBox->setStandardButtons(QDialogButtonBox::Ok);
392 auto *okButton = d->m_buttonBox->button(QDialogButtonBox::Ok);
394 okButton->setFocus();
395 break;
396 }
397 default:
398 break;
399 }
400
401 // Button connections
402 if (!d->m_buttonBoxConnection) {
403 d->m_buttonBoxConnection = connect(d->m_buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton *button) {
404 QDialogButtonBox::StandardButton code = d->m_buttonBox->standardButton(button);
405 const int result = (code == QDialogButtonBox::Ok) ? KMessageDialog::Ok
406 : (code == QDialogButtonBox::Cancel) ? KMessageDialog::Cancel
407 : (code == QDialogButtonBox::Yes) ? KMessageDialog::PrimaryAction
408 : (code == QDialogButtonBox::No) ? KMessageDialog::SecondaryAction
409 :
410 /* else */ -1;
411 if (result != -1) {
412 done(result);
413 }
414 });
415 }
416}
417
418void KMessageDialog::setDontAskAgainText(const QString &dontAskAgainText)
419{
420 d->m_dontAskAgainCB->setVisible(!dontAskAgainText.isEmpty());
421 d->m_dontAskAgainCB->setText(dontAskAgainText);
422}
423
425{
426 if (d->m_dontAskAgainCB->text().isEmpty()) {
427 qCWarning(KWidgetsAddonsLog) << "setDontAskAgainChecked() method was called on a dialog that doesn't "
428 "appear to have a checkbox; you need to use setDontAskAgainText() "
429 "to add a checkbox to the dialog first.";
430 return;
431 }
432
433 d->m_dontAskAgainCB->setChecked(isChecked);
434}
435
437{
438 if (d->m_dontAskAgainCB->text().isEmpty()) {
439 qCWarning(KWidgetsAddonsLog) << "isDontAskAgainChecked() method was called on a dialog that doesn't "
440 "appear to have a checkbox; you need to use setDontAskAgainText() "
441 "to add a checkbox to the dialog first.";
442 return false;
443 }
444
445 return d->m_dontAskAgainCB->isChecked();
446}
447
449{
450 d->m_messageLabel->setOpenExternalLinks(isAllowed);
451 d->m_detailsLabel->setOpenExternalLinks(isAllowed);
452 d->m_detailsTextEdit->setOpenExternalLinks(isAllowed);
453}
454
456{
457 return d->m_notifyEnabled;
458}
459
461{
462 d->m_notifyEnabled = enable;
463}
464
465void KMessageDialog::showEvent(QShowEvent *event)
466{
467 if (d->m_notifyEnabled) {
468 // TODO include m_listWidget items
469 beep(d->m_type, d->m_messageLabel->text(), topLevelWidget());
470 }
472}
473
474void KMessageDialog::beep(Type type, const QString &text, QWidget *widget)
475{
476#ifndef Q_OS_WIN // FIXME problems with KNotify on Windows
478 switch (type) {
481 notifyType = QMessageBox::Question;
482 break;
486 notifyType = QMessageBox::Warning;
487 break;
489 notifyType = QMessageBox::Information;
490 break;
492 notifyType = QMessageBox::Critical;
493 break;
494 }
495
496 KMessageBox::notifyInterface()->sendNotification(notifyType, text, widget);
497#endif
498}
499
500#include "kmessagedialog.moc"
501#include "moc_kmessagedialog.cpp"
A groupbox featuring a clickable header and arrow indicator that can be expanded and collapsed to rev...
An abstract class for setting the text, icon, tooltip and WhatsThis data on a GUI item (e....
Definition kguiitem.h:34
static void assign(QPushButton *button, const KGuiItem &item)
A static method that can be used to set the text, icon, tooltip and WhatThis properties from item on ...
Definition kguiitem.cpp:172
KMessageDialog creates a message box similar to the ones you get from KMessageBox,...
bool isDontAskAgainChecked() const
This can be used to query the status of the "Do not ask again" checkbox; returns true if the box is c...
void setListWidgetItems(const QStringList &strlist)
This will add a QListWidget to the dialog and populate it with strlist.
static void beep(KMessageDialog::Type type, const QString &text=QString(), QWidget *dialog=nullptr)
Manually play the notification sound.
void setNotifyEnabled(bool enable)
Whether to emit a KNotification when the dialog is shown.
void setIcon(const QIcon &icon)
This can be used to set an icon that will be shown next to the main text message.
void setButtons(const KGuiItem &primaryAction=KGuiItem(), const KGuiItem &secondaryAction=KGuiItem(), const KGuiItem &cancelAction=KGuiItem())
Sets the buttons in the buttom box.
void setDontAskAgainChecked(bool isChecked)
This can be used to set the initial status of the "Do not ask again" checkbox, checked or unchecked,...
void setDontAskAgainText(const QString &dontAskAgainText)
This will add a "Do not ask again" checkbox to the dialog with the text from dontAskAgainText.
void setOpenExternalLinks(bool isAllowed)
Sets the text labels in the dialog to either allow opening external links or not.
@ QuestionTwoActions
Question dialog with two buttons;.
@ WarningTwoActions
Warning dialog with two buttons;.
@ WarningTwoActionsCancel
Warning dialog with two buttons and Cancel;.
@ Error
Error dialog.
@ QuestionTwoActionsCancel
Question dialog with two buttons and Cancel;.
@ WarningContinueCancel
Warning dialog with Continue and Cancel.
@ Information
Information dialog.
@ Cancel
Cancel button.
void setCaption(const QString &caption)
This can be used to set the title of the dialog window.
void setDetails(const QString &details)
This will add a KCollapsibleGroupBox with a title "Details", as the class name implies it is collapsi...
~KMessageDialog() override
Destructor.
bool isNotifyEnabled() const
Whether a KNotification is emitted when the dialog is shown.
KMessageDialog(KMessageDialog::Type type, const QString &text, QWidget *parent=nullptr)
Constructs a KMessageDialog.
A replacement for QLabel that squeezes its text into the label.
Type type(const QSqlDatabase &db)
KGuiItem cont()
Returns the 'Continue' gui item.
KGuiItem cancel()
Returns the 'Cancel' gui item.
KGuiItem ok()
Returns the 'Ok' gui item.
void addLayout(QLayout *layout, int stretch)
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
QString translate(const char *context, const char *sourceText, const char *disambiguation, int n)
virtual void done(int r)
void rejected()
virtual void showEvent(QShowEvent *event) override
void clicked(QAbstractButton *button)
QRect boundingRect(QChar ch) const const
void setFrameShape(Shape)
QPixmap pixmap(QWindow *window, const QSize &size, Mode mode, State state) const const
bool isNull() const const
bool isEmpty() const const
Q_OBJECTQ_OBJECT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
void destroyed(QObject *obj)
QObject * parent() const const
void removeEventFilter(QObject *obj)
int height() const const
int width() const const
void setWidget(QWidget *widget)
void setWidgetResizable(bool resizable)
bool isEmpty() const const
qsizetype length() const const
PM_LayoutVerticalSpacing
SP_MessageBoxQuestion
virtual int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const const=0
virtual QIcon standardIcon(StandardPixmap standardIcon, const QStyleOption *option, const QWidget *widget) const const=0
void initFrom(const QWidget *widget)
typedef TextInteractionFlags
WA_NativeWindow
QWidget * topLevelWidget() const const
QWidget(QWidget *parent, Qt::WindowFlags f)
virtual bool event(QEvent *event) override
QWidget * find(WId id)
QScreen * screen() const const
void setAttribute(Qt::WidgetAttribute attribute, bool on)
void setParent(QWidget *parent)
QStyle * style() const const
QWindow * windowHandle() const const
void setWindowTitle(const QString &)
QWindow * fromWinId(WId id)
void setTransientParent(QWindow *parent)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 19 2024 11:51:03 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.