VirtualBox

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

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

FE/Qt: bugref:6066: UIPopupPane: Fixing regression introduced in r129779.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use