VirtualBox

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

Last change on this file since 104158 was 103961, checked in by vboxsync, 9 months ago

FE/Qt. bugref:10622. Using new UITranslationEventListener in wizard class hierarchy.

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