VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/wizards/UINativeWizard.cpp

Last change on this file was 105586, checked in by vboxsync, 6 weeks ago

FE/Qt: bugref:10665, bugref:10744: UINativeWizard: Make sure wizard is cleanup up if aborted (by Escape key, Cancel button or by closing the window).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 27.8 KB
Line 
1/* $Id: UINativeWizard.cpp 105586 2024-08-05 14:28:46Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UINativeWizard class implementation.
4 */
5
6/*
7 * Copyright (C) 2009-2024 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 <QHBoxLayout>
31#include <QLabel>
32#include <QPainter>
33#include <QPushButton>
34#include <QStackedWidget>
35#include <QStyle>
36#include <QVBoxLayout>
37#include <QWindow>
38
39/* GUI includes: */
40#include "QIRichTextLabel.h"
41#include "UICommon.h"
42#include "UIDesktopWidgetWatchdog.h"
43#include "UIExtraDataManager.h"
44#include "UIHelpBrowserDialog.h"
45#include "UIIconPool.h"
46#include "UINativeWizard.h"
47#include "UINativeWizardPage.h"
48#include "UINotificationCenter.h"
49#include "UIShortcutPool.h"
50#include "UITranslationEventListener.h"
51
52#ifdef VBOX_WS_MAC
53UIFrame::UIFrame(QWidget *pParent)
54 : QWidget(pParent)
55{
56}
57
58void UIFrame::paintEvent(QPaintEvent *pEvent)
59{
60 /* Sanity check: */
61 AssertPtrReturnVoid(pEvent);
62
63 /* Prepare painter: */
64 QPainter painter(this);
65
66 /* Limit painting with incoming rectangle: */
67 painter.setClipRect(pEvent->rect());
68
69 /* Check whether we should use Active or Inactive palette: */
70 const bool fActive = parentWidget() && parentWidget()->isActiveWindow();
71
72 /* Paint background: */
73 QColor backgroundColor = QGuiApplication::palette().color(fActive ? QPalette::Active : QPalette::Inactive, QPalette::Window);
74 backgroundColor.setAlpha(100);
75 painter.setPen(backgroundColor);
76 painter.setBrush(backgroundColor);
77 painter.drawRect(rect());
78
79 /* Paint borders: */
80 painter.setPen(QGuiApplication::palette().color(fActive ? QPalette::Active : QPalette::Inactive, QPalette::Window).darker(130));
81 QLine line1(0, 0, rect().width() - 1, 0);
82 QLine line2(rect().width() - 1, 0, rect().width() - 1, rect().height() - 1);
83 QLine line3(rect().width() - 1, rect().height() - 1, 0, rect().height() - 1);
84 QLine line4(0, rect().height() - 1, 0, 0);
85 painter.drawLine(line1);
86 painter.drawLine(line2);
87 painter.drawLine(line3);
88 painter.drawLine(line4);
89}
90#endif /* VBOX_WS_MAC */
91
92
93UINativeWizard::UINativeWizard(QWidget *pParent,
94 WizardType enmType,
95 const QString &strHelpKeyword /* = QString() */)
96 : QDialog(pParent, Qt::Window)
97 , m_enmType(enmType)
98 , m_enmMode(gEDataManager->isSettingsInExpertMode() ? WizardMode_Expert : WizardMode_Basic)
99 , m_strHelpKeyword(strHelpKeyword)
100 , m_iLastIndex(-1)
101 , m_fAborted(true)
102 , m_fClosed(false)
103 , m_pLabelPixmap(0)
104 , m_pLayoutRight(0)
105 , m_pLabelPageTitle(0)
106 , m_pWidgetStack(0)
107 , m_pNotificationCenter(0)
108{
109 prepare();
110}
111
112UINativeWizard::~UINativeWizard()
113{
114 cleanup();
115}
116
117UINotificationCenter *UINativeWizard::notificationCenter() const
118{
119 return m_pNotificationCenter;
120}
121
122bool UINativeWizard::handleNotificationProgressNow(UINotificationProgress *pProgress)
123{
124 const bool fResult = m_pNotificationCenter->handleNow(pProgress);
125 return fResult;
126}
127
128QPushButton *UINativeWizard::wizardButton(const WizardButtonType &enmType) const
129{
130 return m_buttons.value(enmType);
131}
132
133int UINativeWizard::exec()
134{
135 /* Init wizard: */
136 init();
137
138 /* Call to base-class: */
139 return QDialog::exec();
140}
141
142void UINativeWizard::show()
143{
144 /* Init wizard: */
145 init();
146
147 /* Call to base-class: */
148 return QDialog::show();
149}
150
151void UINativeWizard::setPixmapName(const QString &strName)
152{
153 m_strPixmapName = strName;
154}
155
156bool UINativeWizard::isPageVisible(int iIndex) const
157{
158 return !m_invisiblePages.contains(iIndex);
159}
160
161void UINativeWizard::setPageVisible(int iIndex, bool fVisible)
162{
163 AssertMsgReturnVoid(iIndex || fVisible, ("Can't hide 1st wizard page!\n"));
164 if (fVisible)
165 m_invisiblePages.remove(iIndex);
166 else
167 m_invisiblePages.insert(iIndex);
168 /* Update the button labels since the last visible page might have changed. Thus 'Next' <-> 'Finish' might be needed: */
169 sltRetranslateUI();
170}
171
172int UINativeWizard::addPage(UINativeWizardPage *pPage)
173{
174 /* Sanity check: */
175 AssertPtrReturn(pPage, -1);
176 AssertPtrReturn(pPage->layout(), -1);
177
178 /* Adjust page layout: */
179 const int iL = 0;
180 const int iT = 0;
181 const int iR = qApp->style()->pixelMetric(QStyle::PM_LayoutRightMargin);
182 const int iB = qApp->style()->pixelMetric(QStyle::PM_LayoutBottomMargin);
183 pPage->layout()->setContentsMargins(iL, iT, iR, iB);
184
185 /* Add page to wizard's stack: */
186 m_pWidgetStack->blockSignals(true);
187 const int iIndex = m_pWidgetStack->addWidget(pPage);
188 m_pWidgetStack->blockSignals(false);
189
190 /* Make sure wizard is aware of page validity changes: */
191 connect(pPage, &UINativeWizardPage::completeChanged,
192 this, &UINativeWizard::sltCompleteChanged);
193
194 /* Returns added page index: */
195 return iIndex;
196}
197
198void UINativeWizard::sltRetranslateUI()
199{
200 /* Translate Help button: */
201 QPushButton *pButtonHelp = wizardButton(WizardButtonType_Help);
202 if (pButtonHelp)
203 {
204 pButtonHelp->setText(tr("&Help"));
205 pButtonHelp->setToolTip(tr("Open corresponding Help topic."));
206 }
207
208 /* Translate Back button: */
209 QPushButton *pButtonBack = wizardButton(WizardButtonType_Back);
210 AssertMsgReturnVoid(pButtonBack, ("No Back wizard button found!\n"));
211 pButtonBack->setText(tr("&Back"));
212 pButtonBack->setToolTip(tr("Go to previous wizard page."));
213
214 /* Translate Next button: */
215 QPushButton *pButtonNext = wizardButton(WizardButtonType_Next);
216 AssertMsgReturnVoid(pButtonNext, ("No Next wizard button found!\n"));
217 if (!isLastVisiblePage(m_pWidgetStack->currentIndex()))
218 {
219 pButtonNext->setText(tr("&Next"));
220 pButtonNext->setToolTip(tr("Go to next wizard page."));
221 }
222 else
223 {
224 pButtonNext->setText(tr("&Finish"));
225 pButtonNext->setToolTip(tr("Commit all wizard data."));
226 }
227
228 /* Translate Cancel button: */
229 QPushButton *pButtonCancel = wizardButton(WizardButtonType_Cancel);
230 AssertMsgReturnVoid(pButtonCancel, ("No Cancel wizard button found!\n"));
231 pButtonCancel->setText(tr("&Cancel"));
232 pButtonCancel->setToolTip(tr("Cancel wizard execution."));
233}
234
235void UINativeWizard::keyPressEvent(QKeyEvent *pEvent)
236{
237 // WORKAROUND:
238 // In non-modal case we'll have to handle Escape button ourselves.
239 // In modal case QDialog does this itself internally by unwinding the event-loop.
240
241 /* Different handling depending on current modality: */
242 const Qt::WindowModality enmModality = windowHandle()->modality();
243
244 /* For non-modal case: */
245 if (enmModality == Qt::NonModal)
246 {
247 /* Special pre-processing for some keys: */
248 switch (pEvent->key())
249 {
250 case Qt::Key_Escape:
251 {
252 close();
253 return;
254 }
255 default:
256 break;
257 }
258 }
259
260 /* Call to base-class: */
261 return QDialog::keyPressEvent(pEvent);
262}
263
264void UINativeWizard::closeEvent(QCloseEvent *pEvent)
265{
266 /* Different handling depending on current modality: */
267 const Qt::WindowModality enmModality = windowHandle()->modality();
268
269 /* For non-modal case: */
270 if (enmModality == Qt::NonModal)
271 {
272 /* Ignore event initially: */
273 pEvent->ignore();
274
275 /* Let the notification-center abort blocking operations: */
276 if (m_pNotificationCenter->hasOperationsPending())
277 m_pNotificationCenter->abortOperations();
278 else
279 /* Tell the listener to close us (once): */
280 if (!m_fClosed)
281 {
282 m_fClosed = true;
283 if (m_fAborted)
284 cleanWizard();
285 emit sigClose(m_enmType);
286 }
287
288 return;
289 }
290
291 /* Call to base-class: */
292 QDialog::closeEvent(pEvent);
293}
294
295void UINativeWizard::sltCurrentIndexChanged(int iIndex /* = -1 */)
296{
297 /* Update translation: */
298 sltRetranslateUI();
299
300 /* Sanity check: */
301 AssertPtrReturnVoid(m_pWidgetStack);
302
303 /* -1 means current one page: */
304 if (iIndex == -1)
305 iIndex = m_pWidgetStack->currentIndex();
306
307 /* Disable/enable Back button: */
308 QPushButton *pButtonBack = wizardButton(WizardButtonType_Back);
309 AssertMsgReturnVoid(pButtonBack, ("No Back wizard button found!\n"));
310 pButtonBack->setEnabled(iIndex > 0);
311
312 /* Initialize corresponding page: */
313 UINativeWizardPage *pPage = qobject_cast<UINativeWizardPage*>(m_pWidgetStack->widget(iIndex));
314 AssertPtrReturnVoid(pPage);
315 m_pLabelPageTitle->setText(pPage->title());
316 if (iIndex > m_iLastIndex)
317 pPage->initializePage();
318
319 /* Disable/enable Next button: */
320 QPushButton *pButtonNext = wizardButton(WizardButtonType_Next);
321 AssertMsgReturnVoid(pButtonNext, ("No Next wizard button found!\n"));
322 pButtonNext->setEnabled(pPage->isComplete());
323
324 /* Update last index: */
325 m_iLastIndex = iIndex;
326}
327
328void UINativeWizard::sltCompleteChanged()
329{
330 /* Make sure sender is current widget: */
331 QWidget *pSender = qobject_cast<QWidget*>(sender());
332 if (pSender != m_pWidgetStack->currentWidget())
333 return;
334
335 /* Allow Next button only if current page is complete: */
336 UINativeWizardPage *pPage = qobject_cast<UINativeWizardPage*>(pSender);
337 QPushButton *pButtonNext = wizardButton(WizardButtonType_Next);
338 AssertMsgReturnVoid(pButtonNext, ("No Next wizard button found!\n"));
339 pButtonNext->setEnabled(pPage->isComplete());
340}
341
342void UINativeWizard::sltPrevious()
343{
344 /* For all allowed pages besides the 1st one we going backward: */
345 bool fPreviousFound = false;
346 int iIteratedIndex = m_pWidgetStack->currentIndex();
347 while (!fPreviousFound && iIteratedIndex > 0)
348 if (isPageVisible(--iIteratedIndex))
349 fPreviousFound = true;
350 if (fPreviousFound)
351 m_pWidgetStack->setCurrentIndex(iIteratedIndex);
352}
353
354void UINativeWizard::sltNext()
355{
356 /* Look for Next button: */
357 QPushButton *pButtonNext = wizardButton(WizardButtonType_Next);
358 AssertMsgReturnVoid(pButtonNext, ("No Next wizard button found!\n"));
359
360 /* Validate page before going forward: */
361 AssertReturnVoid(m_pWidgetStack->currentIndex() < m_pWidgetStack->count());
362 UINativeWizardPage *pPage = qobject_cast<UINativeWizardPage*>(m_pWidgetStack->currentWidget());
363 AssertPtrReturnVoid(pPage);
364 pButtonNext->setEnabled(false);
365 const bool fIsPageValid = pPage->validatePage();
366 pButtonNext->setEnabled(true);
367 if (!fIsPageValid)
368 return;
369
370 /* For all allowed pages besides the last one we going forward: */
371 bool fNextFound = false;
372 int iIteratedIndex = m_pWidgetStack->currentIndex();
373 while (!fNextFound && iIteratedIndex < m_pWidgetStack->count() - 1)
374 if (isPageVisible(++iIteratedIndex))
375 fNextFound = true;
376 if (fNextFound)
377 m_pWidgetStack->setCurrentIndex(iIteratedIndex);
378 /* For last one we just accept the wizard: */
379 else
380 {
381 /* Different handling depending on current modality: */
382 if (windowHandle()->modality() == Qt::NonModal)
383 {
384 m_fAborted = false;
385 close();
386 }
387 else
388 accept();
389 }
390}
391
392void UINativeWizard::sltHandleHelpRequest()
393{
394 UIHelpBrowserDialog::findManualFileAndShow(uiCommon().helpKeyword(this));
395}
396
397void UINativeWizard::prepare()
398{
399 /* Prepare main layout: */
400 QVBoxLayout *pLayoutMain = new QVBoxLayout(this);
401 if (pLayoutMain)
402 {
403 /* No need for margins and spacings between sub-layouts: */
404 pLayoutMain->setContentsMargins(0, 0, 0, 0);
405 pLayoutMain->setSpacing(0);
406
407 /* Prepare upper layout: */
408 QHBoxLayout *pLayoutUpper = new QHBoxLayout;
409 if (pLayoutUpper)
410 {
411#ifdef VBOX_WS_MAC
412 /* No need for bottom margin on macOS, reseting others to default: */
413 const int iL = qApp->style()->pixelMetric(QStyle::PM_LayoutLeftMargin);
414 const int iT = qApp->style()->pixelMetric(QStyle::PM_LayoutTopMargin);
415 const int iR = qApp->style()->pixelMetric(QStyle::PM_LayoutRightMargin);
416 pLayoutUpper->setContentsMargins(iL, iT, iR, 0);
417#endif /* VBOX_WS_MAC */
418 /* Reset spacing to default, it was flawed by parent inheritance: */
419 const int iSpacing = qApp->style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
420 pLayoutUpper->setSpacing(iSpacing);
421
422 /* Prepare pixmap label: */
423 m_pLabelPixmap = new QLabel(this);
424 if (m_pLabelPixmap)
425 {
426 m_pLabelPixmap->setAlignment(Qt::AlignTop);
427#ifdef VBOX_WS_MAC
428 /* On macOS this label contains background, which isn't a part of layout, moving manually: */
429 m_pLabelPixmap->move(0, 0);
430 /* Spacer to make look&feel native on macOS: */
431 QSpacerItem *pSpacer = new QSpacerItem(200, 0, QSizePolicy::Fixed, QSizePolicy::Minimum);
432 pLayoutUpper->addItem(pSpacer);
433#else /* !VBOX_WS_MAC */
434 /* Just add label into layout on other platforms: */
435 pLayoutUpper->addWidget(m_pLabelPixmap);
436#endif /* !VBOX_WS_MAC */
437 }
438
439 /* Prepare right layout: */
440 m_pLayoutRight = new QVBoxLayout;
441 if (m_pLayoutRight)
442 {
443 /* Prepare page title label: */
444 m_pLabelPageTitle = new QLabel(this);
445 if (m_pLabelPageTitle)
446 {
447 /* Title should have big/fat font: */
448 QFont labelFont = m_pLabelPageTitle->font();
449 labelFont.setBold(true);
450 labelFont.setPointSize(labelFont.pointSize() + 4);
451 m_pLabelPageTitle->setFont(labelFont);
452
453 m_pLayoutRight->addWidget(m_pLabelPageTitle);
454 }
455
456#ifdef VBOX_WS_MAC
457 /* Prepare frame around widget-stack on macOS for nativity purposes: */
458 UIFrame *pFrame = new UIFrame(this);
459 if (pFrame)
460 {
461 /* Prepare frame layout: */
462 QVBoxLayout *pLayoutFrame = new QVBoxLayout(pFrame);
463 if (pLayoutFrame)
464 {
465 /* Prepare widget-stack: */
466 m_pWidgetStack = new QStackedWidget(pFrame);
467 if (m_pWidgetStack)
468 {
469 connect(m_pWidgetStack, &QStackedWidget::currentChanged, this, &UINativeWizard::sltCurrentIndexChanged);
470 pLayoutFrame->addWidget(m_pWidgetStack);
471 }
472 }
473
474 /* Add to layout: */
475 m_pLayoutRight->addWidget(pFrame);
476 }
477#else /* !VBOX_WS_MAC */
478 /* Prepare widget-stack directly on other platforms: */
479 m_pWidgetStack = new QStackedWidget(this);
480 if (m_pWidgetStack)
481 {
482 connect(m_pWidgetStack, &QStackedWidget::currentChanged, this, &UINativeWizard::sltCurrentIndexChanged);
483 m_pLayoutRight->addWidget(m_pWidgetStack);
484 }
485#endif /* !VBOX_WS_MAC */
486
487 /* Add to layout: */
488 pLayoutUpper->addLayout(m_pLayoutRight);
489 }
490
491 /* Add to layout: */
492 pLayoutMain->addLayout(pLayoutUpper, 1);
493 }
494
495 /* Prepare bottom widget: */
496 QWidget *pWidgetBottom = new QWidget(this);
497 if (pWidgetBottom)
498 {
499#ifndef VBOX_WS_MAC
500 /* Adjust palette a bit on Windows/X11 for native purposes: */
501 pWidgetBottom->setAutoFillBackground(true);
502 QPalette pal = QGuiApplication::palette();
503 pal.setColor(QPalette::Active, QPalette::Window, pal.color(QPalette::Active, QPalette::Window).darker(110));
504 pal.setColor(QPalette::Inactive, QPalette::Window, pal.color(QPalette::Inactive, QPalette::Window).darker(110));
505 pWidgetBottom->setPalette(pal);
506#endif /* !VBOX_WS_MAC */
507
508 /* Prepare bottom layout: */
509 QHBoxLayout *pLayoutBottom = new QHBoxLayout(pWidgetBottom);
510 if (pLayoutBottom)
511 {
512 /* Reset margins to default, they were flawed by parent inheritance: */
513 const int iL = qApp->style()->pixelMetric(QStyle::PM_LayoutLeftMargin);
514 const int iT = qApp->style()->pixelMetric(QStyle::PM_LayoutTopMargin);
515 const int iR = qApp->style()->pixelMetric(QStyle::PM_LayoutRightMargin);
516 const int iB = qApp->style()->pixelMetric(QStyle::PM_LayoutBottomMargin);
517 pLayoutBottom->setContentsMargins(iL, iT, iR, iB);
518
519 // WORKAROUND:
520 // Prepare dialog button-box? Huh, no .. QWizard has different opinion.
521 // So we are hardcoding order, same on all platforms, which is the case.
522 for (int i = WizardButtonType_Invalid + 1; i < WizardButtonType_Max; ++i)
523 {
524 const WizardButtonType enmType = (WizardButtonType)i;
525 /* Create Help button only if help keyword is set.
526 * Create other buttons in any case: */
527 if (enmType != WizardButtonType_Help || !m_strHelpKeyword.isEmpty())
528 m_buttons[enmType] = new QPushButton(pWidgetBottom);
529 QPushButton *pButton = wizardButton(enmType);
530 if (pButton)
531 pLayoutBottom->addWidget(pButton);
532 if (enmType == WizardButtonType_Help)
533 pLayoutBottom->addStretch(1);
534 if ( pButton
535 && enmType == WizardButtonType_Next)
536 pButton->setDefault(true);
537 }
538 /* Connect buttons: */
539 if (wizardButton(WizardButtonType_Help))
540 {
541 connect(wizardButton(WizardButtonType_Help), &QPushButton::clicked,
542 this, &UINativeWizard::sltHandleHelpRequest);
543 wizardButton(WizardButtonType_Help)->setShortcut(UIShortcutPool::standardSequence(QKeySequence::HelpContents));
544 uiCommon().setHelpKeyword(this, m_strHelpKeyword);
545 }
546 connect(wizardButton(WizardButtonType_Back), &QPushButton::clicked,
547 this, &UINativeWizard::sltPrevious);
548 connect(wizardButton(WizardButtonType_Next), &QPushButton::clicked,
549 this, &UINativeWizard::sltNext);
550 connect(wizardButton(WizardButtonType_Cancel), &QPushButton::clicked,
551 this, &UINativeWizard::close);
552 }
553
554 /* Add to layout: */
555 pLayoutMain->addWidget(pWidgetBottom);
556 }
557 }
558
559 /* Prepare local notification-center: */
560 m_pNotificationCenter = new UINotificationCenter(this);
561 if (m_pNotificationCenter)
562 connect(m_pNotificationCenter, &UINotificationCenter::sigOperationsAborted,
563 this, &UINativeWizard::close, Qt::QueuedConnection);
564
565 connect(&translationEventListener(), &UITranslationEventListener::sigRetranslateUI,
566 this, &UINativeWizard::sltRetranslateUI);
567}
568
569void UINativeWizard::cleanup()
570{
571 /* Cleanup local notification-center: */
572 delete m_pNotificationCenter;
573 m_pNotificationCenter = 0;
574}
575
576void UINativeWizard::init()
577{
578 /* Populate pages: */
579 populatePages();
580
581 /* Translate wizard: */
582 sltRetranslateUI();
583 /* Translate wizard pages: */
584 retranslatePages();
585
586 /* Resize wizard to 'golden ratio': */
587 resizeToGoldenRatio();
588
589 /* Make sure current page initialized: */
590 sltCurrentIndexChanged();
591}
592
593void UINativeWizard::retranslatePages()
594{
595 /* Translate all the pages: */
596 for (int i = 0; i < m_pWidgetStack->count(); ++i)
597 qobject_cast<UINativeWizardPage*>(m_pWidgetStack->widget(i))->retranslate();
598}
599
600void UINativeWizard::resizeToGoldenRatio()
601{
602 /* Standard top margin: */
603 const int iT = qApp->style()->pixelMetric(QStyle::PM_LayoutTopMargin);
604 m_pLayoutRight->setContentsMargins(0, iT, 0, 0);
605 /* Show title label for Basic mode case: */
606 m_pLabelPageTitle->setVisible(m_enmMode == WizardMode_Basic);
607#ifndef VBOX_WS_MAC
608 /* Hide/show pixmap label on Windows/X11 only, on macOS it's in the background: */
609 m_pLabelPixmap->setVisible(!m_strPixmapName.isEmpty());
610#endif /* !VBOX_WS_MAC */
611
612 /* For wizard in Basic mode: */
613 if (m_enmMode == WizardMode_Basic)
614 {
615 /* Temporary hide all the QIRichTextLabel(s) to exclude
616 * influence onto m_pWidgetStack minimum size-hint below: */
617 foreach (QIRichTextLabel *pLabel, findChildren<QIRichTextLabel*>())
618 pLabel->hide();
619 /* Gather suitable dimensions: */
620 const int iStepWidth = 100;
621 const int iMinWidth = qMax(100, m_pWidgetStack->minimumSizeHint().width());
622 const int iMaxWidth = qMax(iMinWidth, gpDesktop->availableGeometry(this).width() * 3 / 4);
623 /* Show all the QIRichTextLabel(s) again, they were hidden above: */
624 foreach (QIRichTextLabel *pLabel, findChildren<QIRichTextLabel*>())
625 pLabel->show();
626 /* Now look for a golden ratio: */
627 int iCurrentWidth = iMinWidth;
628 do
629 {
630 /* Assign current QIRichTextLabel(s) width: */
631 foreach (QIRichTextLabel *pLabel, findChildren<QIRichTextLabel*>())
632 pLabel->setMinimumTextWidth(iCurrentWidth);
633
634 /* Calculate current ratio: */
635 const QSize msh = m_pWidgetStack->minimumSizeHint();
636 int iWidth = msh.width();
637 int iHeight = msh.height();
638#ifndef VBOX_WS_MAC
639 /* Advance width for standard watermark width: */
640 if (!m_strPixmapName.isEmpty())
641 iWidth += 145;
642 /* Advance height for spacing & title height: */
643 if (m_pLayoutRight)
644 {
645 int iL, iT, iR, iB;
646 m_pLayoutRight->getContentsMargins(&iL, &iT, &iR, &iB);
647 iHeight += iT + m_pLayoutRight->spacing() + iB;
648 }
649 if (m_pLabelPageTitle)
650 iHeight += m_pLabelPageTitle->minimumSizeHint().height();
651#endif /* !VBOX_WS_MAC */
652 const double dRatio = (double)iWidth / iHeight;
653 if (dRatio > 1.6)
654 break;
655
656 /* Advance current width: */
657 iCurrentWidth += iStepWidth;
658 }
659 while (iCurrentWidth < iMaxWidth);
660 }
661
662#ifdef VBOX_WS_MAC
663 /* Assign background finally: */
664 if (!m_strPixmapName.isEmpty())
665 assignBackground();
666#else
667 /* Assign watermark finally: */
668 if (!m_strPixmapName.isEmpty())
669 assignWatermark();
670#endif /* !VBOX_WS_MAC */
671
672 /* Make sure layouts are freshly updated & activated: */
673 foreach (QLayout *pLayout, findChildren<QLayout*>())
674 {
675 pLayout->update();
676 pLayout->activate();
677 }
678 QCoreApplication::sendPostedEvents(0, QEvent::LayoutRequest);
679
680 /* Resize to minimum size-hint: */
681 resize(minimumSizeHint());
682}
683
684bool UINativeWizard::isLastVisiblePage(int iPageIndex) const
685{
686 if (!m_pWidgetStack)
687 return false;
688 if (iPageIndex == -1)
689 return false;
690 /* The page itself is not visible: */
691 if (m_invisiblePages.contains(iPageIndex))
692 return false;
693 bool fLastVisible = true;
694 /* Look at the page coming after the page with @p iPageIndex and check if they are visible: */
695 for (int i = iPageIndex + 1; i < m_pWidgetStack->count(); ++i)
696 {
697 if (!m_invisiblePages.contains(i))
698 {
699 fLastVisible = false;
700 break;
701 }
702 }
703 return fLastVisible;
704}
705
706#ifdef VBOX_WS_MAC
707void UINativeWizard::assignBackground()
708{
709 /* Load pixmap to icon first, this will gather HiDPI pixmaps as well: */
710 const QIcon icon = UIIconPool::iconSet(m_strPixmapName);
711
712 /* Acquire pixmap of required size and scale (on basis of parent-widget's device pixel ratio): */
713 const QSize standardSize(620, 440);
714 const qreal fDevicePixelRatio = parentWidget() && parentWidget()->windowHandle() ? parentWidget()->windowHandle()->devicePixelRatio() : 1;
715 const QPixmap pixmapOld = icon.pixmap(standardSize, fDevicePixelRatio);
716
717 /* Assign background finally: */
718 m_pLabelPixmap->setPixmap(pixmapOld);
719 m_pLabelPixmap->resize(m_pLabelPixmap->minimumSizeHint());
720}
721
722#else
723
724void UINativeWizard::assignWatermark()
725{
726 /* Load pixmap to icon first, this will gather HiDPI pixmaps as well: */
727 const QIcon icon = UIIconPool::iconSet(m_strPixmapName);
728
729 /* Acquire pixmap of required size and scale (on basis of parent-widget's device pixel ratio): */
730 const QSize standardSize(145, 290);
731 const qreal fDevicePixelRatio = parentWidget() && parentWidget()->windowHandle() ? parentWidget()->windowHandle()->devicePixelRatio() : 1;
732 const QPixmap pixmapOld = icon.pixmap(standardSize, fDevicePixelRatio);
733
734 /* Convert watermark to image which allows to manage pixel data directly: */
735 const QImage imageOld = pixmapOld.toImage();
736 /* Use the right-top watermark pixel as frame color: */
737 const QRgb rgbFrame = imageOld.pixel(imageOld.width() - 1, 0);
738
739 /* Compose desired height up to pixmap device pixel ratio: */
740 int iL, iT, iR, iB;
741 m_pLayoutRight->getContentsMargins(&iL, &iT, &iR, &iB);
742 const int iSpacing = iT + m_pLayoutRight->spacing() + iB;
743 const int iTitleHeight = m_pLabelPageTitle->minimumSizeHint().height();
744 const int iStackHeight = m_pWidgetStack->minimumSizeHint().height();
745 const int iDesiredHeight = (iTitleHeight + iSpacing + iStackHeight) * pixmapOld.devicePixelRatio();
746 /* Create final image on the basis of incoming, applying the rules: */
747 QImage imageNew(imageOld.width(), qMax(imageOld.height(), iDesiredHeight), imageOld.format());
748 for (int y = 0; y < imageNew.height(); ++y)
749 {
750 for (int x = 0; x < imageNew.width(); ++x)
751 {
752 /* Border rule: */
753 if (x == imageNew.width() - 1)
754 imageNew.setPixel(x, y, rgbFrame);
755 /* Horizontal extension rule - use last used color: */
756 else if (x >= imageOld.width() && y < imageOld.height())
757 imageNew.setPixel(x, y, imageOld.pixel(imageOld.width() - 1, y));
758 /* Vertical extension rule - use last used color: */
759 else if (y >= imageOld.height() && x < imageOld.width())
760 imageNew.setPixel(x, y, imageOld.pixel(x, imageOld.height() - 1));
761 /* Common extension rule - use last used color: */
762 else if (x >= imageOld.width() && y >= imageOld.height())
763 imageNew.setPixel(x, y, imageOld.pixel(imageOld.width() - 1, imageOld.height() - 1));
764 /* Else just copy color: */
765 else
766 imageNew.setPixel(x, y, imageOld.pixel(x, y));
767 }
768 }
769
770 /* Convert processed image to pixmap: */
771 QPixmap pixmapNew = QPixmap::fromImage(imageNew);
772 /* For HiDPI support parent-widget's device pixel ratio is to be taken into account: */
773 double dRatio = 1.0;
774 if ( parentWidget()
775 && parentWidget()->window()
776 && parentWidget()->window()->windowHandle())
777 dRatio = parentWidget()->window()->windowHandle()->devicePixelRatio();
778 pixmapNew.setDevicePixelRatio(dRatio);
779 /* Assign watermark finally: */
780 m_pLabelPixmap->setPixmap(pixmapNew);
781}
782
783#endif /* !VBOX_WS_MAC */
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette