VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/settings/UIAdvancedSettingsDialog.cpp

Last change on this file was 106064, checked in by vboxsync, 3 days ago

FE/Qt: bugref:10513: Global Preferences / VM Settings: Handle Alt+NUMERIC mnemonics for a tab-widget of current page.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 56.1 KB
Line 
1/* $Id: UIAdvancedSettingsDialog.cpp 106064 2024-09-16 17:26:03Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UIAdvancedSettingsDialog class implementation.
4 */
5
6/*
7 * Copyright (C) 2006-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 <QAbstractButton>
30#include <QAbstractScrollArea>
31#include <QAbstractSpinBox>
32#include <QApplication>
33#include <QCheckBox>
34#include <QCloseEvent>
35#include <QComboBox>
36#include <QCoreApplication>
37#include <QGridLayout>
38#include <QPainter>
39#include <QPainterPath>
40#include <QProgressBar>
41#include <QPropertyAnimation>
42#include <QPushButton>
43#include <QScrollArea>
44#include <QScrollBar>
45#include <QSlider>
46#include <QStackedWidget>
47#include <QTimer>
48#include <QToolButton>
49#include <QVariant>
50#include <QVBoxLayout>
51
52/* GUI includes: */
53#include "QIDialogButtonBox.h"
54#include "QILineEdit.h"
55#include "UIAdvancedSettingsDialog.h"
56#include "UIAnimationFramework.h"
57#include "UICommon.h"
58#include "UIDesktopWidgetWatchdog.h"
59#include "UIExtraDataManager.h"
60#include "UIIconPool.h"
61#include "UIImageTools.h"
62#include "UILoggingDefs.h"
63#include "UIMessageCenter.h"
64#include "UIModalWindowManager.h"
65#include "UIPopupCenter.h"
66#include "UISettingsPage.h"
67#include "UISettingsPageValidator.h"
68#include "UISettingsSelector.h"
69#include "UISettingsSerializer.h"
70#include "UISettingsWarningPane.h"
71#include "UIShortcutPool.h"
72#include "UITranslationEventListener.h"
73#ifdef VBOX_WS_MAC
74# include "VBoxUtils.h"
75#endif
76
77
78/** QCheckBox subclass used as mode checkbox. */
79class UIModeCheckBox : public QCheckBox
80{
81 Q_OBJECT;
82
83public:
84
85 /** Constructs checkbox passing @a pParent to the base-class. */
86 UIModeCheckBox(QWidget *pParent);
87
88 /** Returns text 1. */
89 QString text1() const { return m_strText1; }
90 /** Defines @a strText1. */
91 void setText1(const QString &strText1);
92 /** Returns text 2. */
93 QString text2() const { return m_strText2; }
94 /** Defines @a strText2. */
95 void setText2(const QString &strText2);
96
97protected:
98
99 /** Handles any @a pEvent. */
100 virtual bool event(QEvent *pEvent) RT_OVERRIDE;
101
102 /** Handles paint @a pEvent. */
103 virtual void paintEvent(QPaintEvent *pEvent) RT_OVERRIDE;
104
105 /** Calculates and returns minimum size-hint. */
106 virtual QSize minimumSizeHint() const RT_OVERRIDE;
107
108private:
109
110 /** Returns text 1. */
111 QString m_strText1;
112 /** Returns text 2. */
113 QString m_strText2;
114};
115
116
117/** QWidget reimplementation
118 * wrapping QILineEdit and
119 * representing filter editor for advanced settings dialog. */
120class UIFilterEditor : public QWidget
121{
122 Q_OBJECT;
123 Q_PROPERTY(int editorWidth READ editorWidth WRITE setEditorWidth);
124 Q_PROPERTY(int unfocusedEditorWidth READ unfocusedEditorWidth);
125 Q_PROPERTY(int focusedEditorWidth READ focusedEditorWidth);
126
127signals:
128
129 /** Notifies listeners about @a strText changed. */
130 void sigTextChanged(const QString &strText);
131
132 /** Notifies listeners about editor focused. */
133 void sigFocused();
134 /** Notifies listeners about editor unfocused. */
135 void sigUnfocused();
136
137public:
138
139 /** Constructs filter editor passing @a pParent to the base-class. */
140 UIFilterEditor(QWidget *pParent);
141 /** Destructs filter editor. */
142 virtual ~UIFilterEditor() RT_OVERRIDE;
143
144 /** Defines placeholder @a strText. */
145 void setPlaceholderText(const QString &strText);
146
147 /** Returns filter editor text. */
148 QString text() const;
149
150protected:
151
152 /** Returns the minimum widget size. */
153 virtual QSize minimumSizeHint() const RT_OVERRIDE;
154
155 /** Preprocesses Qt @a pEvent for passed @a pObject. */
156 virtual bool eventFilter(QObject *pObject, QEvent *pEvent) RT_OVERRIDE;
157
158 /** Handles resize @a pEvent. */
159 virtual void resizeEvent(QResizeEvent *pEvent) RT_OVERRIDE;
160
161 /** Handles paint @a pEvent. */
162 virtual void paintEvent(QPaintEvent *pEvent) RT_OVERRIDE;
163
164private slots:
165
166 /** Handles editor @a strText change. */
167 void sltHandleEditorTextChanged(const QString &strText);
168 /** Handles button click. */
169 void sltHandleButtonClicked();
170
171private:
172
173 /** Prepares all. */
174 void prepare();
175 /** Cleanups all. */
176 void cleanup();
177
178 /** Returns painter path for the passed @a pathRect. */
179 static QPainterPath cookPainterPath(const QRect &pathRect, int iRadius);
180
181 /** Adjusts editor geometry. */
182 void adjustEditorGeometry();
183 /** Adjusts editor button icon. */
184 void adjustEditorButtonIcon();
185
186 /** Defines internal widget @a iWidth. */
187 void setEditorWidth(int iWidth);
188 /** Returns internal widget width. */
189 int editorWidth() const;
190 /** Returns internal widget width when it's unfocused. */
191 int unfocusedEditorWidth() const { return m_iUnfocusedEditorWidth; }
192 /** Returns internal widget width when it's focused. */
193 int focusedEditorWidth() const { return m_iFocusedEditorWidth; }
194
195 /** Holds the decoration radius. */
196 int m_iRadius;
197
198 /** Holds the filter editor instance. */
199 QILineEdit *m_pLineEdit;
200 /** Holds the filter reset button instance. */
201 QToolButton *m_pToolButton;
202
203 /** Holds whether filter editor focused. */
204 bool m_fFocused;
205 /** Holds unfocused filter editor width. */
206 int m_iUnfocusedEditorWidth;
207 /** Holds focused filter editor width. */
208 int m_iFocusedEditorWidth;
209 /** Holds the animation framework object. */
210 UIAnimation *m_pAnimation;
211};
212
213
214/** QScrollArea extension to be used for
215 * advanced settings dialog. The idea is to make
216 * vertical scroll-bar always visible, keeping
217 * horizontal scroll-bar always hidden. */
218class UIVerticalScrollArea : public QScrollArea
219{
220 Q_OBJECT;
221 Q_PROPERTY(int verticalScrollBarPosition READ verticalScrollBarPosition WRITE setVerticalScrollBarPosition);
222
223signals:
224
225 /** Notifies listeners about wheel-event. */
226 void sigWheelEvent();
227
228public:
229
230 /** Constructs vertical scroll-area passing @a pParent to the base-class. */
231 UIVerticalScrollArea(QWidget *pParent);
232
233 /** Returns vertical scrollbar position. */
234 int verticalScrollBarPosition() const;
235 /** Defines vertical scrollbar @a iPosition. */
236 void setVerticalScrollBarPosition(int iPosition) const;
237
238 /** Requests vertical scrollbar @a iPosition. */
239 void requestVerticalScrollBarPosition(int iPosition);
240
241protected:
242
243 /** Returns the minimum widget size. */
244 virtual QSize minimumSizeHint() const RT_OVERRIDE;
245
246 /** Handles wheel @a pEvent. */
247 virtual void wheelEvent(QWheelEvent *pEvent) RT_OVERRIDE;
248
249private:
250
251 /** Prepares all. */
252 void prepare();
253
254 /** Holds the vertical scrollbar animation instance. */
255 QPropertyAnimation *m_pAnimation;
256};
257
258
259/*********************************************************************************************************************************
260* Class UIModeCheckBox implementation. *
261*********************************************************************************************************************************/
262
263UIModeCheckBox::UIModeCheckBox(QWidget *pParent)
264 : QCheckBox(pParent)
265{
266 setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
267}
268
269void UIModeCheckBox::setText1(const QString &strText1)
270{
271 m_strText1 = strText1;
272 updateGeometry();
273}
274
275void UIModeCheckBox::setText2(const QString &strText2)
276{
277 m_strText2 = strText2;
278 updateGeometry();
279}
280
281bool UIModeCheckBox::event(QEvent *pEvent)
282{
283 /* Handle desired events: */
284 switch (pEvent->type())
285 {
286 /* Handles mouse button press/release: */
287 case QEvent::MouseButtonPress:
288 case QEvent::MouseButtonRelease:
289 {
290 /* Handle release, ignore press: */
291 if (pEvent->type() == QEvent::MouseButtonRelease)
292 {
293 QMouseEvent *pMouseEvent = static_cast<QMouseEvent*>(pEvent);
294 setCheckState(pMouseEvent->pos().x() < width() / 2 ? Qt::Unchecked : Qt::Checked);
295 }
296 /* Prevent from handling somewhere else: */
297 pEvent->accept();
298 return true;
299 }
300
301 default:
302 break;
303 }
304
305 return QCheckBox::event(pEvent);
306}
307
308void UIModeCheckBox::paintEvent(QPaintEvent *pEvent)
309{
310 /* Prepare painter: */
311 QPainter painter(this);
312 painter.setRenderHint(QPainter::Antialiasing);
313 painter.setRenderHint(QPainter::TextAntialiasing);
314 /* Avoid painting more than necessary: */
315 painter.setClipRect(pEvent->rect());
316
317 /* Acquire useful properties: */
318 const QPalette pal = qApp->palette();
319 QRect contentRect = rect();
320#ifdef VBOX_WS_MAC
321 contentRect.setLeft(contentRect.left() + 2); /// @todo justify!
322 contentRect.setWidth(contentRect.width() - 10); /// @todo justify!
323#endif
324
325 /* Prepare left painter paths: */
326 QPainterPath painterPath1;
327 painterPath1.moveTo(contentRect.x(), contentRect.y());
328 painterPath1.lineTo(contentRect.width() / 2, contentRect.y());
329 painterPath1.lineTo(contentRect.width() / 2 - contentRect.height(), contentRect.height());
330 painterPath1.lineTo(contentRect.x(), contentRect.height());
331 painterPath1.closeSubpath();
332
333 /* Prepare right painter paths: */
334 QPainterPath painterPath2;
335 painterPath2.moveTo(contentRect.width() / 2, contentRect.y());
336 painterPath2.lineTo(contentRect.width(), contentRect.y());
337 painterPath2.lineTo(contentRect.width() - contentRect.height(), contentRect.height());
338 painterPath2.lineTo(contentRect.width() / 2 - contentRect.height(), contentRect.height());
339 painterPath2.closeSubpath();
340
341 /* Prepare left painting gradient: */
342 const QColor backColor1 = pal.color(QPalette::Active, isChecked() ? QPalette::Window : QPalette::Highlight);
343 const QColor bcTone11 = backColor1.lighter(isChecked() ? 120 : 100);
344 const QColor bcTone12 = backColor1.lighter(isChecked() ? 140 : 120);
345 QLinearGradient grad1(painterPath1.boundingRect().topLeft(), painterPath1.boundingRect().bottomRight());
346 grad1.setColorAt(0, bcTone11);
347 grad1.setColorAt(1, bcTone12);
348
349 /* Prepare right painting gradient: */
350 const QColor backColor2 = pal.color(QPalette::Active, isChecked() ? QPalette::Highlight : QPalette::Window);
351 const QColor bcTone21 = backColor2.lighter(isChecked() ? 100 : 120);
352 const QColor bcTone22 = backColor2.lighter(isChecked() ? 120 : 140);
353 QLinearGradient grad2(painterPath2.boundingRect().topLeft(), painterPath2.boundingRect().bottomRight());
354 grad2.setColorAt(0, bcTone21);
355 grad2.setColorAt(1, bcTone22);
356
357 /* Paint fancy shape: */
358 painter.save();
359 painter.fillPath(painterPath1, grad1);
360 painter.strokePath(painterPath1, uiCommon().isInDarkMode() ? backColor1.lighter(120) : backColor1.darker(110));
361 painter.fillPath(painterPath2, grad2);
362 painter.strokePath(painterPath2, uiCommon().isInDarkMode() ? backColor2.lighter(120) : backColor2.darker(110));
363 painter.restore();
364
365 /* Prepare text stuff: */
366 const QFont fnt = font();
367 const QFontMetrics fm(fnt);
368 const QColor foreground1 = suitableForegroundColor(pal, backColor1);
369 const QColor foreground2 = suitableForegroundColor(pal, backColor2);
370 /* Calculate text1 position: */
371 const int iMaxSpace1 = contentRect.width() / 2 - 2 * fm.height();
372 const int iTextSize1 = fm.horizontalAdvance(m_strText1);
373 const int iIndent1 = iMaxSpace1 > iTextSize1 ? (iMaxSpace1 - iTextSize1) / 2 : 0;
374 const QPoint point1 = QPoint(contentRect.left() + 5 /* margin */ + iIndent1,
375 contentRect.height() / 2 + fm.ascent() / 2 - 1 /* base line */);
376 /* Calculate text2 position: */
377 const int iMaxSpace2 = contentRect.width() / 2 - 2 * fm.height();
378 const int iTextSize2 = fm.horizontalAdvance(m_strText2);
379 const int iIndent2 = iMaxSpace2 > iTextSize2 ? (iMaxSpace2 - iTextSize2) / 2 : 0;
380 const QPoint point2 = QPoint(contentRect.width() / 2 + iIndent2,
381 contentRect.height() / 2 + fm.ascent() / 2 - 1 /* base line */);
382
383 /* Paint text: */
384 painter.save();
385 painter.setFont(fnt);
386 painter.setPen(foreground1);
387 painter.drawText(point1, text1());
388 painter.setPen(foreground2);
389 painter.drawText(point2, text2());
390 painter.restore();
391}
392
393QSize UIModeCheckBox::minimumSizeHint() const
394{
395 /* Acquire metrics: */
396 const QFontMetrics fm(font());
397
398 /* Looking for a max text size among those two: */
399 int iMaxLength = 0;
400 iMaxLength = qMax(iMaxLength, fm.horizontalAdvance(m_strText1));
401 iMaxLength = qMax(iMaxLength, fm.horizontalAdvance(m_strText2));
402
403 /* Composing result: */
404 QSize result( 5 /* left margin */
405 + iMaxLength + 2 /* padding */
406 + 2 * fm.height() /* spacing */
407 + iMaxLength + 2 /* padding */
408 + 2 * fm.height() /* right marging */,
409 2 * fm.height() /* vertical hint */);
410 //printf("UIModeCheckBox::minimumSizeHint(%dx%d)\n",
411 // result.width(), result.height());
412 return result;
413}
414
415
416/*********************************************************************************************************************************
417* Class UIFilterEditor implementation. *
418*********************************************************************************************************************************/
419
420UIFilterEditor::UIFilterEditor(QWidget *pParent)
421 : QWidget(pParent)
422 , m_iRadius(0)
423 , m_pLineEdit(0)
424 , m_pToolButton(0)
425 , m_fFocused(false)
426 , m_iUnfocusedEditorWidth(0)
427 , m_iFocusedEditorWidth(0)
428 , m_pAnimation(0)
429{
430 prepare();
431}
432
433UIFilterEditor::~UIFilterEditor()
434{
435 cleanup();
436}
437
438void UIFilterEditor::setPlaceholderText(const QString &strText)
439{
440 if (m_pLineEdit)
441 {
442 m_pLineEdit->setPlaceholderText(strText);
443 adjustEditorGeometry();
444 }
445}
446
447QString UIFilterEditor::text() const
448{
449 return m_pLineEdit ? m_pLineEdit->text() : QString();
450}
451
452QSize UIFilterEditor::minimumSizeHint() const
453{
454 return m_pLineEdit ? m_pLineEdit->minimumSizeHint() : QWidget::minimumSizeHint();
455}
456
457bool UIFilterEditor::eventFilter(QObject *pObject, QEvent *pEvent)
458{
459 /* Preprocess events for m_pLineEdit only: */
460 if (pObject != m_pLineEdit)
461 return QWidget::eventFilter(pObject, pEvent);
462
463 /* Handles various event types: */
464 switch (pEvent->type())
465 {
466 /* Foreard animation on focus-in: */
467 case QEvent::FocusIn:
468 m_fFocused = true;
469 emit sigFocused();
470 update();
471 break;
472 /* Backward animation on focus-out: */
473 case QEvent::FocusOut:
474 m_fFocused = false;
475 emit sigUnfocused();
476 update();
477 break;
478 default:
479 break;
480 }
481
482 /* Call to base-class: */
483 return QWidget::eventFilter(pObject, pEvent);
484}
485
486void UIFilterEditor::resizeEvent(QResizeEvent *pEvent)
487{
488 /* Call to base-class: */
489 QWidget::resizeEvent(pEvent);
490
491 /* Adjust filter editor geometry on each parent resize: */
492 adjustEditorGeometry();
493}
494
495void UIFilterEditor::paintEvent(QPaintEvent *pEvent)
496{
497 /* Prepare painter: */
498 QPainter painter(this);
499 painter.setRenderHint(QPainter::Antialiasing);
500 painter.setRenderHint(QPainter::TextAntialiasing);
501 /* Avoid painting more than necessary: */
502 painter.setClipRect(pEvent->rect());
503
504 /* Prepare colors: */
505 const bool fActive = window() && window()->isActiveWindow();
506 const QPalette::ColorGroup enmColorGroup = fActive ? QPalette::Active : QPalette::Inactive;
507#ifdef VBOX_WS_MAC
508 const QColor colorHighlight = uiCommon().isInDarkMode()
509 ? qApp->palette().color(enmColorGroup, QPalette::Highlight).lighter(110)
510 : qApp->palette().color(enmColorGroup, QPalette::Highlight).darker(110);
511#endif
512 const QColor colorBase = qApp->palette().color(enmColorGroup, QPalette::Base);
513 const QColor colorFrame = uiCommon().isInDarkMode()
514 ? qApp->palette().color(enmColorGroup, QPalette::Window).lighter(120)
515 : qApp->palette().color(enmColorGroup, QPalette::Window).darker(120);
516
517 /* Prepare base/frame painter path: */
518 const QRegion totalRegion = QRegion(m_pLineEdit->geometry()) + QRegion(m_pToolButton->geometry());
519 QRect widgetRect = totalRegion.boundingRect();
520#ifdef VBOX_WS_MAC
521 const QRect focusRect = widgetRect;
522 widgetRect.adjust(3, 3, -3, -3);
523 const QPainterPath focusPath = cookPainterPath(focusRect, m_iRadius + 2);
524#endif
525 const QPainterPath widgetPath = cookPainterPath(widgetRect, m_iRadius);
526
527 /* Draw base/frame: */
528#ifdef VBOX_WS_MAC
529 if (m_pLineEdit->hasFocus())
530 painter.fillPath(focusPath, colorHighlight);
531#endif
532 painter.fillPath(widgetPath, colorBase);
533 painter.strokePath(widgetPath, colorFrame);
534}
535
536void UIFilterEditor::sltHandleEditorTextChanged(const QString &strText)
537{
538 adjustEditorButtonIcon();
539 emit sigTextChanged(strText);
540}
541
542void UIFilterEditor::sltHandleButtonClicked()
543{
544 m_pLineEdit->clear();
545}
546
547void UIFilterEditor::prepare()
548{
549 /* Init the decoration radius: */
550 m_iRadius = 10;
551
552 /* Prepare filter editor: */
553 m_pLineEdit = new QILineEdit(this);
554 if (m_pLineEdit)
555 {
556#ifdef VBOX_WS_MAC
557 /* A bit of magic to be able to replace the frame.
558 * Disable border, adjust margins and make background transparent.
559 * Left and right margins also take focus ring into account. */
560 m_pLineEdit->setStyleSheet("QLineEdit {\
561 background-color: rgba(255, 255, 255, 0%);\
562 border: 0px none black;\
563 margin: 6px 0px 6px 10px;\
564 }");
565#else
566 /* A bit of magic to be able to replace the frame.
567 * Disable border, adjust margins and make background transparent. */
568 m_pLineEdit->setStyleSheet("QLineEdit {\
569 background-color: rgba(255, 255, 255, 0%);\
570 border: 0px none black;\
571 margin: 3px 0px 3px 10px;\
572 }");
573#endif
574 m_pLineEdit->installEventFilter(this);
575 connect(m_pLineEdit, &QILineEdit::textChanged,
576 this, &UIFilterEditor::sltHandleEditorTextChanged);
577 }
578
579 /* Prepare filter reset button: */
580 m_pToolButton = new QToolButton(this);
581 if (m_pToolButton)
582 {
583 m_pToolButton->setStyleSheet("QToolButton {\
584 border: 0px none black;\
585 margin: 0px 5px 0px 5px;\
586 }\
587 QToolButton::menu-indicator {\
588 image: none;\
589 }");
590 m_pToolButton->setIconSize(QSize(10, 10));
591 connect(m_pToolButton, &QToolButton::clicked,
592 this, &UIFilterEditor::sltHandleButtonClicked);
593 }
594
595 /* Install 'unfocus/focus' animation to 'editorWidth' property: */
596 m_pAnimation = UIAnimation::installPropertyAnimation(this,
597 "editorWidth",
598 "unfocusedEditorWidth", "focusedEditorWidth",
599 SIGNAL(sigFocused()), SIGNAL(sigUnfocused()));
600
601 /* Adjust stuff initially: */
602 adjustEditorGeometry();
603 adjustEditorButtonIcon();
604}
605
606void UIFilterEditor::cleanup()
607{
608 /* Cleanup 'unfocus/focus' animation: */
609 delete m_pAnimation;
610 m_pAnimation = 0;
611}
612
613/* static */
614QPainterPath UIFilterEditor::cookPainterPath(const QRect &pathRect, int iRadius)
615{
616 QPainterPath path;
617 const QSizeF arcSize(2 * iRadius, 2 * iRadius);
618 path.moveTo(pathRect.x() + iRadius, pathRect.y());
619 path.arcTo(QRectF(path.currentPosition(), arcSize).translated(-iRadius, 0), 90, 90);
620 path.lineTo(path.currentPosition().x(), path.currentPosition().y() + pathRect.height() - 2 * iRadius);
621 path.arcTo(QRectF(path.currentPosition(), arcSize).translated(0, -iRadius), 180, 90);
622 path.lineTo(path.currentPosition().x() + pathRect.width() - 2 * iRadius, path.currentPosition().y());
623 path.arcTo(QRectF(path.currentPosition(), arcSize).translated(-iRadius, -2 * iRadius), 270, 90);
624 path.lineTo(path.currentPosition().x(), path.currentPosition().y() - pathRect.height() + 2 * iRadius);
625 path.arcTo(QRectF(path.currentPosition(), arcSize).translated(-2 * iRadius, -iRadius), 0, 90);
626 path.closeSubpath();
627 return path;
628}
629
630void UIFilterEditor::adjustEditorGeometry()
631{
632 /* Acquire maximum widget width: */
633 const int iWidth = width();
634
635 /* Acquire filter editor placeholder width: */
636 QFontMetrics fm(m_pLineEdit->font());
637 const int iPlaceholderWidth = fm.horizontalAdvance(m_pLineEdit->placeholderText())
638 + 2 * 5 /* left/right panel/frame width, no pixelMetric */
639 + 10 /* left margin, assigned via setStyleSheet */;
640 /* Acquire filter editor size-hint: */
641 const QSize esh = m_pLineEdit->minimumSizeHint();
642 const int iMinimumEditorWidth = qMax(esh.width(), iPlaceholderWidth);
643 const int iMinimumEditorHeight = esh.height();
644 /* Acquire filter button size-hint: */
645 const QSize bsh = m_pToolButton->minimumSizeHint();
646 const int iMinimumButtonWidth = bsh.width();
647 const int iMinimumButtonHeight = bsh.height();
648
649 /* Update filter button geo: */
650 const int iButtonX = iWidth - iMinimumButtonWidth;
651 const int iButtonY = iMinimumEditorHeight > iMinimumButtonHeight
652 ? (iMinimumEditorHeight - iMinimumButtonHeight) / 2 + 1
653 : 0;
654 m_pToolButton->setGeometry(iButtonX, iButtonY, iMinimumButtonWidth, iMinimumButtonHeight);
655
656 /* Update minimum/maximum filter editor width: */
657 m_iUnfocusedEditorWidth = qMin(iWidth / 2 - iMinimumButtonWidth, iMinimumEditorWidth);
658 m_iFocusedEditorWidth = qMax(iWidth - iMinimumButtonWidth, iMinimumEditorWidth);
659 m_pAnimation->update();
660 setEditorWidth(m_fFocused ? m_iFocusedEditorWidth : m_iUnfocusedEditorWidth);
661}
662
663void UIFilterEditor::adjustEditorButtonIcon()
664{
665 AssertPtrReturnVoid(m_pLineEdit);
666 AssertPtrReturnVoid(m_pToolButton);
667 m_pToolButton->setIcon( m_pLineEdit->text().isEmpty()
668 ? UIIconPool::iconSet(":/search_16px.png")
669 : UIIconPool::iconSet(":/close_16px.png"));
670}
671
672void UIFilterEditor::setEditorWidth(int iEditorWidth)
673{
674 /* Align filter editor right: */
675 const int iX = m_pToolButton->x() - iEditorWidth;
676 const int iY = 0;
677 const int iEditorHeight = m_pLineEdit->minimumSizeHint().height();
678 const QRect oldGeo = m_pLineEdit->geometry();
679 m_pLineEdit->setGeometry(iX, iY, iEditorWidth, iEditorHeight);
680 const QRect newGeo = m_pLineEdit->geometry();
681
682 /* Update rasterizer: */
683 const QRect rasterizer = oldGeo | newGeo;
684 update(rasterizer.adjusted(-1, -1, 1, 1));
685}
686
687int UIFilterEditor::editorWidth() const
688{
689 return m_pLineEdit->width();
690}
691
692
693/*********************************************************************************************************************************
694* Class UIVerticalScrollArea implementation. *
695*********************************************************************************************************************************/
696
697UIVerticalScrollArea::UIVerticalScrollArea(QWidget *pParent)
698 : QScrollArea(pParent)
699 , m_pAnimation(0)
700{
701 prepare();
702}
703
704int UIVerticalScrollArea::verticalScrollBarPosition() const
705{
706 return verticalScrollBar()->value();
707}
708
709void UIVerticalScrollArea::setVerticalScrollBarPosition(int iPosition) const
710{
711 verticalScrollBar()->setValue(iPosition);
712}
713
714void UIVerticalScrollArea::requestVerticalScrollBarPosition(int iPosition)
715{
716 /* Acquire scroll-bar minumum, maximum and length: */
717 const int iScrollBarMinimum = verticalScrollBar()->minimum();
718 const int iScrollBarMaximum = verticalScrollBar()->maximum();
719 const int iScrollBarLength = qAbs(iScrollBarMaximum - iScrollBarMinimum);
720
721 /* Acquire start, final position and total shift:: */
722 const int iStartPosition = verticalScrollBarPosition();
723 const int iFinalPosition = iPosition;
724 int iShift = qAbs(iFinalPosition - iStartPosition);
725 /* Make sure iShift is no more than iScrollBarLength: */
726 iShift = qMin(iShift, iScrollBarLength);
727
728 /* Calculate walking ratio: */
729 const float dRatio = iScrollBarLength > 0 ? (double)iShift / iScrollBarLength : 0;
730 m_pAnimation->setDuration(dRatio * 500 /* 500ms is the max */);
731 m_pAnimation->setStartValue(iStartPosition);
732 m_pAnimation->setEndValue(iFinalPosition);
733 m_pAnimation->start();
734}
735
736QSize UIVerticalScrollArea::minimumSizeHint() const
737{
738 /* To make horizontal scroll-bar always hidden we'll
739 * have to make sure minimum size-hint updated accordingly. */
740 const int iMinWidth = viewportSizeHint().width()
741 + verticalScrollBar()->sizeHint().width()
742 + frameWidth() * 2;
743 const int iMinHeight = qMax(QScrollArea::minimumSizeHint().height(),
744 (int)(iMinWidth / 1.6));
745 return QSize(iMinWidth, iMinHeight);
746}
747
748void UIVerticalScrollArea::wheelEvent(QWheelEvent *pEvent)
749{
750 /* Call to base-class: */
751 QScrollArea::wheelEvent(pEvent);
752
753 /* Notify listeners: */
754 emit sigWheelEvent();
755}
756
757void UIVerticalScrollArea::prepare()
758{
759 /* Make vertical scroll-bar always hidden: */
760 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
761
762 /* Prepare vertical scrollbar animation: */
763 m_pAnimation = new QPropertyAnimation(this, "verticalScrollBarPosition", this);
764}
765
766
767/*********************************************************************************************************************************
768* Class UIAdvancedSettingsDialog implementation. *
769*********************************************************************************************************************************/
770
771UIAdvancedSettingsDialog::UIAdvancedSettingsDialog(QWidget *pParent,
772 const QString &strCategory,
773 const QString &strControl)
774 : QMainWindow(pParent)
775 , m_strCategory(strCategory)
776 , m_strControl(strControl)
777 , m_pSelector(0)
778 , m_enmConfigurationAccessLevel(ConfigurationAccessLevel_Null)
779 , m_pSerializeProcess(0)
780 , m_fPolished(false)
781 , m_fFirstSerializationDone(false)
782 , m_fSerializationIsInProgress(false)
783 , m_fSerializationClean(false)
784 , m_fClosed(false)
785 , m_iPageId(MachineSettingsPageType_Invalid)
786 , m_pStatusBar(0)
787 , m_pProcessBar(0)
788 , m_pWarningPane(0)
789 , m_fValid(true)
790 , m_fSilent(true)
791 , m_pLayoutMain(0)
792 , m_pCheckBoxMode(0)
793 , m_pEditorFilter(0)
794 , m_pScrollArea(0)
795 , m_pScrollViewport(0)
796 , m_pButtonBox(0)
797{
798 prepare();
799}
800
801UIAdvancedSettingsDialog::~UIAdvancedSettingsDialog()
802{
803 cleanup();
804}
805
806void UIAdvancedSettingsDialog::accept()
807{
808 /* Save data: */
809 save();
810
811 /* Close if there is no ongoing serialization: */
812 if (!isSerializationInProgress())
813 sltClose();
814}
815
816void UIAdvancedSettingsDialog::reject()
817{
818 /* Close if there is no ongoing serialization: */
819 if (!isSerializationInProgress())
820 sltClose();
821}
822
823void UIAdvancedSettingsDialog::sltCategoryChanged(int cId)
824{
825 /* Cache current page ID for reusing: */
826 m_iPageId = cId;
827
828 /* Let's calculate required scroll-bar position: */
829 int iPosition = 0;
830 /* We'll have to take upper content's margin into account: */
831 int iL, iT, iR, iB;
832 m_pScrollViewport->layout()->getContentsMargins(&iL, &iT, &iR, &iB);
833 iPosition -= iT;
834 /* And actual page position according to parent: */
835 UISettingsPageFrame *pFrame = m_frames.value(m_iPageId, 0);
836 AssertPtr(pFrame);
837 if (pFrame)
838 {
839 const QPoint pnt = pFrame->pos();
840 iPosition += pnt.y();
841 }
842 /* Make sure corresponding page is visible: */
843 m_pScrollArea->requestVerticalScrollBarPosition(iPosition);
844
845#ifndef VBOX_WS_MAC
846 uiCommon().setHelpKeyword(m_pButtonBox->button(QDialogButtonBox::Help), m_pageHelpKeywords.value(cId));
847#endif
848}
849
850void UIAdvancedSettingsDialog::sltHandleSerializationStarted()
851{
852 m_pProcessBar->setValue(0);
853 m_pStatusBar->setCurrentWidget(m_pProcessBar);
854}
855
856void UIAdvancedSettingsDialog::sltHandleSerializationProgressChange(int iValue)
857{
858 m_pProcessBar->setValue(iValue);
859 if (m_pProcessBar->value() == m_pProcessBar->maximum())
860 {
861 if (!m_fValid || !m_fSilent)
862 m_pStatusBar->setCurrentWidget(m_pWarningPane);
863 else
864 m_pStatusBar->setCurrentIndex(0);
865 }
866}
867
868void UIAdvancedSettingsDialog::sltHandleSerializationFinished()
869{
870 /* Delete serializer if exists: */
871 delete m_pSerializeProcess;
872 m_pSerializeProcess = 0;
873
874 /* Mark serialization finished: */
875 m_fSerializationIsInProgress = false;
876
877 /* Finally make sure layouts freshly activated after
878 * all the pages loaded (as overall size-hint changed): */
879 foreach (QLayout *pLayout, findChildren<QLayout*>())
880 pLayout->activate();
881 /* Update scroll-area geometry finally: */
882 m_pScrollArea->updateGeometry();
883
884 /* For the 1st serialization we have some additional handling: */
885 if (!m_fFirstSerializationDone)
886 {
887 /* Which should be called just once: */
888 m_fFirstSerializationDone = true;
889
890 /* Make sure layout request processed before we resize widget to new size: */
891 QCoreApplication::sendPostedEvents(0, QEvent::LayoutRequest);
892 /* Resize to minimum size: */
893 resize(minimumSizeHint());
894 /* Explicit centering according to our parent: */
895 gpDesktop->centerWidget(this, parentWidget(), false);
896 }
897}
898
899bool UIAdvancedSettingsDialog::eventFilter(QObject *pObject, QEvent *pEvent)
900{
901 /* Handle wheel events: */
902 if (pEvent->type() == QEvent::Wheel)
903 {
904 /* Ignore events to anything but widgets in this handler: */
905 QWidget *pWidget = qobject_cast<QWidget*>(pObject);
906 if (!pWidget)
907 return QMainWindow::eventFilter(pObject, pEvent);
908
909 /* Do not touch wheel events for m_pScrollArea or it's children: */
910 if ( pWidget == m_pScrollArea
911 || pWidget->parent() == m_pScrollArea)
912 return QMainWindow::eventFilter(pWidget, pEvent);
913
914 /* Unconditionally and for good
915 * redirect wheel event for widgets of following types to m_pScrollViewport: */
916 if ( qobject_cast<QAbstractButton*>(pWidget)
917 || qobject_cast<QAbstractSpinBox*>(pWidget)
918 || qobject_cast<QAbstractSpinBox*>(pWidget->parent())
919 || qobject_cast<QComboBox*>(pWidget)
920 || qobject_cast<QSlider*>(pWidget)
921 || qobject_cast<QTabWidget*>(pWidget)
922 || qobject_cast<QTabWidget*>(pWidget->parent()))
923 {
924 /* Check if redirected event was really handled, otherwise give it back: */
925 if (QCoreApplication::sendEvent(m_pScrollViewport, pEvent))
926 return true;
927 }
928
929 /* Unless widget of QAbstractScrollArea subclass is focused
930 * redirect it's wheel event to m_pScrollViewport: */
931 if ( ( qobject_cast<QAbstractScrollArea*>(pWidget)
932 || qobject_cast<QAbstractScrollArea*>(pWidget->parent()))
933 && !pWidget->hasFocus()
934 && !pWidget->parentWidget()->hasFocus())
935 {
936 /* Check if redirected event was really handled, otherwise give it back: */
937 if (QCoreApplication::sendEvent(m_pScrollViewport, pEvent))
938 return true;
939 }
940 }
941
942 /* Handle key-press events: */
943 if (pEvent->type() == QEvent::KeyPress)
944 {
945 /* Convert to key-press event and acquire the key: */
946 QKeyEvent *pKeyEvent = static_cast<QKeyEvent*>(pEvent);
947 const int iKey = pKeyEvent->key();
948 /* Handle Alt+<NUMERIC> menemonics: */
949 if ( pKeyEvent->modifiers() & Qt::AltModifier
950 && iKey >= Qt::Key_1
951 && iKey <= Qt::Key_9)
952 {
953 /* Stop further event handling anyway: */
954 pEvent->accept();
955
956 /* Acquire current page: */
957 const int iCurrentId = m_pSelector->currentId();
958 QWidget *pPage = m_pSelector->idToPage(iCurrentId);
959 if (pPage)
960 {
961 /* Look the page for a suitable tab-widget: */
962 const QList<QTabWidget*> tabWidgets = pPage->findChildren<QTabWidget*>();
963 if (!tabWidgets.isEmpty())
964 {
965 /* Look for a proper tab offset: */
966 const int iShift = iKey - Qt::Key_1;
967 QTabWidget *pTabWidget = tabWidgets.first();
968 int iVisibleTabNumber = 0;
969 for (int i = 0; i < pTabWidget->count(); ++i)
970 if (pTabWidget->isTabVisible(i))
971 {
972 if (iVisibleTabNumber == iShift)
973 {
974 /* Activate proper tab and leave: */
975 pTabWidget->setCurrentIndex(iVisibleTabNumber);
976 break;
977 }
978 ++iVisibleTabNumber;
979 }
980 }
981 }
982 }
983 }
984
985 /* Call to base-class: */
986 return QMainWindow::eventFilter(pObject, pEvent);
987}
988
989void UIAdvancedSettingsDialog::sltRetranslateUI()
990{
991 /* Translate mode checkbox: */
992 m_pCheckBoxMode->setText1(tr("Basic"));
993 m_pCheckBoxMode->setText2(tr("Expert"));
994
995 /* Translate filter editor placeholder: */
996 if (m_pEditorFilter)
997 m_pEditorFilter->setPlaceholderText(tr("Search settings"));
998
999 /* Translate warning-pane stuff: */
1000 m_pWarningPane->setWarningLabelText(tr("Invalid settings detected"));
1001
1002 /* Translate page-frames: */
1003 foreach (int cId, m_frames.keys())
1004 m_frames.value(cId)->setName(m_pSelector->itemText(cId));
1005
1006 /* Retranslate all validators: */
1007 foreach (UISettingsPageValidator *pValidator, findChildren<UISettingsPageValidator*>())
1008 pValidator->setTitlePrefix(m_pSelector->itemTextByPage(pValidator->page()));
1009 revalidate();
1010}
1011
1012void UIAdvancedSettingsDialog::showEvent(QShowEvent *pEvent)
1013{
1014 /* Polish stuff: */
1015 if (!m_fPolished)
1016 polishEvent();
1017
1018 /* Call to base-class: */
1019 QMainWindow::showEvent(pEvent);
1020}
1021
1022void UIAdvancedSettingsDialog::polishEvent()
1023{
1024 /* Prevent handler from calling twice: */
1025 m_fPolished = true;
1026
1027 /* Install event-filters for all the required children.
1028 * These children can be added together with pages. */
1029 foreach (QWidget *pChild, findChildren<QWidget*>())
1030 {
1031 if ( qobject_cast<QAbstractButton*>(pChild)
1032 || qobject_cast<QAbstractScrollArea*>(pChild)
1033 || qobject_cast<QAbstractScrollArea*>(pChild->parent())
1034 || qobject_cast<QAbstractSpinBox*>(pChild)
1035 || qobject_cast<QAbstractSpinBox*>(pChild->parent())
1036 || qobject_cast<QComboBox*>(pChild)
1037 || qobject_cast<QSlider*>(pChild)
1038 || qobject_cast<QTabWidget*>(pChild)
1039 || qobject_cast<QTabWidget*>(pChild->parent()))
1040 pChild->installEventFilter(this);
1041 }
1042
1043 /* Choose page/tab finally: */
1044 choosePageAndTab();
1045
1046 /* Apply actual experience mode: */
1047 sltHandleExperienceModeChanged();
1048
1049 /* Resize to minimum size: */
1050 resize(minimumSizeHint());
1051 /* Explicit centering according to our parent: */
1052 gpDesktop->centerWidget(this, parentWidget(), false);
1053}
1054
1055void UIAdvancedSettingsDialog::closeEvent(QCloseEvent *pEvent)
1056{
1057 /* Ignore event initially: */
1058 pEvent->ignore();
1059
1060 /* Use pure QWidget close functionality,
1061 * QWindow stuff is kind of overkill here.. */
1062 sltClose();
1063}
1064
1065void UIAdvancedSettingsDialog::choosePageAndTab(bool fKeepPreviousByDefault /* = false */)
1066{
1067 /* Setup settings window: */
1068 if (!m_strCategory.isNull())
1069 {
1070 m_pSelector->selectByLink(m_strCategory);
1071 /* Search for a widget with the given name: */
1072 if (!m_strControl.isNull())
1073 {
1074 if (QWidget *pWidget = m_pScrollViewport->findChild<QWidget*>(m_strControl))
1075 {
1076 QList<QWidget*> parents;
1077 QWidget *pParentWidget = pWidget;
1078 while ((pParentWidget = pParentWidget->parentWidget()) != 0)
1079 {
1080 if (QTabWidget *pTabWidget = qobject_cast<QTabWidget*>(pParentWidget))
1081 {
1082 // WORKAROUND:
1083 // The tab contents widget is two steps down
1084 // (QTabWidget -> QStackedWidget -> QWidget).
1085 QWidget *pTabPage = parents[parents.count() - 1];
1086 if (pTabPage)
1087 pTabPage = parents[parents.count() - 2];
1088 if (pTabPage)
1089 pTabWidget->setCurrentWidget(pTabPage);
1090 }
1091 parents.append(pParentWidget);
1092 }
1093 pWidget->setFocus();
1094 }
1095 }
1096 }
1097 /* First item as default (if previous is not guarded): */
1098 else if (!fKeepPreviousByDefault)
1099 m_pSelector->selectById(1);
1100}
1101
1102void UIAdvancedSettingsDialog::loadData(QVariant &data)
1103{
1104 /* Mark serialization started: */
1105 m_fSerializationIsInProgress = true;
1106
1107 /* Create settings loader: */
1108 m_pSerializeProcess = new UISettingsSerializer(this, UISettingsSerializer::Load,
1109 data, m_pSelector->settingPages());
1110 if (m_pSerializeProcess)
1111 {
1112 /* Configure settings loader: */
1113 connect(m_pSerializeProcess, &UISettingsSerializer::sigNotifyAboutProcessStarted,
1114 this, &UIAdvancedSettingsDialog::sltHandleSerializationStarted);
1115 connect(m_pSerializeProcess, &UISettingsSerializer::sigNotifyAboutProcessProgressChanged,
1116 this, &UIAdvancedSettingsDialog::sltHandleSerializationProgressChange);
1117 connect(m_pSerializeProcess, &UISettingsSerializer::sigNotifyAboutProcessFinished,
1118 this, &UIAdvancedSettingsDialog::sltHandleSerializationFinished);
1119
1120 /* Raise current page priority: */
1121 m_pSerializeProcess->raisePriorityOfPage(m_pSelector->currentId());
1122
1123 /* Start settings loader: */
1124 m_pSerializeProcess->start();
1125
1126 /* Upload data finally: */
1127 data = m_pSerializeProcess->data();
1128 }
1129}
1130
1131void UIAdvancedSettingsDialog::saveData(QVariant &data)
1132{
1133 /* Mark serialization started: */
1134 m_fSerializationIsInProgress = true;
1135
1136 /* Create the 'settings saver': */
1137 QPointer<UISettingsSerializerProgress> pDlgSerializeProgress =
1138 new UISettingsSerializerProgress(this, UISettingsSerializer::Save,
1139 data, m_pSelector->settingPages());
1140 if (pDlgSerializeProgress)
1141 {
1142 /* Make the 'settings saver' temporary parent for all sub-dialogs: */
1143 windowManager().registerNewParent(pDlgSerializeProgress, windowManager().realParentWindow(this));
1144
1145 /* Execute the 'settings saver': */
1146 pDlgSerializeProgress->exec();
1147
1148 /* Any modal dialog can be destroyed in own event-loop
1149 * as a part of application termination procedure..
1150 * We have to check if the dialog still valid. */
1151 if (pDlgSerializeProgress)
1152 {
1153 /* Remember whether the serialization was clean: */
1154 m_fSerializationClean = pDlgSerializeProgress->isClean();
1155
1156 /* Upload 'settings saver' data: */
1157 data = pDlgSerializeProgress->data();
1158
1159 /* Delete the 'settings saver': */
1160 delete pDlgSerializeProgress;
1161 }
1162 }
1163}
1164
1165void UIAdvancedSettingsDialog::setConfigurationAccessLevel(ConfigurationAccessLevel enmConfigurationAccessLevel)
1166{
1167 /* Make sure something changed: */
1168 if (m_enmConfigurationAccessLevel == enmConfigurationAccessLevel)
1169 return;
1170
1171 /* Apply new configuration access level: */
1172 m_enmConfigurationAccessLevel = enmConfigurationAccessLevel;
1173
1174 /* And propagate it to settings-page(s): */
1175 foreach (UISettingsPage *pPage, m_pSelector->settingPages())
1176 pPage->setConfigurationAccessLevel(configurationAccessLevel());
1177}
1178
1179void UIAdvancedSettingsDialog::setOptionalFlags(const QMap<QString, QVariant> &flags)
1180{
1181 /* Avoid excessive calls: */
1182 if (m_flags == flags)
1183 return;
1184
1185 /* Save new flags: */
1186 m_flags = flags;
1187
1188 /* Reapply optional flags: */
1189 sltApplyFilteringRules();
1190}
1191
1192void UIAdvancedSettingsDialog::addItem(const QString &strBigIcon,
1193 const QString &strMediumIcon,
1194 const QString &strSmallIcon,
1195 int cId,
1196 const QString &strLink,
1197 UISettingsPage *pSettingsPage /* = 0 */,
1198 int iParentId /* = -1 */)
1199{
1200 /* Init m_iPageId if we haven't yet: */
1201 if (m_iPageId == MachineSettingsPageType_Invalid)
1202 m_iPageId = cId;
1203
1204 /* Add new selector item: */
1205 if (m_pSelector->addItem(strBigIcon, strMediumIcon, strSmallIcon,
1206 cId, strLink, pSettingsPage, iParentId))
1207 {
1208 /* Create frame with page inside: */
1209 UISettingsPageFrame *pFrame = new UISettingsPageFrame(pSettingsPage, m_pScrollViewport);
1210 if (pFrame)
1211 {
1212 /* Add frame to scroll-viewport: */
1213 m_pScrollViewport->layout()->addWidget(pFrame);
1214
1215 /* Remember page-frame for referencing: */
1216 m_frames[cId] = pFrame;
1217
1218 /* Notify about frame visibility changes: */
1219 connect(pFrame, &UISettingsPageFrame::sigVisibilityChange,
1220 this, &UIAdvancedSettingsDialog::sltHandleFrameVisibilityChange);
1221 }
1222 }
1223
1224 /* Assign validator if necessary: */
1225 if (pSettingsPage)
1226 {
1227 pSettingsPage->setId(cId);
1228
1229 /* Create validator: */
1230 UISettingsPageValidator *pValidator = new UISettingsPageValidator(this, pSettingsPage);
1231 connect(pValidator, &UISettingsPageValidator::sigValidityChanged,
1232 this, &UIAdvancedSettingsDialog::sltHandleValidityChange);
1233 pSettingsPage->setValidator(pValidator);
1234 m_pWarningPane->registerValidator(pValidator);
1235
1236 /* Update navigation (tab-order): */
1237 pSettingsPage->setOrderAfter(m_pSelector->widget());
1238 }
1239}
1240
1241void UIAdvancedSettingsDialog::addPageHelpKeyword(int iPageType, const QString &strHelpKeyword)
1242{
1243 m_pageHelpKeywords[iPageType] = strHelpKeyword;
1244}
1245
1246void UIAdvancedSettingsDialog::revalidate()
1247{
1248 /* Perform dialog revalidation: */
1249 m_fValid = true;
1250 m_fSilent = true;
1251
1252 /* Enumerating all the validators we have: */
1253 foreach (UISettingsPageValidator *pValidator, findChildren<UISettingsPageValidator*>())
1254 {
1255 /* Is current validator have something to say? */
1256 if (!pValidator->lastMessage().isEmpty())
1257 {
1258 /* What page is it related to? */
1259 UISettingsPage *pFailedSettingsPage = pValidator->page();
1260 LogRelFlow(("Settings Dialog: Dialog validation FAILED: Page *%s*\n",
1261 pFailedSettingsPage->internalName().toUtf8().constData()));
1262
1263 /* Show error first: */
1264 if (!pValidator->isValid())
1265 m_fValid = false;
1266 /* Show warning if message is not an error: */
1267 else
1268 m_fSilent = false;
1269
1270 /* Stop dialog revalidation on first error/warning: */
1271 break;
1272 }
1273 }
1274
1275 /* Update warning-pane visibility: */
1276 m_pWarningPane->setWarningLabelVisible(!m_fValid || !m_fSilent);
1277
1278 /* Make sure warning-pane visible if necessary: */
1279 if ((!m_fValid || !m_fSilent) && m_pStatusBar->currentIndex() == 0)
1280 m_pStatusBar->setCurrentWidget(m_pWarningPane);
1281 /* Make sure empty-pane visible otherwise: */
1282 else if (m_fValid && m_fSilent && m_pStatusBar->currentWidget() == m_pWarningPane)
1283 m_pStatusBar->setCurrentIndex(0);
1284
1285 /* Lock/unlock settings-page OK button according global validity status: */
1286 m_pButtonBox->button(QDialogButtonBox::Ok)->setEnabled(m_fValid);
1287}
1288
1289bool UIAdvancedSettingsDialog::isSettingsChanged()
1290{
1291 bool fIsSettingsChanged = false;
1292 foreach (UISettingsPage *pPage, m_pSelector->settingPages())
1293 {
1294 pPage->putToCache();
1295 if (!fIsSettingsChanged && pPage->changed())
1296 fIsSettingsChanged = true;
1297 }
1298 return fIsSettingsChanged;
1299}
1300
1301void UIAdvancedSettingsDialog::sltClose()
1302{
1303 /* Check whether serialization was clean (save)
1304 * or there are no unsaved settings to be lost (cancel): */
1305 if ( m_fSerializationClean
1306 || !isSettingsChanged()
1307 || msgCenter().confirmSettingsDiscarding(this))
1308 {
1309 /* Tell the listener to close us (once): */
1310 if (!m_fClosed)
1311 {
1312 m_fClosed = true;
1313 emit sigClose();
1314 return;
1315 }
1316 }
1317}
1318
1319void UIAdvancedSettingsDialog::sltHandleValidityChange(UISettingsPageValidator *pValidator)
1320{
1321 /* Determine which settings-page had called for revalidation: */
1322 if (UISettingsPage *pSettingsPage = pValidator->page())
1323 {
1324 /* Determine settings-page name: */
1325 const QString strPageName(pSettingsPage->internalName());
1326
1327 LogRelFlow(("Settings Dialog: %s Page: Revalidation in progress..\n",
1328 strPageName.toUtf8().constData()));
1329
1330 /* Perform page revalidation: */
1331 pValidator->revalidate();
1332 /* Perform inter-page recorrelation: */
1333 recorrelate(pSettingsPage);
1334 /* Perform dialog revalidation: */
1335 revalidate();
1336
1337 LogRelFlow(("Settings Dialog: %s Page: Revalidation complete.\n",
1338 strPageName.toUtf8().constData()));
1339 }
1340}
1341
1342void UIAdvancedSettingsDialog::sltHandleWarningPaneHovered(UISettingsPageValidator *pValidator)
1343{
1344 LogRelFlow(("Settings Dialog: Warning-icon hovered: %s.\n", pValidator->internalName().toUtf8().constData()));
1345
1346 /* Show corresponding popup: */
1347 if (!m_fValid || !m_fSilent)
1348 popupCenter().popup(m_pScrollArea, "SettingsDialogWarning",
1349 pValidator->lastMessage());
1350}
1351
1352void UIAdvancedSettingsDialog::sltHandleWarningPaneUnhovered(UISettingsPageValidator *pValidator)
1353{
1354 LogRelFlow(("Settings Dialog: Warning-icon unhovered: %s.\n", pValidator->internalName().toUtf8().constData()));
1355
1356 /* Recall corresponding popup: */
1357 popupCenter().recall(m_pScrollArea, "SettingsDialogWarning");
1358}
1359
1360void UIAdvancedSettingsDialog::sltHandleExperienceModeCheckBoxChanged()
1361{
1362 /* Save new value: */
1363 gEDataManager->setSettingsInExpertMode(m_pCheckBoxMode->isChecked());
1364}
1365
1366void UIAdvancedSettingsDialog::sltHandleExperienceModeChanged()
1367{
1368 /* Acquire actual value: */
1369 const bool fExpertMode = gEDataManager->isSettingsInExpertMode();
1370
1371 /* Update check-box state: */
1372 m_pCheckBoxMode->blockSignals(true);
1373 m_pCheckBoxMode->setChecked(fExpertMode);
1374 m_pCheckBoxMode->blockSignals(false);
1375
1376 /* Reapply mode: */
1377 sltApplyFilteringRules();
1378}
1379
1380void UIAdvancedSettingsDialog::sltApplyFilteringRules()
1381{
1382 /* Filter-out page contents: */
1383 foreach (UISettingsPageFrame *pFrame, m_frames.values())
1384 pFrame->filterOut(m_pCheckBoxMode->isChecked(),
1385 m_pEditorFilter->text(),
1386 m_flags);
1387
1388 /* Make sure current page chosen again: */
1389 /// @todo fix this WORKAROUND properly!
1390 // Why the heck simple call to
1391 // QCoreApplication::sendPostedEvents(0, QEvent::LayoutRequest);
1392 // isn't enough and we still need some time to let system
1393 // process the layouts and vertical scroll-bar position?
1394 QTimer::singleShot(50, this, SLOT(sltCategoryChangedRepeat()));
1395}
1396
1397void UIAdvancedSettingsDialog::sltHandleFrameVisibilityChange(bool fVisible)
1398{
1399 /* Acquire frame: */
1400 UISettingsPageFrame *pFrame = qobject_cast<UISettingsPageFrame*>(sender());
1401 AssertPtrReturnVoid(pFrame);
1402
1403 /* Update selector item visibility: */
1404 const int iId = m_frames.key(pFrame);
1405 m_pSelector->setItemVisible(iId, fVisible);
1406}
1407
1408void UIAdvancedSettingsDialog::sltHandleVerticalScrollAreaWheelEvent()
1409{
1410 /* Acquire layout info: */
1411 int iL = 0, iT = 0, iR = 0, iB = 0;
1412 if ( m_pScrollViewport
1413 && m_pScrollViewport->layout())
1414 m_pScrollViewport->layout()->getContentsMargins(&iL, &iT, &iR, &iB);
1415
1416 /* Search through all the frame keys we have: */
1417 int iActualKey = -1;
1418 foreach (int iKey, m_frames.keys())
1419 {
1420 /* Let's calculate scroll-bar position for enumerated frame: */
1421 int iPosition = 0;
1422 /* We'll have to take upper content's margin into account: */
1423 iPosition -= iT;
1424 /* And actual page position according to parent: */
1425 const QPoint pnt = m_frames.value(iKey)->pos();
1426 iPosition += pnt.y();
1427
1428 /* Check if scroll-bar haven't passed this position yet: */
1429 if (m_pScrollArea->verticalScrollBarPosition() < iPosition)
1430 break;
1431
1432 /* Remember last suitable frame key: */
1433 iActualKey = iKey;
1434 }
1435
1436 /* Silently update the selector with frame number we found: */
1437 if (iActualKey != -1)
1438 m_pSelector->selectById(iActualKey, true /* silently */);
1439}
1440
1441void UIAdvancedSettingsDialog::prepare()
1442{
1443 /* Prepare central-widget: */
1444 setCentralWidget(new QWidget);
1445 if (centralWidget())
1446 {
1447 /* Prepare main layout: */
1448 m_pLayoutMain = new QGridLayout(centralWidget());
1449 if (m_pLayoutMain)
1450 {
1451 /* Prepare widgets: */
1452 prepareSelector();
1453 prepareScrollArea();
1454 prepareButtonBox();
1455 }
1456 }
1457
1458 /* Apply language settings: */
1459 sltRetranslateUI();
1460 connect(&translationEventListener(), &UITranslationEventListener::sigRetranslateUI,
1461 this, &UIAdvancedSettingsDialog::sltRetranslateUI);
1462}
1463
1464void UIAdvancedSettingsDialog::prepareSelector()
1465{
1466 /* Make sure there is a serious spacing between selector and pages: */
1467 m_pLayoutMain->setColumnMinimumWidth(1, 20);
1468 m_pLayoutMain->setRowStretch(1, 1);
1469 m_pLayoutMain->setColumnStretch(2, 1);
1470
1471 /* Prepare mode checkbox: */
1472 m_pCheckBoxMode = new UIModeCheckBox(centralWidget());
1473 if (m_pCheckBoxMode)
1474 {
1475 connect(m_pCheckBoxMode, &UIModeCheckBox::stateChanged,
1476 this, &UIAdvancedSettingsDialog::sltHandleExperienceModeCheckBoxChanged);
1477 connect(gEDataManager, &UIExtraDataManager::sigSettingsExpertModeChange,
1478 this, &UIAdvancedSettingsDialog::sltHandleExperienceModeChanged);
1479 m_pLayoutMain->addWidget(m_pCheckBoxMode, 0, 0);
1480 }
1481
1482 /* Prepare classical tree-view selector: */
1483 m_pSelector = new UISettingsSelectorTreeView(centralWidget());
1484 if (m_pSelector)
1485 {
1486 m_pLayoutMain->addWidget(m_pSelector->widget(), 1, 0);
1487 m_pSelector->widget()->setFocus();
1488 }
1489
1490 /* Prepare filter editor: */
1491 m_pEditorFilter = new UIFilterEditor(centralWidget());
1492 if (m_pEditorFilter)
1493 {
1494 connect(m_pEditorFilter, &UIFilterEditor::sigTextChanged,
1495 this, &UIAdvancedSettingsDialog::sltApplyFilteringRules);
1496 m_pLayoutMain->addWidget(m_pEditorFilter, 0, 2);
1497 }
1498
1499 /* Configure selector created above: */
1500 if (m_pSelector)
1501 connect(m_pSelector, &UISettingsSelectorTreeView::sigCategoryChanged,
1502 this, &UIAdvancedSettingsDialog::sltCategoryChanged);
1503}
1504
1505void UIAdvancedSettingsDialog::prepareScrollArea()
1506{
1507 /* Prepare scroll-area: */
1508 m_pScrollArea = new UIVerticalScrollArea(centralWidget());
1509 if (m_pScrollArea)
1510 {
1511 /* Configure popup-stack: */
1512 popupCenter().setPopupStackOrientation(m_pScrollArea, UIPopupStackOrientation_Bottom);
1513
1514 m_pScrollArea->setWidgetResizable(true);
1515 m_pScrollArea->setFrameShape(QFrame::NoFrame);
1516 connect(m_pScrollArea, &UIVerticalScrollArea::sigWheelEvent,
1517 this, &UIAdvancedSettingsDialog::sltHandleVerticalScrollAreaWheelEvent);
1518
1519 /* Prepare scroll-viewport: */
1520 m_pScrollViewport = new QWidget(m_pScrollArea);
1521 if (m_pScrollViewport)
1522 {
1523 QVBoxLayout *pLayout = new QVBoxLayout(m_pScrollViewport);
1524 if (pLayout)
1525 {
1526 pLayout->setAlignment(Qt::AlignTop);
1527 pLayout->setContentsMargins(0, 0, 0, 0);
1528 int iSpacing = pLayout->spacing();
1529 pLayout->setSpacing(2 * iSpacing);
1530 }
1531 m_pScrollArea->setWidget(m_pScrollViewport);
1532 }
1533
1534 /* Add scroll-area into main layout: */
1535 m_pLayoutMain->addWidget(m_pScrollArea, 1, 2);
1536 }
1537}
1538
1539void UIAdvancedSettingsDialog::prepareButtonBox()
1540{
1541 /* Prepare button-box: */
1542 m_pButtonBox = new QIDialogButtonBox(centralWidget());
1543 if (m_pButtonBox)
1544 {
1545#ifndef VBOX_WS_MAC
1546 m_pButtonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel |
1547 QDialogButtonBox::NoButton | QDialogButtonBox::Help);
1548 m_pButtonBox->button(QDialogButtonBox::Help)->setShortcut(UIShortcutPool::standardSequence(QKeySequence::HelpContents));
1549#else
1550 // WORKAROUND:
1551 // No Help button on macOS for now, conflict with old Qt.
1552 m_pButtonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel |
1553 QDialogButtonBox::NoButton);
1554#endif
1555 m_pButtonBox->button(QDialogButtonBox::Ok)->setShortcut(Qt::Key_Return);
1556 m_pButtonBox->button(QDialogButtonBox::Cancel)->setShortcut(Qt::Key_Escape);
1557 connect(m_pButtonBox, &QIDialogButtonBox::rejected, this, &UIAdvancedSettingsDialog::sltClose);
1558 connect(m_pButtonBox, &QIDialogButtonBox::accepted, this, &UIAdvancedSettingsDialog::accept);
1559#ifndef VBOX_WS_MAC
1560 connect(m_pButtonBox->button(QDialogButtonBox::Help), &QAbstractButton::pressed,
1561 m_pButtonBox, &QIDialogButtonBox::sltHandleHelpRequest);
1562#endif
1563
1564 /* Prepare status-bar: */
1565 m_pStatusBar = new QStackedWidget(m_pButtonBox);
1566 if (m_pStatusBar)
1567 {
1568 /* Add empty widget: */
1569 m_pStatusBar->addWidget(new QWidget);
1570
1571 /* Prepare process-bar: */
1572 m_pProcessBar = new QProgressBar(m_pStatusBar);
1573 if (m_pProcessBar)
1574 {
1575 m_pProcessBar->setMinimum(0);
1576 m_pProcessBar->setMaximum(100);
1577 m_pStatusBar->addWidget(m_pProcessBar);
1578 }
1579
1580 /* Prepare warning-pane: */
1581 m_pWarningPane = new UISettingsWarningPane(m_pStatusBar);
1582 if (m_pWarningPane)
1583 {
1584 connect(m_pWarningPane, &UISettingsWarningPane::sigHoverEnter,
1585 this, &UIAdvancedSettingsDialog::sltHandleWarningPaneHovered);
1586 connect(m_pWarningPane, &UISettingsWarningPane::sigHoverLeave,
1587 this, &UIAdvancedSettingsDialog::sltHandleWarningPaneUnhovered);
1588 m_pStatusBar->addWidget(m_pWarningPane);
1589 }
1590
1591 /* Add status-bar to button-box: */
1592 m_pButtonBox->addExtraWidget(m_pStatusBar);
1593 }
1594
1595 /* Add button-box into main layout: */
1596 m_pLayoutMain->addWidget(m_pButtonBox, 2, 0, 1, 3);
1597 }
1598}
1599
1600void UIAdvancedSettingsDialog::cleanup()
1601{
1602 /* Delete serializer if exists: */
1603 delete m_pSerializeProcess;
1604 m_pSerializeProcess = 0;
1605
1606 /* Recall popup-pane if any: */
1607 popupCenter().recall(m_pScrollArea, "SettingsDialogWarning");
1608
1609 /* Delete selector early! */
1610 delete m_pSelector;
1611}
1612
1613#include "UIAdvancedSettingsDialog.moc"
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