1 // Copyright (C) 2007-2024 CEA, EDF, OPEN CASCADE
3 // Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 // Lesser General Public License for more details.
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
23 #include "QtxNotify.h"
27 #include <QMouseEvent>
28 #include <QApplication>
29 #include <QTextDocument>
30 #include <QDesktopWidget>
31 #include <QSharedPointer>
32 #include <QPropertyAnimation>
33 #include <QParallelAnimationGroup>
36 \brief QtxNotify::NotifyWidget
37 Class represented notification widget
40 class QtxNotify::NotifyWidget : public QWidget
42 typedef QSharedPointer<QTextDocument> TextDocument;
45 NotifyWidget(QtxNotify*, const QString& title,
46 const QString& text, const int timeout, QWidget* parent = 0);
47 virtual ~NotifyWidget();
49 QtxNotify* notifyMgr() const;
53 QString title() const;
56 void setId(const int);
57 void setText(const QString&);
58 void setTitle(const QString&);
59 void setTimeout(const int);
61 virtual QSize sizeHint() const;
62 virtual QSize minimumSizeHint() const;
65 virtual void showEvent(QShowEvent*);
66 virtual void paintEvent(QPaintEvent*);
67 virtual void mouseMoveEvent(QMouseEvent*);
68 virtual void mousePressEvent(QMouseEvent*);
69 virtual void mouseReleaseEvent(QMouseEvent*);
72 int frameWidth() const;
73 int notificationWidth() const;
75 QRect textRect() const;
76 QRect titleRect() const;
77 QRect closeRect() const;
78 TextDocument textDocument() const;
87 QtxNotify* myNotifyMgr;
88 bool myIsClosePressed;
92 \brief Constructor [private]
94 QtxNotify::NotifyWidget::NotifyWidget(QtxNotify* mgr, const QString& title, const QString& text,
95 const int timeout, QWidget* parent)
96 : QWidget(parent, (parent ? Qt::Widget : Qt::Tool) | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint),
99 myTimer(new QTimer(this)),
101 myIsClosePressed(false)
103 myTimer->setSingleShot(true);
105 connect(myTimer, &QTimer::timeout, this, &QtxNotify::NotifyWidget::onTimeout);
108 setMouseTracking(true);
114 QtxNotify::NotifyWidget::~NotifyWidget()
119 \brief Gets the notification manager.
120 \return notification manager instance
122 QtxNotify* QtxNotify::NotifyWidget::notifyMgr() const
128 \brief Gets the notification identifier.
129 \return notification identifier
131 int QtxNotify::NotifyWidget::id() const
137 \brief Gets the notification text.
138 \return notification text string
140 QString QtxNotify::NotifyWidget::text() const
146 \brief Gets the notification title.
147 \return notification title string
149 QString QtxNotify::NotifyWidget::title() const
155 \brief Gets the notification timeout.
156 \return notification timeout
158 int QtxNotify::NotifyWidget::timeout() const
160 return myTimer->interval();
164 \brief Sets the notification timeout.
165 \param id - notification identifier
167 void QtxNotify::NotifyWidget::setId(const int id)
173 \brief Sets the notification text.
174 \param text - notification text string
176 void QtxNotify::NotifyWidget::setText(const QString& text)
184 \brief Sets the notification title.
185 \param title - notification title string
187 void QtxNotify::NotifyWidget::setTitle(const QString& title)
194 \brief Sets the notification timeout.
195 \param timeout - notification timeout
197 void QtxNotify::NotifyWidget::setTimeout(const int timeout)
199 if (myTimer->isActive())
201 myTimer->setInterval(timeout);
203 if (isVisible() && myTimer->interval() > 0)
208 \brief Gets the notification widget size hint.
211 QSize QtxNotify::NotifyWidget::sizeHint() const
213 return minimumSizeHint();
217 \brief Gets the notification widget minimum size hint.
218 \return minimum size hint
220 QSize QtxNotify::NotifyWidget::minimumSizeHint() const
222 int frame = frameWidth();
223 int width = notificationWidth();
224 TextDocument doc = textDocument();
225 int height = (int)doc->size().height() + 2 * frame;
227 height += fontMetrics().height() + 2 * frame;
229 return QSize(width, height);
232 void QtxNotify::NotifyWidget::showEvent(QShowEvent* e)
234 QWidget::showEvent(e);
236 if (!myTimer->isActive() && myTimer->interval() > 0)
241 \brief Reimplemented for notification drawing.
243 void QtxNotify::NotifyWidget::paintEvent(QPaintEvent*)
245 QPainter painter(this);
247 QRect clsRect = closeRect();
248 QRect ttlRect = titleRect();
249 QRect txtRect = textRect();
251 // Fills background and frame
252 painter.setRenderHint(QPainter::TextAntialiasing);
253 painter.fillRect(QRect(QPoint(0,0), size()), Qt::darkGray);
254 painter.fillRect(txtRect, Qt::white);
259 painter.setPen(Qt::white);
262 painter.setFont(fnt);
263 ttlRect.setRight(clsRect.left());
265 QString titleText = title().trimmed();
266 if (QFontMetrics(painter.font()).width(titleText) > ttlRect.width())
267 titleText = QFontMetrics(painter.font()).elidedText(titleText, Qt::ElideRight, ttlRect.width() - 5);
268 painter.drawText(ttlRect.adjusted(2, 0, 0, 0), Qt::AlignVCenter, titleText);
275 if (myIsClosePressed && clsRect.contains(mapFromGlobal(QCursor::pos())))
277 painter.fillRect(clsRect.adjusted(0, 0, -1, -1), Qt::gray);
278 clsRect = clsRect.adjusted(1, 1, 0, 0);
282 painter.fillRect(clsRect.adjusted(1, 1, 0, 0), Qt::lightGray);
283 clsRect = clsRect.adjusted(0, 0, -1, -1);
285 painter.fillRect(clsRect, Qt::red);
288 painter.setPen(Qt::white);
289 QRect rect = clsRect.adjusted(m, m, -m, -m);
290 painter.drawLine(rect.topLeft(), rect.bottomRight());
291 painter.drawLine(rect.topRight(), rect.bottomLeft());
298 painter.setPen(Qt::black);
299 TextDocument doc = textDocument();
300 painter.translate(txtRect.topLeft());
301 doc->drawContents(&painter, QRectF(QPointF(0, 0), txtRect.size()));
309 \brief Reimplemented for handling close button.
311 void QtxNotify::NotifyWidget::mouseMoveEvent(QMouseEvent*)
313 if (myIsClosePressed)
318 \brief Reimplemented for handling close button.
320 void QtxNotify::NotifyWidget::mousePressEvent(QMouseEvent* e)
322 if (e->button() == Qt::LeftButton && closeRect().contains(e->pos()))
324 myIsClosePressed = true;
330 \brief Reimplemented for handling close button.
332 void QtxNotify::NotifyWidget::mouseReleaseEvent(QMouseEvent* e)
334 if (e->button() == Qt::LeftButton)
336 if (myIsClosePressed && closeRect().contains(e->pos()))
338 notifyMgr()->hideNotification(id());
340 myIsClosePressed = false;
346 \brief Gets the notification frame with
347 \return Frame width in pixels
349 int QtxNotify::NotifyWidget::frameWidth() const
355 \brief Gets the notification window with
356 \return width in pixels
358 int QtxNotify::NotifyWidget::notificationWidth() const
360 return notifyMgr()->notificationWidth();
364 \brief Gets the text document with actual text and size.
365 \return text document instance
367 QtxNotify::NotifyWidget::TextDocument QtxNotify::NotifyWidget::textDocument() const
369 TextDocument doc(new QTextDocument());
370 if (Qt::mightBeRichText(text()))
371 doc->setHtml(text());
373 doc->setPlainText(text());
374 doc->setTextWidth(notificationWidth() - 2 * frameWidth());
379 \brief Gets the notification message text rectangle.
380 \return text rectangle
382 QRect QtxNotify::NotifyWidget::textRect() const
384 int frame = frameWidth();
385 return QRect(frame, 3 * frame + fontMetrics().height(),
386 width() - 2 * frame, height() - 4 * frame - fontMetrics().height());
390 \brief Gets the notification title rectangle
391 \return title rectangle
393 QRect QtxNotify::NotifyWidget::titleRect() const
395 int frame = frameWidth();
396 return QRect(frame, frame, width() - 2 * frame, fontMetrics().height() + 2 * frame);
400 \brief Gets the notification close button rectangle.
401 \return close rectangle
403 QRect QtxNotify::NotifyWidget::closeRect() const
405 int frame = frameWidth();
406 int size = fontMetrics().height();
407 return QRect(width() - 2 * frame - size, 2 * frame, size, size);
411 \brief Invoked when notification close timeout and hide the notification.
413 void QtxNotify::NotifyWidget::onTimeout()
415 notifyMgr()->hideNotification(id());
420 Class that manages all notification instances. Performs show, hide, placing, etc
426 QtxNotify::QtxNotify(QObject* parent)
430 myPlacement(TopToBottom),
431 myAlignment(Qt::AlignRight|Qt::AlignTop),
435 myArrangeTimer = new QTimer(this);
436 myArrangeTimer->setInterval(0);
437 myArrangeTimer->setSingleShot(true);
438 connect(myArrangeTimer, SIGNAL(timeout()), this, SLOT(onArrangeTimeout()));
441 QWidgetList windows = QApplication::topLevelWidgets();
442 for ( QWidgetList::iterator it = windows.begin(); it != windows.end() && !window; ++it )
445 if (win->isVisible() &&
446 (win->windowType() == Qt::Widget || win->windowType() == Qt::Window))
450 setWindow(window ? window : QApplication::desktop());
456 QtxNotify::QtxNotify(QWidget* win, QObject* parent)
460 myPlacement(TopToBottom),
461 myAlignment(Qt::AlignRight|Qt::AlignTop),
465 myArrangeTimer = new QTimer(this);
466 myArrangeTimer->setInterval(0);
467 myArrangeTimer->setSingleShot(true);
468 connect(myArrangeTimer, SIGNAL(timeout()), this, SLOT(onArrangeTimeout()));
476 QtxNotify::~QtxNotify()
481 \brief Shows the notifications with spectified text, title and automatic close timeout.
482 Notification will be automatically closed after specified timeout in msec. If
483 timeout is zero then automatic closing doesn't performed.
484 \param text - Notification text
485 \param title - Notification title
486 \param timeout - Notification close timeout in msec
487 \return notification identifier
489 int QtxNotify::showNotification(const QString& text, const QString& title, int timeout)
491 QtxNotify::NotifyWidget* notify = notification(text);
494 notify = new QtxNotify::NotifyWidget(this, title, text, timeout, window());
495 notify->setId(generateId());
499 myNotifications.removeAll(notify);
500 notify->setTimeout(timeout);
501 notify->setTitle(title);
505 myNotifications.append(notify);
512 \brief Closes the notifications with spectified text.
513 \param text - Notification text
515 void QtxNotify::hideNotification(const QString& text)
517 removeNotification(notification(text));
521 \brief Closes the notifications with spectified identifier.
522 \param text - Notification identifier
524 void QtxNotify::hideNotification(const int id)
526 removeNotification(notification(id));
530 \brief Gets the reference widget for all notifications.
531 \return reference widget
533 QWidget* QtxNotify::window() const
539 \brief Sets the reference widget for all notifications.
540 \param win - reference widget
542 void QtxNotify::setWindow(QWidget* window)
544 if (myWindow != window)
547 myWindow->removeEventFilter(this);
550 myWindow->installEventFilter(this);
556 \brief Gets the animation time in msec for notification modifications.
557 \return animation time
559 int QtxNotify::animationTime() const
565 \brief Sets the animation time in msec for notification modifications.
566 If animation time is zero then animation is disabled.
567 \param time - animation time
569 void QtxNotify::setAnimationTime(const int time)
575 \brief Gets the notification placement policy.
576 \return notification placement policy
578 QtxNotify::PlacementPolicy QtxNotify::placementPolicy() const
584 \brief Sets the notification placement policy.
585 \param placement - notification placement policy
587 void QtxNotify::setPlacementPolicy(const QtxNotify::PlacementPolicy& placement)
589 if (myPlacement != placement)
591 myPlacement = placement;
597 \brief Gets the notification placement base point alignment.
598 \return alignment flags
600 int QtxNotify::alignment() const
606 \brief Sets the notification placement base point alignment.
607 \param falgs - alignment flags
609 void QtxNotify::setAlignment(const int flags)
611 if (myAlignment != flags)
619 \brief Reimplemented for tracking reference widget size changing and
620 update notification geometries.
621 \param o - handled object
622 \param e - handled event
624 bool QtxNotify::eventFilter(QObject* o, QEvent* e)
626 if (e->type() == QEvent::Resize && o == window())
630 return QObject::eventFilter(o, e);
634 \brief Gets the notification width size of each notification. If size value more than 1
635 then it will be interpreted as absolute width in pixels. If size value in range
636 between 0 and 1 then notification width will be calculated as relative part of
637 reference window width.
638 \return notification size
640 double QtxNotify::notificationSize() const
646 \brief Sets the notification width size of each notification.
647 \param size - notification size
649 void QtxNotify::setNotificationSize(const double size)
659 \brief Performs the update of all notification geometries.
660 If parameter anim is true then arrangment will be animated otherwise not.
661 \param anim - use animation
663 void QtxNotify::arrangeNotifications(bool anim)
665 if (myNotifications.isEmpty())
668 QWidget* ref = window() ? window() : QApplication::desktop();
669 QPoint refPoint = referencePoint();
670 if (myNotifications.first()->isWindow())
671 refPoint = ref->mapToGlobal(refPoint);
674 NotifyList notifications;
675 bool reverse = placementPolicy() == QtxNotify::BottomToTop;
676 if (alignment() & Qt::AlignBottom)
681 for ( NotifyList::reverse_iterator it = myNotifications.rbegin(); it != myNotifications.rend(); ++it )
682 notifications.append(*it);
686 for ( NotifyList::iterator it = myNotifications.begin(); it != myNotifications.end(); ++it )
687 notifications.append(*it);
690 int sign = alignment() & Qt::AlignBottom ? -1 : 1;
691 QPoint pos = refPoint;
692 QMap<QtxNotify::NotifyWidget*, QRect> geoms;
693 for ( NotifyList::iterator it = notifications.begin(); it != notifications.end(); ++it )
695 QtxNotify::NotifyWidget* notify = *it;
696 QSize size = notify->sizeHint();
697 geoms.insert(notify, QRect(QPoint(pos.x(), pos.y() - (sign < 0 ? size.height() : 0)), size));
698 pos = QPoint(pos.x(), pos.y() + sign * ( size.height() + space ));
701 QParallelAnimationGroup* animGroup = 0;
702 if (isAnimated() && anim)
703 animGroup = new QParallelAnimationGroup();
705 for ( NotifyList::iterator iter = notifications.begin(); iter != notifications.end(); ++iter )
707 QtxNotify::NotifyWidget* notify = *iter;
708 QRect geom = geoms[notify];
709 if (notify->parentWidget())
710 geom = QRect(notify->parentWidget()->mapFromGlobal(geom.topLeft()), geom.size());
712 bool isvis = notify->isVisible();
716 QPropertyAnimation* animation = new QPropertyAnimation(notify, "geometry");
717 animation->setDuration(animationTime());
718 animGroup->addAnimation(animation);
721 animation->setStartValue(notify->geometry());
722 animation->setEndValue(geom);
727 animation->setStartValue(QRect(QPoint(geom.center().x(), geom.top()), QSize(0, geom.height())));
728 animation->setEndValue(geom);
733 notify->setGeometry(geom);
738 startAnimation(animGroup);
741 QPoint QtxNotify::referencePoint() const
744 QWidget* ref = window() ? window() : QApplication::desktop();
747 int notifywidth = notificationWidth();
748 int align = alignment() > 0 ? alignment() : Qt::AlignLeft | Qt::AlignTop;
750 if (align & Qt::AlignLeft)
751 refPoint.setX(margin);
752 else if (align & Qt::AlignRight)
753 refPoint.setX(ref->width() - notifywidth - margin);
754 else if (align & Qt::AlignHCenter)
755 refPoint.setX((ref->width() - notifywidth) / 2);
757 if (align & Qt::AlignTop)
758 refPoint.setY(margin);
759 else if (align & Qt::AlignBottom)
760 refPoint.setY(ref->height() - margin);
761 else if (align & Qt::AlignVCenter)
762 refPoint.setY(ref->height() / 2);
764 return ref->mapToGlobal(refPoint);
768 \brief Gets the notification with specified identifier.
769 \param id - notification identifier
770 \return notification instance
772 QtxNotify::NotifyWidget* QtxNotify::notification(const int id)
774 QtxNotify::NotifyWidget* notify = 0;
775 for (NotifyList::const_iterator it = myNotifications.begin(); it != myNotifications.end() && !notify; ++it)
777 if ((*it)->id() == id)
784 \brief Gets the notification with specified text.
785 \param text - notification text
786 \return notification instance
788 QtxNotify::NotifyWidget* QtxNotify::notification(const QString& text)
790 QtxNotify::NotifyWidget* notify = 0;
791 for (NotifyList::const_iterator it = myNotifications.begin(); it != myNotifications.end() && !notify; ++it)
793 if ((*it)->text() == text)
800 \brief Removes the specified notification.
801 \param notify - notification instance
803 void QtxNotify::removeNotification(QtxNotify::NotifyWidget* notify)
808 myNotifications.removeAll(notify);
809 notify->setTimeout(0);
812 QPropertyAnimation* animation = new QPropertyAnimation(notify, "geometry");
813 animation->setDuration(animationTime());
814 animation->setStartValue(notify->geometry());
815 animation->setEndValue(QRect(QPoint(notify->geometry().center().x(), notify->geometry().top()),
816 QSize(0, notify->geometry().height())));
818 connect(animation, SIGNAL(finished()), notify, SLOT(hide()));
819 connect(animation, SIGNAL(finished()), notify, SLOT(deleteLater()));
820 connect(animation, SIGNAL(finished()), this, SLOT(onArrangeTimeout()));
821 startAnimation(animation);
826 notify->deleteLater();
827 arrangeNotifications();
832 \brief Generates the new free notification identifier.
833 \return generated identifier
835 int QtxNotify::generateId() const
838 for (NotifyList::const_iterator it = myNotifications.begin(); it != myNotifications.end(); ++it)
839 map.insert((*it)->id(), true);
842 while (map.contains(id))
848 \brief Gets the notification window with
849 \return width in pixels
851 int QtxNotify::notificationWidth() const
853 QWidget* refWin = window();
854 double size = notificationSize();
855 int width = (int)( size > 1 ? size : size * refWin->width() );
856 width = qMin(width, refWin->width() - 10);
861 \brief Schedule delayed notification arrangement.
863 void QtxNotify::triggerArrange()
865 if (myArrangeTimer->isActive())
866 myArrangeTimer->stop();
867 myArrangeTimer->start();
871 \brief Gets the animation using state.
872 If it's true then animation will be used during notification modifcations.
873 \return animation using state
875 bool QtxNotify::isAnimated() const
877 return myAnimTime > 0;
881 \brief Gets the active animation state. If there is exist active animation then return true otherwise false.
882 \return active animation state
884 bool QtxNotify::hasAcitveAnimation() const
887 for (AnimationList::const_iterator it = myAnimations.begin(); it != myAnimations.end() && !has; ++it)
888 has = (*it)->state() == QAbstractAnimation::Running;
893 \brief Starts the given animation.
894 \param animation - animation instance
896 void QtxNotify::startAnimation(QAbstractAnimation* animation)
898 myAnimations.append(animation);
899 connect(animation, SIGNAL(destroyed(QObject*)), this, SLOT(onAnimationDestroyed(QObject*)));
901 animation->start(QAbstractAnimation::DeleteWhenStopped);
905 \brief Stops the given animation.
906 \param animation - animation instance
908 void QtxNotify::stopAnimation(QAbstractAnimation* animation)
911 animation->deleteLater();
915 \brief Invoked when animation is destroyed. Removes the destroyed animation reference from list.
916 \param obj - destroyed animation reference
918 void QtxNotify::onAnimationDestroyed(QObject* obj)
920 myAnimations.removeAll((QAbstractAnimation*)obj);
924 \brief Performs the scheduled delayed notification arrangment.
926 void QtxNotify::onArrangeTimeout()
928 if (hasAcitveAnimation())
932 arrangeNotifications(!myAnimBlock);
938 \brief Performs the notification arrangment with disabled animation.
940 void QtxNotify::updateArrangement()