Warning, file /frameworks/kwidgetsaddons/src/kmessagedialog.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2020 Ahmad Samir <a.samirh78@gmail.com> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0006 */ 0007 0008 #include "kmessagedialog.h" 0009 #include "kmessagebox_p.h" 0010 0011 #include "loggingcategory.h" 0012 0013 #include <QApplication> 0014 #include <QCheckBox> 0015 #include <QDebug> 0016 #include <QDialogButtonBox> 0017 #include <QHBoxLayout> 0018 #include <QLabel> 0019 #include <QListWidget> 0020 #include <QPushButton> 0021 #include <QScreen> 0022 #include <QScrollArea> 0023 #include <QScrollBar> 0024 #include <QStyle> 0025 #include <QStyleOption> 0026 #include <QTextBrowser> 0027 #include <QVBoxLayout> 0028 #include <QWindow> 0029 0030 #include <KCollapsibleGroupBox> 0031 #include <KSqueezedTextLabel> 0032 0033 static const Qt::TextInteractionFlags s_textFlags = Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard; 0034 0035 // TODO KF6 remove QObject inheritance again 0036 class KMessageDialogPrivate : public QObject 0037 { 0038 Q_OBJECT 0039 0040 public: 0041 explicit KMessageDialogPrivate(KMessageDialog::Type type, KMessageDialog *qq) 0042 : m_type(type) 0043 , q(qq) 0044 { 0045 } 0046 0047 bool eventFilter(QObject *watched, QEvent *event) override 0048 { 0049 if (event->type() == QEvent::Show && watched == q) { 0050 Q_ASSERT(m_notifyEnabled); 0051 doNotify(); 0052 } 0053 return false; 0054 } 0055 0056 void doNotify() 0057 { 0058 #ifndef Q_OS_WIN // FIXME problems with KNotify on Windows 0059 QMessageBox::Icon notifyType = QMessageBox::NoIcon; 0060 switch (m_type) { 0061 case KMessageDialog::QuestionTwoActions: 0062 case KMessageDialog::QuestionTwoActionsCancel: 0063 notifyType = QMessageBox::Question; 0064 break; 0065 case KMessageDialog::WarningTwoActions: 0066 case KMessageDialog::WarningTwoActionsCancel: 0067 case KMessageDialog::WarningContinueCancel: 0068 case KMessageDialog::Information: 0069 notifyType = QMessageBox::Information; 0070 break; 0071 case KMessageDialog::Error: 0072 notifyType = QMessageBox::Critical; 0073 break; 0074 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 97) 0075 case KMessageDialog::Sorry: 0076 notifyType = QMessageBox::Warning; 0077 break; 0078 #endif 0079 } 0080 0081 // TODO include m_listWidget items 0082 KMessageBox::notifyInterface()->sendNotification(notifyType, m_messageLabel->text(), q->topLevelWidget()); 0083 #endif 0084 } 0085 0086 KMessageDialog::Type m_type; 0087 KMessageDialog *const q; 0088 0089 QVBoxLayout *m_topLayout = nullptr; 0090 QWidget *m_mainWidget = nullptr; 0091 QLabel *m_iconLabel = nullptr; 0092 QLabel *m_messageLabel = nullptr; 0093 QListWidget *m_listWidget = nullptr; 0094 QTextBrowser *m_detailsTextEdit = nullptr; 0095 KCollapsibleGroupBox *m_detailsGroup = nullptr; 0096 QCheckBox *m_dontAskAgainCB = nullptr; 0097 QDialogButtonBox *m_buttonBox = nullptr; 0098 QMetaObject::Connection m_buttonBoxConnection; 0099 bool m_notifyEnabled = true; 0100 }; 0101 0102 KMessageDialog::KMessageDialog(KMessageDialog::Type type, const QString &text, QWidget *parent) 0103 : QDialog(parent) 0104 , d(new KMessageDialogPrivate(type, this)) 0105 { 0106 // Dialog top-level layout 0107 d->m_topLayout = new QVBoxLayout(this); 0108 d->m_topLayout->setSizeConstraint(QLayout::SetFixedSize); 0109 0110 // Main widget 0111 d->m_mainWidget = new QWidget(this); 0112 d->m_topLayout->addWidget(d->m_mainWidget); 0113 0114 // Layout for the main widget 0115 auto *mainLayout = new QVBoxLayout(d->m_mainWidget); 0116 QStyle *widgetStyle = d->m_mainWidget->style(); 0117 // Provide extra spacing 0118 mainLayout->setSpacing(widgetStyle->pixelMetric(QStyle::PM_LayoutVerticalSpacing) * 2); 0119 mainLayout->setContentsMargins(0, 0, 0, 0); 0120 0121 auto *hLayout = new QHBoxLayout{}; 0122 mainLayout->addLayout(hLayout, 5); 0123 0124 // Icon 0125 auto *iconLayout = new QVBoxLayout{}; 0126 hLayout->addLayout(iconLayout, 0); 0127 0128 d->m_iconLabel = new QLabel(d->m_mainWidget); 0129 d->m_iconLabel->setVisible(false); 0130 iconLayout->addWidget(d->m_iconLabel); 0131 hLayout->addSpacing(widgetStyle->pixelMetric(QStyle::PM_LayoutHorizontalSpacing)); 0132 0133 const QRect desktop = screen()->geometry(); 0134 const auto desktopWidth = desktop.width(); 0135 // Main message text 0136 d->m_messageLabel = new QLabel(text, d->m_mainWidget); 0137 if (d->m_messageLabel->sizeHint().width() > (desktopWidth * 0.5)) { 0138 // Enable automatic wrapping of messages which are longer than 50% of screen width 0139 d->m_messageLabel->setWordWrap(true); 0140 // Use a squeezed label if text is still too wide 0141 const bool usingSqueezedLabel = d->m_messageLabel->sizeHint().width() > (desktopWidth * 0.85); 0142 if (usingSqueezedLabel) { 0143 delete d->m_messageLabel; 0144 d->m_messageLabel = new KSqueezedTextLabel(text, d->m_mainWidget); 0145 } 0146 } 0147 0148 d->m_messageLabel->setTextInteractionFlags(s_textFlags); 0149 0150 const bool usingScrollArea = (desktop.height() / 3) < d->m_messageLabel->sizeHint().height(); 0151 if (usingScrollArea) { 0152 QScrollArea *messageScrollArea = new QScrollArea(d->m_mainWidget); 0153 messageScrollArea->setWidget(d->m_messageLabel); 0154 messageScrollArea->setFrameShape(QFrame::NoFrame); 0155 messageScrollArea->setWidgetResizable(true); 0156 hLayout->addWidget(messageScrollArea, 5); 0157 } else { 0158 hLayout->addWidget(d->m_messageLabel, 5); 0159 } 0160 0161 // List widget, will be populated by setListWidgetItems() 0162 d->m_listWidget = new QListWidget(d->m_mainWidget); 0163 mainLayout->addWidget(d->m_listWidget, usingScrollArea ? 10 : 50); 0164 d->m_listWidget->setVisible(false); 0165 0166 // DontAskAgain checkbox, will be set up by setDontAskAgainText() 0167 d->m_dontAskAgainCB = new QCheckBox(d->m_mainWidget); 0168 mainLayout->addWidget(d->m_dontAskAgainCB); 0169 d->m_dontAskAgainCB->setVisible(false); 0170 0171 // Details widget, text will be added by setDetails() 0172 auto *detailsHLayout = new QHBoxLayout{}; 0173 d->m_topLayout->addLayout(detailsHLayout); 0174 0175 d->m_detailsGroup = new KCollapsibleGroupBox(); 0176 d->m_detailsGroup->setVisible(false); 0177 d->m_detailsGroup->setTitle(QApplication::translate("KMessageDialog", "Details")); 0178 QVBoxLayout *detailsLayout = new QVBoxLayout(d->m_detailsGroup); 0179 0180 d->m_detailsTextEdit = new QTextBrowser{}; 0181 d->m_detailsTextEdit->setMinimumHeight(d->m_detailsTextEdit->fontMetrics().lineSpacing() * 11); 0182 detailsLayout->addWidget(d->m_detailsTextEdit, 50); 0183 0184 detailsHLayout->addWidget(d->m_detailsGroup); 0185 0186 // Button box 0187 d->m_buttonBox = new QDialogButtonBox(this); 0188 d->m_topLayout->addWidget(d->m_buttonBox); 0189 0190 // Default buttons 0191 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 100) 0192 setButtons(); 0193 #else 0194 if ((d->m_type == KMessageDialog::Information) || (d->m_type != KMessageDialog::Error)) { 0195 // set Ok button 0196 setButtons(); 0197 } else if ((d->m_type == KMessageDialog::WarningContinueCancel)) { 0198 // set Continue & Cancel buttons 0199 setButtons(KStandardGuiItem::cont(), KGuiItem(), KStandardGuiItem::cancel()); 0200 } 0201 #endif 0202 0203 setNotifyEnabled(true); 0204 0205 // If the dialog is rejected, e.g. by pressing Esc, done() signal connected to the button box 0206 // won't be emitted 0207 connect(this, &QDialog::rejected, this, [this]() { 0208 done(KMessageDialog::Cancel); 0209 }); 0210 } 0211 0212 // This method has been copied from KWindowSystem to avoid depending on it 0213 static void setMainWindow(QDialog *dialog, WId mainWindowId) 0214 { 0215 #ifdef Q_OS_OSX 0216 if (!QWidget::find(mainWindowId)) { 0217 return; 0218 } 0219 #endif 0220 // Set the WA_NativeWindow attribute to force the creation of the QWindow. 0221 // Without this QWidget::windowHandle() returns 0. 0222 dialog->setAttribute(Qt::WA_NativeWindow, true); 0223 QWindow *subWindow = dialog->windowHandle(); 0224 Q_ASSERT(subWindow); 0225 0226 QWindow *mainWindow = QWindow::fromWinId(mainWindowId); 0227 if (!mainWindow) { 0228 // foreign windows not supported on all platforms 0229 return; 0230 } 0231 // mainWindow is not the child of any object, so make sure it gets deleted at some point 0232 QObject::connect(dialog, &QObject::destroyed, mainWindow, &QObject::deleteLater); 0233 subWindow->setTransientParent(mainWindow); 0234 } 0235 0236 KMessageDialog::KMessageDialog(KMessageDialog::Type type, const QString &text, WId parent_id) 0237 : KMessageDialog(type, text) 0238 { 0239 QWidget *parent = QWidget::find(parent_id); 0240 setParent(parent); 0241 if (!parent && parent_id) { 0242 setMainWindow(this, parent_id); 0243 } 0244 } 0245 0246 KMessageDialog::~KMessageDialog() 0247 { 0248 removeEventFilter(d.get()); 0249 } 0250 0251 void KMessageDialog::setCaption(const QString &caption) 0252 { 0253 if (!caption.isEmpty()) { 0254 setWindowTitle(caption); 0255 return; 0256 } 0257 0258 QString title; 0259 switch (d->m_type) { // Get a title based on the dialog Type 0260 case KMessageDialog::QuestionTwoActions: 0261 case KMessageDialog::QuestionTwoActionsCancel: 0262 title = QApplication::translate("KMessageDialog", "Question"); 0263 break; 0264 case KMessageDialog::WarningTwoActions: 0265 case KMessageDialog::WarningTwoActionsCancel: 0266 case KMessageDialog::WarningContinueCancel: 0267 title = QApplication::translate("KMessageDialog", "Warning"); 0268 break; 0269 case KMessageDialog::Information: 0270 title = QApplication::translate("KMessageDialog", "Information"); 0271 break; 0272 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 97) 0273 case KMessageDialog::Sorry: 0274 title = QApplication::translate("KMessageDialog", "Sorry"); 0275 break; 0276 #endif 0277 case KMessageDialog::Error: { 0278 title = QApplication::translate("KMessageDialog", "Error"); 0279 break; 0280 } 0281 default: 0282 break; 0283 } 0284 0285 setWindowTitle(title); 0286 } 0287 0288 void KMessageDialog::setIcon(const QIcon &icon) 0289 { 0290 QIcon effectiveIcon(icon); 0291 if (effectiveIcon.isNull()) { // Fallback to an icon based on the dialog Type 0292 QStyle *style = this->style(); 0293 switch (d->m_type) { 0294 case KMessageDialog::QuestionTwoActions: 0295 case KMessageDialog::QuestionTwoActionsCancel: 0296 effectiveIcon = style->standardIcon(QStyle::SP_MessageBoxQuestion, nullptr, this); 0297 break; 0298 case KMessageDialog::WarningTwoActions: 0299 case KMessageDialog::WarningTwoActionsCancel: 0300 case KMessageDialog::WarningContinueCancel: 0301 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 97) 0302 case KMessageDialog::Sorry: 0303 #endif 0304 effectiveIcon = style->standardIcon(QStyle::SP_MessageBoxWarning, nullptr, this); 0305 break; 0306 case KMessageDialog::Information: 0307 effectiveIcon = style->standardIcon(QStyle::SP_MessageBoxInformation, nullptr, this); 0308 break; 0309 case KMessageDialog::Error: 0310 effectiveIcon = style->standardIcon(QStyle::SP_MessageBoxCritical, nullptr, this); 0311 break; 0312 default: 0313 break; 0314 } 0315 } 0316 0317 if (effectiveIcon.isNull()) { 0318 qCWarning(KWidgetsAddonsLog) << "Neither the requested icon nor a generic one based on the " 0319 "dialog type could be found."; 0320 return; 0321 } 0322 0323 d->m_iconLabel->setVisible(true); 0324 0325 QStyleOption option; 0326 option.initFrom(d->m_mainWidget); 0327 QStyle *widgetStyle = d->m_mainWidget->style(); 0328 const int size = widgetStyle->pixelMetric(QStyle::PM_MessageBoxIconSize, &option, d->m_mainWidget); 0329 d->m_iconLabel->setPixmap(effectiveIcon.pixmap(size)); 0330 } 0331 0332 void KMessageDialog::setListWidgetItems(const QStringList &strlist) 0333 { 0334 const bool isEmpty = strlist.isEmpty(); 0335 d->m_listWidget->setVisible(!isEmpty); 0336 if (isEmpty) { 0337 return; 0338 } 0339 0340 // Enable automatic wrapping since the listwidget already has a good initial width 0341 d->m_messageLabel->setWordWrap(true); 0342 d->m_listWidget->addItems(strlist); 0343 0344 QStyleOptionViewItem styleOption; 0345 styleOption.initFrom(d->m_listWidget); 0346 QFontMetrics fm(styleOption.font); 0347 int listWidth = d->m_listWidget->width(); 0348 for (const QString &str : strlist) { 0349 listWidth = qMax(listWidth, fm.boundingRect(str).width()); 0350 } 0351 const int borderWidth = (d->m_listWidget->width() - d->m_listWidget->viewport()->width() // 0352 + d->m_listWidget->verticalScrollBar()->height()); 0353 listWidth += borderWidth; 0354 const auto deskWidthPortion = screen()->geometry().width() * 0.85; 0355 if (listWidth > deskWidthPortion) { // Limit the list widget size to 85% of screen width 0356 listWidth = qRound(deskWidthPortion); 0357 } 0358 d->m_listWidget->setMinimumWidth(listWidth); 0359 d->m_listWidget->setSelectionMode(QListWidget::NoSelection); 0360 d->m_messageLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); 0361 } 0362 0363 void KMessageDialog::setDetails(const QString &details) 0364 { 0365 d->m_detailsGroup->setVisible(!details.isEmpty()); 0366 d->m_detailsTextEdit->setText(details); 0367 } 0368 0369 void KMessageDialog::setButtons(const KGuiItem &primaryAction, const KGuiItem &secondaryAction, const KGuiItem &cancelAction) 0370 { 0371 switch (d->m_type) { 0372 case KMessageDialog::QuestionTwoActions: { 0373 d->m_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No); 0374 auto *buttonYes = d->m_buttonBox->button(QDialogButtonBox::Yes); 0375 KGuiItem::assign(buttonYes, primaryAction); 0376 buttonYes->setFocus(); 0377 KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::No), secondaryAction); 0378 break; 0379 } 0380 case KMessageDialog::QuestionTwoActionsCancel: { 0381 d->m_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); 0382 auto *buttonYes = d->m_buttonBox->button(QDialogButtonBox::Yes); 0383 KGuiItem::assign(buttonYes, primaryAction); 0384 buttonYes->setFocus(); 0385 KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::No), secondaryAction); 0386 KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::Cancel), cancelAction); 0387 break; 0388 } 0389 case KMessageDialog::WarningTwoActions: { 0390 d->m_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No); 0391 KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::Yes), primaryAction); 0392 0393 auto *noBtn = d->m_buttonBox->button(QDialogButtonBox::No); 0394 KGuiItem::assign(noBtn, secondaryAction); 0395 noBtn->setDefault(true); 0396 noBtn->setFocus(); 0397 break; 0398 } 0399 case KMessageDialog::WarningTwoActionsCancel: { 0400 d->m_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No | QDialogButtonBox::Cancel); 0401 KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::Yes), primaryAction); 0402 KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::No), secondaryAction); 0403 0404 auto *cancelButton = d->m_buttonBox->button(QDialogButtonBox::Cancel); 0405 KGuiItem::assign(cancelButton, cancelAction); 0406 cancelButton->setDefault(true); 0407 cancelButton->setFocus(); 0408 break; 0409 } 0410 case KMessageDialog::WarningContinueCancel: { 0411 d->m_buttonBox->setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::Cancel); 0412 0413 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 100) 0414 KGuiItem continueItem = primaryAction; 0415 if (continueItem.text() == KStandardGuiItem::yes().text()) { 0416 continueItem = KStandardGuiItem::cont(); 0417 } 0418 KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::Yes), continueItem); 0419 #else 0420 KGuiItem::assign(d->m_buttonBox->button(QDialogButtonBox::Yes), primaryAction); 0421 #endif 0422 0423 auto *cancelButton = d->m_buttonBox->button(QDialogButtonBox::Cancel); 0424 KGuiItem::assign(cancelButton, cancelAction); 0425 cancelButton->setDefault(true); 0426 cancelButton->setFocus(); 0427 break; 0428 } 0429 case KMessageDialog::Information: 0430 #if KWIDGETSADDONS_BUILD_DEPRECATED_SINCE(5, 97) 0431 case KMessageDialog::Sorry: 0432 #endif 0433 case KMessageDialog::Error: { 0434 d->m_buttonBox->setStandardButtons(QDialogButtonBox::Ok); 0435 auto *okButton = d->m_buttonBox->button(QDialogButtonBox::Ok); 0436 KGuiItem::assign(okButton, KStandardGuiItem::ok()); 0437 okButton->setFocus(); 0438 break; 0439 } 0440 default: 0441 break; 0442 } 0443 0444 // Button connections 0445 if (!d->m_buttonBoxConnection) { 0446 d->m_buttonBoxConnection = connect(d->m_buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton *button) { 0447 QDialogButtonBox::StandardButton code = d->m_buttonBox->standardButton(button); 0448 const int result = (code == QDialogButtonBox::Ok) ? KMessageDialog::Ok 0449 : (code == QDialogButtonBox::Cancel) ? KMessageDialog::Cancel 0450 : (code == QDialogButtonBox::Yes) ? KMessageDialog::PrimaryAction 0451 : (code == QDialogButtonBox::No) ? KMessageDialog::SecondaryAction 0452 : 0453 /* else */ -1; 0454 if (result != -1) { 0455 done(result); 0456 } 0457 }); 0458 } 0459 } 0460 0461 void KMessageDialog::setDontAskAgainText(const QString &dontAskAgainText) 0462 { 0463 d->m_dontAskAgainCB->setVisible(!dontAskAgainText.isEmpty()); 0464 d->m_dontAskAgainCB->setText(dontAskAgainText); 0465 } 0466 0467 void KMessageDialog::setDontAskAgainChecked(bool isChecked) 0468 { 0469 if (d->m_dontAskAgainCB->text().isEmpty()) { 0470 qCWarning(KWidgetsAddonsLog) << "setDontAskAgainChecked() method was called on a dialog that doesn't " 0471 "appear to have a checkbox; you need to use setDontAskAgainText() " 0472 "to add a checkbox to the dialog first."; 0473 return; 0474 } 0475 0476 d->m_dontAskAgainCB->setChecked(isChecked); 0477 } 0478 0479 bool KMessageDialog::isDontAskAgainChecked() const 0480 { 0481 if (d->m_dontAskAgainCB->text().isEmpty()) { 0482 qCWarning(KWidgetsAddonsLog) << "isDontAskAgainChecked() method was called on a dialog that doesn't " 0483 "appear to have a checkbox; you need to use setDontAskAgainText() " 0484 "to add a checkbox to the dialog first."; 0485 return false; 0486 } 0487 0488 return d->m_dontAskAgainCB->isChecked(); 0489 } 0490 0491 void KMessageDialog::setOpenExternalLinks(bool isAllowed) 0492 { 0493 d->m_messageLabel->setOpenExternalLinks(isAllowed); 0494 d->m_detailsTextEdit->setOpenExternalLinks(isAllowed); 0495 } 0496 0497 bool KMessageDialog::isNotifyEnabled() const 0498 { 0499 return d->m_notifyEnabled; 0500 } 0501 0502 void KMessageDialog::setNotifyEnabled(bool enable) 0503 { 0504 d->m_notifyEnabled = enable; 0505 if (enable) { 0506 installEventFilter(d.get()); 0507 } else { 0508 removeEventFilter(d.get()); 0509 } 0510 } 0511 0512 #include "kmessagedialog.moc" 0513 #include "moc_kmessagedialog.cpp"