VirtualBox

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

Last change on this file was 104245, checked in by vboxsync, 8 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
RevLine 
[90288]1/* $Id: UINotificationCenter.cpp 104245 2024-04-09 08:03:20Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UINotificationCenter class implementation.
4 */
5
6/*
[98103]7 * Copyright (C) 2021-2023 Oracle and/or its affiliates.
[90288]8 *
[96407]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
[90288]26 */
27
28/* Qt includes: */
29#include <QApplication>
30#include <QHBoxLayout>
[93414]31#include <QMenu>
[90288]32#include <QPainter>
33#include <QPaintEvent>
34#include <QPropertyAnimation>
[90464]35#include <QScrollArea>
[90288]36#include <QSignalTransition>
37#include <QState>
38#include <QStateMachine>
39#include <QStyle>
[91225]40#include <QTimer>
[90288]41#include <QVBoxLayout>
42
43/* GUI includes: */
44#include "QIToolButton.h"
[91236]45#include "UIExtraDataManager.h"
[90288]46#include "UIIconPool.h"
47#include "UINotificationCenter.h"
48#include "UINotificationObjectItem.h"
49#include "UINotificationModel.h"
[104245]50#include "UITranslationEventListener.h"
[90288]51
52/* Other VBox includes: */
53#include "iprt/assert.h"
54
55
[90484]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. */
[103982]67 virtual QSize minimumSizeHint() const RT_OVERRIDE RT_FINAL;
[90484]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. */
[103982]76 virtual bool eventFilter(QObject *pWatched, QEvent *pEvent) RT_OVERRIDE RT_FINAL;
[90484]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
[90288]135/* static */
136UINotificationCenter *UINotificationCenter::s_pInstance = 0;
137
138/* static */
[90563]139void UINotificationCenter::create(QWidget *pParent /* = 0 */)
[90288]140{
141 AssertReturnVoid(!s_pInstance);
[91753]142 s_pInstance = new UINotificationCenter(pParent);
[90288]143}
144
145/* static */
146void UINotificationCenter::destroy()
147{
148 AssertPtrReturnVoid(s_pInstance);
149 delete s_pInstance;
[91753]150 s_pInstance = 0;
[90288]151}
152
153/* static */
154UINotificationCenter *UINotificationCenter::instance()
155{
156 return s_pInstance;
157}
158
[91753]159UINotificationCenter::UINotificationCenter(QWidget *pParent)
[104245]160 : QWidget(pParent)
[91753]161 , m_pModel(0)
[93408]162 , m_enmAlignment(Qt::AlignTop)
[92400]163 , m_enmOrder(Qt::AscendingOrder)
[91753]164 , m_pLayoutMain(0)
165 , m_pLayoutButtons(0)
[92429]166 , m_pButtonOpen(0)
[92431]167 , m_pButtonToggleSorting(0)
[99184]168#ifdef VBOX_NOTIFICATION_CENTER_WITH_KEEP_BUTTON
[92429]169 , m_pButtonKeepFinished(0)
[99184]170#endif
[92429]171 , m_pButtonRemoveFinished(0)
[91753]172 , m_pLayoutItems(0)
173 , m_pStateMachineSliding(0)
174 , m_iAnimatedValue(0)
175 , m_pTimerOpen(0)
[91877]176 , m_fLastResult(false)
[91753]177{
178 prepare();
179}
180
181UINotificationCenter::~UINotificationCenter()
182{
[92646]183 cleanup();
[91753]184}
185
[90563]186void UINotificationCenter::setParent(QWidget *pParent)
187{
[90566]188 /* Additionally hide if parent unset: */
[90563]189 if (!pParent)
[90566]190 setHidden(true);
191
192 /* Uninstall filter from previous parent: */
[90563]193 if (parent())
194 parent()->removeEventFilter(this);
[90566]195
196 /* Reparent: */
[104245]197 QWidget::setParent(pParent);
[90566]198
199 /* Install filter to new parent: */
[90563]200 if (parent())
201 parent()->installEventFilter(this);
[90566]202
203 /* Show only if there is something to show: */
[90563]204 if (parent())
[90566]205 setHidden(m_pModel->ids().isEmpty());
[90563]206}
207
[90552]208void UINotificationCenter::invoke()
209{
[90627]210 /* Open if center isn't opened yet: */
[92429]211 if (!m_pButtonOpen->isChecked())
212 m_pButtonOpen->animateClick();
[90552]213}
214
[90288]215QUuid UINotificationCenter::append(UINotificationObject *pObject)
216{
[91501]217 /* Sanity check: */
[90600]218 AssertPtrReturn(m_pModel, QUuid());
[91501]219 AssertPtrReturn(pObject, QUuid());
220
221 /* Is object critical? */
222 const bool fCritical = pObject->isCritical();
223 /* Is object progress? */
[94785]224 const bool fProgress = pObject->inherits("UINotificationProgress");
[91501]225
226 /* Handle object. Be aware it can be deleted during handling! */
[90600]227 const QUuid uId = m_pModel->appendObject(pObject);
228
[91225]229 /* If object is critical and center isn't opened yet: */
[92429]230 if (!m_pButtonOpen->isChecked() && fCritical)
[91225]231 {
232 /* We should delay progresses for a bit: */
[91501]233 const int iDelay = fProgress ? 2000 : 0;
[91225]234 /* We should issue an open request: */
235 AssertPtrReturn(m_pTimerOpen, uId);
236 m_uOpenObjectId = uId;
237 m_pTimerOpen->start(iDelay);
238 }
[90288]239
[90600]240 return uId;
[90288]241}
242
243void UINotificationCenter::revoke(const QUuid &uId)
244{
245 AssertReturnVoid(!uId.isNull());
246 return m_pModel->revokeObject(uId);
247}
248
[91877]249bool UINotificationCenter::handleNow(UINotificationProgress *pProgress)
[91755]250{
251 /* Check for the recursive run: */
[91877]252 AssertMsgReturn(!m_pEventLoop, ("UINotificationCenter::handleNow() is called recursively!\n"), false);
[91755]253
[91877]254 /* Reset the result: */
255 m_fLastResult = false;
256
[91755]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())
[91877]266 return m_fLastResult;
[91844]267 /* Is progress still running? */
[92294]268 if (guardProgress->isDone())
[91877]269 return m_fLastResult;
[91755]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())
[91877]284 return false;
[91755]285
286 /* Cleanup event-loop: */
287 m_pEventLoop = 0;
[91877]288
289 /* Return actual result: */
290 return m_fLastResult;
[91755]291}
292
[99265]293bool UINotificationCenter::hasOperationsPending() const
294{
295 return m_pEventLoop;
296}
297
298void UINotificationCenter::abortOperations()
299{
300 m_pEventLoop->exit();
301 emit sigOperationsAborted();
302}
303
[104245]304void UINotificationCenter::sltRetranslateUI()
[91236]305{
[92429]306 if (m_pButtonOpen)
307 m_pButtonOpen->setToolTip(tr("Open notification center"));
[92431]308 if (m_pButtonToggleSorting)
309 m_pButtonToggleSorting->setToolTip(tr("Toggle ascending/descending order"));
[99184]310#ifdef VBOX_NOTIFICATION_CENTER_WITH_KEEP_BUTTON
[92429]311 if (m_pButtonKeepFinished)
312 m_pButtonKeepFinished->setToolTip(tr("Keep finished progresses"));
[99184]313#endif
[92429]314 if (m_pButtonRemoveFinished)
315 m_pButtonRemoveFinished->setToolTip(tr("Delete finished notifications"));
[91236]316}
317
[90288]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: */
[104245]339 return QWidget::eventFilter(pObject, pEvent);
[90288]340}
341
342bool UINotificationCenter::event(QEvent *pEvent)
343{
344 /* Handle required event types: */
345 switch (pEvent->type())
346 {
[90484]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 }
[90288]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
[90371]366 /* Call to base-class: */
[104245]367 return QWidget::event(pEvent);
[90288]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
[93408]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);
[93413]394
395 /* Adjust mask to make sure button visible, layout should be finalized already: */
396 QCoreApplication::sendPostedEvents(0, QEvent::LayoutRequest);
397 adjustMask();
[93408]398}
399
[92431]400void UINotificationCenter::sltIssueOrderChange()
401{
402 const Qt::SortOrder enmSortOrder = m_pButtonToggleSorting->isChecked()
403 ? Qt::AscendingOrder
404 : Qt::DescendingOrder;
405 gEDataManager->setNotificationCenterOrder(enmSortOrder);
406}
407
[92400]408void UINotificationCenter::sltHandleOrderChange()
409{
[92646]410 /* Save new order: */
[92400]411 m_enmOrder = gEDataManager->notificationCenterOrder();
[92646]412
413 /* Cleanup items first: */
[101122]414 cleanupItems();
[92646]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();
[92400]428}
429
[90288]430void UINotificationCenter::sltHandleOpenButtonToggled(bool fToggled)
431{
432 if (fToggled)
433 emit sigOpen();
434 else
435 emit sigClose();
436}
437
[99184]438#ifdef VBOX_NOTIFICATION_CENTER_WITH_KEEP_BUTTON
[91236]439void UINotificationCenter::sltHandleKeepButtonToggled(bool fToggled)
440{
441 gEDataManager->setKeepSuccessfullNotificationProgresses(fToggled);
442}
[99184]443#endif /* VBOX_NOTIFICATION_CENTER_WITH_KEEP_BUTTON */
[91236]444
[92295]445void UINotificationCenter::sltHandleRemoveFinishedButtonClicked()
446{
447 m_pModel->revokeFinishedObjects();
448}
449
[93414]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
[91225]473void UINotificationCenter::sltHandleOpenTimerTimeout()
474{
[91231]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
[91225]483 /* Check whether we really closed: */
[92429]484 if (m_pButtonOpen->isChecked())
[91225]485 return;
486
487 /* Check whether message with particular ID exists: */
488 if (!m_pModel->hasObject(m_uOpenObjectId))
489 return;
490
[92429]491 /* Toggle open button: */
492 m_pButtonOpen->animateClick();
[91225]493}
494
[92646]495void UINotificationCenter::sltHandleModelItemAdded(const QUuid &uId)
[90288]496{
[92646]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);
[90288]502
[92646]503 /* Show if there are notifications to show: */
504 setHidden(m_pModel->ids().isEmpty());
505}
[90464]506
[92646]507void UINotificationCenter::sltHandleModelItemRemoved(const QUuid &uId)
508{
[99263]509 /* Remove corresponding model item representation if present: */
510 if (m_items.contains(uId))
511 delete m_items.take(uId);
[92646]512
[90565]513 /* Hide and slide away if there are no notifications to show: */
514 setHidden(m_pModel->ids().isEmpty());
[92429]515 if (m_pModel->ids().isEmpty() && m_pButtonOpen->isChecked())
516 m_pButtonOpen->toggle();
[90288]517}
518
[91755]519void UINotificationCenter::sltHandleProgressFinished()
520{
[91877]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
[91844]528 /* Break the loop if exists: */
529 if (m_pEventLoop)
530 m_pEventLoop->exit();
[91755]531}
532
[90288]533void UINotificationCenter::prepare()
534{
[90565]535 /* Hide initially: */
536 setHidden(true);
537
[90288]538 /* Listen for parent events: */
[90563]539 if (parent())
540 parent()->installEventFilter(this);
[90288]541
[98928]542 /* Prepare the rest of stuff: */
543 prepareModel();
544 prepareWidgets();
545 prepareStateMachineSliding();
546 prepareOpenTimer();
547
[93408]548 /* Prepare alignment: */
549 m_enmAlignment = gEDataManager->notificationCenterAlignment();
550 connect(gEDataManager, &UIExtraDataManager::sigNotificationCenterAlignmentChange,
551 this, &UINotificationCenter::sltHandleAlignmentChange);
[98928]552 sltHandleAlignmentChange();
[93407]553 /* Prepare order: */
554 m_enmOrder = gEDataManager->notificationCenterOrder();
555 connect(gEDataManager, &UIExtraDataManager::sigNotificationCenterOrderChange,
556 this, &UINotificationCenter::sltHandleOrderChange);
[98928]557 sltHandleOrderChange();
[93407]558
[91236]559 /* Apply language settings: */
[104245]560 sltRetranslateUI();
561
562 connect(&translationEventListener(), &UITranslationEventListener::sigRetranslateUI,
563 this, &UINotificationCenter::sltRetranslateUI);
[90288]564}
565
566void UINotificationCenter::prepareModel()
567{
568 m_pModel = new UINotificationModel(this);
569 if (m_pModel)
[92646]570 {
571 connect(m_pModel, &UINotificationModel::sigItemAdded,
572 this, &UINotificationCenter::sltHandleModelItemAdded);
573 connect(m_pModel, &UINotificationModel::sigItemRemoved,
574 this, &UINotificationCenter::sltHandleModelItemRemoved);
575 }
[90288]576}
577
578void UINotificationCenter::prepareWidgets()
579{
580 /* Prepare main layout: */
581 m_pLayoutMain = new QVBoxLayout(this);
582 if (m_pLayoutMain)
583 {
[93407]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
[91236]621 /* Prepare buttons layout: */
622 m_pLayoutButtons = new QHBoxLayout;
623 if (m_pLayoutButtons)
[90288]624 {
[91236]625 m_pLayoutButtons->setContentsMargins(0, 0, 0, 0);
[90288]626
[92429]627 /* Prepare open button: */
628 m_pButtonOpen = new QIToolButton(this);
629 if (m_pButtonOpen)
[90288]630 {
[93196]631 m_pButtonOpen->setIcon(UIIconPool::iconSet(":/notification_center_16px.png"));
[92429]632 m_pButtonOpen->setCheckable(true);
[93414]633 m_pButtonOpen->setContextMenuPolicy(Qt::CustomContextMenu);
[93413]634 connect(m_pButtonOpen, &QIToolButton::toggled,
635 this, &UINotificationCenter::sltHandleOpenButtonToggled);
[93414]636 connect(m_pButtonOpen, &QIToolButton::customContextMenuRequested,
637 this, &UINotificationCenter::sltHandleOpenButtonContextMenuRequested);
[92429]638 m_pLayoutButtons->addWidget(m_pButtonOpen);
[90288]639 }
640
[92295]641 /* Add stretch: */
642 m_pLayoutButtons->addStretch(1);
643
[92431]644 /* Prepare toggle-sorting button: */
645 m_pButtonToggleSorting = new QIToolButton(this);
646 if (m_pButtonToggleSorting)
647 {
[93196]648 m_pButtonToggleSorting->setIcon(UIIconPool::iconSet(":/notification_center_sort_16px.png"));
[92431]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
[99184]655#ifdef VBOX_NOTIFICATION_CENTER_WITH_KEEP_BUTTON
[92429]656 /* Prepare keep-finished button: */
657 m_pButtonKeepFinished = new QIToolButton(this);
658 if (m_pButtonKeepFinished)
[91236]659 {
[93196]660 m_pButtonKeepFinished->setIcon(UIIconPool::iconSet(":/notification_center_hold_progress_16px.png"));
[92429]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);
[91236]665 }
[99184]666#endif /* VBOX_NOTIFICATION_CENTER_WITH_KEEP_BUTTON */
[90288]667
[92429]668 /* Prepare remove-finished button: */
669 m_pButtonRemoveFinished = new QIToolButton(this);
670 if (m_pButtonRemoveFinished)
[92295]671 {
[93196]672 m_pButtonRemoveFinished->setIcon(UIIconPool::iconSet(":/notification_center_delete_progress_16px.png"));
[92429]673 connect(m_pButtonRemoveFinished, &QIToolButton::clicked, this, &UINotificationCenter::sltHandleRemoveFinishedButtonClicked);
674 m_pLayoutButtons->addWidget(m_pButtonRemoveFinished);
[92295]675 }
[91236]676
[90288]677 /* Add to layout: */
[93408]678 m_pLayoutMain->insertLayout(m_enmAlignment == Qt::AlignTop ? 0 : -1, m_pLayoutButtons);
[90288]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
[91225]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
[101122]759void UINotificationCenter::cleanupModel()
[92646]760{
[101122]761 delete m_pModel;
762 m_pModel = 0;
763}
764
765void UINotificationCenter::cleanupItems()
766{
[92646]767 qDeleteAll(m_items);
768 m_items.clear();
769}
770
[101122]771void UINotificationCenter::cleanup()
772{
773 cleanupModel();
774 cleanupItems();
775}
776
[90288]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{
[93368]854 /* Store recent value: */
[90288]855 m_iAnimatedValue = iValue;
[93368]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: */
[90288]865 adjustGeometry();
866}
867
868int UINotificationCenter::animatedValue() const
869{
870 return m_iAnimatedValue;
871}
872
873void UINotificationCenter::adjustGeometry()
874{
[90562]875 /* Make sure parent exists: */
876 QWidget *pParent = parentWidget();
877 if (!pParent)
878 return;
[90288]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);
[92429]888 const int iMinimumButtonWidth = m_pButtonOpen->minimumSizeHint().width() + iL + iR;
[90288]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;
[93342]902 if (!animatedValue())
[92429]903 region += QRect(m_pButtonOpen->mapToParent(QPoint(0, 0)), m_pButtonOpen->size());
[90288]904 setMask(region);
905}
[90484]906
907
[92012]908/*********************************************************************************************************************************
909* Class UINotificationReceiver implementation. *
910*********************************************************************************************************************************/
911
912void UINotificationReceiver::setReceiverProperty(const QVariant &value)
913{
914 setProperty("received_value", value);
915}
916
917
[90484]918#include "UINotificationCenter.moc"
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use