VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/widgets/UIPopupPane.cpp@ 76553

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

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 17.3 KB
Line 
1/* $Id: UIPopupPane.cpp 76553 2019-01-01 01:45:53Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UIPopupPane class implementation.
4 */
5
6/*
7 * Copyright (C) 2013-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#ifdef VBOX_WITH_PRECOMPILED_HEADERS
19# include <precomp.h>
20#else /* !VBOX_WITH_PRECOMPILED_HEADERS */
21
22/* Qt includes: */
23# include <QPainter>
24# include <QTextEdit>
25
26/* GUI includes: */
27# include "UIPopupPane.h"
28# include "UIPopupPaneMessage.h"
29# include "UIPopupPaneDetails.h"
30# include "UIPopupPaneButtonPane.h"
31# include "UIAnimationFramework.h"
32# include "QIMessageBox.h"
33
34/* Other VBox includes: */
35# include <iprt/assert.h>
36
37#endif /* !VBOX_WITH_PRECOMPILED_HEADERS */
38
39
40UIPopupPane::UIPopupPane(QWidget *pParent,
41 const QString &strMessage, const QString &strDetails,
42 const QMap<int, QString> &buttonDescriptions)
43 : QIWithRetranslateUI<QWidget>(pParent)
44 , m_fPolished(false)
45 , m_iLayoutMargin(10), m_iLayoutSpacing(5)
46 , m_strMessage(strMessage), m_strDetails(strDetails)
47 , m_buttonDescriptions(buttonDescriptions)
48 , m_fShown(false)
49 , m_pShowAnimation(0)
50 , m_fCanLooseFocus(!m_buttonDescriptions.isEmpty())
51 , m_fFocused(!m_fCanLooseFocus)
52 , m_fHovered(m_fFocused)
53 , m_iDefaultOpacity(180)
54 , m_iHoveredOpacity(250)
55 , m_iOpacity(m_fHovered ? m_iHoveredOpacity : m_iDefaultOpacity)
56 , m_pMessagePane(0), m_pDetailsPane(0), m_pButtonPane(0)
57{
58 /* Prepare: */
59 prepare();
60}
61
62void UIPopupPane::recall()
63{
64 /* Close popup-pane with *escape* button: */
65 done(m_pButtonPane->escapeButton());
66}
67
68void UIPopupPane::setMessage(const QString &strMessage)
69{
70 /* Make sure the message has changed: */
71 if (m_strMessage == strMessage)
72 return;
73
74 /* Fetch new message: */
75 m_strMessage = strMessage;
76 m_pMessagePane->setText(m_strMessage);
77}
78
79void UIPopupPane::setDetails(const QString &strDetails)
80{
81 /* Make sure the details has changed: */
82 if (m_strDetails == strDetails)
83 return;
84
85 /* Fetch new details: */
86 m_strDetails = strDetails;
87 m_pDetailsPane->setText(prepareDetailsText());
88}
89
90void UIPopupPane::setMinimumSizeHint(const QSize &minimumSizeHint)
91{
92 /* Make sure the size-hint has changed: */
93 if (m_minimumSizeHint == minimumSizeHint)
94 return;
95
96 /* Fetch new size-hint: */
97 m_minimumSizeHint = minimumSizeHint;
98
99 /* Notify parent popup-stack: */
100 emit sigSizeHintChanged();
101}
102
103void UIPopupPane::layoutContent()
104{
105 /* Variables: */
106 const int iWidth = width();
107 const int iHeight = height();
108 const QSize buttonPaneMinimumSizeHint = m_pButtonPane->minimumSizeHint();
109 const int iButtonPaneMinimumWidth = buttonPaneMinimumSizeHint.width();
110 const int iButtonPaneMinimumHeight = buttonPaneMinimumSizeHint.height();
111 const int iTextPaneWidth = iWidth - 2 * m_iLayoutMargin - m_iLayoutSpacing - iButtonPaneMinimumWidth;
112 const int iTextPaneHeight = m_pMessagePane->minimumSizeHint().height();
113 const int iMaximumHeight = qMax(iTextPaneHeight, iButtonPaneMinimumHeight);
114 const int iMinimumHeight = qMin(iTextPaneHeight, iButtonPaneMinimumHeight);
115 const int iHeightShift = (iMaximumHeight - iMinimumHeight) / 2;
116 const bool fTextPaneShifted = iTextPaneHeight < iButtonPaneMinimumHeight;
117 const int iTextPaneYOffset = fTextPaneShifted ? m_iLayoutMargin + iHeightShift : m_iLayoutMargin;
118
119 /* Message-pane: */
120 m_pMessagePane->move(m_iLayoutMargin, iTextPaneYOffset);
121 m_pMessagePane->resize(iTextPaneWidth, iTextPaneHeight);
122 m_pMessagePane->layoutContent();
123
124 /* Button-pane: */
125 m_pButtonPane->move(m_iLayoutMargin + iTextPaneWidth + m_iLayoutSpacing,
126 m_iLayoutMargin);
127 m_pButtonPane->resize(iButtonPaneMinimumWidth,
128 iHeight - m_iLayoutSpacing);
129
130 /* Details-pane: */
131 if (m_pDetailsPane->isVisible())
132 {
133 m_pDetailsPane->move(m_iLayoutMargin,
134 iTextPaneYOffset + iTextPaneHeight + m_iLayoutSpacing);
135 m_pDetailsPane->resize(iTextPaneWidth + iButtonPaneMinimumWidth,
136 m_pDetailsPane->minimumSizeHint().height());
137 m_pDetailsPane->layoutContent();
138 }
139}
140
141void UIPopupPane::sltMarkAsShown()
142{
143 /* Mark popup-pane as 'shown': */
144 m_fShown = true;
145}
146
147void UIPopupPane::sltHandleProposalForSize(QSize newSize)
148{
149 /* Prepare the width: */
150 int iWidth = newSize.width();
151
152 /* Subtract layout margins: */
153 iWidth -= 2 * m_iLayoutMargin;
154 /* Subtract layout spacing: */
155 iWidth -= m_iLayoutSpacing;
156 /* Subtract button-pane width: */
157 iWidth -= m_pButtonPane->minimumSizeHint().width();
158
159 /* Propose resulting width to the panes: */
160 emit sigProposePaneWidth(iWidth);
161
162 /* Prepare the height: */
163 int iHeight = newSize.height();
164 /* Determine maximum height of the message-pane / button-pane: */
165 int iExtraHeight = qMax(m_pMessagePane->expandedSizeHint().height(),
166 m_pButtonPane->minimumSizeHint().height());
167
168 /* Subtract height of the message pane: */
169 iHeight -= iExtraHeight;
170 /* Subtract layout margins: */
171 iHeight -= 2 * m_iLayoutMargin;
172 /* Subtract layout spacing: */
173 iHeight -= m_iLayoutSpacing;
174
175 /* Propose resulting height to details-pane: */
176 emit sigProposeDetailsPaneHeight(iHeight);
177}
178
179void UIPopupPane::sltUpdateSizeHint()
180{
181 /* Calculate minimum width-hint: */
182 int iMinimumWidthHint = 0;
183 {
184 /* Take into account layout: */
185 iMinimumWidthHint += 2 * m_iLayoutMargin;
186 {
187 /* Take into account widgets: */
188 iMinimumWidthHint += m_pMessagePane->minimumSizeHint().width();
189 iMinimumWidthHint += m_iLayoutSpacing;
190 iMinimumWidthHint += m_pButtonPane->minimumSizeHint().width();
191 }
192 }
193
194 /* Calculate minimum height-hint: */
195 int iMinimumHeightHint = 0;
196 {
197 /* Take into account layout: */
198 iMinimumHeightHint += 2 * m_iLayoutMargin;
199 iMinimumHeightHint += m_iLayoutSpacing;
200 {
201 /* Take into account widgets: */
202 const int iTextPaneHeight = m_pMessagePane->minimumSizeHint().height();
203 const int iButtonBoxHeight = m_pButtonPane->minimumSizeHint().height();
204 iMinimumHeightHint += qMax(iTextPaneHeight, iButtonBoxHeight);
205 /* Add the height of details-pane only if it is visible: */
206 if (m_pDetailsPane->isVisible())
207 iMinimumHeightHint += m_pDetailsPane->minimumSizeHint().height();
208 }
209 }
210
211 /* Compose minimum size-hints: */
212 m_hiddenSizeHint = QSize(iMinimumWidthHint, 1);
213 m_shownSizeHint = QSize(iMinimumWidthHint, iMinimumHeightHint);
214 m_minimumSizeHint = m_fShown ? m_shownSizeHint : m_hiddenSizeHint;
215
216 /* Update 'show/hide' animation: */
217 if (m_pShowAnimation)
218 m_pShowAnimation->update();
219
220 /* Notify parent popup-stack: */
221 emit sigSizeHintChanged();
222}
223
224void UIPopupPane::sltButtonClicked(int iButtonID)
225{
226 /* Complete popup with corresponding code: */
227 done(iButtonID);
228}
229
230void UIPopupPane::prepare()
231{
232 /* Prepare this: */
233 installEventFilter(this);
234 /* Prepare background: */
235 prepareBackground();
236 /* Prepare content: */
237 prepareContent();
238 /* Prepare animation: */
239 prepareAnimation();
240
241 /* Update size-hint: */
242 sltUpdateSizeHint();
243}
244
245void UIPopupPane::prepareBackground()
246{
247 /* Prepare palette: */
248 QPalette pal = palette();
249 pal.setColor(QPalette::Window, QApplication::palette().color(QPalette::Window));
250 setPalette(pal);
251}
252
253void UIPopupPane::prepareContent()
254{
255 /* Create message-pane: */
256 m_pMessagePane = new UIPopupPaneMessage(this, m_strMessage, m_fFocused);
257 {
258 /* Configure message-pane: */
259 connect(this, SIGNAL(sigProposePaneWidth(int)), m_pMessagePane, SLOT(sltHandleProposalForWidth(int)));
260 connect(m_pMessagePane, SIGNAL(sigSizeHintChanged()), this, SLOT(sltUpdateSizeHint()));
261 m_pMessagePane->installEventFilter(this);
262 }
263
264 /* Create button-box: */
265 m_pButtonPane = new UIPopupPaneButtonPane(this);
266 {
267 /* Configure button-box: */
268 connect(m_pButtonPane, SIGNAL(sigButtonClicked(int)), this, SLOT(sltButtonClicked(int)));
269 m_pButtonPane->installEventFilter(this);
270 m_pButtonPane->setButtons(m_buttonDescriptions);
271 }
272
273 /* Create details-pane: */
274 m_pDetailsPane = new UIPopupPaneDetails(this, prepareDetailsText(), m_fFocused);
275 {
276 /* Configure details-pane: */
277 connect(this, &UIPopupPane::sigProposePaneWidth, m_pDetailsPane, &UIPopupPaneDetails::sltHandleProposalForWidth);
278 connect(this, &UIPopupPane::sigProposeDetailsPaneHeight, m_pDetailsPane, &UIPopupPaneDetails::sltHandleProposalForHeight);
279 connect(m_pDetailsPane, &UIPopupPaneDetails::sigSizeHintChanged, this, &UIPopupPane::sltUpdateSizeHint);
280 m_pDetailsPane->installEventFilter(this);
281 }
282
283 /* Prepare focus rules: */
284 setFocusPolicy(Qt::StrongFocus);
285 m_pMessagePane->setFocusPolicy(Qt::StrongFocus);
286 m_pButtonPane->setFocusPolicy(Qt::StrongFocus);
287 m_pDetailsPane->setFocusPolicy(Qt::StrongFocus);
288 setFocusProxy(m_pButtonPane);
289 m_pMessagePane->setFocusProxy(m_pButtonPane);
290 m_pDetailsPane->setFocusProxy(m_pButtonPane);
291
292 /* Translate UI finally: */
293 retranslateUi();
294}
295
296void UIPopupPane::prepareAnimation()
297{
298 /* Install 'show' animation for 'minimumSizeHint' property: */
299 connect(this, SIGNAL(sigToShow()), this, SIGNAL(sigShow()), Qt::QueuedConnection);
300 m_pShowAnimation = UIAnimation::installPropertyAnimation(this, "minimumSizeHint", "hiddenSizeHint", "shownSizeHint",
301 SIGNAL(sigShow()), SIGNAL(sigHide()));
302 connect(m_pShowAnimation, SIGNAL(sigStateEnteredFinal()), this, SLOT(sltMarkAsShown()));
303
304 /* Install 'hover' animation for 'opacity' property: */
305 UIAnimation::installPropertyAnimation(this, "opacity", "defaultOpacity", "hoveredOpacity",
306 SIGNAL(sigHoverEnter()), SIGNAL(sigHoverLeave()), m_fHovered);
307}
308
309void UIPopupPane::retranslateUi()
310{
311 /* Translate tool-tips: */
312 retranslateToolTips();
313}
314
315void UIPopupPane::retranslateToolTips()
316{
317 /* Translate pane & message-pane tool-tips: */
318 if (m_fFocused)
319 {
320 setToolTip(QString());
321 m_pMessagePane->setToolTip(QString());
322 }
323 else
324 {
325 setToolTip(QApplication::translate("UIPopupCenter", "Click for full details"));
326 m_pMessagePane->setToolTip(QApplication::translate("UIPopupCenter", "Click for full details"));
327 }
328}
329
330bool UIPopupPane::eventFilter(QObject *pObject, QEvent *pEvent)
331{
332 /* Depending on event-type: */
333 switch (pEvent->type())
334 {
335 /* Something is hovered: */
336 case QEvent::HoverEnter:
337 case QEvent::Enter:
338 {
339 /* Hover pane if not yet hovered: */
340 if (!m_fHovered)
341 {
342 m_fHovered = true;
343 emit sigHoverEnter();
344 }
345 break;
346 }
347 /* Nothing is hovered: */
348 case QEvent::Leave:
349 {
350 /* Unhover pane if hovered but not focused: */
351 if (pObject == this && m_fHovered && !m_fFocused)
352 {
353 m_fHovered = false;
354 emit sigHoverLeave();
355 }
356 break;
357 }
358 /* Pane is clicked with mouse: */
359 case QEvent::MouseButtonPress:
360 {
361 /* Focus pane if not focused: */
362 if (!m_fFocused)
363 {
364 m_fFocused = true;
365 emit sigFocusEnter();
366 /* Hover pane if not hovered: */
367 if (!m_fHovered)
368 {
369 m_fHovered = true;
370 emit sigHoverEnter();
371 }
372 /* Translate tool-tips: */
373 retranslateToolTips();
374 }
375 break;
376 }
377 /* Pane is unfocused: */
378 case QEvent::FocusOut:
379 {
380 /* Unhocus pane if focused: */
381 if (m_fCanLooseFocus && m_fFocused)
382 {
383 m_fFocused = false;
384 emit sigFocusLeave();
385 /* Unhover pane if hovered: */
386 if (m_fHovered)
387 {
388 m_fHovered = false;
389 emit sigHoverLeave();
390 }
391 /* Translate tool-tips: */
392 retranslateToolTips();
393 }
394 break;
395 }
396 /* Default case: */
397 default: break;
398 }
399 /* Do not filter anything: */
400 return false;
401}
402
403void UIPopupPane::showEvent(QShowEvent *pEvent)
404{
405 /* Call to base-class: */
406 QWidget::showEvent(pEvent);
407
408 /* Polish border: */
409 if (m_fPolished)
410 return;
411 m_fPolished = true;
412
413 /* Call to polish event: */
414 polishEvent(pEvent);
415}
416
417void UIPopupPane::polishEvent(QShowEvent *)
418{
419 /* Focus if marked as 'focused': */
420 if (m_fFocused)
421 setFocus();
422
423 /* Emit signal to start *show* animation: */
424 emit sigToShow();
425}
426
427void UIPopupPane::paintEvent(QPaintEvent *)
428{
429 /* Compose painting rectangle,
430 * Shifts are required for the antialiasing support: */
431 const QRect rect(1, 1, width() - 2, height() - 2);
432
433 /* Create painter: */
434 QPainter painter(this);
435 painter.setRenderHint(QPainter::Antialiasing);
436
437 /* Configure clipping: */
438 configureClipping(rect, painter);
439
440 /* Paint background: */
441 paintBackground(rect, painter);
442
443 /* Paint frame: */
444 paintFrame(painter);
445}
446
447void UIPopupPane::configureClipping(const QRect &rect, QPainter &painter)
448{
449 /* Configure clipping: */
450 QPainterPath path;
451 int iDiameter = 6;
452 QSizeF arcSize(2 * iDiameter, 2 * iDiameter);
453 path.moveTo(rect.x() + iDiameter, rect.y());
454 path.arcTo(QRectF(path.currentPosition(), arcSize).translated(-iDiameter, 0), 90, 90);
455 path.lineTo(path.currentPosition().x(), rect.y() + rect.height() - iDiameter);
456 path.arcTo(QRectF(path.currentPosition(), arcSize).translated(0, -iDiameter), 180, 90);
457 path.lineTo(rect.x() + rect.width() - iDiameter, path.currentPosition().y());
458 path.arcTo(QRectF(path.currentPosition(), arcSize).translated(-iDiameter, -2 * iDiameter), 270, 90);
459 path.lineTo(path.currentPosition().x(), rect.y() + iDiameter);
460 path.arcTo(QRectF(path.currentPosition(), arcSize).translated(-2 * iDiameter, -iDiameter), 0, 90);
461 path.closeSubpath();
462 painter.setClipPath(path);
463}
464
465void UIPopupPane::paintBackground(const QRect &rect, QPainter &painter)
466{
467 /* Paint background: */
468 QColor currentColor(palette().color(QPalette::Window));
469 QColor newColor1(currentColor.red(), currentColor.green(), currentColor.blue(), opacity());
470 QColor newColor2 = newColor1.darker(115);
471 QLinearGradient headerGradient(rect.topLeft(), rect.bottomLeft());
472 headerGradient.setColorAt(0, newColor1);
473 headerGradient.setColorAt(1, newColor2);
474 painter.fillRect(rect, headerGradient);
475}
476
477void UIPopupPane::paintFrame(QPainter &painter)
478{
479 /* Paint frame: */
480 QColor currentColor(palette().color(QPalette::Window).darker(150));
481 QPainterPath path = painter.clipPath();
482 painter.setClipping(false);
483 painter.strokePath(path, currentColor);
484}
485
486void UIPopupPane::done(int iResultCode)
487{
488 /* Notify listeners: */
489 emit sigDone(iResultCode);
490}
491
492QString UIPopupPane::prepareDetailsText() const
493{
494 if (m_strDetails.isEmpty())
495 return QString();
496
497 QStringPairList aDetailsList;
498 prepareDetailsList(aDetailsList);
499 if (aDetailsList.isEmpty())
500 return QString();
501
502 if (aDetailsList.size() == 1)
503 return tr("<p><b>Details:</b>") + m_strDetails + "</p>";
504
505 QString strResultText;
506 for (int iListIdx = 0; iListIdx < aDetailsList.size(); ++iListIdx)
507 {
508 strResultText += tr("<p><b>Details:</b> (%1 of %2)").arg(iListIdx + 1).arg(aDetailsList.size());
509 const QString strFirstPart = aDetailsList.at(iListIdx).first;
510 const QString strSecondPart = aDetailsList.at(iListIdx).second;
511 if (strFirstPart.isEmpty())
512 strResultText += strSecondPart + "</p>";
513 else
514 strResultText += QString("%1<br>%2").arg(strFirstPart, strSecondPart) + "</p>";
515 }
516 return strResultText;
517}
518
519void UIPopupPane::prepareDetailsList(QStringPairList &aDetailsList) const
520{
521 if (m_strDetails.isEmpty())
522 return;
523
524 /* Split details into paragraphs: */
525 QStringList aParagraphs(m_strDetails.split("<!--EOP-->", QString::SkipEmptyParts));
526 /* Make sure details-text has at least one paragraph: */
527 AssertReturnVoid(!aParagraphs.isEmpty());
528
529 /* Enumerate all the paragraphs: */
530 foreach (const QString &strParagraph, aParagraphs)
531 {
532 /* Split each paragraph into pairs: */
533 QStringList aParts(strParagraph.split("<!--EOM-->", QString::KeepEmptyParts));
534 /* Make sure each paragraph consist of 2 parts: */
535 AssertReturnVoid(aParts.size() == 2);
536 /* Append each pair into details-list: */
537 aDetailsList << QStringPair(aParts.at(0), aParts.at(1));
538 }
539}
540
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use