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
Line 
1/* $Id: UIPopupBox.cpp 101560 2023-10-23 16:10:12Z 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#include <QWindow>
35
36/* GUI includes: */
37#include "UIPopupBox.h"
38#ifdef VBOX_WS_MAC
39# include "UIImageTools.h"
40#endif
41
42
43/*********************************************************************************************************************************
44* Class UIPopupBox implementation. *
45*********************************************************************************************************************************/
46
47UIPopupBox::UIPopupBox(QWidget *pParent)
48 : QWidget(pParent)
49 , m_pTitleIcon(0)
50 , m_pWarningIcon(0)
51 , m_pTitleLabel(0)
52 , m_fLinkEnabled(false)
53 , m_fOpened(true)
54 , m_fHovered(false)
55 , m_pContentWidget(0)
56 , m_pLabelPath(0)
57 , m_iArrowWidth(9)
58{
59 /* Configure self: */
60 installEventFilter(this);
61
62 /* Configure painter-path: */
63 m_arrowPath.lineTo(m_iArrowWidth / 2.0, m_iArrowWidth / 2.0);
64 m_arrowPath.lineTo(m_iArrowWidth, 0);
65
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);
80
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
113}
114
115UIPopupBox::~UIPopupBox()
116{
117 /* Delete label painter-path if any: */
118 if (m_pLabelPath)
119 delete m_pLabelPath;
120}
121
122void UIPopupBox::setTitleIcon(const QIcon &icon)
123{
124 /* Remember new title-icon: */
125 m_titleIcon = icon;
126 /* Update title-icon: */
127 updateTitleIcon();
128 /* Recalculate title-size: */
129 recalc();
130}
131
132QIcon UIPopupBox::titleIcon() const
133{
134 return m_titleIcon;
135}
136
137void UIPopupBox::setWarningIcon(const QIcon &icon)
138{
139 /* Remember new warning-icon: */
140 m_warningIcon = icon;
141 /* Update warning-icon: */
142 updateWarningIcon();
143 /* Recalculate title-size: */
144 recalc();
145}
146
147QIcon UIPopupBox::warningIcon() const
148{
149 return m_warningIcon;
150}
151
152void UIPopupBox::setTitle(const QString &strTitle)
153{
154 /* Remember new title: */
155 m_strTitle = strTitle;
156 /* Update title: */
157 updateTitle();
158 /* Recalculate title-size: */
159 recalc();
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: */
170 m_strLink = strLink;
171 /* Update title: */
172 updateTitle();
173}
174
175QString UIPopupBox::titleLink() const
176{
177 return m_strLink;
178}
179
180void UIPopupBox::setTitleLinkEnabled(bool fEnabled)
181{
182 /* Remember new title-link availability flag: */
183 m_fLinkEnabled = fEnabled;
184 /* Update title: */
185 updateTitle();
186}
187
188bool UIPopupBox::isTitleLinkEnabled() const
189{
190 return m_fLinkEnabled;
191}
192
193void UIPopupBox::setContentWidget(QWidget *pWidget)
194{
195 /* Cleanup old content-widget if any: */
196 if (m_pContentWidget)
197 {
198 m_pContentWidget->removeEventFilter(this);
199 layout()->removeWidget(m_pContentWidget);
200 }
201 /* Prepare new content-wodget: */
202 m_pContentWidget = pWidget;
203 layout()->addWidget(m_pContentWidget);
204 m_pContentWidget->installEventFilter(this);
205 recalc();
206}
207
208QWidget* UIPopupBox::contentWidget() const
209{
210 return m_pContentWidget;
211}
212
213void UIPopupBox::setOpen(bool fOpened)
214{
215 /* Check if we should toggle popup-box: */
216 if (m_fOpened == fOpened)
217 return;
218
219 /* Store new value: */
220 m_fOpened = fOpened;
221
222 /* Update content-widget if present or this itself: */
223 if (m_pContentWidget)
224 m_pContentWidget->setVisible(m_fOpened);
225 else
226 update();
227
228 /* Notify listeners about content-widget visibility: */
229 if (m_pContentWidget && m_pContentWidget->isVisible())
230 emit sigUpdateContentWidget();
231}
232
233void UIPopupBox::toggleOpen()
234{
235 /* Switch 'opened' state: */
236 setOpen(!m_fOpened);
237
238 /* Notify listeners about toggling: */
239 emit sigToggled(m_fOpened);
240}
241
242bool UIPopupBox::isOpen() const
243{
244 return m_fOpened;
245}
246
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
268bool UIPopupBox::eventFilter(QObject *pObject, QEvent *pEvent)
269{
270 /* Handle all mouse-event to update hover: */
271 QEvent::Type type = pEvent->type();
272 if (type == QEvent::Enter ||
273 type == QEvent::Leave ||
274 type == QEvent::MouseMove ||
275 type == QEvent::Wheel)
276 updateHover();
277 /* Call to base-class: */
278 return QWidget::eventFilter(pObject, pEvent);
279}
280
281void UIPopupBox::resizeEvent(QResizeEvent *pEvent)
282{
283 /* Recalculate title-size: */
284 recalc();
285 /* Call to base-class: */
286 QWidget::resizeEvent(pEvent);
287}
288
289void UIPopupBox::paintEvent(QPaintEvent *pEvent)
290{
291 /* Create painter: */
292 QPainter painter(this);
293 painter.setClipRect(pEvent->rect());
294
295 QPalette pal = QApplication::palette();
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 */
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);
305 lg.setColorAt(0, base.darker(95));
306 lg.setColorAt(1, base.darker(110));
307 int theight = rect.height();
308 if (m_fOpened)
309 theight = 2 * 5 + iMaxHeightHint;
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 */
315 if (m_fHovered)
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();
320 if (m_fOpened)
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
334void UIPopupBox::mouseDoubleClickEvent(QMouseEvent *)
335{
336 /* Toggle popup-box: */
337 toggleOpen();
338}
339
340void UIPopupBox::updateTitleIcon()
341{
342 /* Assign title-icon: */
343 const int iIconMetric = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize);
344 const qreal fDevicePixelRatio = window() && window()->windowHandle() ? window()->windowHandle()->devicePixelRatio() : 1;
345 m_pTitleIcon->setPixmap(m_titleIcon.pixmap(QSize(iIconMetric, iIconMetric), fDevicePixelRatio));
346}
347
348void UIPopupBox::updateWarningIcon()
349{
350 /* Hide warning-icon if its null: */
351 m_pWarningIcon->setHidden(m_warningIcon.isNull());
352
353 /* Assign warning-icon: */
354 const int iIconMetric = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize);
355 const qreal fDevicePixelRatio = window() && window()->windowHandle() ? window()->windowHandle()->devicePixelRatio() : 1;
356 m_pWarningIcon->setPixmap(m_warningIcon.pixmap(QSize(iIconMetric, iIconMetric), fDevicePixelRatio));
357}
358
359void UIPopupBox::updateTitle()
360{
361 /* If title-link is disabled or not set: */
362 if (!m_fLinkEnabled || m_strLink.isEmpty())
363 {
364 /* We should just set simple text title: */
365 m_pTitleLabel->setText(QString("<b>%1</b>").arg(m_strTitle));
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>")
373 .arg(m_fHovered ? pal.color(QPalette::Link).name() : pal.color(QPalette::WindowText).name())
374 .arg(m_strLink)
375 .arg(m_strTitle));
376 }
377}
378
379void UIPopupBox::updateHover()
380{
381 /* Calculate new header-hover state: */
382 bool fNewHovered = m_fHovered;
383 if (m_pLabelPath && m_pLabelPath->contains(mapFromGlobal(QCursor::pos())))
384 fNewHovered = true;
385 else
386 fNewHovered = false;
387
388 /* Check if we should toggle hover: */
389 if (m_fHovered == fNewHovered)
390 return;
391
392 /* If header-hover state switched from disabled to enabled: */
393 if (!m_fHovered && fNewHovered)
394 /* Notify listeners: */
395 emit sigGotHover();
396
397 /* Toggle hover: */
398 toggleHover(fNewHovered);
399}
400
401void UIPopupBox::revokeHover()
402{
403 /* Check if we should toggle hover: */
404 if (m_fHovered == false)
405 return;
406
407 /* Toggle hover off: */
408 toggleHover(false);
409}
410
411void UIPopupBox::toggleHover(bool fHeaderHover)
412{
413 /* Remember header-hover state: */
414 m_fHovered = fHeaderHover;
415
416 /* Update title: */
417 updateTitle();
418
419 /* Call for update: */
420 update();
421}
422
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
438
439/*********************************************************************************************************************************
440* Class UIPopupBoxGroup implementation. *
441*********************************************************************************************************************************/
442
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: */
460 connect(pPopupBox, &UIPopupBox::sigGotHover, this, &UIPopupBoxGroup::sltHoverChanged);
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