VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/wizards/UIWizard.cpp@ 82781

Last change on this file since 82781 was 79365, checked in by vboxsync, 5 years ago

Renaming VBoxGlobal to UICommon for bugref:9049 as planned.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.0 KB
Line 
1/* $Id: UIWizard.cpp 79365 2019-06-26 15:57:32Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UIWizard class implementation.
4 */
5
6/*
7 * Copyright (C) 2009-2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18/* Qt includes: */
19#include <QAbstractButton>
20#include <QLayout>
21#include <QStyle>
22
23/* GUI includes: */
24#include "UIIconPool.h"
25#include "UIWizard.h"
26#include "UIWizardPage.h"
27#include "UICommon.h"
28#include "QIRichTextLabel.h"
29#include "UIExtraDataManager.h"
30
31/* Qt includes: */
32#include <QtMath>
33
34
35void UIWizard::prepare()
36{
37 /* Translate wizard: */
38 retranslateUi();
39 /* Translate wizard pages: */
40 retranslatePages();
41
42 /* Resize wizard to 'golden ratio': */
43 resizeToGoldenRatio();
44
45 /* Notify pages they are ready: */
46 QList<int> ids = pageIds();
47 for (int i = 0; i < ids.size(); ++i)
48 qobject_cast<UIWizardPage*>(page(ids[i]))->markReady();
49
50 /* Make sure custom buttons shown even if final page is first to show: */
51 sltCurrentIdChanged(startId());
52}
53
54UIWizard::UIWizard(QWidget *pParent, WizardType enmType, WizardMode enmMode /* = WizardMode_Auto */)
55 : QIWithRetranslateUI<QWizard>(pParent)
56 , m_enmType(enmType)
57 , m_enmMode(enmMode == WizardMode_Auto ? gEDataManager->modeForWizardType(m_enmType) : enmMode)
58{
59#ifdef VBOX_WS_WIN
60 /* Hide window icon: */
61 setWindowIcon(QIcon());
62#endif
63
64#ifdef VBOX_WS_MAC
65 // WORKAROUND:
66 // Since wizards are now represented as Mac OS X Sheets
67 // we would like to have possibility to cancel them.
68 setOption(QWizard::NoCancelButton, false);
69
70 // WORKAROUND:
71 // I'm really not sure why there shouldn't be any default button on Mac OS X.
72 // This prevents the using of Enter to jump to the next page.
73 setOptions(options() ^ QWizard::NoDefaultButton);
74#endif /* VBOX_WS_MAC */
75
76 /* Using window-modality: */
77 setWindowModality(Qt::WindowModal);
78
79 /* Setup connections: */
80 connect(this, &UIWizard::currentIdChanged, this, &UIWizard::sltCurrentIdChanged);
81 connect(this, &UIWizard::customButtonClicked, this, &UIWizard::sltCustomButtonClicked);
82}
83
84void UIWizard::retranslateUi()
85{
86 /* Translate basic/expert button: */
87 switch (m_enmMode)
88 {
89 case WizardMode_Basic:
90 setButtonText(QWizard::CustomButton1, tr("&Expert Mode"));
91 button(QWizard::CustomButton1)->setToolTip(tr("Switch to <nobr><b>Expert Mode</b></nobr>, a one-page dialog for experienced users."));
92 break;
93 case WizardMode_Expert:
94 setButtonText(QWizard::CustomButton1, tr("&Guided Mode"));
95 button(QWizard::CustomButton1)->setToolTip(tr("Switch to <nobr><b>Guided Mode</b></nobr>, a step-by-step dialog with detailed explanations."));
96 break;
97 default:
98 AssertMsgFailed(("Invalid mode: %d", m_enmMode));
99 break;
100 }
101}
102
103void UIWizard::showEvent(QShowEvent *pEvent)
104{
105 /* Resize to minimum possible size: */
106 resize(0, 0);
107
108 /* Call to base-class: */
109 QWizard::showEvent(pEvent);
110}
111
112void UIWizard::setPage(int iId, UIWizardPage *pPage)
113{
114 /* Configure page first: */
115 configurePage(pPage);
116 /* Add page finally: */
117 QWizard::setPage(iId, pPage);
118}
119
120void UIWizard::cleanup()
121{
122 /* Remove all the pages: */
123 const QList<int> ids = pageIds();
124 for (int i = ids.size() - 1; i >= 0 ; --i)
125 {
126 /* Get enumerated page ID: */
127 const int iId = ids.at(i);
128 /* Get corresponding page: */
129 QWizardPage *pWizardPage = page(iId);
130
131 /* Remove page from the wizard: */
132 removePage(iId);
133 /* Delete page finally: */
134 delete pWizardPage;
135 }
136
137#ifndef VBOX_WS_MAC
138 /* Cleanup watermark: */
139 if (!m_strWatermarkName.isEmpty())
140 setPixmap(QWizard::WatermarkPixmap, QPixmap());
141#endif
142}
143
144void UIWizard::resizeToGoldenRatio()
145{
146 /* Check if wizard is in basic or expert mode: */
147 if (m_enmMode == WizardMode_Expert)
148 {
149 // WORKAROUND:
150 // Unfortunately QWizard hides some of useful API in private part,
151 // and also have few layouting bugs which could be easy fixed
152 // by that API, so we will use QWizard::restart() method
153 // to call the same functionality indirectly...
154 // Early call restart() which is usually goes on show()!
155 restart();
156
157 // WORKAROUND:
158 // Now we have correct label size-hint(s) for all the pages.
159 // We have to make sure all the pages uses maximum available size-hint.
160 QSize maxOfSizeHints;
161 QList<UIWizardPage*> pages = findChildren<UIWizardPage*>();
162 /* Search for the maximum available size-hint: */
163 foreach (UIWizardPage *pPage, pages)
164 {
165 maxOfSizeHints.rwidth() = pPage->sizeHint().width() > maxOfSizeHints.width() ?
166 pPage->sizeHint().width() : maxOfSizeHints.width();
167 maxOfSizeHints.rheight() = pPage->sizeHint().height() > maxOfSizeHints.height() ?
168 pPage->sizeHint().height() : maxOfSizeHints.height();
169 }
170 /* Feat corresponding height: */
171 maxOfSizeHints.setWidth(qMax((int)(1.5 * maxOfSizeHints.height()), maxOfSizeHints.width()));
172 /* Use that size-hint for all the pages: */
173 foreach (UIWizardPage *pPage, pages)
174 pPage->setMinimumSize(maxOfSizeHints);
175
176 /* Relayout widgets: */
177 QList<QLayout*> layouts = findChildren<QLayout*>();
178 foreach(QLayout *pLayout, layouts)
179 pLayout->activate();
180
181 // WORKAROUND:
182 // Unfortunately QWizard hides some of useful API in private part,
183 // and also have few layouting bugs which could be easy fixed
184 // by that API, so we will use QWizard::restart() method
185 // to call the same functionality indirectly...
186 // And now we call restart() after layout activation procedure!
187 restart();
188
189 /* Resize it to minimum size: */
190 resize(QSize(0, 0));
191 }
192 else
193 {
194 /* Use some small (!) initial QIRichTextLabel width: */
195 int iInitialLabelWidth = 200;
196
197 /* Resize wizard according that initial width,
198 * actually there could be other content
199 * which wants to be wider than that initial width. */
200 resizeAccordingLabelWidth(iInitialLabelWidth);
201
202 /* Get some (first) of those pages: */
203 QList<int> pids = pageIds();
204 UIWizardPage *pPage = qobject_cast<UIWizardPage*>(page(pids.first()));
205 /* Calculate actual label width: */
206 int iPageWidth = pPage->minimumWidth();
207 int iLeft, iTop, iRight, iBottom;
208 pPage->layout()->getContentsMargins(&iLeft, &iTop, &iRight, &iBottom);
209 int iCurrentLabelWidth = iPageWidth - iLeft - iRight;
210 /* Calculate summary margin length,
211 * including margins of the page and the wizard: */
212 int iMarginsLength = width() - iCurrentLabelWidth;
213
214 /* Get current wizard width and height: */
215 int iCurrentWizardWidth = width();
216 int iCurrentWizardHeight = height();
217#ifndef VBOX_WS_MAC
218 /* Calculate metric and ratio: */
219 const int iIconMetric = QApplication::style()->pixelMetric(QStyle::PM_LargeIconSize);
220 const double dRatio = (double)iIconMetric / 32;
221 /* Load pixmap to icon first: */
222 QIcon icon = UIIconPool::iconSet(m_strWatermarkName);
223 QSize size = icon.availableSizes().value(0, QSize(145, 290));
224 size *= dRatio;
225 /* We should take into account watermark like its assigned already: */
226 QPixmap watermarkPixmap(icon.pixmap(size));
227 const int iWatermarkWidth = watermarkPixmap.width() * dRatio;
228 iCurrentWizardWidth += iWatermarkWidth;
229#endif /* !VBOX_WS_MAC */
230 /* Calculating nearest to 'golden ratio' label width: */
231 int iGoldenRatioWidth = (int)qSqrt(ratio() * iCurrentWizardWidth * iCurrentWizardHeight);
232 int iProposedLabelWidth = iGoldenRatioWidth - iMarginsLength;
233#ifndef VBOX_WS_MAC
234 /* We should take into account watermark like its assigned already: */
235 iProposedLabelWidth -= iWatermarkWidth;
236#endif /* !VBOX_WS_MAC */
237
238 /* Choose maximum between current and proposed label width: */
239 int iNewLabelWidth = qMax(iCurrentLabelWidth, iProposedLabelWidth);
240
241 /* Finally resize wizard according new label width,
242 * taking into account all the content and 'golden ratio' rule: */
243 resizeAccordingLabelWidth(iNewLabelWidth);
244 }
245
246#ifndef VBOX_WS_MAC
247 /* Really assign watermark: */
248 if (!m_strWatermarkName.isEmpty())
249 assignWatermarkHelper();
250#endif
251}
252
253#ifndef VBOX_WS_MAC
254
255void UIWizard::assignWatermark(const QString &strWatermark)
256{
257 m_strWatermarkName = strWatermark;
258}
259
260#else
261
262void UIWizard::assignBackground(const QString &strBackground)
263{
264 setPixmap(QWizard::BackgroundPixmap, strBackground);
265}
266
267#endif
268
269void UIWizard::sltCurrentIdChanged(int iId)
270{
271 /* Hide/show description button disabled by default: */
272 bool fIsHideShowDescriptionButtonAvailable = false;
273 /* Enable hide/show description button for 1st page: */
274 if (iId == 0)
275 fIsHideShowDescriptionButtonAvailable = true;
276 /* But first-run wizard has no such button anyway: */
277 if (m_enmType == WizardType_FirstRun)
278 fIsHideShowDescriptionButtonAvailable = false;
279 /* Set a flag for hide/show description button finally: */
280 setOption(QWizard::HaveCustomButton1, fIsHideShowDescriptionButtonAvailable);
281}
282
283void UIWizard::sltCustomButtonClicked(int iId)
284{
285 /* Handle 1st button: */
286 if (iId == CustomButton1)
287 {
288 /* Cleanup: */
289 cleanup();
290
291 /* Toggle mode: */
292 switch (m_enmMode)
293 {
294 case WizardMode_Basic: m_enmMode = WizardMode_Expert; break;
295 case WizardMode_Expert: m_enmMode = WizardMode_Basic; break;
296 default: AssertMsgFailed(("Invalid mode: %d", m_enmMode)); break;
297 }
298 /* Save mode: */
299 gEDataManager->setModeForWizardType(m_enmType, m_enmMode);
300
301 /* Prepare: */
302 prepare();
303 }
304}
305
306void UIWizard::retranslatePages()
307{
308 /* Translate all the pages: */
309 QList<int> ids = pageIds();
310 for (int i = 0; i < ids.size(); ++i)
311 qobject_cast<UIWizardPage*>(page(ids[i]))->retranslate();
312}
313
314void UIWizard::configurePage(UIWizardPage *pPage)
315{
316 /* Page margins: */
317 switch (wizardStyle())
318 {
319 case QWizard::ClassicStyle:
320 {
321 int iLeft, iTop, iRight, iBottom;
322 pPage->layout()->getContentsMargins(&iLeft, &iTop, &iRight, &iBottom);
323 pPage->layout()->setContentsMargins(iLeft, iTop, 0, 0);
324 break;
325 }
326 default:
327 break;
328 }
329}
330
331void UIWizard::resizeAccordingLabelWidth(int iLabelsWidth)
332{
333 // WORKAROUND:
334 // Unfortunately QWizard hides some of useful API in private part,
335 // and also have few layouting bugs which could be easy fixed
336 // by that API, so we will use QWizard::restart() method
337 // to call the same functionality indirectly...
338 // Early call restart() which is usually goes on show()!
339 restart();
340
341 /* Update QIRichTextLabel(s) text-width(s): */
342 QList<QIRichTextLabel*> labels = findChildren<QIRichTextLabel*>();
343 foreach (QIRichTextLabel *pLabel, labels)
344 pLabel->setMinimumTextWidth(iLabelsWidth);
345
346 /* Now we have correct label size-hint(s) for all the pages.
347 * We have to make sure all the pages uses maximum available size-hint. */
348 QSize maxOfSizeHints;
349 QList<UIWizardPage*> pages = findChildren<UIWizardPage*>();
350 /* Search for the maximum available size-hint: */
351 foreach (UIWizardPage *pPage, pages)
352 {
353 maxOfSizeHints.rwidth() = pPage->sizeHint().width() > maxOfSizeHints.width() ?
354 pPage->sizeHint().width() : maxOfSizeHints.width();
355 maxOfSizeHints.rheight() = pPage->sizeHint().height() > maxOfSizeHints.height() ?
356 pPage->sizeHint().height() : maxOfSizeHints.height();
357 }
358 /* Use that size-hint for all the pages: */
359 foreach (UIWizardPage *pPage, pages)
360 pPage->setMinimumSize(maxOfSizeHints);
361
362 /* Relayout widgets: */
363 QList<QLayout*> layouts = findChildren<QLayout*>();
364 foreach(QLayout *pLayout, layouts)
365 pLayout->activate();
366
367 // WORKAROUND:
368 // Unfortunately QWizard hides some of useful API in private part,
369 // and also have few layouting bugs which could be easy fixed
370 // by that API, so we will use QWizard::restart() method
371 // to call the same functionality indirectly...
372 // And now we call restart() after layout activation procedure!
373 restart();
374
375 /* Resize it to minimum size: */
376 resize(QSize(0, 0));
377}
378
379double UIWizard::ratio() const
380{
381 /* Default value: */
382 double dRatio = 1.6;
383
384#ifdef VBOX_WS_WIN
385 switch (wizardStyle())
386 {
387 case QWizard::ClassicStyle:
388 case QWizard::ModernStyle:
389 // WORKAROUND:
390 // There is a Qt bug about Windows7 do NOT match conditions for 'aero' wizard-style,
391 // so its silently fallbacks to 'modern' one without any notification,
392 // so QWizard::wizardStyle() returns QWizard::ModernStyle, while using aero, at least partially.
393 if (QSysInfo::windowsVersion() != QSysInfo::WV_WINDOWS7)
394 {
395 dRatio = 2;
396 break;
397 }
398 case QWizard::AeroStyle:
399 dRatio = 2.2;
400 break;
401 default:
402 break;
403 }
404#endif /* VBOX_WS_WIN */
405
406 switch (m_enmType)
407 {
408 case WizardType_CloneVM:
409 dRatio -= 0.4;
410 break;
411 case WizardType_NewVD:
412 case WizardType_CloneVD:
413 dRatio += 0.1;
414 break;
415 case WizardType_ExportAppliance:
416 case WizardType_ImportAppliance:
417 dRatio += 0.3;
418 break;
419 case WizardType_NewCloudVM:
420 dRatio += 0.7;
421 break;
422 case WizardType_FirstRun:
423 dRatio += 0.3;
424 break;
425 default:
426 break;
427 }
428
429 /* Return final result: */
430 return dRatio;
431}
432
433#ifndef VBOX_WS_MAC
434int UIWizard::proposedWatermarkHeight()
435{
436 /* We should calculate suitable height for watermark pixmap,
437 * for that we have to take into account:
438 * 1. wizard-layout top-margin (for modern style),
439 * 2. wizard-header height,
440 * 3. spacing between wizard-header and wizard-page,
441 * 4. wizard-page height,
442 * 5. wizard-layout bottom-margin (for modern style). */
443
444 /* Get current application style: */
445 QStyle *pStyle = QApplication::style();
446
447 /* Acquire wizard-layout top-margin: */
448 int iTopMargin = 0;
449 if (m_enmMode == WizardMode_Basic)
450 {
451 if (wizardStyle() == QWizard::ModernStyle)
452 iTopMargin = pStyle->pixelMetric(QStyle::PM_LayoutTopMargin);
453 }
454
455 /* Acquire wizard-header height: */
456 int iTitleHeight = 0;
457 if (m_enmMode == WizardMode_Basic)
458 {
459 /* We have no direct access to QWizardHeader inside QWizard private data...
460 * From Qt sources it seems title font is hardcoded as current font point-size + 4: */
461 QFont titleFont(QApplication::font());
462 titleFont.setPointSize(titleFont.pointSize() + 4);
463 QFontMetrics titleFontMetrics(titleFont);
464 iTitleHeight = titleFontMetrics.height();
465 }
466
467 /* Acquire spacing between wizard-header and wizard-page: */
468 int iMarginBetweenTitleAndPage = 0;
469 if (m_enmMode == WizardMode_Basic)
470 {
471 /* We have no direct access to margin between QWizardHeader and wizard-pages...
472 * From Qt sources it seems its hardcoded as just 7 pixels: */
473 iMarginBetweenTitleAndPage = 7;
474 }
475
476 /* Acquire wizard-page height: */
477 int iPageHeight = 0;
478 if (page(0))
479 {
480 iPageHeight = page(0)->minimumSize().height();
481 }
482
483 /* Acquire wizard-layout bottom-margin: */
484 int iBottomMargin = 0;
485 if (wizardStyle() == QWizard::ModernStyle)
486 iBottomMargin = pStyle->pixelMetric(QStyle::PM_LayoutBottomMargin);
487
488 /* Finally, calculate summary height: */
489 return iTopMargin + iTitleHeight + iMarginBetweenTitleAndPage + iPageHeight + iBottomMargin;
490}
491
492void UIWizard::assignWatermarkHelper()
493{
494 /* Calculate metric and ratio: */
495 const int iIconMetric = QApplication::style()->pixelMetric(QStyle::PM_LargeIconSize);
496 const double dRatio = (double)iIconMetric / 32;
497 /* Load pixmap to icon first: */
498 QIcon icon = UIIconPool::iconSet(m_strWatermarkName);
499 QSize size = icon.availableSizes().value(0, QSize(145, 290));
500 size *= dRatio;
501 /* Create initial watermark: */
502 QPixmap pixWaterMark(icon.pixmap(size));
503 /* Convert watermark to image which
504 * allows to manage pixel data directly: */
505 QImage imgWatermark = pixWaterMark.toImage();
506 /* Use the right-top watermark pixel as frame color: */
507 QRgb rgbFrame = imgWatermark.pixel(imgWatermark.width() - 1, 0);
508 /* Create final image on the basis of incoming, applying the rules: */
509 QImage imgWatermarkNew(imgWatermark.width(), qMax(imgWatermark.height(), proposedWatermarkHeight()), imgWatermark.format());
510 for (int y = 0; y < imgWatermarkNew.height(); ++y)
511 {
512 for (int x = 0; x < imgWatermarkNew.width(); ++x)
513 {
514 /* Border rule 1 - draw border for ClassicStyle */
515 if (wizardStyle() == QWizard::ClassicStyle &&
516 (x == 0 || y == 0 || x == imgWatermarkNew.width() - 1 || y == imgWatermarkNew.height() - 1))
517 imgWatermarkNew.setPixel(x, y, rgbFrame);
518 /* Border rule 2 - draw border for ModernStyle */
519 else if (wizardStyle() == QWizard::ModernStyle && x == imgWatermarkNew.width() - 1)
520 imgWatermarkNew.setPixel(x, y, rgbFrame);
521 /* Horizontal extension rule - use last used color */
522 else if (x >= imgWatermark.width() && y < imgWatermark.height())
523 imgWatermarkNew.setPixel(x, y, imgWatermark.pixel(imgWatermark.width() - 1, y));
524 /* Vertical extension rule - use last used color */
525 else if (y >= imgWatermark.height() && x < imgWatermark.width())
526 imgWatermarkNew.setPixel(x, y, imgWatermark.pixel(x, imgWatermark.height() - 1));
527 /* Common extension rule - use last used color */
528 else if (x >= imgWatermark.width() && y >= imgWatermark.height())
529 imgWatermarkNew.setPixel(x, y, imgWatermark.pixel(imgWatermark.width() - 1, imgWatermark.height() - 1));
530 /* Else just copy color */
531 else
532 imgWatermarkNew.setPixel(x, y, imgWatermark.pixel(x, y));
533 }
534 }
535 /* Convert processed image to pixmap and assign it to wizard's watermark. */
536 QPixmap pixWatermarkNew = QPixmap::fromImage(imgWatermarkNew);
537 setPixmap(QWizard::WatermarkPixmap, pixWatermarkNew);
538}
539#endif /* !VBOX_WS_MAC */
540
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use