VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/widgets/UIPopupBox.cpp

Last change on this file was 101560, checked in by vboxsync, 7 months ago

FE/Qt: bugref:10450: Bits forgotten in r159636.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 13.5 KB
RevLine 
[30868]1/* $Id: UIPopupBox.cpp 101560 2023-10-23 16:10:12Z vboxsync $ */
2/** @file
[52727]3 * VBox Qt GUI - UIPopupBox/UIPopupBoxGroup classes implementation.
[30868]4 */
5
6/*
[98103]7 * Copyright (C) 2010-2023 Oracle and/or its affiliates.
[30868]8 *
[96407]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
[30868]26 */
27
[56039]28/* Qt includes: */
[76606]29#include <QApplication>
30#include <QLabel>
31#include <QPaintEvent>
32#include <QStyle>
33#include <QVBoxLayout>
[101560]34#include <QWindow>
[41255]35
[56039]36/* GUI includes: */
[76606]37#include "UIPopupBox.h"
38#ifdef VBOX_WS_MAC
39# include "UIImageTools.h"
40#endif
[30868]41
[52730]42
[71484]43/*********************************************************************************************************************************
44* Class UIPopupBox implementation. *
45*********************************************************************************************************************************/
46
[30868]47UIPopupBox::UIPopupBox(QWidget *pParent)
48 : QWidget(pParent)
[71484]49 , m_pTitleIcon(0)
50 , m_pWarningIcon(0)
51 , m_pTitleLabel(0)
[30868]52 , m_fLinkEnabled(false)
[71484]53 , m_fOpened(true)
54 , m_fHovered(false)
[30868]55 , m_pContentWidget(0)
56 , m_pLabelPath(0)
[41255]57 , m_iArrowWidth(9)
[30868]58{
[71484]59 /* Configure self: */
60 installEventFilter(this);
[30868]61
[71484]62 /* Configure painter-path: */
[41255]63 m_arrowPath.lineTo(m_iArrowWidth / 2.0, m_iArrowWidth / 2.0);
64 m_arrowPath.lineTo(m_iArrowWidth, 0);
[30868]65
[71484]66 /* Create main-layout: */
67 QVBoxLayout *pMainLayout = new QVBoxLayout(this);
68 if (pMainLayout)
69 {
70 /* Create title-layout: */
71 QHBoxLayout *pTitleLayout = new QHBoxLayout;
72 if (pTitleLayout)
73 {
74 /* Create title-icon label. */
75 m_pTitleIcon = new QLabel;
76 if (m_pTitleIcon)
77 {
78 /* Configure label: */
79 m_pTitleIcon->installEventFilter(this);
[39261]80
[71484]81 /* Add into layout: */
82 pTitleLayout->addWidget(m_pTitleIcon);
83 }
84 /* Create warning-icon label. */
85 m_pWarningIcon = new QLabel;
86 if (m_pWarningIcon)
87 {
88 /* Configure label: */
89 m_pWarningIcon->setHidden(true);
90 m_pWarningIcon->installEventFilter(this);
91
92 /* Add into layout: */
93 pTitleLayout->addWidget(m_pWarningIcon);
94 }
95 /* Create title-text label. */
96 m_pTitleLabel = new QLabel;
97 if (m_pTitleLabel)
98 {
99 /* Configure label: */
100 m_pTitleLabel->installEventFilter(this);
101 connect(m_pTitleLabel, SIGNAL(linkActivated(const QString)),
102 this, SIGNAL(sigTitleClicked(const QString)));
103
104 /* Add into layout: */
105 pTitleLayout->addWidget(m_pTitleLabel, Qt::AlignLeft);
106 }
107
108 /* Add into layout: */
109 pMainLayout->addLayout(pTitleLayout);
110 }
111 }
112
[30868]113}
114
115UIPopupBox::~UIPopupBox()
116{
[71484]117 /* Delete label painter-path if any: */
[30868]118 if (m_pLabelPath)
119 delete m_pLabelPath;
120}
121
[41255]122void UIPopupBox::setTitleIcon(const QIcon &icon)
[30868]123{
[41255]124 /* Remember new title-icon: */
[39261]125 m_titleIcon = icon;
[41255]126 /* Update title-icon: */
127 updateTitleIcon();
[41257]128 /* Recalculate title-size: */
129 recalc();
[30868]130}
131
132QIcon UIPopupBox::titleIcon() const
133{
[39261]134 return m_titleIcon;
[30868]135}
136
[41255]137void UIPopupBox::setWarningIcon(const QIcon &icon)
[39261]138{
[41255]139 /* Remember new warning-icon: */
[39261]140 m_warningIcon = icon;
[41255]141 /* Update warning-icon: */
142 updateWarningIcon();
[41257]143 /* Recalculate title-size: */
144 recalc();
[39261]145}
146
147QIcon UIPopupBox::warningIcon() const
148{
149 return m_warningIcon;
150}
151
[41255]152void UIPopupBox::setTitle(const QString &strTitle)
[30868]153{
[41255]154 /* Remember new title: */
155 m_strTitle = strTitle;
156 /* Update title: */
157 updateTitle();
[41257]158 /* Recalculate title-size: */
159 recalc();
[41255]160}
161
162QString UIPopupBox::title() const
163{
164 return m_strTitle;
165}
166
167void UIPopupBox::setTitleLink(const QString &strLink)
168{
169 /* Remember new title-link: */
[30868]170 m_strLink = strLink;
[41255]171 /* Update title: */
172 updateTitle();
[30868]173}
174
175QString UIPopupBox::titleLink() const
176{
177 return m_strLink;
178}
179
180void UIPopupBox::setTitleLinkEnabled(bool fEnabled)
181{
[41255]182 /* Remember new title-link availability flag: */
[30868]183 m_fLinkEnabled = fEnabled;
[41255]184 /* Update title: */
185 updateTitle();
[30868]186}
187
188bool UIPopupBox::isTitleLinkEnabled() const
189{
190 return m_fLinkEnabled;
191}
192
193void UIPopupBox::setContentWidget(QWidget *pWidget)
194{
[41255]195 /* Cleanup old content-widget if any: */
[30868]196 if (m_pContentWidget)
197 {
[41250]198 m_pContentWidget->removeEventFilter(this);
[30868]199 layout()->removeWidget(m_pContentWidget);
200 }
[41255]201 /* Prepare new content-wodget: */
[30868]202 m_pContentWidget = pWidget;
[41250]203 layout()->addWidget(m_pContentWidget);
204 m_pContentWidget->installEventFilter(this);
[30868]205 recalc();
206}
207
208QWidget* UIPopupBox::contentWidget() const
209{
210 return m_pContentWidget;
211}
212
[71484]213void UIPopupBox::setOpen(bool fOpened)
[30868]214{
[41255]215 /* Check if we should toggle popup-box: */
[71484]216 if (m_fOpened == fOpened)
[39225]217 return;
218
219 /* Store new value: */
[71484]220 m_fOpened = fOpened;
[39225]221
[41255]222 /* Update content-widget if present or this itself: */
[30868]223 if (m_pContentWidget)
[71484]224 m_pContentWidget->setVisible(m_fOpened);
[30868]225 else
226 update();
[39225]227
[41255]228 /* Notify listeners about content-widget visibility: */
[39225]229 if (m_pContentWidget && m_pContentWidget->isVisible())
230 emit sigUpdateContentWidget();
[30868]231}
232
233void UIPopupBox::toggleOpen()
234{
[39225]235 /* Switch 'opened' state: */
[71484]236 setOpen(!m_fOpened);
[39225]237
238 /* Notify listeners about toggling: */
[71484]239 emit sigToggled(m_fOpened);
[30868]240}
241
242bool UIPopupBox::isOpen() const
243{
[71484]244 return m_fOpened;
[30868]245}
246
[72814]247bool UIPopupBox::event(QEvent *pEvent)
248{
249 /* Handle know event types: */
250 switch (pEvent->type())
251 {
252 case QEvent::Show:
253 case QEvent::ScreenChangeInternal:
254 {
255 /* Update pixmaps: */
256 updateTitleIcon();
257 updateWarningIcon();
258 break;
259 }
260 default:
261 break;
262 }
263
264 /* Call to base-class: */
265 return QWidget::event(pEvent);
266}
267
[71484]268bool UIPopupBox::eventFilter(QObject *pObject, QEvent *pEvent)
[30868]269{
[41255]270 /* Handle all mouse-event to update hover: */
[30868]271 QEvent::Type type = pEvent->type();
[41250]272 if (type == QEvent::Enter ||
273 type == QEvent::Leave ||
274 type == QEvent::MouseMove ||
275 type == QEvent::Wheel)
[30868]276 updateHover();
[41255]277 /* Call to base-class: */
[71484]278 return QWidget::eventFilter(pObject, pEvent);
[30868]279}
280
281void UIPopupBox::resizeEvent(QResizeEvent *pEvent)
282{
[41255]283 /* Recalculate title-size: */
[30868]284 recalc();
[41255]285 /* Call to base-class: */
[30868]286 QWidget::resizeEvent(pEvent);
287}
288
289void UIPopupBox::paintEvent(QPaintEvent *pEvent)
290{
[41255]291 /* Create painter: */
[30868]292 QPainter painter(this);
293 painter.setClipRect(pEvent->rect());
294
[88862]295 QPalette pal = QApplication::palette();
[30868]296 painter.setClipPath(*m_pLabelPath);
297 QColor base = pal.color(QPalette::Active, QPalette::Window);
298 QRect rect = QRect(QPoint(0, 0), size()).adjusted(0, 0, -1, -1);
299 /* Base background */
300 painter.fillRect(QRect(QPoint(0, 0), size()), pal.brush(QPalette::Active, QPalette::Base));
301 /* Top header background */
[56039]302 const int iMaxHeightHint = qMax(m_pTitleLabel->sizeHint().height(),
303 m_pTitleIcon->sizeHint().height());
304 QLinearGradient lg(rect.x(), rect.y(), rect.x(), rect.y() + 2 * 5 + iMaxHeightHint);
[30868]305 lg.setColorAt(0, base.darker(95));
306 lg.setColorAt(1, base.darker(110));
307 int theight = rect.height();
[71484]308 if (m_fOpened)
[56039]309 theight = 2 * 5 + iMaxHeightHint;
[30868]310 painter.fillRect(QRect(rect.x(), rect.y(), rect.width(), theight), lg);
311 /* Outer round rectangle line */
312 painter.setClipping(false);
313 painter.strokePath(*m_pLabelPath, base.darker(110));
314 /* Arrow */
[71484]315 if (m_fHovered)
[30868]316 {
317 painter.setBrush(base.darker(106));
318 painter.setPen(QPen(base.darker(128), 3, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin));
319 QSizeF s = m_arrowPath.boundingRect().size();
[71484]320 if (m_fOpened)
[30868]321 {
322 painter.translate(rect.x() + rect.width() - s.width() - 10, rect.y() + theight / 2 + s.height() / 2);
323 /* Flip */
324 painter.scale(1, -1);
325 }
326 else
327 painter.translate(rect.x() + rect.width() - s.width() - 10, rect.y() + theight / 2 - s.height() / 2 + 1);
328
329 painter.setRenderHint(QPainter::Antialiasing);
330 painter.drawPath(m_arrowPath);
331 }
332}
333
[71484]334void UIPopupBox::mouseDoubleClickEvent(QMouseEvent *)
335{
336 /* Toggle popup-box: */
337 toggleOpen();
338}
339
[41255]340void UIPopupBox::updateTitleIcon()
[30868]341{
[41255]342 /* Assign title-icon: */
[72814]343 const int iIconMetric = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize);
[100075]344 const qreal fDevicePixelRatio = window() && window()->windowHandle() ? window()->windowHandle()->devicePixelRatio() : 1;
345 m_pTitleIcon->setPixmap(m_titleIcon.pixmap(QSize(iIconMetric, iIconMetric), fDevicePixelRatio));
[41255]346}
[30868]347
[41255]348void UIPopupBox::updateWarningIcon()
349{
350 /* Hide warning-icon if its null: */
351 m_pWarningIcon->setHidden(m_warningIcon.isNull());
[72814]352
[41255]353 /* Assign warning-icon: */
[72814]354 const int iIconMetric = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize);
[100075]355 const qreal fDevicePixelRatio = window() && window()->windowHandle() ? window()->windowHandle()->devicePixelRatio() : 1;
356 m_pWarningIcon->setPixmap(m_warningIcon.pixmap(QSize(iIconMetric, iIconMetric), fDevicePixelRatio));
[41255]357}
358
359void UIPopupBox::updateTitle()
360{
[41257]361 /* If title-link is disabled or not set: */
[41255]362 if (!m_fLinkEnabled || m_strLink.isEmpty())
[41257]363 {
364 /* We should just set simple text title: */
[30868]365 m_pTitleLabel->setText(QString("<b>%1</b>").arg(m_strTitle));
[41257]366 }
367 /* If title-link is enabled and set: */
368 else if (m_fLinkEnabled && !m_strLink.isEmpty())
369 {
370 /* We should set html reference title: */
371 QPalette pal = m_pTitleLabel->palette();
372 m_pTitleLabel->setText(QString("<b><a style=\"text-decoration: none; color: %1\" href=\"%2\">%3</a></b>")
[71484]373 .arg(m_fHovered ? pal.color(QPalette::Link).name() : pal.color(QPalette::WindowText).name())
[41257]374 .arg(m_strLink)
375 .arg(m_strTitle));
376 }
[41255]377}
[30868]378
[41255]379void UIPopupBox::updateHover()
380{
381 /* Calculate new header-hover state: */
[71484]382 bool fNewHovered = m_fHovered;
[41255]383 if (m_pLabelPath && m_pLabelPath->contains(mapFromGlobal(QCursor::pos())))
[71484]384 fNewHovered = true;
[41255]385 else
[71484]386 fNewHovered = false;
[41255]387
388 /* Check if we should toggle hover: */
[71484]389 if (m_fHovered == fNewHovered)
[41255]390 return;
391
392 /* If header-hover state switched from disabled to enabled: */
[71484]393 if (!m_fHovered && fNewHovered)
[41255]394 /* Notify listeners: */
395 emit sigGotHover();
396
397 /* Toggle hover: */
[71484]398 toggleHover(fNewHovered);
[30868]399}
400
[41255]401void UIPopupBox::revokeHover()
402{
403 /* Check if we should toggle hover: */
[71484]404 if (m_fHovered == false)
[41255]405 return;
406
407 /* Toggle hover off: */
408 toggleHover(false);
409}
410
411void UIPopupBox::toggleHover(bool fHeaderHover)
412{
413 /* Remember header-hover state: */
[71484]414 m_fHovered = fHeaderHover;
[41255]415
416 /* Update title: */
[41257]417 updateTitle();
418
419 /* Call for update: */
[41255]420 update();
421}
422
[30868]423void UIPopupBox::recalc()
424{
425 if (m_pLabelPath)
426 delete m_pLabelPath;
427 QRect rect = QRect(QPoint(0, 0), size()).adjusted(0, 0, -1, -1);
428 int d = 18; // 22
429 m_pLabelPath = new QPainterPath(QPointF(rect.x() + rect.width() - d, rect.y()));
430 m_pLabelPath->arcTo(QRectF(rect.x(), rect.y(), d, d), 90, 90);
431 m_pLabelPath->arcTo(QRectF(rect.x(), rect.y() + rect.height() - d, d, d), 180, 90);
432 m_pLabelPath->arcTo(QRectF(rect.x() + rect.width() - d, rect.y() + rect.height() - d, d, d), 270, 90);
433 m_pLabelPath->arcTo(QRectF(rect.x() + rect.width() - d, rect.y(), d, d), 0, 90);
434 m_pLabelPath->closeSubpath();
435 update();
436}
437
[71484]438
439/*********************************************************************************************************************************
440* Class UIPopupBoxGroup implementation. *
441*********************************************************************************************************************************/
442
[41255]443UIPopupBoxGroup::UIPopupBoxGroup(QObject *pParent)
444 : QObject(pParent)
445{
446}
447
448UIPopupBoxGroup::~UIPopupBoxGroup()
449{
450 /* Clear the list early: */
451 m_list.clear();
452}
453
454void UIPopupBoxGroup::addPopupBox(UIPopupBox *pPopupBox)
455{
456 /* Add popup-box into list: */
457 m_list << pPopupBox;
458
459 /* Connect got-hover signal of the popup-box to hover-change slot of the popup-box group: */
[80914]460 connect(pPopupBox, &UIPopupBox::sigGotHover, this, &UIPopupBoxGroup::sltHoverChanged);
[41255]461}
462
463void UIPopupBoxGroup::sltHoverChanged()
464{
465 /* Fetch the sender: */
466 UIPopupBox *pPopupBox = qobject_cast<UIPopupBox*>(sender());
467
468 /* Check if sender popup-box exists/registered: */
469 if (!pPopupBox || !m_list.contains(pPopupBox))
470 return;
471
472 /* Filter the sender: */
473 QList<UIPopupBox*> list(m_list);
474 list.removeOne(pPopupBox);
475
476 /* Notify all other popup-boxes: */
477 for (int i = 0; i < list.size(); ++i)
478 list[i]->revokeHover();
479}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use