VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/extensions/QIMessageBox.cpp

Last change on this file was 104586, checked in by vboxsync, 4 months ago

FE/Qt: bugref:10450: Get rid of pre-5.14 code related to QString/QStringList splitting.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.7 KB
Line 
1/* $Id: QIMessageBox.cpp 104586 2024-05-13 12:12:44Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - Qt extensions: QIMessageBox class implementation.
4 */
5
6/*
7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/* Qt includes: */
29#include <QApplication>
30#include <QCheckBox>
31#include <QClipboard>
32#include <QHBoxLayout>
33#include <QLabel>
34#include <QMimeData>
35#include <QPushButton>
36#include <QRegularExpression>
37#include <QStyle>
38#include <QVBoxLayout>
39
40/* GUI includes: */
41#include "QIArrowSplitter.h"
42#include "QIDialogButtonBox.h"
43#include "QIMessageBox.h"
44#include "QIRichTextLabel.h"
45#include "UICommon.h"
46#include "UIIconPool.h"
47#include "UIHelpBrowserDialog.h"
48#include "UIMessageCenter.h"
49
50/* Other VBox includes: */
51#include <iprt/assert.h>
52
53
54QIMessageBox::QIMessageBox(const QString &strTitle, const QString &strMessage, AlertIconType iconType,
55 int iButton1 /* = 0*/, int iButton2 /* = 0*/, int iButton3 /* = 0*/, QWidget *pParent /* = 0*/,
56 const QString &strHelpKeyword /* = QString() */)
57 : QIDialog(pParent)
58 , m_strTitle(strTitle)
59 , m_iconType(iconType)
60 , m_pLabelIcon(0)
61 , m_strMessage(strMessage)
62 , m_pLabelText(0)
63 , m_pFlagCheckBox(0)
64 , m_pDetailsContainer(0)
65 , m_iButton1(iButton1)
66 , m_iButton2(iButton2)
67 , m_iButton3(iButton3)
68 , m_iButtonEsc(0)
69 , m_pButton1(0)
70 , m_pButton2(0)
71 , m_pButton3(0)
72 , m_pButtonHelp(0)
73 , m_pButtonBox(0)
74 , m_strHelpKeyword(strHelpKeyword)
75 , m_fDone(false)
76{
77 /* Prepare: */
78 prepare();
79}
80
81void QIMessageBox::setDetailsText(const QString &strText)
82{
83 /* Make sure details-text is NOT empty: */
84 AssertReturnVoid(!strText.isEmpty());
85
86 /* Split details into paragraphs: */
87 QStringList paragraphs(strText.split("<!--EOP-->", Qt::SkipEmptyParts));
88 /* Make sure details-text has at least one paragraph: */
89 AssertReturnVoid(!paragraphs.isEmpty());
90
91 /* Enumerate all the paragraphs: */
92 QStringPairList details;
93 foreach (const QString &strParagraph, paragraphs)
94 {
95 /* Split each paragraph into pairs: */
96 QStringList parts(strParagraph.split("<!--EOM-->", Qt::KeepEmptyParts));
97 /* Make sure each paragraph consist of 2 parts: */
98 AssertReturnVoid(parts.size() == 2);
99 /* Append each pair into details-list: */
100 details << QStringPair(parts[0], parts[1]);
101 }
102
103 /* Pass details-list to details-container: */
104 m_pDetailsContainer->setDetails(details);
105 /* Update details-container finally: */
106 updateDetailsContainer();
107}
108
109bool QIMessageBox::flagChecked() const
110{
111 return m_pFlagCheckBox->isChecked();
112}
113
114void QIMessageBox::setFlagChecked(bool fChecked)
115{
116 m_pFlagCheckBox->setChecked(fChecked);
117}
118
119void QIMessageBox::setFlagText(const QString &strFlagText)
120{
121 /* Pass text to flag check-box: */
122 m_pFlagCheckBox->setText(strFlagText);
123 /* Update flag check-box finally: */
124 updateCheckBox();
125}
126
127void QIMessageBox::setButtonText(int iButton, const QString &strText)
128{
129 switch (iButton)
130 {
131 case 0: if (m_pButton1) m_pButton1->setText(strText); break;
132 case 1: if (m_pButton2) m_pButton2->setText(strText); break;
133 case 2: if (m_pButton3) m_pButton3->setText(strText); break;
134 default: break;
135 }
136}
137
138void QIMessageBox::polishEvent(QShowEvent *pPolishEvent)
139{
140 /* Call to base-class: */
141 QIDialog::polishEvent(pPolishEvent);
142
143 /* Update size finally: */
144 sltUpdateSize();
145}
146
147void QIMessageBox::closeEvent(QCloseEvent *pCloseEvent)
148{
149 if (m_fDone)
150 pCloseEvent->accept();
151 else
152 {
153 pCloseEvent->ignore();
154 reject();
155 }
156}
157
158void QIMessageBox::sltUpdateSize()
159{
160 /* Fix minimum possible size: */
161 setFixedSize(minimumSizeHint());
162}
163
164void QIMessageBox::sltCopy() const
165{
166 /* Create the error string with all errors. First the html version. */
167 QString strError = "<html><body><p>" + m_strMessage + "</p>";
168 foreach (const QStringPair &pair, m_pDetailsContainer->details())
169 strError += pair.first + pair.second + "<br>";
170 strError += "</body></html>";
171 strError.remove(QRegularExpression("</+qt>"));
172 strError = strError.replace(QRegularExpression("&nbsp;"), " ");
173 /* Create a new mime data object holding both the html and the plain text version. */
174 QMimeData *pMimeData = new QMimeData();
175 pMimeData->setHtml(strError);
176 /* Replace all the html entities. */
177 strError = strError.replace(QRegularExpression("<br>|</tr>"), "\n");
178 strError = strError.replace(QRegularExpression("</p>"), "\n\n");
179 strError = strError.remove(QRegularExpression("<[^>]*>"));
180 pMimeData->setText(strError);
181 /* Add the mime data to the global clipboard. */
182 QClipboard *pClipboard = QApplication::clipboard();
183 pClipboard->setMimeData(pMimeData);
184}
185
186void QIMessageBox::reject()
187{
188 if (m_iButtonEsc)
189 {
190 QDialog::reject();
191 setResult(m_iButtonEsc & AlertButtonMask);
192 }
193}
194
195void QIMessageBox::prepare()
196{
197 /* Set caption: */
198 setWindowTitle(m_strTitle);
199
200 /* Create main-layout: */
201 QVBoxLayout *pMainLayout = new QVBoxLayout(this);
202 AssertPtrReturnVoid(pMainLayout);
203 {
204 /* Configure main-layout: */
205#ifdef VBOX_WS_MAC
206 pMainLayout->setContentsMargins(40, 20, 40, 20);
207 pMainLayout->setSpacing(15);
208#else
209 pMainLayout->setSpacing(qApp->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing) * 2);
210#endif
211 /* Create top-layout: */
212 QHBoxLayout *pTopLayout = new QHBoxLayout;
213 AssertPtrReturnVoid(pTopLayout);
214 {
215 /* Configure top-layout: */
216 pTopLayout->setContentsMargins(0, 0, 0, 0);
217 /* Create icon-label: */
218 m_pLabelIcon = new QLabel;
219 AssertPtrReturnVoid(m_pLabelIcon);
220 {
221 /* Configure icon-label: */
222 m_pLabelIcon->setPixmap(standardPixmap(m_iconType, this));
223 m_pLabelIcon->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
224 m_pLabelIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
225 /* Add icon-label into top-layout: */
226 pTopLayout->addWidget(m_pLabelIcon);
227 }
228 /* Create text-label: */
229 m_pLabelText = new QIRichTextLabel;
230 AssertPtrReturnVoid(m_pLabelText);
231 {
232 /* Configure text-label: */
233 m_pLabelText->setText(compressLongWords(m_strMessage));
234 /* Add text-label into top-layout: */
235 pTopLayout->addWidget(m_pLabelText);
236 }
237 /* Add top-layout into main-layout: */
238 pMainLayout->addLayout(pTopLayout);
239 }
240 /* Create details-container: */
241 m_pDetailsContainer = new QIArrowSplitter;
242 AssertPtrReturnVoid(m_pDetailsContainer);
243 {
244 /* Configure container: */
245 connect(m_pDetailsContainer, &QIArrowSplitter::sigSizeHintChange,
246 this, &QIMessageBox::sltUpdateSize);
247 /* Add details-container into main-layout: */
248 pMainLayout->addWidget(m_pDetailsContainer);
249 /* Update details-container finally: */
250 updateDetailsContainer();
251 }
252 /* Create flag check-box: */
253 m_pFlagCheckBox = new QCheckBox;
254 AssertPtrReturnVoid(m_pFlagCheckBox);
255 {
256 /* Configure flag check-box: */
257 m_pFlagCheckBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
258 /* Add flag check-box into main-layout: */
259 pMainLayout->addWidget(m_pFlagCheckBox, 0, Qt::AlignHCenter | Qt::AlignVCenter);
260 /* Update flag check-box finally: */
261 updateCheckBox();
262 }
263 /* Create button-box: */
264 m_pButtonBox = new QIDialogButtonBox;
265 AssertPtrReturnVoid(m_pButtonBox);
266 {
267 /* Configure button-box: */
268 m_pButtonBox->setCenterButtons(true);
269 m_pButton1 = createButton(m_iButton1);
270 if (m_pButton1)
271 connect(m_pButton1, &QPushButton::clicked, this, &QIMessageBox::sltDone1);
272 m_pButton2 = createButton(m_iButton2);
273 if (m_pButton2)
274 connect(m_pButton2, &QPushButton::clicked, this, &QIMessageBox::sltDone2);
275 m_pButton3 = createButton(m_iButton3);
276 if (m_pButton3)
277 connect(m_pButton3, &QPushButton::clicked, this, &QIMessageBox::sltDone3);
278 /* Create the help button and connect it to relevant slot in case a help word is supplied: */
279 if (!m_strHelpKeyword.isEmpty())
280 {
281 m_pButtonHelp = createButton(AlertButton_Help);
282 if (m_pButtonHelp)
283 {
284 uiCommon().setHelpKeyword(m_pButtonHelp, m_strHelpKeyword);
285 connect(m_pButtonHelp, &QPushButton::clicked, m_pButtonBox, &QIDialogButtonBox::sltHandleHelpRequest);
286 }
287 }
288
289 /* Make sure Escape button always set: */
290 Assert(m_iButtonEsc);
291 /* If this is a critical message add a "Copy to clipboard" button: */
292 if (m_iconType == AlertIconType_Critical)
293 {
294 QPushButton *pCopyButton = createButton(AlertButton_Copy);
295 pCopyButton->setToolTip(tr("Copy all errors to the clipboard"));
296 connect(pCopyButton, &QPushButton::clicked, this, &QIMessageBox::sltCopy);
297 }
298 /* Add button-box into main-layout: */
299 pMainLayout->addWidget(m_pButtonBox);
300
301 /* Prepare focus. It is important to prepare focus after adding button-box to the layout as
302 * parenting the button-box to the QDialog changes default button focus by Qt: */
303 prepareFocus();
304 }
305 }
306}
307
308void QIMessageBox::prepareFocus()
309{
310 /* Configure default button and focus: */
311 if (m_pButton1 && (m_iButton1 & AlertButtonOption_Default))
312 {
313 m_pButton1->setDefault(true);
314 m_pButton1->setFocus();
315 }
316 if (m_pButton2 && (m_iButton2 & AlertButtonOption_Default))
317 {
318 m_pButton2->setDefault(true);
319 m_pButton2->setFocus();
320 }
321 if (m_pButton3 && (m_iButton3 & AlertButtonOption_Default))
322 {
323 m_pButton3->setDefault(true);
324 m_pButton3->setFocus();
325 }
326}
327
328QPushButton *QIMessageBox::createButton(int iButton)
329{
330 /* Not for AlertButton_NoButton: */
331 if (iButton == 0)
332 return 0;
333
334 /* Prepare button text & role: */
335 QString strText;
336 QDialogButtonBox::ButtonRole role;
337 switch (iButton & AlertButtonMask)
338 {
339 case AlertButton_Ok: strText = tr("OK"); role = QDialogButtonBox::AcceptRole; break;
340 case AlertButton_Cancel: strText = tr("Cancel"); role = QDialogButtonBox::RejectRole; break;
341 case AlertButton_Choice1: strText = tr("Yes"); role = QDialogButtonBox::YesRole; break;
342 case AlertButton_Choice2: strText = tr("No"); role = QDialogButtonBox::NoRole; break;
343 case AlertButton_Copy: strText = tr("Copy"); role = QDialogButtonBox::ActionRole; break;
344 case AlertButton_Help: strText = tr("Help"); role = QDialogButtonBox::HelpRole; break;
345 default:
346 AssertMsgFailed(("Type %d is not supported!", iButton));
347 return 0;
348 }
349
350 /* Create push-button: */
351 QPushButton *pButton = m_pButtonBox->addButton(strText, role);
352
353 /* Configure <escape> button: */
354 if (iButton & AlertButtonOption_Escape)
355 m_iButtonEsc = iButton & AlertButtonMask;
356
357 /* Return button: */
358 return pButton;
359}
360
361void QIMessageBox::updateDetailsContainer()
362{
363 /* Details-container with details is always visible: */
364 m_pDetailsContainer->setVisible(!m_pDetailsContainer->details().isEmpty());
365 /* Update size: */
366 sltUpdateSize();
367}
368
369void QIMessageBox::updateCheckBox()
370{
371 /* Flag check-box with text is always visible: */
372 m_pFlagCheckBox->setVisible(!m_pFlagCheckBox->text().isEmpty());
373 /* Update size: */
374 sltUpdateSize();
375}
376
377/* static */
378QPixmap QIMessageBox::standardPixmap(AlertIconType iconType, QWidget *pWidget /* = 0*/)
379{
380 /* Prepare standard icon: */
381 QIcon icon;
382 switch (iconType)
383 {
384 case AlertIconType_Information: icon = UIIconPool::defaultIcon(UIIconPool::UIDefaultIconType_MessageBoxInformation, pWidget); break;
385 case AlertIconType_Warning: icon = UIIconPool::defaultIcon(UIIconPool::UIDefaultIconType_MessageBoxWarning, pWidget); break;
386 case AlertIconType_Critical: icon = UIIconPool::defaultIcon(UIIconPool::UIDefaultIconType_MessageBoxCritical, pWidget); break;
387 case AlertIconType_Question: icon = UIIconPool::defaultIcon(UIIconPool::UIDefaultIconType_MessageBoxQuestion, pWidget); break;
388 case AlertIconType_GuruMeditation: icon = UIIconPool::iconSet(":/meditation_32px.png"); break;
389 default: break;
390 }
391 /* Return empty pixmap if nothing found: */
392 if (icon.isNull())
393 return QPixmap();
394 /* Return pixmap of standard size if possible: */
395 QStyle *pStyle = pWidget ? pWidget->style() : QApplication::style();
396 int iSize = pStyle->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, pWidget);
397 return icon.pixmap(iSize, iSize);
398}
399
400/* static */
401QString QIMessageBox::compressLongWords(QString strText)
402{
403 // WORKAROUND:
404 // The idea is to compress long words of more than 100 symbols in size consisting of alphanumeric
405 // characters with ellipsiss using the following template:
406 // "[50 first symbols]...[50 last symbols]"
407 const QRegularExpression re("[a-zA-Z0-9]{101,}");
408 QRegularExpressionMatch mt = re.match(strText);
409 int iPosition = mt.capturedStart();
410 bool fChangeAllowed = iPosition != -1;
411 while (fChangeAllowed)
412 {
413 QString strNewText = strText;
414 const QString strFound = mt.captured();
415 strNewText.replace(iPosition, strFound.size(), strFound.left(50) + "..." + strFound.right(50));
416 fChangeAllowed = fChangeAllowed && strText != strNewText;
417 strText = strNewText;
418 mt = re.match(strText);
419 iPosition = mt.capturedStart();
420 fChangeAllowed = fChangeAllowed && iPosition != -1;
421 }
422 return strText;
423}
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle
ContactPrivacy/Do Not Sell My InfoTerms of Use