From 1bd04fc2bea6dba898b81b46b2d82056c36598ca Mon Sep 17 00:00:00 2001 From: vsr Date: Thu, 31 May 2018 13:31:43 +0300 Subject: [PATCH] 0023564: [EDF] AsterStudy: introduce a feature to show popup notifications --- src/CAM/CAM_Module.cxx | 35 + src/CAM/CAM_Module.h | 4 + src/LightApp/LightApp_Application.cxx | 6 + src/LightApp/resources/LightApp.xml | 5 + src/LightApp/resources/LightApp_msg_en.ts | 10 +- src/LightApp/resources/LightApp_msg_fr.ts | 20 + src/LightApp/resources/LightApp_msg_ja.ts | 20 + src/Qtx/CMakeLists.txt | 2 + src/Qtx/QtxNotify.cxx | 945 ++++++++++++++++++++++ src/Qtx/QtxNotify.h | 117 +++ src/SALOME_PYQT/SalomePyQt/SalomePyQt.cxx | 87 ++ src/SALOME_PYQT/SalomePyQt/SalomePyQt.h | 6 +- src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip | 8 +- src/STD/STD_Application.cxx | 75 ++ src/STD/STD_Application.h | 11 + 15 files changed, 1347 insertions(+), 4 deletions(-) create mode 100644 src/Qtx/QtxNotify.cxx create mode 100644 src/Qtx/QtxNotify.h diff --git a/src/CAM/CAM_Module.cxx b/src/CAM/CAM_Module.cxx index b886d8f55..a5bcaf714 100755 --- a/src/CAM/CAM_Module.cxx +++ b/src/CAM/CAM_Module.cxx @@ -327,6 +327,41 @@ void CAM_Module::putInfo( const QString& msg, const int msec ) myInfo = msg; } +/*! + \brief Shows the notifications with spectified text, title and automatic close timeout. + Notification will be automatically closed after specified timeout in msec. If + timeout is zero then automatic closing doesn't performed. + \param text - Notification text + \param title - Notification title + \param timeout - Notification close timeout in msec + \return notification identifier +*/ +int CAM_Module::showNotification( const QString& message, const QString& title, int timeout ) +{ + if ( application() ) + application()->showNotification( message, title, timeout ); +} + +/*! + \brief Closes the notifications with spectified text. + \param text - Notification text +*/ +void CAM_Module::hideNotification( const QString& message ) +{ + if ( application() ) + application()->hideNotification( message ); +} + +/*! + \brief Closes the notifications with spectified identifier. + \param text - Notification text +*/ +void CAM_Module::hideNotification( int id ) +{ + if ( application() ) + application()->hideNotification( id ); +} + /*! \brief Restore message info. diff --git a/src/CAM/CAM_Module.h b/src/CAM/CAM_Module.h index bc96dc908..3cb43c161 100755 --- a/src/CAM/CAM_Module.h +++ b/src/CAM/CAM_Module.h @@ -73,6 +73,10 @@ public: virtual void putInfo( const QString&, const int = -1 ); + int showNotification(const QString& message, const QString& title, int timeout = -1); + void hideNotification(const QString& message); + void hideNotification(int id); + bool isActiveModule() const; virtual void setMenuShown( const bool ); diff --git a/src/LightApp/LightApp_Application.cxx b/src/LightApp/LightApp_Application.cxx index 128df3e64..9da7c829a 100644 --- a/src/LightApp/LightApp_Application.cxx +++ b/src/LightApp/LightApp_Application.cxx @@ -2399,6 +2399,12 @@ void LightApp_Application::createPreferences( LightApp_Preferences* pref ) pref->addPreference( tr( "PREF_OPAQUE_RESIZE" ), lookGroup, LightApp_Preferences::Bool, "desktop", "opaque_resize" ); // .... -> drop-down buttons pref->addPreference( tr( "PREF_DROP_DOWN_BUTTONS" ), lookGroup, LightApp_Preferences::Bool, "viewers", "drop_down_buttons" ); + // .... -> Notification timeout + int delay = pref->addPreference( tr( "PREF_NOTIFY_TIMEOUT" ), lookGroup, LightApp_Preferences::IntSpin, "notification", "timeout" ); + pref->setItemProperty( "special", tr("PREF_NOTIFY_TIMEOUT_NONE"), delay ); + pref->setItemProperty( "min", 0, delay ); + pref->setItemProperty( "max", 100, delay ); + pref->setItemProperty( "suffix", " sec", delay ); // ... "Look and feel" group <> // ... "Study properties" group <> diff --git a/src/LightApp/resources/LightApp.xml b/src/LightApp/resources/LightApp.xml index 9004e7b2a..7adc8b93d 100644 --- a/src/LightApp/resources/LightApp.xml +++ b/src/LightApp/resources/LightApp.xml @@ -255,6 +255,11 @@
+
+ + + +
diff --git a/src/LightApp/resources/LightApp_msg_en.ts b/src/LightApp/resources/LightApp_msg_en.ts index 9efc62202..8752d4c0f 100644 --- a/src/LightApp/resources/LightApp_msg_en.ts +++ b/src/LightApp/resources/LightApp_msg_en.ts @@ -528,7 +528,7 @@ The changes will be applied on the next application session. NEW_WINDOW_6 ParaVie&w view - + NEW_WINDOW_7 P&ython view @@ -648,6 +648,14 @@ The changes will be applied on the next application session. PREF_DROP_DOWN_BUTTONS Drop-down buttons in toolbars for action groups + + PREF_NOTIFY_TIMEOUT + Notifications auto-hide timeout + + + PREF_NOTIFY_TIMEOUT_NONE + None + PREF_GROUP_COMMON Common diff --git a/src/LightApp/resources/LightApp_msg_fr.ts b/src/LightApp/resources/LightApp_msg_fr.ts index 4a3b4610f..9a07d916e 100755 --- a/src/LightApp/resources/LightApp_msg_fr.ts +++ b/src/LightApp/resources/LightApp_msg_fr.ts @@ -46,6 +46,18 @@ CEA/DEN, CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITASABOUT_VERSION Version %1 + + SALOME_SITE + SALOME Website + + + SALOME_FORUM + SALOME Forum + + + SALOME_VIDEO_TUTORIALS + Video Tutorials + ENTRY_COLUMN Entrée @@ -636,6 +648,14 @@ Les modifications seront appliquées à la prochaine session. PREF_DROP_DOWN_BUTTONS Boutons alignés dans la barre + + PREF_NOTIFY_TIMEOUT + Notifications auto-hide timeout + + + PREF_NOTIFY_TIMEOUT_NONE + None + PREF_GROUP_COMMON Commun diff --git a/src/LightApp/resources/LightApp_msg_ja.ts b/src/LightApp/resources/LightApp_msg_ja.ts index 632c65c91..6ba9e65b4 100644 --- a/src/LightApp/resources/LightApp_msg_ja.ts +++ b/src/LightApp/resources/LightApp_msg_ja.ts @@ -46,6 +46,18 @@ CEA/DEN, CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITASABOUT_VERSION Version %1 + + SALOME_SITE + SALOME Website + + + SALOME_FORUM + SALOME Forum + + + SALOME_VIDEO_TUTORIALS + Video Tutorials + ENTRY_COLUMN エントリ @@ -636,6 +648,14 @@ Pythonファイルは、文字、数字、アンダースコアが含まれて PREF_DROP_DOWN_BUTTONS バーでのボタンの配置 + + PREF_NOTIFY_TIMEOUT + Notifications auto-hide timeout + + + PREF_NOTIFY_TIMEOUT_NONE + None + PREF_GROUP_COMMON 共通 diff --git a/src/Qtx/CMakeLists.txt b/src/Qtx/CMakeLists.txt index ad1337ffc..bca0f0dc9 100755 --- a/src/Qtx/CMakeLists.txt +++ b/src/Qtx/CMakeLists.txt @@ -61,6 +61,7 @@ SET(_moc_HEADERS QtxMainWindow.h QtxMenu.h QtxMultiAction.h + QtxNotify.h QtxPagePrefMgr.h QtxPathDialog.h QtxPathEdit.h @@ -152,6 +153,7 @@ SET(_other_SOURCES QtxMainWindow.cxx QtxMenu.cxx QtxMultiAction.cxx + QtxNotify.cxx QtxPagePrefMgr.cxx QtxPathDialog.cxx QtxPathEdit.cxx diff --git a/src/Qtx/QtxNotify.cxx b/src/Qtx/QtxNotify.cxx new file mode 100644 index 000000000..c95890593 --- /dev/null +++ b/src/Qtx/QtxNotify.cxx @@ -0,0 +1,945 @@ +// Copyright (C) 2007-2018 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#include "QtxNotify.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/*! + \brief QtxNotify::NotifyWidget + Class represented notification widget +*/ + +class QtxNotify::NotifyWidget : public QWidget +{ + typedef QSharedPointer TextDocument; + +public: + NotifyWidget(QtxNotify*, const QString& title, + const QString& text, const int timeout, QWidget* parent = 0); + virtual ~NotifyWidget(); + + QtxNotify* notifyMgr() const; + + int id() const; + QString text() const; + QString title() const; + int timeout() const; + + void setId(const int); + void setText(const QString&); + void setTitle(const QString&); + void setTimeout(const int); + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + +protected: + virtual void showEvent(QShowEvent*); + virtual void paintEvent(QPaintEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + +private: + int frameWidth() const; + int notificationWidth() const; + + QRect textRect() const; + QRect titleRect() const; + QRect closeRect() const; + TextDocument textDocument() const; + + void onTimeout(); + +private: + int myId; + QString myText; + QString myTitle; + QTimer* myTimer; + QtxNotify* myNotifyMgr; + + bool myIsClosePressed; +}; + +/*! + \brief Constructor [private] +*/ +QtxNotify::NotifyWidget::NotifyWidget(QtxNotify* mgr, const QString& title, const QString& text, + const int timeout, QWidget* parent) + : QWidget(parent, (parent ? Qt::Widget : Qt::Tool) | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint), + myTitle(title), + myText(text), + myTimer(new QTimer(this)), + myNotifyMgr(mgr), + myIsClosePressed(false) +{ + myTimer->setSingleShot(true); + + connect(myTimer, &QTimer::timeout, this, &QtxNotify::NotifyWidget::onTimeout); + + setTimeout(timeout); + setMouseTracking(true); +} + +/*! + \brief Destructor +*/ +QtxNotify::NotifyWidget::~NotifyWidget() +{ +} + +/*! + \brief Gets the notification manager. + \return notification manager instance +*/ +QtxNotify* QtxNotify::NotifyWidget::notifyMgr() const +{ + return myNotifyMgr; +} + +/*! + \brief Gets the notification identifier. + \return notification identifier +*/ +int QtxNotify::NotifyWidget::id() const +{ + return myId; +} + +/*! + \brief Gets the notification text. + \return notification text string +*/ +QString QtxNotify::NotifyWidget::text() const +{ + return myText; +} + +/*! + \brief Gets the notification title. + \return notification title string +*/ +QString QtxNotify::NotifyWidget::title() const +{ + return myTitle; +} + +/*! + \brief Gets the notification timeout. + \return notification timeout +*/ +int QtxNotify::NotifyWidget::timeout() const +{ + return myTimer->interval(); +} + +/*! + \brief Sets the notification timeout. + \param id - notification identifier +*/ +void QtxNotify::NotifyWidget::setId(const int id) +{ + myId = id; +} + +/*! + \brief Sets the notification text. + \param text - notification text string +*/ +void QtxNotify::NotifyWidget::setText(const QString& text) +{ + myText = text; + updateGeometry(); + update(); +} + +/*! + \brief Sets the notification title. + \param title - notification title string +*/ +void QtxNotify::NotifyWidget::setTitle(const QString& title) +{ + myTitle = title; + update(); +} + +/*! + \brief Sets the notification timeout. + \param timeout - notification timeout +*/ +void QtxNotify::NotifyWidget::setTimeout(const int timeout) +{ + if (myTimer->isActive()) + myTimer->stop(); + myTimer->setInterval(timeout); + + if (isVisible() && myTimer->interval() > 0) + myTimer->start(); +} + +/*! + \brief Gets the notification widget size hint. + \return size hint +*/ +QSize QtxNotify::NotifyWidget::sizeHint() const +{ + return minimumSizeHint(); +} + +/*! + \brief Gets the notification widget minimum size hint. + \return minimum size hint +*/ +QSize QtxNotify::NotifyWidget::minimumSizeHint() const +{ + int frame = frameWidth(); + int width = notificationWidth(); + TextDocument doc = textDocument(); + int height = (int)doc->size().height() + 2 * frame; + + height += fontMetrics().height() + 2 * frame; + + return QSize(width, height); +} + +void QtxNotify::NotifyWidget::showEvent(QShowEvent* e) +{ + QWidget::showEvent(e); + + if (!myTimer->isActive() && myTimer->interval() > 0) + myTimer->start(); +} + +/*! + \brief Reimplemented for notification drawing. +*/ +void QtxNotify::NotifyWidget::paintEvent(QPaintEvent*) +{ + QPainter painter(this); + + QRect clsRect = closeRect(); + QRect ttlRect = titleRect(); + QRect txtRect = textRect(); + + // Fills background and frame + painter.setRenderHint(QPainter::TextAntialiasing); + painter.fillRect(QRect(QPoint(0,0), size()), Qt::darkGray); + painter.fillRect(txtRect, Qt::white); + + // Draw title + painter.save(); + + painter.setPen(Qt::white); + QFont fnt = font(); + fnt.setBold(true); + painter.setFont(fnt); + ttlRect.setRight(clsRect.left()); + + QString titleText = title().trimmed(); + if (QFontMetrics(painter.font()).width(titleText) > ttlRect.width()) + titleText = QFontMetrics(painter.font()).elidedText(titleText, Qt::ElideRight, ttlRect.width() - 5); + painter.drawText(ttlRect.adjusted(2, 0, 0, 0), Qt::AlignVCenter, titleText); + + painter.restore(); + + // Draw close button + painter.save(); + + if (myIsClosePressed && clsRect.contains(mapFromGlobal(QCursor::pos()))) + { + painter.fillRect(clsRect.adjusted(0, 0, -1, -1), Qt::gray); + clsRect = clsRect.adjusted(1, 1, 0, 0); + } + else + { + painter.fillRect(clsRect.adjusted(1, 1, 0, 0), Qt::lightGray); + clsRect = clsRect.adjusted(0, 0, -1, -1); + } + painter.fillRect(clsRect, Qt::red); + + int m = 2; + painter.setPen(Qt::white); + QRect rect = clsRect.adjusted(m, m, -m, -m); + painter.drawLine(rect.topLeft(), rect.bottomRight()); + painter.drawLine(rect.topRight(), rect.bottomLeft()); + + painter.restore(); + + // Draw text + painter.save(); + + painter.setPen(Qt::black); + TextDocument doc = textDocument(); + painter.translate(txtRect.topLeft()); + doc->drawContents(&painter, QRectF(QPointF(0, 0), txtRect.size())); + + painter.restore(); + + painter.end(); +} + +/*! + \brief Reimplemented for handling close button. +*/ +void QtxNotify::NotifyWidget::mouseMoveEvent(QMouseEvent*) +{ + if (myIsClosePressed) + update(); +} + +/*! + \brief Reimplemented for handling close button. +*/ +void QtxNotify::NotifyWidget::mousePressEvent(QMouseEvent* e) +{ + if (e->button() == Qt::LeftButton && closeRect().contains(e->pos())) + { + myIsClosePressed = true; + update(); + } +} + +/*! + \brief Reimplemented for handling close button. +*/ +void QtxNotify::NotifyWidget::mouseReleaseEvent(QMouseEvent* e) +{ + if (e->button() == Qt::LeftButton) + { + if (myIsClosePressed && closeRect().contains(e->pos())) + { + notifyMgr()->hideNotification(id()); + } + myIsClosePressed = false; + update(); + } +} + +/*! + \brief Gets the notification frame with + \return Frame width in pixels +*/ +int QtxNotify::NotifyWidget::frameWidth() const +{ + return 2; +} + +/*! + \brief Gets the notification window with + \return width in pixels +*/ +int QtxNotify::NotifyWidget::notificationWidth() const +{ + return notifyMgr()->notificationWidth(); +} + +/*! + \brief Gets the text document with actual text and size. + \return text document instance +*/ +QtxNotify::NotifyWidget::TextDocument QtxNotify::NotifyWidget::textDocument() const +{ + TextDocument doc(new QTextDocument()); + if (Qt::mightBeRichText(text())) + doc->setHtml(text()); + else + doc->setPlainText(text()); + doc->setTextWidth(notificationWidth() - 2 * frameWidth()); + return doc; +} + +/*! + \brief Gets the notification message text rectangle. + \return text rectangle +*/ +QRect QtxNotify::NotifyWidget::textRect() const +{ + int frame = frameWidth(); + return QRect(frame, 3 * frame + fontMetrics().height(), + width() - 2 * frame, height() - 4 * frame - fontMetrics().height()); +} + +/*! + \brief Gets the notification title rectangle + \return title rectangle +*/ +QRect QtxNotify::NotifyWidget::titleRect() const +{ + int frame = frameWidth(); + return QRect(frame, frame, width() - 2 * frame, fontMetrics().height() + 2 * frame); +} + +/*! + \brief Gets the notification close button rectangle. + \return close rectangle +*/ +QRect QtxNotify::NotifyWidget::closeRect() const +{ + int frame = frameWidth(); + int size = fontMetrics().height(); + return QRect(width() - 2 * frame - size, 2 * frame, size, size); +} + +/*! + \brief Invoked when notification close timeout and hide the notification. +*/ +void QtxNotify::NotifyWidget::onTimeout() +{ + notifyMgr()->hideNotification(id()); +} + +/*! + \brief QtxNotify + Class that manages all notification instances. Performs show, hide, placing, etc +*/ + +/*! + \brief Constructor. +*/ +QtxNotify::QtxNotify(QObject* parent) + : QObject(parent), + mySize(250), + myWindow(0), + myPlacement(TopToBottom), + myAlignment(Qt::AlignRight|Qt::AlignTop), + myAnimTime(0), + myAnimBlock(false) +{ + myArrangeTimer = new QTimer(this); + myArrangeTimer->setInterval(0); + myArrangeTimer->setSingleShot(true); + connect(myArrangeTimer, SIGNAL(timeout()), this, SLOT(onArrangeTimeout())); + + QWidget* window = 0; + QWidgetList windows = QApplication::topLevelWidgets(); + for ( QWidgetList::iterator it = windows.begin(); it != windows.end() && !window; ++it ) + { + QWidget* win = *it; + if (win->isVisible() && + (win->windowType() == Qt::Widget || win->windowType() == Qt::Window)) + window = win; + } + + setWindow(window ? window : QApplication::desktop()); +} + +/*! + \brief Constructor. +*/ +QtxNotify::QtxNotify(QWidget* win, QObject* parent) + : QObject(parent), + mySize(250), + myWindow(0), + myPlacement(TopToBottom), + myAlignment(Qt::AlignRight|Qt::AlignTop), + myAnimTime(0), + myAnimBlock(false) +{ + myArrangeTimer = new QTimer(this); + myArrangeTimer->setInterval(0); + myArrangeTimer->setSingleShot(true); + connect(myArrangeTimer, SIGNAL(timeout()), this, SLOT(onArrangeTimeout())); + + setWindow(win); +} + +/*! + \brief Destructor. +*/ +QtxNotify::~QtxNotify() +{ +} + +/*! + \brief Shows the notifications with spectified text, title and automatic close timeout. + Notification will be automatically closed after specified timeout in msec. If + timeout is zero then automatic closing doesn't performed. + \param text - Notification text + \param title - Notification title + \param timeout - Notification close timeout in msec + \return notification identifier +*/ +int QtxNotify::showNotification(const QString& text, const QString& title, int timeout) +{ + QtxNotify::NotifyWidget* notify = notification(text); + if (!notify) + { + notify = new QtxNotify::NotifyWidget(this, title, text, timeout, window()); + notify->setId(generateId()); + } + else + { + myNotifications.removeAll(notify); + notify->setTimeout(timeout); + notify->setTitle(title); + notify->hide(); + } + + myNotifications.append(notify); + triggerArrange(); + + return notify->id(); +} + +/*! + \brief Closes the notifications with spectified text. + \param text - Notification text +*/ +void QtxNotify::hideNotification(const QString& text) +{ + removeNotification(notification(text)); +} + +/*! + \brief Closes the notifications with spectified identifier. + \param text - Notification identifier +*/ +void QtxNotify::hideNotification(const int id) +{ + removeNotification(notification(id)); +} + +/*! + \brief Gets the reference widget for all notifications. + \return reference widget +*/ +QWidget* QtxNotify::window() const +{ + return myWindow; +} + +/*! + \brief Sets the reference widget for all notifications. + \param win - reference widget +*/ +void QtxNotify::setWindow(QWidget* window) +{ + if (myWindow != window) + { + if (myWindow) + myWindow->removeEventFilter(this); + myWindow = window; + if (myWindow) + myWindow->installEventFilter(this); + updateArrangement(); + } +} + +/*! + \brief Gets the animation time in msec for notification modifications. + \return animation time +*/ +int QtxNotify::animationTime() const +{ + return myAnimTime; +} + +/*! + \brief Sets the animation time in msec for notification modifications. + If animation time is zero then animation is disabled. + \param time - animation time +*/ +void QtxNotify::setAnimationTime(const int time) +{ + myAnimTime = time; +} + +/*! + \brief Gets the notification placement policy. + \return notification placement policy +*/ +QtxNotify::PlacementPolicy QtxNotify::placementPolicy() const +{ + return myPlacement; +} + +/*! + \brief Sets the notification placement policy. + \param placement - notification placement policy +*/ +void QtxNotify::setPlacementPolicy(const QtxNotify::PlacementPolicy& placement) +{ + if (myPlacement != placement) + { + myPlacement = placement; + updateArrangement(); + } +} + +/*! + \brief Gets the notification placement base point alignment. + \return alignment flags +*/ +int QtxNotify::alignment() const +{ + return myAlignment; +} + +/*! + \brief Sets the notification placement base point alignment. + \param falgs - alignment flags +*/ +void QtxNotify::setAlignment(const int flags) +{ + if (myAlignment != flags) + { + myAlignment = flags; + updateArrangement(); + } +} + +/*! + \brief Reimplemented for tracking reference widget size changing and + update notification geometries. + \param o - handled object + \param e - handled event +*/ +bool QtxNotify::eventFilter(QObject* o, QEvent* e) +{ + if (e->type() == QEvent::Resize && o == window()) + { + updateArrangement(); + } + return QObject::eventFilter(o, e); +} + +/*! + \brief Gets the notification width size of each notification. If size value more than 1 + then it will be interpreted as absolute width in pixels. If size value in range + between 0 and 1 then notification width will be calculated as relative part of + reference window width. + \return notification size +*/ +double QtxNotify::notificationSize() const +{ + return mySize; +} + +/*! + \brief Sets the notification width size of each notification. + \param size - notification size +*/ +void QtxNotify::setNotificationSize(const double size) +{ + if (mySize != size) + { + mySize = size; + updateArrangement(); + } +} + +/*! + \brief Performs the update of all notification geometries. + If parameter anim is true then arrangment will be animated otherwise not. + \param anim - use animation +*/ +void QtxNotify::arrangeNotifications(bool anim) +{ + if (myNotifications.isEmpty()) + return; + + QWidget* ref = window() ? window() : QApplication::desktop(); + QPoint refPoint = referencePoint(); + if (myNotifications.first()->isWindow()) + refPoint = ref->mapToGlobal(refPoint); + + int space = 10; + NotifyList notifications; + bool reverse = placementPolicy() == QtxNotify::BottomToTop; + if (alignment() & Qt::AlignBottom) + reverse = !reverse; + + if (reverse) + { + for ( NotifyList::reverse_iterator it = myNotifications.rbegin(); it != myNotifications.rend(); ++it ) + notifications.append(*it); + } + else + { + for ( NotifyList::iterator it = myNotifications.begin(); it != myNotifications.end(); ++it ) + notifications.append(*it); + } + + int sign = alignment() & Qt::AlignBottom ? -1 : 1; + QPoint pos = refPoint; + QMap geoms; + for ( NotifyList::iterator it = notifications.begin(); it != notifications.end(); ++it ) + { + QtxNotify::NotifyWidget* notify = *it; + QSize size = notify->sizeHint(); + geoms.insert(notify, QRect(QPoint(pos.x(), pos.y() - (sign < 0 ? size.height() : 0)), size)); + pos = QPoint(pos.x(), pos.y() + sign * ( size.height() + space )); + } + + QParallelAnimationGroup* animGroup = 0; + if (isAnimated() && anim) + animGroup = new QParallelAnimationGroup(); + + for ( NotifyList::iterator iter = notifications.begin(); iter != notifications.end(); ++iter ) + { + QtxNotify::NotifyWidget* notify = *iter; + QRect geom = geoms[notify]; + if (notify->parentWidget()) + geom = QRect(notify->parentWidget()->mapFromGlobal(geom.topLeft()), geom.size()); + + bool isvis = notify->isVisible(); + + if (animGroup) + { + QPropertyAnimation* animation = new QPropertyAnimation(notify, "geometry"); + animation->setDuration(animationTime()); + animGroup->addAnimation(animation); + if (isvis) + { + animation->setStartValue(notify->geometry()); + animation->setEndValue(geom); + } + else + { + notify->show(); + animation->setStartValue(QRect(QPoint(geom.center().x(), geom.top()), QSize(0, geom.height()))); + animation->setEndValue(geom); + } + } + else + { + notify->setGeometry(geom); + notify->show(); + } + } + if (animGroup) + startAnimation(animGroup); +} + +QPoint QtxNotify::referencePoint() const +{ + int margin = 5; + QWidget* ref = window() ? window() : QApplication::desktop(); + + QPoint refPoint; + int notifywidth = notificationWidth(); + int align = alignment() > 0 ? alignment() : Qt::AlignLeft | Qt::AlignTop; + + if (align & Qt::AlignLeft) + refPoint.setX(margin); + else if (align & Qt::AlignRight) + refPoint.setX(ref->width() - notifywidth - margin); + else if (align & Qt::AlignHCenter) + refPoint.setX((ref->width() - notifywidth) / 2); + + if (align & Qt::AlignTop) + refPoint.setY(margin); + else if (align & Qt::AlignBottom) + refPoint.setY(ref->height() - margin); + else if (align & Qt::AlignVCenter) + refPoint.setY(ref->height() / 2); + + return ref->mapToGlobal(refPoint); +} + +/*! + \brief Gets the notification with specified identifier. + \param id - notification identifier + \return notification instance +*/ +QtxNotify::NotifyWidget* QtxNotify::notification(const int id) +{ + QtxNotify::NotifyWidget* notify = 0; + for (NotifyList::const_iterator it = myNotifications.begin(); it != myNotifications.end() && !notify; ++it) + { + if ((*it)->id() == id) + notify = *it; + } + return notify; +} + +/*! + \brief Gets the notification with specified text. + \param text - notification text + \return notification instance +*/ +QtxNotify::NotifyWidget* QtxNotify::notification(const QString& text) +{ + QtxNotify::NotifyWidget* notify = 0; + for (NotifyList::const_iterator it = myNotifications.begin(); it != myNotifications.end() && !notify; ++it) + { + if ((*it)->text() == text) + notify = *it; + } + return notify; +} + +/*! + \brief Removes the specified notification. + \param notify - notification instance +*/ +void QtxNotify::removeNotification(QtxNotify::NotifyWidget* notify) +{ + if (!notify) + return; + + myNotifications.removeAll(notify); + notify->setTimeout(0); + + if (isAnimated()) { + QPropertyAnimation* animation = new QPropertyAnimation(notify, "geometry"); + animation->setDuration(animationTime()); + animation->setStartValue(notify->geometry()); + animation->setEndValue(QRect(QPoint(notify->geometry().center().x(), notify->geometry().top()), + QSize(0, notify->geometry().height()))); + + connect(animation, SIGNAL(finished()), notify, SLOT(hide())); + connect(animation, SIGNAL(finished()), notify, SLOT(deleteLater())); + connect(animation, SIGNAL(finished()), this, SLOT(onArrangeTimeout())); + startAnimation(animation); + } + else + { + notify->hide(); + notify->deleteLater(); + arrangeNotifications(); + } +} + +/*! + \brief Generates the new free notification identifier. + \return generated identifier +*/ +int QtxNotify::generateId() const +{ + QMap map; + for (NotifyList::const_iterator it = myNotifications.begin(); it != myNotifications.end(); ++it) + map.insert((*it)->id(), true); + + int id = 0; + while (map.contains(id)) + id++; + return id; +} + +/*! + \brief Gets the notification window with + \return width in pixels +*/ +int QtxNotify::notificationWidth() const +{ + QWidget* refWin = window(); + double size = notificationSize(); + int width = (int)( size > 1 ? size : size * refWin->width() ); + width = qMin(width, refWin->width() - 10); + return width; +} + +/*! + \brief Schedule delayed notification arrangement. +*/ +void QtxNotify::triggerArrange() +{ + if (myArrangeTimer->isActive()) + myArrangeTimer->stop(); + myArrangeTimer->start(); +} + +/*! + \brief Gets the animation using state. + If it's true then animation will be used during notification modifcations. + \return animation using state +*/ +bool QtxNotify::isAnimated() const +{ + return myAnimTime > 0; +} + +/*! + \brief Gets the active animation state. If there is exist active animation then return true otherwise false. + \return active animation state +*/ +bool QtxNotify::hasAcitveAnimation() const +{ + bool has = false; + for (AnimationList::const_iterator it = myAnimations.begin(); it != myAnimations.end() && !has; ++it) + has = (*it)->state() == QAbstractAnimation::Running; + return has; +} + +/*! + \brief Starts the given animation. + \param animation - animation instance +*/ +void QtxNotify::startAnimation(QAbstractAnimation* animation) +{ + myAnimations.append(animation); + connect(animation, SIGNAL(destroyed(QObject*)), this, SLOT(onAnimationDestroyed(QObject*))); + + animation->start(QAbstractAnimation::DeleteWhenStopped); +} + +/*! + \brief Stops the given animation. + \param animation - animation instance +*/ +void QtxNotify::stopAnimation(QAbstractAnimation* animation) +{ + animation->stop(); + animation->deleteLater(); +} + +/*! + \brief Invoked when animation is destroyed. Removes the destroyed animation reference from list. + \param obj - destroyed animation reference +*/ +void QtxNotify::onAnimationDestroyed(QObject* obj) +{ + myAnimations.removeAll((QAbstractAnimation*)obj); +} + +/*! + \brief Performs the scheduled delayed notification arrangment. +*/ +void QtxNotify::onArrangeTimeout() +{ + if (hasAcitveAnimation()) + triggerArrange(); + else + { + arrangeNotifications(!myAnimBlock); + myAnimBlock = false; + } +} + +/*! + \brief Performs the notification arrangment with disabled animation. +*/ +void QtxNotify::updateArrangement() +{ + myAnimBlock = true; + triggerArrange(); +} diff --git a/src/Qtx/QtxNotify.h b/src/Qtx/QtxNotify.h new file mode 100644 index 000000000..f5647c7c2 --- /dev/null +++ b/src/Qtx/QtxNotify.h @@ -0,0 +1,117 @@ +// Copyright (C) 2007-2018 CEA/DEN, EDF R&D, OPEN CASCADE +// +// Copyright (C) 2003-2007 OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN, +// CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// +// See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com +// + +#ifndef QTXNOTIFY_H +#define QTXNOTIFY_H + +#include + +#include +#include + +class QTimer; + +class QtxNotify : public QObject +{ + Q_OBJECT + + class NotifyWidget; + +public: + typedef enum { + TopToBottom, + BottomToTop + } PlacementPolicy; + + Q_ENUM(PlacementPolicy) + +public: + QtxNotify(QObject* = 0); + QtxNotify(QWidget*, QObject* = 0); + virtual ~QtxNotify(); + + int showNotification(const QString&, const QString&, int timeout = -1); + void hideNotification(const QString& = QString()); + void hideNotification(const int); + + QWidget* window() const; + void setWindow(QWidget* window); + + double notificationSize() const; + void setNotificationSize(const double); + + int animationTime() const; + void setAnimationTime(const int); + + PlacementPolicy placementPolicy() const; + void setPlacementPolicy(const PlacementPolicy&); + + int alignment() const; + void setAlignment(const int); + + virtual bool eventFilter(QObject*, QEvent*); + +private Q_SLOTS: + void onArrangeTimeout(); + void onAnimationDestroyed(QObject*); + +private: + void triggerArrange(); + void updateArrangement(); + void arrangeNotifications(bool = true); + + NotifyWidget* notification(const int); + NotifyWidget* notification(const QString&); + void removeNotification(NotifyWidget*); + + int generateId() const; + + bool isAnimated() const; + + bool hasAcitveAnimation() const; + void startAnimation(QAbstractAnimation*); + void stopAnimation(QAbstractAnimation*); + + QPoint referencePoint() const; + int notificationWidth() const; + +private: + typedef QList NotifyList; + typedef QList AnimationList; + +private: + double mySize; + QWidget* myWindow; + PlacementPolicy myPlacement; + int myAlignment; + QTimer* myArrangeTimer; + + int myAnimTime; + bool myAnimBlock; + AnimationList myAnimations; + + NotifyList myNotifications; + + friend class QtxNotify::NotifyWidget; +}; + +#endif // QTXNOTIFY_H diff --git a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.cxx b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.cxx index 7b0220096..d69a60b1b 100644 --- a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.cxx +++ b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.cxx @@ -750,6 +750,93 @@ void SalomePyQt::putInfo( const QString& msg, const int sec ) ProcessVoidEvent( new TPutInfoEvent( msg, sec ) ); } +/*! + \fn int SalomePyQt::showNotification( const QString& msg, const QString& title, const int sec ); + \brief Show notification in the application's desktop window. + + Optional third delay parameter (\a sec) can be used to specify + time of the notification diplaying in seconds. If this parameter is less + or equal to zero, the permanent notification will be put. + + Notification can be forcibly hidden via hideNotification() method. + + \param msg message text + \param title title text + \param sec notification displaying time in seconds + \return unique ID of the notification (can be used to hide notification) + \sa hideNotification() +*/ + +class TShowNotifyEvent: public SALOME_Event +{ + QString myMsg; + QString myTitle; + int mySecs; + +public: + typedef int TResult; + TResult myResult; + +public: + TShowNotifyEvent( const QString& msg, const QString& title, const int sec = -1 ) : myMsg( msg ), myTitle( title), mySecs( sec ), myResult( -1 ) {} + virtual void Execute() + { + if ( LightApp_Application* anApp = getApplication() ) { + myResult = anApp->showNotification( myMsg, myTitle, mySecs * 1000 ); + } + } +}; + +int SalomePyQt::showNotification( const QString& msg, const QString& title, const int sec ) +{ + return ProcessEvent( new TShowNotifyEvent( msg, title, sec ) ); +} + +/*! + \fn void SalomePyQt::hideNotification( const QString& msg ); + \brief Remove notification with given message text from the application's desktop. + + \param msg message text + \sa showNotification() +*/ + +/*! + \fn void SalomePyQt::hideNotification( const int id ); + \brief Remove notification with given \a id from the application's desktop. + + \param id notification id + \sa showNotification() +*/ + +class THideNotifyEvent: public SALOME_Event +{ + int myId; + QString myMsg; + +public: + THideNotifyEvent( const QString& msg ) : myId( -1 ), myMsg( msg ) {} + THideNotifyEvent( const int id ) : myId( id ) {} + virtual void Execute() + { + if ( LightApp_Application* anApp = getApplication() ) { + if ( myId >= 0 ) + anApp->hideNotification( myId ); + else + anApp->hideNotification( myMsg ); + } + } +}; + +void SalomePyQt::hideNotification( const QString& msg ) +{ + ProcessVoidEvent( new THideNotifyEvent( msg ) ); +} + +void SalomePyQt::hideNotification( const int id ) +{ + ProcessVoidEvent( new THideNotifyEvent( id ) ); +} + /*! \fn const QString SalomePyQt::getActiveComponent(); \brief Get the currently active module name (for the current study). diff --git a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.h b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.h index 718758998..2bac58d1a 100644 --- a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.h +++ b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.h @@ -190,12 +190,16 @@ public: static SALOME_Selection* getSelection(); static void setSelection( const QStringList& ); static int getStudyId(); - static void putInfo( const QString&, const int = 0 ); static const QString getActiveComponent(); static PyObject* getActivePythonModule(); static bool activateModule( const QString& ); static void updateObjBrowser( const int = 0, bool = true ); + static void putInfo( const QString&, const int = 0 ); + static int showNotification( const QString&, const QString&, const int = -1 ); + static void hideNotification( const QString& ); + static void hideNotification( const int ); + static bool isModified(); static void setModified( bool ); diff --git a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip index bf9a84a18..e7e726ccb 100644 --- a/src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip +++ b/src/SALOME_PYQT/SalomePyQt/SalomePyQt.sip @@ -301,12 +301,16 @@ public: static SALOME_Selection* getSelection() /Factory,ReleaseGIL/ ; static void setSelection( const QStringList& ) /ReleaseGIL/ ; static int getStudyId() /ReleaseGIL/ ; - static void putInfo( const QString&, const int = 0 ) /ReleaseGIL/ ; static const QString getActiveComponent() /ReleaseGIL/ ; static SIP_PYOBJECT getActivePythonModule() /ReleaseGIL/ ; static bool activateModule( const QString& ) /ReleaseGIL/ ; static void updateObjBrowser( const int = 0, bool = true ) /ReleaseGIL/ ; - + + static void putInfo( const QString&, const int = 0 ) /ReleaseGIL/ ; + static int showNotification( const QString&, const QString&, const int = -1 ) /ReleaseGIL/ ; + static void hideNotification( const QString& ) /ReleaseGIL/ ; + static void hideNotification( const int ) /ReleaseGIL/ ; + static bool isModified() /ReleaseGIL/ ; static void setModified( bool ) /ReleaseGIL/ ; diff --git a/src/STD/STD_Application.cxx b/src/STD/STD_Application.cxx index 208115b83..1aacab913 100755 --- a/src/STD/STD_Application.cxx +++ b/src/STD/STD_Application.cxx @@ -53,6 +53,7 @@ extern "C" STD_EXPORT SUIT_Application* createApplication() STD_Application::STD_Application() : SUIT_Application(), myActiveViewMgr( 0 ), + myNotify( 0 ), myExitConfirm( true ), myEditEnabled( true ) { @@ -721,6 +722,56 @@ void STD_Application::updateCommandsStatus() action( NewWindowId )->setEnabled( aHasStudy ); } +/*! + \brief Show notification with specified text and title. + + Notification will be automatically hidden after specified \a timeout + (given in milliseconds). If \a timeout is zero, the notification + is not automatically hidden; it can be only closed by the user manually. + + \param text - Notification text + \param title - Notification title + \param timeout - Timeout in milliseconds + \return Notification's unique identifier +*/ +int STD_Application::showNotification(const QString& message, const QString& title, int timeout) +{ + QtxNotify* ntfMgr = notifyMgr(); + if (ntfMgr) + { + int delay = timeout; + if (delay < 0) + { + SUIT_ResourceMgr* aResMgr = resourceMgr(); + if (aResMgr) + delay = aResMgr->integerValue("notification", "timeout", 0) * 1000; + } + ntfMgr->showNotification(message, title, qMax(delay, 0)); + } +} + +/*! + \brief Close notifications with specified text. + \param text - Notification text +*/ +void STD_Application::hideNotification(const QString& message) +{ + QtxNotify* ntfMgr = notifyMgr(); + if (ntfMgr) + ntfMgr->hideNotification(message); +} + +/*! + \brief Closes the notifications with specified identifier. + \param id - Notification identifier +*/ +void STD_Application::hideNotification(int id) +{ + QtxNotify* ntfMgr = notifyMgr(); + if (ntfMgr) + ntfMgr->hideNotification(id); +} + /*!\retval SUIT_ViewManager by viewer manager type name.*/ SUIT_ViewManager* STD_Application::viewManager( const QString& vmType ) const { @@ -1037,3 +1088,27 @@ bool STD_Application::abortAllOperations() { return true; } + +/*! + \brief Gets the notification manager. Creates it if not exists. + \return \c notification manager instance +*/ +QtxNotify* STD_Application::notifyMgr() +{ + if ( !myNotify ) + { + myNotify = new QtxNotify(desktop()); + myNotify->setWindow(desktop()); + + SUIT_ResourceMgr* aResMgr = resourceMgr(); + if (aResMgr) + { + int anim = aResMgr->integerValue("notification", "animation", 0); + myNotify->setAnimationTime(anim); + + double size = aResMgr->integerValue("notification", "size", 250); + myNotify->setNotificationSize(size); + } + } + return myNotify; +} diff --git a/src/STD/STD_Application.h b/src/STD/STD_Application.h index c709c95cd..666024bd4 100755 --- a/src/STD/STD_Application.h +++ b/src/STD/STD_Application.h @@ -27,7 +27,10 @@ #include +#include + #include +#include class QMenu; class QCloseEvent; @@ -104,6 +107,10 @@ public: virtual void updateDesktopTitle(); + int showNotification(const QString& message, const QString& title, int timeout = -1); + void hideNotification(const QString& message); + void hideNotification(int id); + signals: /*!emit that view manager added*/ void viewManagerAdded( SUIT_ViewManager* ); @@ -173,11 +180,15 @@ protected: virtual bool abortAllOperations(); + private: + QtxNotify* notifyMgr(); + private: ViewManagerList myViewMgrs; SUIT_ViewManager* myActiveViewMgr; private: + QPointer myNotify; bool myExitConfirm; bool myEditEnabled; }; -- 2.39.2