VirtualBox

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

Last change on this file since 103551 was 101559, checked in by vboxsync, 14 months ago

FE/Qt: bugref:10450: Get rid of Qt5 stuff; This one is about device-pixel-ratio stuff.

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