VirtualBox

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

Last change on this file was 104358, checked in by vboxsync, 5 weeks ago

FE/Qt. bugref:10622. More refactoring around the retranslation functionality.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 14.9 KB
Line 
1/* $Id: QIMessageBox.cpp 104358 2024-04-18 05:33:40Z 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#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
88 QStringList paragraphs(strText.split("<!--EOP-->", Qt::SkipEmptyParts));
89#else
90 QStringList paragraphs(strText.split("<!--EOP-->", QString::SkipEmptyParts));
91#endif
92 /* Make sure details-text has at least one paragraph: */
93 AssertReturnVoid(!paragraphs.isEmpty());
94
95 /* Enumerate all the paragraphs: */
96 QStringPairList details;
97 foreach (const QString &strParagraph, paragraphs)
98 {
99 /* Split each paragraph into pairs: */
100#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
101 QStringList parts(strParagraph.split("<!--EOM-->", Qt::KeepEmptyParts));
102#else
103 QStringList parts(strParagraph.split("<!--EOM-->", QString::KeepEmptyParts));
104#endif
105 /* Make sure each paragraph consist of 2 parts: */
106 AssertReturnVoid(parts.size() == 2);
107 /* Append each pair into details-list: */
108 details << QStringPair(parts[0], parts[1]);
109 }
110
111 /* Pass details-list to details-container: */
112 m_pDetailsContainer->setDetails(details);
113 /* Update details-container finally: */
114 updateDetailsContainer();
115}
116
117bool QIMessageBox::flagChecked() const
118{
119 return m_pFlagCheckBox->isChecked();
120}
121
122void QIMessageBox::setFlagChecked(bool fChecked)
123{
124 m_pFlagCheckBox->setChecked(fChecked);
125}
126
127void QIMessageBox::setFlagText(const QString &strFlagText)
128{
129 /* Pass text to flag check-box: */
130 m_pFlagCheckBox->setText(strFlagText);
131 /* Update flag check-box finally: */
132 updateCheckBox();
133}
134
135void QIMessageBox::setButtonText(int iButton, const QString &strText)
136{
137 switch (iButton)
138 {
139 case 0: if (m_pButton1) m_pButton1->setText(strText); break;
140 case 1: if (m_pButton2) m_pButton2->setText(strText); break;
141 case 2: if (m_pButton3) m_pButton3->setText(strText); break;
142 default: break;
143 }
144}
145
146void QIMessageBox::polishEvent(QShowEvent *pPolishEvent)
147{
148 /* Call to base-class: */
149 QIDialog::polishEvent(pPolishEvent);
150
151 /* Update size finally: */
152 sltUpdateSize();
153}
154
155void QIMessageBox::closeEvent(QCloseEvent *pCloseEvent)
156{
157 if (m_fDone)
158 pCloseEvent->accept();
159 else
160 {
161 pCloseEvent->ignore();
162 reject();
163 }
164}
165
166void QIMessageBox::sltUpdateSize()
167{
168 /* Fix minimum possible size: */
169 setFixedSize(minimumSizeHint());
170}
171
172void QIMessageBox::sltCopy() const
173{
174 /* Create the error string with all errors. First the html version. */
175 QString strError = "<html><body><p>" + m_strMessage + "</p>";
176 foreach (const QStringPair &pair, m_pDetailsContainer->details())
177 strError += pair.first + pair.second + "<br>";
178 strError += "</body></html>";
179 strError.remove(QRegularExpression("</+qt>"));
180 strError = strError.replace(QRegularExpression("&nbsp;"), " ");
181 /* Create a new mime data object holding both the html and the plain text version. */
182 QMimeData *pMimeData = new QMimeData();
183 pMimeData->setHtml(strError);
184 /* Replace all the html entities. */
185 strError = strError.replace(QRegularExpression("<br>|</tr>"), "\n");
186 strError = strError.replace(QRegularExpression("</p>"), "\n\n");
187 strError = strError.remove(QRegularExpression("<[^>]*>"));
188 pMimeData->setText(strError);
189 /* Add the mime data to the global clipboard. */
190 QClipboard *pClipboard = QApplication::clipboard();
191 pClipboard->setMimeData(pMimeData);
192}
193
194void QIMessageBox::reject()
195{
196 if (m_iButtonEsc)
197 {
198 QDialog::reject();
199 setResult(m_iButtonEsc & AlertButtonMask);
200 }
201}
202
203void QIMessageBox::prepare()
204{
205 /* Set caption: */
206 setWindowTitle(m_strTitle);
207
208 /* Create main-layout: */
209 QVBoxLayout *pMainLayout = new QVBoxLayout(this);
210 AssertPtrReturnVoid(pMainLayout);
211 {
212 /* Configure main-layout: */
213#ifdef VBOX_WS_MAC
214 pMainLayout->setContentsMargins(40, 20, 40, 20);
215 pMainLayout->setSpacing(15);
216#else
217 pMainLayout->setSpacing(qApp->style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing) * 2);
218#endif
219 /* Create top-layout: */
220 QHBoxLayout *pTopLayout = new QHBoxLayout;
221 AssertPtrReturnVoid(pTopLayout);
222 {
223 /* Configure top-layout: */
224 pTopLayout->setContentsMargins(0, 0, 0, 0);
225 /* Create icon-label: */
226 m_pLabelIcon = new QLabel;
227 AssertPtrReturnVoid(m_pLabelIcon);
228 {
229 /* Configure icon-label: */
230 m_pLabelIcon->setPixmap(standardPixmap(m_iconType, this));
231 m_pLabelIcon->setAlignment(Qt::AlignHCenter | Qt::AlignTop);
232 m_pLabelIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum);
233 /* Add icon-label into top-layout: */
234 pTopLayout->addWidget(m_pLabelIcon);
235 }
236 /* Create text-label: */
237 m_pLabelText = new QIRichTextLabel;
238 AssertPtrReturnVoid(m_pLabelText);
239 {
240 /* Configure text-label: */
241 m_pLabelText->setText(compressLongWords(m_strMessage));
242 /* Add text-label into top-layout: */
243 pTopLayout->addWidget(m_pLabelText);
244 }
245 /* Add top-layout into main-layout: */
246 pMainLayout->addLayout(pTopLayout);
247 }
248 /* Create details-container: */
249 m_pDetailsContainer = new QIArrowSplitter;
250 AssertPtrReturnVoid(m_pDetailsContainer);
251 {
252 /* Configure container: */
253 connect(m_pDetailsContainer, &QIArrowSplitter::sigSizeHintChange,
254 this, &QIMessageBox::sltUpdateSize);
255 /* Add details-container into main-layout: */
256 pMainLayout->addWidget(m_pDetailsContainer);
257 /* Update details-container finally: */
258 updateDetailsContainer();
259 }
260 /* Create flag check-box: */
261 m_pFlagCheckBox = new QCheckBox;
262 AssertPtrReturnVoid(m_pFlagCheckBox);
263 {
264 /* Configure flag check-box: */
265 m_pFlagCheckBox->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
266 /* Add flag check-box into main-layout: */
267 pMainLayout->addWidget(m_pFlagCheckBox, 0, Qt::AlignHCenter | Qt::AlignVCenter);
268 /* Update flag check-box finally: */
269 updateCheckBox();
270 }
271 /* Create button-box: */
272 m_pButtonBox = new QIDialogButtonBox;
273 AssertPtrReturnVoid(m_pButtonBox);
274 {
275 /* Configure button-box: */
276 m_pButtonBox->setCenterButtons(true);
277 m_pButton1 = createButton(m_iButton1);
278 if (m_pButton1)
279 connect(m_pButton1, &QPushButton::clicked, this, &QIMessageBox::sltDone1);
280 m_pButton2 = createButton(m_iButton2);
281 if (m_pButton2)
282 connect(m_pButton2, &QPushButton::clicked, this, &QIMessageBox::sltDone2);
283 m_pButton3 = createButton(m_iButton3);
284 if (m_pButton3)
285 connect(m_pButton3, &QPushButton::clicked, this, &QIMessageBox::sltDone3);
286 /* Create the help button and connect it to relevant slot in case a help word is supplied: */
287 if (!m_strHelpKeyword.isEmpty())
288 {
289 m_pButtonHelp = createButton(AlertButton_Help);
290 if (m_pButtonHelp)
291 {
292 uiCommon().setHelpKeyword(m_pButtonHelp, m_strHelpKeyword);
293 connect(m_pButtonHelp, &QPushButton::clicked, m_pButtonBox, &QIDialogButtonBox::sltHandleHelpRequest);
294 }
295 }
296
297 /* Make sure Escape button always set: */
298 Assert(m_iButtonEsc);
299 /* If this is a critical message add a "Copy to clipboard" button: */
300 if (m_iconType == AlertIconType_Critical)
301 {
302 QPushButton *pCopyButton = createButton(AlertButton_Copy);
303 pCopyButton->setToolTip(tr("Copy all errors to the clipboard"));
304 connect(pCopyButton, &QPushButton::clicked, this, &QIMessageBox::sltCopy);
305 }
306 /* Add button-box into main-layout: */
307 pMainLayout->addWidget(m_pButtonBox);
308
309 /* Prepare focus. It is important to prepare focus after adding button-box to the layout as
310 * parenting the button-box to the QDialog changes default button focus by Qt: */
311 prepareFocus();
312 }
313 }
314}
315
316void QIMessageBox::prepareFocus()
317{
318 /* Configure default button and focus: */
319 if (m_pButton1 && (m_iButton1 & AlertButtonOption_Default))
320 {
321 m_pButton1->setDefault(true);
322 m_pButton1->setFocus();
323 }
324 if (m_pButton2 && (m_iButton2 & AlertButtonOption_Default))
325 {
326 m_pButton2->setDefault(true);
327 m_pButton2->setFocus();
328 }
329 if (m_pButton3 && (m_iButton3 & AlertButtonOption_Default))
330 {
331 m_pButton3->setDefault(true);
332 m_pButton3->setFocus();
333 }
334}
335
336QPushButton *QIMessageBox::createButton(int iButton)
337{
338 /* Not for AlertButton_NoButton: */
339 if (iButton == 0)
340 return 0;
341
342 /* Prepare button text & role: */
343 QString strText;
344 QDialogButtonBox::ButtonRole role;
345 switch (iButton & AlertButtonMask)
346 {
347 case AlertButton_Ok: strText = tr("OK"); role = QDialogButtonBox::AcceptRole; break;
348 case AlertButton_Cancel: strText = tr("Cancel"); role = QDialogButtonBox::RejectRole; break;
349 case AlertButton_Choice1: strText = tr("Yes"); role = QDialogButtonBox::YesRole; break;
350 case AlertButton_Choice2: strText = tr("No"); role = QDialogButtonBox::NoRole; break;
351 case AlertButton_Copy: strText = tr("Copy"); role = QDialogButtonBox::ActionRole; break;
352 case AlertButton_Help: strText = tr("Help"); role = QDialogButtonBox::HelpRole; break;
353 default:
354 AssertMsgFailed(("Type %d is not supported!", iButton));
355 return 0;
356 }
357
358 /* Create push-button: */
359 QPushButton *pButton = m_pButtonBox->addButton(strText, role);
360
361 /* Configure <escape> button: */
362 if (iButton & AlertButtonOption_Escape)
363 m_iButtonEsc = iButton & AlertButtonMask;
364
365 /* Return button: */
366 return pButton;
367}
368
369void QIMessageBox::updateDetailsContainer()
370{
371 /* Details-container with details is always visible: */
372 m_pDetailsContainer->setVisible(!m_pDetailsContainer->details().isEmpty());
373 /* Update size: */
374 sltUpdateSize();
375}
376
377void QIMessageBox::updateCheckBox()
378{
379 /* Flag check-box with text is always visible: */
380 m_pFlagCheckBox->setVisible(!m_pFlagCheckBox->text().isEmpty());
381 /* Update size: */
382 sltUpdateSize();
383}
384
385/* static */
386QPixmap QIMessageBox::standardPixmap(AlertIconType iconType, QWidget *pWidget /* = 0*/)
387{
388 /* Prepare standard icon: */
389 QIcon icon;
390 switch (iconType)
391 {
392 case AlertIconType_Information: icon = UIIconPool::defaultIcon(UIIconPool::UIDefaultIconType_MessageBoxInformation, pWidget); break;
393 case AlertIconType_Warning: icon = UIIconPool::defaultIcon(UIIconPool::UIDefaultIconType_MessageBoxWarning, pWidget); break;
394 case AlertIconType_Critical: icon = UIIconPool::defaultIcon(UIIconPool::UIDefaultIconType_MessageBoxCritical, pWidget); break;
395 case AlertIconType_Question: icon = UIIconPool::defaultIcon(UIIconPool::UIDefaultIconType_MessageBoxQuestion, pWidget); break;
396 case AlertIconType_GuruMeditation: icon = UIIconPool::iconSet(":/meditation_32px.png"); break;
397 default: break;
398 }
399 /* Return empty pixmap if nothing found: */
400 if (icon.isNull())
401 return QPixmap();
402 /* Return pixmap of standard size if possible: */
403 QStyle *pStyle = pWidget ? pWidget->style() : QApplication::style();
404 int iSize = pStyle->pixelMetric(QStyle::PM_MessageBoxIconSize, 0, pWidget);
405 return icon.pixmap(iSize, iSize);
406}
407
408/* static */
409QString QIMessageBox::compressLongWords(QString strText)
410{
411 // WORKAROUND:
412 // The idea is to compress long words of more than 100 symbols in size consisting of alphanumeric
413 // characters with ellipsiss using the following template:
414 // "[50 first symbols]...[50 last symbols]"
415 const QRegularExpression re("[a-zA-Z0-9]{101,}");
416 QRegularExpressionMatch mt = re.match(strText);
417 int iPosition = mt.capturedStart();
418 bool fChangeAllowed = iPosition != -1;
419 while (fChangeAllowed)
420 {
421 QString strNewText = strText;
422 const QString strFound = mt.captured();
423 strNewText.replace(iPosition, strFound.size(), strFound.left(50) + "..." + strFound.right(50));
424 fChangeAllowed = fChangeAllowed && strText != strNewText;
425 strText = strNewText;
426 mt = re.match(strText);
427 iPosition = mt.capturedStart();
428 fChangeAllowed = fChangeAllowed && iPosition != -1;
429 }
430 return strText;
431}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use