VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/notificationcenter/UINotificationCenter.cpp@ 103977

Last change on this file since 103977 was 101122, checked in by vboxsync, 15 months ago

FE/Qt: Fixing UINotificationCenter use-after-free revealed by asan; This was reproducible only for the case when keep finished progresses alive functionality is enabled.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 29.4 KB
Line 
1/* $Id: UINotificationCenter.cpp 101122 2023-09-14 15:59:37Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UINotificationCenter 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 <QHBoxLayout>
31#include <QMenu>
32#include <QPainter>
33#include <QPaintEvent>
34#include <QPropertyAnimation>
35#include <QScrollArea>
36#include <QSignalTransition>
37#include <QState>
38#include <QStateMachine>
39#include <QStyle>
40#include <QTimer>
41#include <QVBoxLayout>
42
43/* GUI includes: */
44#include "QIToolButton.h"
45#include "UIExtraDataManager.h"
46#include "UIIconPool.h"
47#include "UINotificationCenter.h"
48#include "UINotificationObjectItem.h"
49#include "UINotificationModel.h"
50
51/* Other VBox includes: */
52#include "iprt/assert.h"
53
54
55/** QScrollArea extension to make notification scroll-area more versatile. */
56class UINotificationScrollArea : public QScrollArea
57{
58 Q_OBJECT;
59
60public:
61
62 /** Creates notification scroll-area passing @a pParent to the base-class. */
63 UINotificationScrollArea(QWidget *pParent = 0);
64
65 /** Returns minimum size-hint. */
66 virtual QSize minimumSizeHint() const /* override final */;
67
68 /** Assigns scrollable @a pWidget.
69 * @note Keep in mind that's an override, but NOT a virtual method. */
70 void setWidget(QWidget *pWidget);
71
72protected:
73
74 /** Preprocesses @a pEvent for registered @a pWatched object. */
75 virtual bool eventFilter(QObject *pWatched, QEvent *pEvent) /* override final */;
76};
77
78
79/*********************************************************************************************************************************
80* Class UINotificationScrollArea implementation. *
81*********************************************************************************************************************************/
82
83UINotificationScrollArea::UINotificationScrollArea(QWidget *pParent /* = 0 */)
84 : QScrollArea(pParent)
85{
86 setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
87}
88
89QSize UINotificationScrollArea::minimumSizeHint() const
90{
91 /* So, here is the logic,
92 * we are taking width from widget if it's present,
93 * while keeping height calculated by the base-class. */
94 const QSize msh = QScrollArea::minimumSizeHint();
95 return widget() ? QSize(widget()->minimumSizeHint().width(), msh.height()) : msh;
96}
97
98void UINotificationScrollArea::setWidget(QWidget *pWidget)
99{
100 /* We'd like to listen for a new widget's events: */
101 if (widget())
102 widget()->removeEventFilter(this);
103 pWidget->installEventFilter(this);
104
105 /* Call to base-class: */
106 QScrollArea::setWidget(pWidget);
107}
108
109bool UINotificationScrollArea::eventFilter(QObject *pWatched, QEvent *pEvent)
110{
111 /* For listened widget: */
112 if (pWatched == widget())
113 {
114 switch (pEvent->type())
115 {
116 /* We'd like to handle layout-request events: */
117 case QEvent::LayoutRequest:
118 updateGeometry();
119 break;
120 default:
121 break;
122 }
123 }
124
125 /* Call to base-class: */
126 return QScrollArea::eventFilter(pWatched, pEvent);
127}
128
129
130/*********************************************************************************************************************************
131* Class UINotificationCenter implementation. *
132*********************************************************************************************************************************/
133
134/* static */
135UINotificationCenter *UINotificationCenter::s_pInstance = 0;
136
137/* static */
138void UINotificationCenter::create(QWidget *pParent /* = 0 */)
139{
140 AssertReturnVoid(!s_pInstance);
141 s_pInstance = new UINotificationCenter(pParent);
142}
143
144/* static */
145void UINotificationCenter::destroy()
146{
147 AssertPtrReturnVoid(s_pInstance);
148 delete s_pInstance;
149 s_pInstance = 0;
150}
151
152/* static */
153UINotificationCenter *UINotificationCenter::instance()
154{
155 return s_pInstance;
156}
157
158UINotificationCenter::UINotificationCenter(QWidget *pParent)
159 : QIWithRetranslateUI<QWidget>(pParent)
160 , m_pModel(0)
161 , m_enmAlignment(Qt::AlignTop)
162 , m_enmOrder(Qt::AscendingOrder)
163 , m_pLayoutMain(0)
164 , m_pLayoutButtons(0)
165 , m_pButtonOpen(0)
166 , m_pButtonToggleSorting(0)
167#ifdef VBOX_NOTIFICATION_CENTER_WITH_KEEP_BUTTON
168 , m_pButtonKeepFinished(0)
169#endif
170 , m_pButtonRemoveFinished(0)
171 , m_pLayoutItems(0)
172 , m_pStateMachineSliding(0)
173 , m_iAnimatedValue(0)
174 , m_pTimerOpen(0)
175 , m_fLastResult(false)
176{
177 prepare();
178}
179
180UINotificationCenter::~UINotificationCenter()
181{
182 cleanup();
183}
184
185void UINotificationCenter::setParent(QWidget *pParent)
186{
187 /* Additionally hide if parent unset: */
188 if (!pParent)
189 setHidden(true);
190
191 /* Uninstall filter from previous parent: */
192 if (parent())
193 parent()->removeEventFilter(this);
194
195 /* Reparent: */
196 QIWithRetranslateUI<QWidget>::setParent(pParent);
197
198 /* Install filter to new parent: */
199 if (parent())
200 parent()->installEventFilter(this);
201
202 /* Show only if there is something to show: */
203 if (parent())
204 setHidden(m_pModel->ids().isEmpty());
205}
206
207void UINotificationCenter::invoke()
208{
209 /* Open if center isn't opened yet: */
210 if (!m_pButtonOpen->isChecked())
211 m_pButtonOpen->animateClick();
212}
213
214QUuid UINotificationCenter::append(UINotificationObject *pObject)
215{
216 /* Sanity check: */
217 AssertPtrReturn(m_pModel, QUuid());
218 AssertPtrReturn(pObject, QUuid());
219
220 /* Is object critical? */
221 const bool fCritical = pObject->isCritical();
222 /* Is object progress? */
223 const bool fProgress = pObject->inherits("UINotificationProgress");
224
225 /* Handle object. Be aware it can be deleted during handling! */
226 const QUuid uId = m_pModel->appendObject(pObject);
227
228 /* If object is critical and center isn't opened yet: */
229 if (!m_pButtonOpen->isChecked() && fCritical)
230 {
231 /* We should delay progresses for a bit: */
232 const int iDelay = fProgress ? 2000 : 0;
233 /* We should issue an open request: */
234 AssertPtrReturn(m_pTimerOpen, uId);
235 m_uOpenObjectId = uId;
236 m_pTimerOpen->start(iDelay);
237 }
238
239 return uId;
240}
241
242void UINotificationCenter::revoke(const QUuid &uId)
243{
244 AssertReturnVoid(!uId.isNull());
245 return m_pModel->revokeObject(uId);
246}
247
248bool UINotificationCenter::handleNow(UINotificationProgress *pProgress)
249{
250 /* Check for the recursive run: */
251 AssertMsgReturn(!m_pEventLoop, ("UINotificationCenter::handleNow() is called recursively!\n"), false);
252
253 /* Reset the result: */
254 m_fLastResult = false;
255
256 /* Guard progress for the case
257 * it destroyed itself in his append call: */
258 QPointer<UINotificationProgress> guardProgress = pProgress;
259 connect(pProgress, &UINotificationProgress::sigProgressFinished,
260 this, &UINotificationCenter::sltHandleProgressFinished);
261 append(pProgress);
262
263 /* Is progress still valid? */
264 if (guardProgress.isNull())
265 return m_fLastResult;
266 /* Is progress still running? */
267 if (guardProgress->isDone())
268 return m_fLastResult;
269
270 /* Create a local event-loop: */
271 QEventLoop eventLoop;
272 m_pEventLoop = &eventLoop;
273
274 /* Guard ourself for the case
275 * we destroyed ourself in our event-loop: */
276 QPointer<UINotificationCenter> guardThis = this;
277
278 /* Start the blocking event-loop: */
279 eventLoop.exec();
280
281 /* Are we still valid? */
282 if (guardThis.isNull())
283 return false;
284
285 /* Cleanup event-loop: */
286 m_pEventLoop = 0;
287
288 /* Return actual result: */
289 return m_fLastResult;
290}
291
292bool UINotificationCenter::hasOperationsPending() const
293{
294 return m_pEventLoop;
295}
296
297void UINotificationCenter::abortOperations()
298{
299 m_pEventLoop->exit();
300 emit sigOperationsAborted();
301}
302
303void UINotificationCenter::retranslateUi()
304{
305 if (m_pButtonOpen)
306 m_pButtonOpen->setToolTip(tr("Open notification center"));
307 if (m_pButtonToggleSorting)
308 m_pButtonToggleSorting->setToolTip(tr("Toggle ascending/descending order"));
309#ifdef VBOX_NOTIFICATION_CENTER_WITH_KEEP_BUTTON
310 if (m_pButtonKeepFinished)
311 m_pButtonKeepFinished->setToolTip(tr("Keep finished progresses"));
312#endif
313 if (m_pButtonRemoveFinished)
314 m_pButtonRemoveFinished->setToolTip(tr("Delete finished notifications"));
315}
316
317bool UINotificationCenter::eventFilter(QObject *pObject, QEvent *pEvent)
318{
319 /* For parent object only: */
320 if (pObject == parent())
321 {
322 /* Handle required event types: */
323 switch (pEvent->type())
324 {
325 case QEvent::Resize:
326 {
327 /* When parent being resized we want
328 * to adjust overlay accordingly. */
329 adjustGeometry();
330 break;
331 }
332 default:
333 break;
334 }
335 }
336
337 /* Call to base-class: */
338 return QIWithRetranslateUI<QWidget>::eventFilter(pObject, pEvent);
339}
340
341bool UINotificationCenter::event(QEvent *pEvent)
342{
343 /* Handle required event types: */
344 switch (pEvent->type())
345 {
346 /* When we are being asked to update layout
347 * we want to adjust overlay accordingly. */
348 case QEvent::LayoutRequest:
349 {
350 adjustGeometry();
351 break;
352 }
353 /* When we are being resized or moved we want
354 * to adjust transparency mask accordingly. */
355 case QEvent::Move:
356 case QEvent::Resize:
357 {
358 adjustMask();
359 break;
360 }
361 default:
362 break;
363 }
364
365 /* Call to base-class: */
366 return QIWithRetranslateUI<QWidget>::event(pEvent);
367}
368
369void UINotificationCenter::paintEvent(QPaintEvent *pEvent)
370{
371 /* Sanity check: */
372 AssertPtrReturnVoid(pEvent);
373
374 /* Prepare painter: */
375 QPainter painter(this);
376
377 /* Limit painting with incoming rectangle: */
378 painter.setClipRect(pEvent->rect());
379
380 /* Paint background: */
381 paintBackground(&painter);
382 paintFrame(&painter);
383}
384
385void UINotificationCenter::sltHandleAlignmentChange()
386{
387 /* Update alignment: */
388 m_enmAlignment = gEDataManager->notificationCenterAlignment();
389
390 /* Re-insert to layout: */
391 m_pLayoutMain->removeItem(m_pLayoutButtons);
392 m_pLayoutMain->insertLayout(m_enmAlignment == Qt::AlignTop ? 0 : -1, m_pLayoutButtons);
393
394 /* Adjust mask to make sure button visible, layout should be finalized already: */
395 QCoreApplication::sendPostedEvents(0, QEvent::LayoutRequest);
396 adjustMask();
397}
398
399void UINotificationCenter::sltIssueOrderChange()
400{
401 const Qt::SortOrder enmSortOrder = m_pButtonToggleSorting->isChecked()
402 ? Qt::AscendingOrder
403 : Qt::DescendingOrder;
404 gEDataManager->setNotificationCenterOrder(enmSortOrder);
405}
406
407void UINotificationCenter::sltHandleOrderChange()
408{
409 /* Save new order: */
410 m_enmOrder = gEDataManager->notificationCenterOrder();
411
412 /* Cleanup items first: */
413 cleanupItems();
414
415 /* Populate model contents again: */
416 foreach (const QUuid &uId, m_pModel->ids())
417 {
418 UINotificationObjectItem *pItem = UINotificationItem::create(this, m_pModel->objectById(uId));
419 m_items[uId] = pItem;
420 m_pLayoutItems->insertWidget(m_enmOrder == Qt::AscendingOrder ? -1 : 0, pItem);
421 }
422
423 /* Hide and slide away if there are no notifications to show: */
424 setHidden(m_pModel->ids().isEmpty());
425 if (m_pModel->ids().isEmpty() && m_pButtonOpen->isChecked())
426 m_pButtonOpen->toggle();
427}
428
429void UINotificationCenter::sltHandleOpenButtonToggled(bool fToggled)
430{
431 if (fToggled)
432 emit sigOpen();
433 else
434 emit sigClose();
435}
436
437#ifdef VBOX_NOTIFICATION_CENTER_WITH_KEEP_BUTTON
438void UINotificationCenter::sltHandleKeepButtonToggled(bool fToggled)
439{
440 gEDataManager->setKeepSuccessfullNotificationProgresses(fToggled);
441}
442#endif /* VBOX_NOTIFICATION_CENTER_WITH_KEEP_BUTTON */
443
444void UINotificationCenter::sltHandleRemoveFinishedButtonClicked()
445{
446 m_pModel->revokeFinishedObjects();
447}
448
449void UINotificationCenter::sltHandleOpenButtonContextMenuRequested(const QPoint &)
450{
451 /* Create menu: */
452 QMenu menu(m_pButtonOpen);
453
454 /* Create action: */
455 QAction action( m_enmAlignment == Qt::AlignTop
456 ? tr("Align Bottom")
457 : tr("Align Top"),
458 m_pButtonOpen);
459 menu.addAction(&action);
460
461 /* Execute menu, check if any (single) action is clicked: */
462 QAction *pAction = menu.exec(m_pButtonOpen->mapToGlobal(QPoint(m_pButtonOpen->width(), 0)));
463 if (pAction)
464 {
465 const Qt::Alignment enmAlignment = m_enmAlignment == Qt::AlignTop
466 ? Qt::AlignBottom
467 : Qt::AlignTop;
468 gEDataManager->setNotificationCenterAlignment(enmAlignment);
469 }
470}
471
472void UINotificationCenter::sltHandleOpenTimerTimeout()
473{
474 /* Make sure it's invoked by corresponding timer only: */
475 QTimer *pTimer = qobject_cast<QTimer*>(sender());
476 AssertPtrReturnVoid(pTimer);
477 AssertReturnVoid(pTimer == m_pTimerOpen);
478
479 /* Stop corresponding timer: */
480 m_pTimerOpen->stop();
481
482 /* Check whether we really closed: */
483 if (m_pButtonOpen->isChecked())
484 return;
485
486 /* Check whether message with particular ID exists: */
487 if (!m_pModel->hasObject(m_uOpenObjectId))
488 return;
489
490 /* Toggle open button: */
491 m_pButtonOpen->animateClick();
492}
493
494void UINotificationCenter::sltHandleModelItemAdded(const QUuid &uId)
495{
496 /* Add corresponding model item representation: */
497 AssertReturnVoid(!m_items.contains(uId));
498 UINotificationObjectItem *pItem = UINotificationItem::create(this, m_pModel->objectById(uId));
499 m_items[uId] = pItem;
500 m_pLayoutItems->insertWidget(m_enmOrder == Qt::AscendingOrder ? -1 : 0, pItem);
501
502 /* Show if there are notifications to show: */
503 setHidden(m_pModel->ids().isEmpty());
504}
505
506void UINotificationCenter::sltHandleModelItemRemoved(const QUuid &uId)
507{
508 /* Remove corresponding model item representation if present: */
509 if (m_items.contains(uId))
510 delete m_items.take(uId);
511
512 /* Hide and slide away if there are no notifications to show: */
513 setHidden(m_pModel->ids().isEmpty());
514 if (m_pModel->ids().isEmpty() && m_pButtonOpen->isChecked())
515 m_pButtonOpen->toggle();
516}
517
518void UINotificationCenter::sltHandleProgressFinished()
519{
520 /* Acquire the sender: */
521 UINotificationProgress *pProgress = qobject_cast<UINotificationProgress*>(sender());
522 AssertPtrReturnVoid(pProgress);
523
524 /* Set the result: */
525 m_fLastResult = pProgress->error().isNull();
526
527 /* Break the loop if exists: */
528 if (m_pEventLoop)
529 m_pEventLoop->exit();
530}
531
532void UINotificationCenter::prepare()
533{
534 /* Hide initially: */
535 setHidden(true);
536
537 /* Listen for parent events: */
538 if (parent())
539 parent()->installEventFilter(this);
540
541 /* Prepare the rest of stuff: */
542 prepareModel();
543 prepareWidgets();
544 prepareStateMachineSliding();
545 prepareOpenTimer();
546
547 /* Prepare alignment: */
548 m_enmAlignment = gEDataManager->notificationCenterAlignment();
549 connect(gEDataManager, &UIExtraDataManager::sigNotificationCenterAlignmentChange,
550 this, &UINotificationCenter::sltHandleAlignmentChange);
551 sltHandleAlignmentChange();
552 /* Prepare order: */
553 m_enmOrder = gEDataManager->notificationCenterOrder();
554 connect(gEDataManager, &UIExtraDataManager::sigNotificationCenterOrderChange,
555 this, &UINotificationCenter::sltHandleOrderChange);
556 sltHandleOrderChange();
557
558 /* Apply language settings: */
559 retranslateUi();
560}
561
562void UINotificationCenter::prepareModel()
563{
564 m_pModel = new UINotificationModel(this);
565 if (m_pModel)
566 {
567 connect(m_pModel, &UINotificationModel::sigItemAdded,
568 this, &UINotificationCenter::sltHandleModelItemAdded);
569 connect(m_pModel, &UINotificationModel::sigItemRemoved,
570 this, &UINotificationCenter::sltHandleModelItemRemoved);
571 }
572}
573
574void UINotificationCenter::prepareWidgets()
575{
576 /* Prepare main layout: */
577 m_pLayoutMain = new QVBoxLayout(this);
578 if (m_pLayoutMain)
579 {
580 /* Create container scroll-area: */
581 UINotificationScrollArea *pScrollAreaContainer = new UINotificationScrollArea(this);
582 if (pScrollAreaContainer)
583 {
584 /* Prepare container widget: */
585 QWidget *pWidgetContainer = new QWidget(pScrollAreaContainer);
586 if (pWidgetContainer)
587 {
588 /* Prepare container layout: */
589 QVBoxLayout *pLayoutContainer = new QVBoxLayout(pWidgetContainer);
590 if (pLayoutContainer)
591 {
592 pLayoutContainer->setContentsMargins(0, 0, 0, 0);
593
594 /* Prepare items layout: */
595 m_pLayoutItems = new QVBoxLayout;
596 if (m_pLayoutItems)
597 pLayoutContainer->addLayout(m_pLayoutItems);
598
599 pLayoutContainer->addStretch();
600 }
601
602 /* Add to scroll-area: */
603 pScrollAreaContainer->setWidget(pWidgetContainer);
604 }
605
606 /* Configure container scroll-area: */
607 pScrollAreaContainer->setWidgetResizable(true);
608 pScrollAreaContainer->setFrameShape(QFrame::NoFrame);
609 pScrollAreaContainer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
610 pScrollAreaContainer->viewport()->setAutoFillBackground(false);
611 pScrollAreaContainer->widget()->setAutoFillBackground(false);
612
613 /* Add to layout: */
614 m_pLayoutMain->addWidget(pScrollAreaContainer);
615 }
616
617 /* Prepare buttons layout: */
618 m_pLayoutButtons = new QHBoxLayout;
619 if (m_pLayoutButtons)
620 {
621 m_pLayoutButtons->setContentsMargins(0, 0, 0, 0);
622
623 /* Prepare open button: */
624 m_pButtonOpen = new QIToolButton(this);
625 if (m_pButtonOpen)
626 {
627 m_pButtonOpen->setIcon(UIIconPool::iconSet(":/notification_center_16px.png"));
628 m_pButtonOpen->setCheckable(true);
629 m_pButtonOpen->setContextMenuPolicy(Qt::CustomContextMenu);
630 connect(m_pButtonOpen, &QIToolButton::toggled,
631 this, &UINotificationCenter::sltHandleOpenButtonToggled);
632 connect(m_pButtonOpen, &QIToolButton::customContextMenuRequested,
633 this, &UINotificationCenter::sltHandleOpenButtonContextMenuRequested);
634 m_pLayoutButtons->addWidget(m_pButtonOpen);
635 }
636
637 /* Add stretch: */
638 m_pLayoutButtons->addStretch(1);
639
640 /* Prepare toggle-sorting button: */
641 m_pButtonToggleSorting = new QIToolButton(this);
642 if (m_pButtonToggleSorting)
643 {
644 m_pButtonToggleSorting->setIcon(UIIconPool::iconSet(":/notification_center_sort_16px.png"));
645 m_pButtonToggleSorting->setCheckable(true);
646 m_pButtonToggleSorting->setChecked(gEDataManager->notificationCenterOrder() == Qt::AscendingOrder);
647 connect(m_pButtonToggleSorting, &QIToolButton::toggled, this, &UINotificationCenter::sltIssueOrderChange);
648 m_pLayoutButtons->addWidget(m_pButtonToggleSorting);
649 }
650
651#ifdef VBOX_NOTIFICATION_CENTER_WITH_KEEP_BUTTON
652 /* Prepare keep-finished button: */
653 m_pButtonKeepFinished = new QIToolButton(this);
654 if (m_pButtonKeepFinished)
655 {
656 m_pButtonKeepFinished->setIcon(UIIconPool::iconSet(":/notification_center_hold_progress_16px.png"));
657 m_pButtonKeepFinished->setCheckable(true);
658 m_pButtonKeepFinished->setChecked(gEDataManager->keepSuccessfullNotificationProgresses());
659 connect(m_pButtonKeepFinished, &QIToolButton::toggled, this, &UINotificationCenter::sltHandleKeepButtonToggled);
660 m_pLayoutButtons->addWidget(m_pButtonKeepFinished);
661 }
662#endif /* VBOX_NOTIFICATION_CENTER_WITH_KEEP_BUTTON */
663
664 /* Prepare remove-finished button: */
665 m_pButtonRemoveFinished = new QIToolButton(this);
666 if (m_pButtonRemoveFinished)
667 {
668 m_pButtonRemoveFinished->setIcon(UIIconPool::iconSet(":/notification_center_delete_progress_16px.png"));
669 connect(m_pButtonRemoveFinished, &QIToolButton::clicked, this, &UINotificationCenter::sltHandleRemoveFinishedButtonClicked);
670 m_pLayoutButtons->addWidget(m_pButtonRemoveFinished);
671 }
672
673 /* Add to layout: */
674 m_pLayoutMain->insertLayout(m_enmAlignment == Qt::AlignTop ? 0 : -1, m_pLayoutButtons);
675 }
676 }
677}
678
679void UINotificationCenter::prepareStateMachineSliding()
680{
681 /* Create sliding animation state-machine: */
682 m_pStateMachineSliding = new QStateMachine(this);
683 if (m_pStateMachineSliding)
684 {
685 /* Create 'closed' state: */
686 QState *pStateClosed = new QState(m_pStateMachineSliding);
687 /* Create 'opened' state: */
688 QState *pStateOpened = new QState(m_pStateMachineSliding);
689
690 /* Configure 'closed' state: */
691 if (pStateClosed)
692 {
693 /* When we entering closed state => we assigning animatedValue to 0: */
694 pStateClosed->assignProperty(this, "animatedValue", 0);
695
696 /* Add state transitions: */
697 QSignalTransition *pClosedToOpened = pStateClosed->addTransition(this, SIGNAL(sigOpen()), pStateOpened);
698 if (pClosedToOpened)
699 {
700 /* Create forward animation: */
701 QPropertyAnimation *pAnimationForward = new QPropertyAnimation(this, "animatedValue", this);
702 if (pAnimationForward)
703 {
704 pAnimationForward->setEasingCurve(QEasingCurve::InCubic);
705 pAnimationForward->setDuration(300);
706 pAnimationForward->setStartValue(0);
707 pAnimationForward->setEndValue(100);
708
709 /* Add to transition: */
710 pClosedToOpened->addAnimation(pAnimationForward);
711 }
712 }
713 }
714
715 /* Configure 'opened' state: */
716 if (pStateOpened)
717 {
718 /* When we entering opened state => we assigning animatedValue to 100: */
719 pStateOpened->assignProperty(this, "animatedValue", 100);
720
721 /* Add state transitions: */
722 QSignalTransition *pOpenedToClosed = pStateOpened->addTransition(this, SIGNAL(sigClose()), pStateClosed);
723 if (pOpenedToClosed)
724 {
725 /* Create backward animation: */
726 QPropertyAnimation *pAnimationBackward = new QPropertyAnimation(this, "animatedValue", this);
727 if (pAnimationBackward)
728 {
729 pAnimationBackward->setEasingCurve(QEasingCurve::InCubic);
730 pAnimationBackward->setDuration(300);
731 pAnimationBackward->setStartValue(100);
732 pAnimationBackward->setEndValue(0);
733
734 /* Add to transition: */
735 pOpenedToClosed->addAnimation(pAnimationBackward);
736 }
737 }
738 }
739
740 /* Initial state is 'closed': */
741 m_pStateMachineSliding->setInitialState(pStateClosed);
742 /* Start state-machine: */
743 m_pStateMachineSliding->start();
744 }
745}
746
747void UINotificationCenter::prepareOpenTimer()
748{
749 m_pTimerOpen = new QTimer(this);
750 if (m_pTimerOpen)
751 connect(m_pTimerOpen, &QTimer::timeout,
752 this, &UINotificationCenter::sltHandleOpenTimerTimeout);
753}
754
755void UINotificationCenter::cleanupModel()
756{
757 delete m_pModel;
758 m_pModel = 0;
759}
760
761void UINotificationCenter::cleanupItems()
762{
763 qDeleteAll(m_items);
764 m_items.clear();
765}
766
767void UINotificationCenter::cleanup()
768{
769 cleanupModel();
770 cleanupItems();
771}
772
773void UINotificationCenter::paintBackground(QPainter *pPainter)
774{
775 /* Acquire palette: */
776 const bool fActive = parentWidget() && parentWidget()->isActiveWindow();
777 const QPalette pal = QApplication::palette();
778
779 /* Gather suitable color: */
780 QColor backgroundColor = pal.color(fActive ? QPalette::Active : QPalette::Inactive, QPalette::Window).darker(120);
781 backgroundColor.setAlpha((double)animatedValue() / 100 * 220);
782
783 /* Acquire pixel metric: */
784 const int iMetric = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize) / 4;
785
786 /* Adjust rectangle: */
787 QRect rectAdjusted = rect();
788 rectAdjusted.adjust(iMetric, iMetric, 0, -iMetric);
789
790 /* Paint background: */
791 pPainter->fillRect(rectAdjusted, backgroundColor);
792}
793
794void UINotificationCenter::paintFrame(QPainter *pPainter)
795{
796 /* Acquire palette: */
797 const bool fActive = parentWidget() && parentWidget()->isActiveWindow();
798 QPalette pal = QApplication::palette();
799
800 /* Gather suitable colors: */
801 QColor color1 = pal.color(fActive ? QPalette::Active : QPalette::Inactive, QPalette::Window).lighter(110);
802 color1.setAlpha(0);
803 QColor color2 = pal.color(fActive ? QPalette::Active : QPalette::Inactive, QPalette::Window).darker(200);
804
805 /* Acquire pixel metric: */
806 const int iMetric = QApplication::style()->pixelMetric(QStyle::PM_SmallIconSize) / 4;
807
808 /* Top-left corner: */
809 QRadialGradient grad1(QPointF(iMetric, iMetric), iMetric);
810 {
811 grad1.setColorAt(0, color2);
812 grad1.setColorAt(1, color1);
813 }
814 /* Bottom-left corner: */
815 QRadialGradient grad2(QPointF(iMetric, height() - iMetric), iMetric);
816 {
817 grad2.setColorAt(0, color2);
818 grad2.setColorAt(1, color1);
819 }
820
821 /* Top line: */
822 QLinearGradient grad3(QPointF(iMetric, 0), QPointF(iMetric, iMetric));
823 {
824 grad3.setColorAt(0, color1);
825 grad3.setColorAt(1, color2);
826 }
827 /* Bottom line: */
828 QLinearGradient grad4(QPointF(iMetric, height()), QPointF(iMetric, height() - iMetric));
829 {
830 grad4.setColorAt(0, color1);
831 grad4.setColorAt(1, color2);
832 }
833 /* Left line: */
834 QLinearGradient grad5(QPointF(0, height() - iMetric), QPointF(iMetric, height() - iMetric));
835 {
836 grad5.setColorAt(0, color1);
837 grad5.setColorAt(1, color2);
838 }
839
840 /* Paint shape/shadow: */
841 pPainter->fillRect(QRect(0, 0, iMetric, iMetric), grad1);
842 pPainter->fillRect(QRect(0, height() - iMetric, iMetric, iMetric), grad2);
843 pPainter->fillRect(QRect(iMetric, 0, width() - iMetric, iMetric), grad3);
844 pPainter->fillRect(QRect(iMetric, height() - iMetric, width() - iMetric, iMetric), grad4);
845 pPainter->fillRect(QRect(0, iMetric, iMetric, height() - iMetric * 2), grad5);
846}
847
848void UINotificationCenter::setAnimatedValue(int iValue)
849{
850 /* Store recent value: */
851 m_iAnimatedValue = iValue;
852
853 // WORKAROUND:
854 // Hide items if they are masked anyway.
855 // This actually shouldn't be necessary but
856 // *is* required to avoid painting artifacts.
857 foreach (QWidget *pItem, m_items.values())
858 pItem->setVisible(animatedValue());
859
860 /* Adjust geometry: */
861 adjustGeometry();
862}
863
864int UINotificationCenter::animatedValue() const
865{
866 return m_iAnimatedValue;
867}
868
869void UINotificationCenter::adjustGeometry()
870{
871 /* Make sure parent exists: */
872 QWidget *pParent = parentWidget();
873 if (!pParent)
874 return;
875 /* Acquire parent width and height: */
876 const int iParentWidth = pParent->width();
877 const int iParentHeight = pParent->height();
878
879 /* Acquire minimum width (includes margins by default): */
880 int iMinimumWidth = minimumSizeHint().width();
881 /* Acquire minimum button width (including margins manually): */
882 int iL, iT, iR, iB;
883 m_pLayoutMain->getContentsMargins(&iL, &iT, &iR, &iB);
884 const int iMinimumButtonWidth = m_pButtonOpen->minimumSizeHint().width() + iL + iR;
885
886 /* Make sure we have some default width if there is no contents: */
887 iMinimumWidth = qMax(iMinimumWidth, 200);
888
889 /* Move and resize notification-center finally: */
890 move(iParentWidth - (iMinimumButtonWidth + (double)animatedValue() / 100 * (iMinimumWidth - iMinimumButtonWidth)), 0);
891 resize(iMinimumWidth, iParentHeight);
892}
893
894void UINotificationCenter::adjustMask()
895{
896 /* We do include open-button mask only if center is opened or animated to be: */
897 QRegion region;
898 if (!animatedValue())
899 region += QRect(m_pButtonOpen->mapToParent(QPoint(0, 0)), m_pButtonOpen->size());
900 setMask(region);
901}
902
903
904/*********************************************************************************************************************************
905* Class UINotificationReceiver implementation. *
906*********************************************************************************************************************************/
907
908void UINotificationReceiver::setReceiverProperty(const QVariant &value)
909{
910 setProperty("received_value", value);
911}
912
913
914#include "UINotificationCenter.moc"
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette