VirtualBox

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

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

FE/Qt: bugref:10450: Get rid of Qt5 stuff; This one is about device-pixel-ratio stuff.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use