VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/notificationcenter/UINotificationObjectItem.cpp

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

FE/Qt: bugref:10067: Notification-center: Do not delete notifications immediately in dismiss/close buttons handlers, perform async delete instead; This should help fixing use-after-free in QToolButton internals.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 15.7 KB
Line 
1/* $Id: UINotificationObjectItem.cpp 101669 2023-10-31 04:47:47Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UINotificationObjectItem class implementation.
4 */
5
6/*
7 * Copyright (C) 2021-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 <QFont>
31#include <QHBoxLayout>
32#include <QLabel>
33#include <QPainter>
34#include <QPaintEvent>
35#include <QProgressBar>
36#include <QVBoxLayout>
37
38/* GUI includes: */
39#include "QIRichTextLabel.h"
40#include "QIToolButton.h"
41#include "UIHelpBrowserDialog.h"
42#include "UIIconPool.h"
43#include "UIMessageCenter.h"
44#include "UINotificationObject.h"
45#include "UINotificationObjectItem.h"
46
47
48/*********************************************************************************************************************************
49* Class UINotificationObjectItem implementation. *
50*********************************************************************************************************************************/
51
52UINotificationObjectItem::UINotificationObjectItem(QWidget *pParent, UINotificationObject *pObject /* = 0 */)
53 : QWidget(pParent)
54 , m_pObject(pObject)
55 , m_pLayoutMain(0)
56 , m_pLayoutUpper(0)
57 , m_pLabelName(0)
58 , m_pButtonHelp(0)
59 , m_pButtonForget(0)
60 , m_pButtonClose(0)
61 , m_pLabelDetails(0)
62 , m_fHovered(false)
63 , m_fToggled(false)
64{
65 /* Make sure item is opaque. */
66 setAutoFillBackground(true);
67
68 /* Prepare main layout: */
69 m_pLayoutMain = new QVBoxLayout(this);
70 if (m_pLayoutMain)
71 {
72 /* Prepare upper layout: */
73 m_pLayoutUpper = new QHBoxLayout;
74 if (m_pLayoutUpper)
75 {
76 /* Prepare name label: */
77 m_pLabelName = new QLabel(this);
78 if (m_pLabelName)
79 {
80 m_pLabelName->setText(m_pObject->name());
81 m_pLayoutUpper->addWidget(m_pLabelName);
82 }
83
84 /* Prepare help button: */
85 if (!m_pObject->helpKeyword().isEmpty())
86 m_pButtonHelp = new QIToolButton(this);
87 if (m_pButtonHelp)
88 {
89 m_pButtonHelp->setIcon(UIIconPool::iconSet(":/help_16px.png"));
90 m_pButtonHelp->setIconSize(QSize(10, 10));
91 m_pButtonHelp->setProperty("helpkeyword", m_pObject->helpKeyword());
92 connect(m_pButtonHelp, &QIToolButton::clicked,
93 this, &UINotificationObjectItem::sltHandleHelpRequest);
94
95 m_pLayoutUpper->addWidget(m_pButtonHelp);
96 }
97
98 /* Prepare forget button: */
99 if (!m_pObject->internalName().isEmpty())
100 m_pButtonForget = new QIToolButton(this);
101 if (m_pButtonForget)
102 {
103 m_pButtonForget->setIcon(UIIconPool::iconSet(":/close_popup_16px.png"));
104 m_pButtonForget->setIconSize(QSize(10, 10));
105 connect(m_pButtonForget, &QIToolButton::clicked,
106 m_pObject, &UINotificationObject::dismiss,
107 Qt::QueuedConnection);
108
109 m_pLayoutUpper->addWidget(m_pButtonForget);
110 }
111
112 /* Prepare close button: */
113 m_pButtonClose = new QIToolButton(this);
114 if (m_pButtonClose)
115 {
116 m_pButtonClose->setIcon(UIIconPool::iconSet(":/close_16px.png"));
117 m_pButtonClose->setIconSize(QSize(10, 10));
118 connect(m_pButtonClose, &QIToolButton::clicked,
119 m_pObject, &UINotificationObject::close,
120 Qt::QueuedConnection);
121
122 m_pLayoutUpper->addWidget(m_pButtonClose);
123 }
124
125 /* Add to layout: */
126 m_pLayoutMain->addLayout(m_pLayoutUpper);
127 }
128
129 /* Prepare details label: */
130 m_pLabelDetails = new QIRichTextLabel(this);
131 if (m_pLabelDetails)
132 {
133 QFont myFont = m_pLabelDetails->font();
134 myFont.setPointSize(myFont.pointSize() - 1);
135 m_pLabelDetails->setBrowserFont(myFont);
136 m_pLabelDetails->setVisible(false);
137 int iHint = m_pLabelName->minimumSizeHint().width();
138 if (m_pButtonHelp)
139 iHint += m_pLayoutUpper->spacing() + m_pButtonHelp->minimumSizeHint().width();
140 if (m_pButtonForget)
141 iHint += m_pLayoutUpper->spacing() + m_pButtonForget->minimumSizeHint().width();
142 if (m_pButtonClose)
143 iHint += m_pLayoutUpper->spacing() + m_pButtonClose->minimumSizeHint().width();
144 m_pLabelDetails->setMinimumTextWidth(iHint);
145 m_pLabelDetails->setText(m_pObject->details());
146
147 m_pLayoutMain->addWidget(m_pLabelDetails);
148 }
149 }
150}
151
152bool UINotificationObjectItem::event(QEvent *pEvent)
153{
154 /* Handle required event types: */
155 switch (pEvent->type())
156 {
157 case QEvent::Enter:
158 case QEvent::MouseMove:
159 {
160 m_fHovered = true;
161 update();
162 break;
163 }
164 case QEvent::Leave:
165 {
166 m_fHovered = false;
167 update();
168 break;
169 }
170 case QEvent::MouseButtonRelease:
171 {
172 m_fToggled = !m_fToggled;
173 m_pLabelDetails->setVisible(m_fToggled);
174 break;
175 }
176 default:
177 break;
178 }
179
180 /* Call to base-class: */
181 return QWidget::event(pEvent);
182}
183
184void UINotificationObjectItem::paintEvent(QPaintEvent *pPaintEvent)
185{
186 /* Prepare painter: */
187 QPainter painter(this);
188 painter.setClipRect(pPaintEvent->rect());
189 /* Acquire palette: */
190 const bool fActive = isActiveWindow();
191 QPalette pal = QApplication::palette();
192
193 /* Gather suitable colors: */
194 QColor color = pal.color(fActive ? QPalette::Active : QPalette::Inactive, QPalette::Window);
195 QColor color1;
196 QColor color2;
197 if (color.black() > 128)
198 {
199 color1 = color.lighter(110);
200 color2 = color.lighter(105);
201 }
202 else
203 {
204 color1 = color.darker(105);
205 color2 = color.darker(110);
206 }
207 /* Prepare background gradient: */
208 QLinearGradient grad(QPointF(0, 0), QPointF(width(), height()));
209 {
210 grad.setColorAt(0, color1);
211 grad.setColorAt(1, color2);
212 }
213 /* Fill background: */
214 painter.fillRect(rect(), grad);
215
216 /* If item is hovered: */
217 if (m_fHovered)
218 {
219 /* Gather suitable color: */
220 QColor color3 = pal.color(fActive ? QPalette::Active : QPalette::Inactive, QPalette::Highlight);
221 /* Override painter pen: */
222 painter.setPen(color3);
223 /* Draw frame: */
224 painter.drawRect(rect());
225 }
226}
227
228
229/*********************************************************************************************************************************
230* Class UINotificationProgressItem implementation. *
231*********************************************************************************************************************************/
232
233UINotificationProgressItem::UINotificationProgressItem(QWidget *pParent, UINotificationProgress *pProgress /* = 0 */)
234 : UINotificationObjectItem(pParent, pProgress)
235 , m_pProgressBar(0)
236{
237 /* Main layout was prepared in base-class: */
238 if (m_pLayoutMain)
239 {
240 /* Name label was prepared in base-class: */
241 if (m_pLabelName)
242 m_pLabelName->setText(progress()->name());
243 /* Details label was prepared in base-class: */
244 if (m_pLabelDetails)
245 {
246 const int iHint = m_pLabelName->minimumSizeHint().width()
247 + m_pLayoutUpper->spacing()
248 + m_pButtonClose->minimumSizeHint().width();
249 m_pLabelDetails->setMinimumTextWidth(iHint);
250 updateDetails();
251 }
252
253 /* Prepare progress-bar: */
254 m_pProgressBar = new QProgressBar(this);
255 if (m_pProgressBar)
256 {
257 m_pProgressBar->setMinimum(0);
258 m_pProgressBar->setMaximum(100);
259 m_pProgressBar->setValue(progress()->percent());
260
261 m_pLayoutMain->addWidget(m_pProgressBar);
262 }
263 }
264
265 /* Prepare progress connections: */
266 connect(progress(), &UINotificationProgress::sigProgressStarted,
267 this, &UINotificationProgressItem::sltHandleProgressStarted);
268 connect(progress(), &UINotificationProgress::sigProgressChange,
269 this, &UINotificationProgressItem::sltHandleProgressChange);
270 connect(progress(), &UINotificationProgress::sigProgressFinished,
271 this, &UINotificationProgressItem::sltHandleProgressFinished);
272}
273
274void UINotificationProgressItem::sltHandleProgressStarted()
275{
276 /* Init close-button and progress-bar states: */
277 if (m_pButtonClose)
278 m_pButtonClose->setEnabled(progress()->isCancelable());
279 if (m_pProgressBar)
280 m_pProgressBar->setValue(0);
281 /* Update details with fetched stuff if any: */
282 if (m_pLabelDetails)
283 updateDetails();
284}
285
286void UINotificationProgressItem::sltHandleProgressChange(ulong uPercent)
287{
288 /* Update close-button and progress-bar states: */
289 if (m_pButtonClose)
290 m_pButtonClose->setEnabled(progress()->isCancelable());
291 if (m_pProgressBar)
292 m_pProgressBar->setValue(uPercent);
293}
294
295void UINotificationProgressItem::sltHandleProgressFinished()
296{
297 /* Finalize close-button and progress-bar states: */
298 if (m_pButtonClose)
299 m_pButtonClose->setEnabled(true);
300 if (m_pProgressBar)
301 m_pProgressBar->setValue(100);
302 /* Update details with error text if any: */
303 if (m_pLabelDetails)
304 updateDetails();
305}
306
307UINotificationProgress *UINotificationProgressItem::progress() const
308{
309 return qobject_cast<UINotificationProgress*>(m_pObject);
310}
311
312void UINotificationProgressItem::updateDetails()
313{
314 AssertPtrReturnVoid(m_pLabelDetails);
315 const QString strDetails = progress()->details();
316 const QString strError = progress()->error();
317 const QString strFullDetails = strError.isNull()
318 ? strDetails
319 : QString("%1<br>%2").arg(strDetails, strError);
320 m_pLabelDetails->setText(strFullDetails);
321 if (!strError.isEmpty())
322 {
323 m_fToggled = true;
324 m_pLabelDetails->setVisible(m_fToggled);
325 }
326}
327
328
329#ifdef VBOX_GUI_WITH_NETWORK_MANAGER
330
331
332/*********************************************************************************************************************************
333* Class UINotificationDownloaderItem implementation. *
334*********************************************************************************************************************************/
335
336UINotificationDownloaderItem::UINotificationDownloaderItem(QWidget *pParent, UINotificationDownloader *pDownloader /* = 0 */)
337 : UINotificationObjectItem(pParent, pDownloader)
338 , m_pProgressBar(0)
339{
340 /* Main layout was prepared in base-class: */
341 if (m_pLayoutMain)
342 {
343 /* Name label was prepared in base-class: */
344 if (m_pLabelName)
345 m_pLabelName->setText(downloader()->name());
346 /* Details label was prepared in base-class: */
347 if (m_pLabelDetails)
348 {
349 const int iHint = m_pLabelName->minimumSizeHint().width()
350 + m_pLayoutUpper->spacing()
351 + m_pButtonClose->minimumSizeHint().width();
352 m_pLabelDetails->setMinimumTextWidth(iHint);
353 updateDetails();
354 }
355
356 /* Prepare progress-bar: */
357 m_pProgressBar = new QProgressBar(this);
358 if (m_pProgressBar)
359 {
360 m_pProgressBar->setMinimum(0);
361 m_pProgressBar->setMaximum(100);
362 m_pProgressBar->setValue(downloader()->percent());
363
364 m_pLayoutMain->addWidget(m_pProgressBar);
365 }
366 }
367
368 /* Prepare downloader connections: */
369 connect(downloader(), &UINotificationDownloader::sigProgressStarted,
370 this, &UINotificationDownloaderItem::sltHandleProgressStarted);
371 connect(downloader(), &UINotificationDownloader::sigProgressChange,
372 this, &UINotificationDownloaderItem::sltHandleProgressChange);
373 connect(downloader(), &UINotificationDownloader::sigProgressFailed,
374 this, &UINotificationDownloaderItem::sltHandleProgressFinished);
375 connect(downloader(), &UINotificationDownloader::sigProgressCanceled,
376 this, &UINotificationDownloaderItem::sltHandleProgressFinished);
377 connect(downloader(), &UINotificationDownloader::sigProgressFinished,
378 this, &UINotificationDownloaderItem::sltHandleProgressFinished);
379}
380
381void UINotificationDownloaderItem::sltHandleProgressStarted()
382{
383 /* Init progress-bar state: */
384 if (m_pProgressBar)
385 m_pProgressBar->setValue(0);
386 /* Update details with fetched stuff if any: */
387 if (m_pLabelDetails)
388 updateDetails();
389}
390
391void UINotificationDownloaderItem::sltHandleProgressChange(ulong uPercent)
392{
393 /* Update progress-bar state: */
394 if (m_pProgressBar)
395 m_pProgressBar->setValue(uPercent);
396}
397
398void UINotificationDownloaderItem::sltHandleProgressFinished()
399{
400 /* Finalize progress-bar state: */
401 if (m_pProgressBar)
402 m_pProgressBar->setValue(100);
403 /* Update details with error text if any: */
404 if (m_pLabelDetails)
405 updateDetails();
406}
407
408UINotificationDownloader *UINotificationDownloaderItem::downloader() const
409{
410 return qobject_cast<UINotificationDownloader*>(m_pObject);
411}
412
413void UINotificationDownloaderItem::updateDetails()
414{
415 AssertPtrReturnVoid(m_pLabelDetails);
416 const QString strDetails = downloader()->details();
417 const QString strError = downloader()->error();
418 const QString strFullDetails = strError.isNull()
419 ? strDetails
420 : QString("%1<br>%2").arg(strDetails, strError);
421 m_pLabelDetails->setText(strFullDetails);
422 if (!strError.isEmpty())
423 {
424 m_fToggled = true;
425 m_pLabelDetails->setVisible(m_fToggled);
426 }
427}
428
429#endif /* VBOX_GUI_WITH_NETWORK_MANAGER */
430
431
432/*********************************************************************************************************************************
433* Namespace UINotificationProgressItem implementation. *
434*********************************************************************************************************************************/
435
436UINotificationObjectItem *UINotificationItem::create(QWidget *pParent, UINotificationObject *pObject)
437{
438 /* Handle known types: */
439 if (pObject->inherits("UINotificationProgress"))
440 return new UINotificationProgressItem(pParent, static_cast<UINotificationProgress*>(pObject));
441#ifdef VBOX_GUI_WITH_NETWORK_MANAGER
442 else if (pObject->inherits("UINotificationDownloader"))
443 return new UINotificationDownloaderItem(pParent, static_cast<UINotificationDownloader*>(pObject));
444#endif
445 /* Handle defaults: */
446 return new UINotificationObjectItem(pParent, pObject);
447}
448
449void UINotificationObjectItem::sltHandleHelpRequest()
450{
451 UIHelpBrowserDialog::findManualFileAndShow("helpkeyword");
452}
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use