VirtualBox

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

Last change on this file was 104245, checked in by vboxsync, 6 weeks ago

FE/Qt. bugref:10622. Using new UITranslationEventListener in the extenion pack manager and notification center classes.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use