/*************************************************************************** * Copyright (c) 2022 Abdullah Tahiri * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 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 Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include "PreCompiled.h" #ifndef _PreComp_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif #include #include #include "Application.h" #include "BitmapFactory.h" #include "MDIView.h" #include "MainWindow.h" #include "NotificationBox.h" #include "NotificationArea.h" using namespace Gui; using Connection = boost::signals2::connection; namespace bp = boost::placeholders; class NotificationAreaObserver; namespace Gui { /** PImpl idiom structure having all data necessary for the notification area */ struct NotificationAreaP { // Structure holding all variables necessary for the Notification Area. // Preference parameters are updated by NotificationArea::ParameterObserver /** @name Non-intrusive notifications parameters */ //@{ /// Parameter controlled int maxOpenNotifications = 15; /// Parameter controlled unsigned int notificationExpirationTime = 10000; /// minimum time that the notification will remain unclosed unsigned int minimumOnScreenTime = 5000; /// Parameter controlled bool notificationsDisabled = false; /// Control of confirmation mechanism (for Critical Messages) bool requireConfirmationCriticalMessageDuringRestoring = true; /// Width of the non-intrusive notification int notificationWidth = 800; /// Any open non-intrusive notifications will disappear when another window is activated bool hideNonIntrusiveNotificationsWhenWindowDeactivated = true; /// Prevent non-intrusive notifications from appearing when the FreeCAD Window is not the active /// window bool preventNonIntrusiveNotificationsWhenWindowNotActive = true; //@} /** @name Widget parameters */ //@{ /// Parameter controlled - maximum number of message allowed in the notification area widget (0 /// means no limit) int maxWidgetMessages = 1000; /// User notifications get automatically removed from the Widget after the non-intrusive /// notification expiration time bool autoRemoveUserNotifications; //@} /** @name Notification rate control */ //@{ /* Control rate of updates of non-intrusive messages (avoids visual artifacts when messages are * constantly being received) */ // Timer to delay notification until a minimum time between two consecutive messages have lapsed QTimer inhibitTimer; // The time between two consecutive messages forced by the inhibitTimer const unsigned int inhibitNotificationTime = 250; //@} bool missedNotifications = false; // Access control std::mutex mutexNotification; // Pointers to widgets (no ownership) QMenu* menu; QWidgetAction* notificationaction; /** @name Resources */ //@{ /// Base::Console Message observer std::unique_ptr observer; Connection finishRestoreDocumentConnection; /// Preference Parameter observer std::unique_ptr parameterObserver; //@} }; }// namespace Gui /******************* Resource Management *****************************************/ /** Simple class to manage Notification Area Resources*/ class ResourceManager { private: ResourceManager() { error = BitmapFactory().pixmapFromSvg(":/icons/edit_Cancel.svg", QSize(16, 16)); warning = BitmapFactory().pixmapFromSvg(":/icons/Warning.svg", QSize(16, 16)); critical = BitmapFactory().pixmapFromSvg(":/icons/critical-info.svg", QSize(16, 16)); info = BitmapFactory().pixmapFromSvg(":/icons/info.svg", QSize(16, 16)); notificationArea = QIcon(QStringLiteral(":/icons/InTray.svg")); notificationAreaMissedNotifications = QIcon(QStringLiteral(":/icons/InTray_missed_notifications.svg")); } inline static const auto& getResourceManager() { static ResourceManager manager; return manager; } public: inline static auto ErrorPixmap() { auto rm = getResourceManager(); return rm.error; } inline static auto WarningPixmap() { auto rm = getResourceManager(); return rm.warning; } inline static auto CriticalPixmap() { auto rm = getResourceManager(); return rm.critical; } inline static auto InfoPixmap() { auto rm = getResourceManager(); return rm.info; } inline static auto NotificationAreaIcon() { auto rm = getResourceManager(); return rm.notificationArea; } inline static auto notificationAreaMissedNotificationsIcon() { auto rm = getResourceManager(); return rm.notificationAreaMissedNotifications; } private: QPixmap error; QPixmap warning; QPixmap critical; QPixmap info; QIcon notificationArea; QIcon notificationAreaMissedNotifications; }; /******************** Console Messages Observer (Console Interface) ************************/ /** This class listens to all messages sent via the console interface and feeds the non-intrusive notification system and the notifications widget */ class NotificationAreaObserver: public Base::ILogger { public: NotificationAreaObserver(NotificationArea* notificationarea); ~NotificationAreaObserver() override; /// Function that is called by the console interface for this observer with the message /// information void SendLog(const std::string& notifiername, const std::string& msg, Base::LogStyle level) override; /// Name of the observer const char* Name() override { return "NotificationAreaObserver"; } private: NotificationArea* notificationArea; }; NotificationAreaObserver::NotificationAreaObserver(NotificationArea* notificationarea) : notificationArea(notificationarea) { Base::Console().AttachObserver(this); bLog = false; // ignore log messages bMsg = false; // ignore messages bNotification = true; // activate user notifications bTranslatedNotification = true;// activate translated user notifications } NotificationAreaObserver::~NotificationAreaObserver() { Base::Console().DetachObserver(this); } void NotificationAreaObserver::SendLog(const std::string& notifiername, const std::string& msg, Base::LogStyle level) { // 1. As notification system is shared with report view and others, the expectation is that any // individual error and warning message will end in "\n". This means the string must be stripped // of this character for representation in the notification area. // 2. However, any message marked with the QT_TRANSLATE_NOOT macro with the "Notifications" // context, shall not include // "\n", as this generates problems with the translation system. Then the string must be // stripped of "\n" before translation. auto simplifiedstring = QString::fromStdString(msg) .trimmed();// remove any leading and trailing whitespace character ('\n') // avoid processing empty strings if (simplifiedstring.isEmpty()) return; if (level == Base::LogStyle::TranslatedNotification) { notificationArea->pushNotification( QString::fromStdString(notifiername), simplifiedstring, level); } else { notificationArea->pushNotification( QString::fromStdString(notifiername), QCoreApplication::translate("Notifications", simplifiedstring.toUtf8()), level); } } /******************* Notification Widget *******************************************************/ /** Specialised Item class for the Widget notifications/errors/warnings It holds all item specific data, including visualisation data and controls how the item should appear in the widget. */ class NotificationItem: public QTreeWidgetItem { public: NotificationItem(Base::LogStyle notificationtype, QString notifiername, QString message) : notificationType(notificationtype), notifierName(std::move(notifiername)), msg(std::move(message)) {} QVariant data(int column, int role) const override { // strings that will be displayed for each column of the widget if (role == Qt::DisplayRole) { switch (column) { case 1: return notifierName; break; case 2: return msg; break; } } else if (column == 0 && role == Qt::DecorationRole) { // Icons to be displayed for the first row if (notificationType == Base::LogStyle::Error) { return std::move(ResourceManager::ErrorPixmap()); } else if (notificationType == Base::LogStyle::Warning) { return std::move(ResourceManager::WarningPixmap()); } else if (notificationType == Base::LogStyle::Critical) { return std::move(ResourceManager::CriticalPixmap()); } else { return std::move(ResourceManager::InfoPixmap()); } } else if (role == Qt::FontRole) { // Visualisation control of unread messages static QFont font; static QFont boldFont(font.family(), font.pointSize(), QFont::Bold); if (unread) { return boldFont; } return font; } return QVariant(); } Base::LogStyle notificationType; QString notifierName; QString msg; bool unread = true; // item is unread in the Notification Area Widget bool notifying = true;// item is to be notified or being notified as non-intrusive message bool shown = false; // item is already being notified (it is onScreen) }; /** Drop menu Action containing the notifications widget. * It stores all the notification item information in the form * of NotificationItems (QTreeWidgetItem). This information is used * by the Widget and by the non-intrusive messages. * It owns the notification resources and is responsible for the release * of the memory resources, either directly for the intermediate fast cache * or indirectly via QT for the case of the QTreeWidgetItems. */ class NotificationsAction: public QWidgetAction { public: NotificationsAction(QWidget* parent) : QWidgetAction(parent) {} ~NotificationsAction() { for (auto* item : pushedItems) { if (item) { delete item; } } } public: /// deletes only notifications (messages of type Notification and TranslatedNotification) void deleteNotifications() { if (tableWidget) { for (int i = tableWidget->topLevelItemCount() - 1; i >= 0; i--) { auto* item = static_cast(tableWidget->topLevelItem(i)); if (item->notificationType == Base::LogStyle::Notification || item->notificationType == Base::LogStyle::TranslatedNotification) { delete item; } } } for (int i = pushedItems.size() - 1; i >= 0; i--) { auto* item = static_cast(pushedItems.at(i)); if (item->notificationType == Base::LogStyle::Notification || item->notificationType == Base::LogStyle::TranslatedNotification) { delete pushedItems.takeAt(i); } } } /// deletes all notifications, errors and warnings void deleteAll() { if (tableWidget) { tableWidget->clear();// the parent will delete the items. } while (!pushedItems.isEmpty()) delete pushedItems.takeFirst(); } /// returns the amount of unread notifications, errors and warnings inline int getUnreadCount() const { return getCurrently([](auto* item) { return item->unread; }); } /// returns the amount of notifications, errors and warnings currently being notified inline int getCurrentlyNotifyingCount() const { return getCurrently([](auto* item) { return item->notifying; }); } /// returns the amount of notifications, errors and warnings currently being shown as /// non-intrusive messages (on-screen) inline int getShownCount() const { return getCurrently([](auto* item) { return item->shown; }); } /// marks all notifications, errors and warnings as read void clearUnreadFlag() { for (auto i = 0; i < tableWidget->topLevelItemCount(); i++) {// all messages were read, so clear the unread flag auto* item = static_cast(tableWidget->topLevelItem(i)); item->unread = false; } } /// pushes all Notification Items to the Widget, so that they can be shown void synchroniseWidget() { tableWidget->insertTopLevelItems(0, pushedItems); pushedItems.clear(); } /** pushes all Notification Items to the fast cache (this also prevents all unnecessary * signaling from parents) and allows to accelerate insertions and deletions */ void shiftToCache() { tableWidget->blockSignals(true); tableWidget->clearSelection(); while (tableWidget->topLevelItemCount() > 0) { auto* item = tableWidget->takeTopLevelItem(0); pushedItems.push_back(item); } tableWidget->blockSignals(false); } /// returns if there are no notifications, errors and warnings at all bool isEmpty() const { return tableWidget->topLevelItemCount() == 0 && pushedItems.isEmpty(); } /// returns the total amount of notifications, errors and warnings currently stored auto count() const { return tableWidget->topLevelItemCount() + pushedItems.count(); } /// retrieves a pointer to a given notification from storage. auto getItem(int index) const { if (index < pushedItems.count()) { return pushedItems.at(index); } else { return tableWidget->topLevelItem(index - pushedItems.count()); } } /// deletes a given notification, errors or warnings by index void deleteItem(int index) { if (index < pushedItems.count()) { delete pushedItems.takeAt(index); } else { delete tableWidget->topLevelItem(index - pushedItems.count()); } } /// deletes a given notification, errors or warnings by pointer void deleteItem(NotificationItem* item) { for (int i = 0; i < count(); i++) { if (getItem(i) == item) { deleteItem(i); return; } } } /// deletes the last Notification Item void deleteLastItem() { deleteItem(count() - 1); } /// pushes a notification item to the front void push_front(NotificationItem* item) { pushedItems.push_front(item); } protected: /// creates the Notifications Widget QWidget* createWidget(QWidget* parent) override { QWidget* notificationsWidget = new QWidget(parent); QHBoxLayout* layout = new QHBoxLayout(notificationsWidget); notificationsWidget->setLayout(layout); tableWidget = new QTreeWidget(parent); tableWidget->setColumnCount(3); QStringList headers; headers << QObject::tr("Type") << QObject::tr("Notifier") << QObject::tr("Message"); tableWidget->setHeaderLabels(headers); layout->addWidget(tableWidget); tableWidget->setMaximumSize(1200, 600); tableWidget->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); tableWidget->header()->setStretchLastSection(false); tableWidget->header()->setSectionResizeMode(QHeaderView::ResizeToContents); tableWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); tableWidget->setContextMenuPolicy(Qt::CustomContextMenu); // context menu on any item (row) of the widget QObject::connect( tableWidget, &QTreeWidget::customContextMenuRequested, [&](const QPoint& pos) { // auto item = tableWidget->itemAt(pos); auto selectedItems = tableWidget->selectedItems(); QMenu menu; QAction* del = menu.addAction(tr("Delete"), this, [&]() { for (auto it : selectedItems) { delete it; } }); del->setEnabled(!selectedItems.isEmpty()); menu.addSeparator(); QAction* delnotifications = menu.addAction(tr("Delete user notifications"), this, &NotificationsAction::deleteNotifications); delnotifications->setEnabled(tableWidget->topLevelItemCount() > 0); QAction* delall = menu.addAction(tr("Delete All"), this, &NotificationsAction::deleteAll); delall->setEnabled(tableWidget->topLevelItemCount() > 0); menu.setDefaultAction(del); menu.exec(tableWidget->mapToGlobal(pos)); }); return notificationsWidget; } private: /// utility function to return the number of Notification Items meeting the functor/lambda /// criteria int getCurrently(std::function F) const { int instate = 0; for (auto i = 0; i < tableWidget->topLevelItemCount(); i++) { auto* item = static_cast(tableWidget->topLevelItem(i)); if (F(item)) { instate++; } } for (auto i = 0; i < pushedItems.count(); i++) { auto* item = static_cast(pushedItems.at(i)); if (F(item)) { instate++; } } return instate; } private: QTreeWidget* tableWidget; // Intermediate storage // Note: QTreeWidget is helplessly slow to single insertions, QTreeWidget is actually only // necessary when showing the widget. A single QList insertion into a QTreeWidget is actually // not that slow. The use of this intermediate storage substantially accelerates non-intrusive // notifications. QList pushedItems; }; /************ Parameter Observer (preferences) **************************************/ NotificationArea::ParameterObserver::ParameterObserver(NotificationArea* notificationarea) : notificationArea(notificationarea) { hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/NotificationArea"); parameterMap = { {"NotificationAreaEnabled", [this](const std::string& string) { auto enabled = hGrp->GetBool(string.c_str(), true); if (!enabled) notificationArea->deleteLater(); }}, {"NonIntrusiveNotificationsEnabled", [this](const std::string& string) { auto enabled = hGrp->GetBool(string.c_str(), true); notificationArea->pImp->notificationsDisabled = !enabled; }}, {"NotificationTime", [this](const std::string& string) { auto time = hGrp->GetInt(string.c_str(), 20) * 1000; if (time < 0) time = 0; notificationArea->pImp->notificationExpirationTime = static_cast(time); }}, {"MinimumOnScreenTime", [this](const std::string& string) { auto time = hGrp->GetInt(string.c_str(), 5) * 1000; if (time < 0) time = 0; notificationArea->pImp->minimumOnScreenTime = static_cast(time); }}, {"MaxOpenNotifications", [this](const std::string& string) { auto limit = hGrp->GetInt(string.c_str(), 15); if (limit < 0) limit = 0; notificationArea->pImp->maxOpenNotifications = static_cast(limit); }}, {"MaxWidgetMessages", [this](const std::string& string) { auto limit = hGrp->GetInt(string.c_str(), 1000); if (limit < 0) limit = 0; notificationArea->pImp->maxWidgetMessages = static_cast(limit); }}, {"AutoRemoveUserNotifications", [this](const std::string& string) { auto enabled = hGrp->GetBool(string.c_str(), true); notificationArea->pImp->autoRemoveUserNotifications = enabled; }}, {"NotificiationWidth", [this](const std::string& string) { auto width = hGrp->GetInt(string.c_str(), 800); if (width < 300) width = 300; notificationArea->pImp->notificationWidth = width; }}, {"HideNonIntrusiveNotificationsWhenWindowDeactivated", [this](const std::string& string) { auto enabled = hGrp->GetBool(string.c_str(), true); notificationArea->pImp->hideNonIntrusiveNotificationsWhenWindowDeactivated = enabled; }}, {"PreventNonIntrusiveNotificationsWhenWindowNotActive", [this](const std::string& string) { auto enabled = hGrp->GetBool(string.c_str(), true); notificationArea->pImp->preventNonIntrusiveNotificationsWhenWindowNotActive = enabled; }}, {"ErrorSubscriptionEnabled", [this](const std::string& string) { auto enabled = hGrp->GetBool(string.c_str(), true); notificationArea->pImp->observer->bErr = enabled; }}, {"WarningSubscriptionEnabled", [this](const std::string& string) { auto enabled = hGrp->GetBool(string.c_str(), true); notificationArea->pImp->observer->bWrn = enabled; }}, }; for (auto& val : parameterMap) { auto string = val.first; auto update = val.second; update(string); } hGrp->Attach(this); } NotificationArea::ParameterObserver::~ParameterObserver() { hGrp->Detach(this); } void NotificationArea::ParameterObserver::OnChange(Base::Subject& rCaller, const char* sReason) { (void)rCaller; auto key = parameterMap.find(sReason); if (key != parameterMap.end()) { auto string = key->first; auto update = key->second; update(string); } } /************************* Notification Area *****************************************/ NotificationArea::NotificationArea(QWidget* parent) : QPushButton(parent) { // QPushButton appearance setText(QString()); setFlat(true); // Initialisation of pImpl structure pImp = std::make_unique(); pImp->observer = std::make_unique(this); pImp->parameterObserver = std::make_unique(this); pImp->menu = new QMenu(parent); setMenu(pImp->menu); auto na = new NotificationsAction(pImp->menu); pImp->menu->addAction(na); pImp->notificationaction = na; // Signals for synchronisation of storage before showing/hiding the widget QObject::connect(pImp->menu, &QMenu::aboutToHide, [&]() { std::lock_guard g(pImp->mutexNotification); static_cast(pImp->notificationaction)->clearUnreadFlag(); static_cast(pImp->notificationaction)->shiftToCache(); }); QObject::connect(pImp->menu, &QMenu::aboutToShow, [this]() { std::lock_guard g( pImp->mutexNotification);// guard to avoid modifying the notification list and indices // while creating the tooltip setText(QString::number(0)); // no unread notifications if (pImp->missedNotifications) { setIcon(TrayIcon::Normal); pImp->missedNotifications = false; } static_cast(pImp->notificationaction)->synchroniseWidget(); // the position of the action has already been calculated (for a non-synchronised widget), // the size could be recalculated here, but not the position according to the previous size. // This resize event forces this recalculation. QResizeEvent re(pImp->menu->size(), pImp->menu->size()); qApp->sendEvent(pImp->menu, &re); }); // Connection to the finish restore signal to rearm Critical messages modal mode when action is // user initiated pImp->finishRestoreDocumentConnection = App::GetApplication().signalFinishRestoreDocument.connect( boost::bind(&Gui::NotificationArea::slotRestoreFinished, this, bp::_1)); // Initialisation of the timer to inhibit continuous updates of the notification system in case // clusters of messages arrive (so as to delay the actual notification until the whole cluster // has arrived) pImp->inhibitTimer.setSingleShot(true); pImp->inhibitTimer.callOnTimeout([this, na]() { setText(QString::number(na->getUnreadCount())); showInNotificationArea(); }); setIcon(TrayIcon::Normal); } NotificationArea::~NotificationArea() { pImp->finishRestoreDocumentConnection.disconnect(); } void NotificationArea::mousePressEvent(QMouseEvent* e) { // Contextual menu (e.g. to delete Notifications or all messages (Notifications, Errors, // Warnings and Critical messages) if (e->button() == Qt::RightButton && hitButton(e->pos())) { QMenu menu; NotificationsAction* na = static_cast(pImp->notificationaction); QAction* delnotifications = menu.addAction(tr("Delete user notifications"), [&]() { std::lock_guard g( pImp->mutexNotification);// guard to avoid modifying the notification list and // indices while creating the tooltip na->deleteNotifications(); setText(QString::number(na->getUnreadCount())); }); delnotifications->setEnabled(!na->isEmpty()); QAction* delall = menu.addAction(tr("Delete All"), [&]() { std::lock_guard g( pImp->mutexNotification);// guard to avoid modifying the notification list and // indices while creating the tooltip na->deleteAll(); setText(QString::number(0)); }); delall->setEnabled(!na->isEmpty()); menu.setDefaultAction(delall); menu.exec(this->mapToGlobal(e->pos())); } QPushButton::mousePressEvent(e); } void NotificationArea::pushNotification(const QString& notifiername, const QString& message, Base::LogStyle level) { auto* item = new NotificationItem(level, notifiername, message); bool confirmation = confirmationRequired(level); if (confirmation) { showConfirmationDialog(notifiername, message); } std::lock_guard g( pImp->mutexNotification);// guard to avoid modifying the notification list and indices while // creating the tooltip NotificationsAction* na = static_cast(pImp->notificationaction); // Limit the maximum number of messages stored in the widget (0 means no limit) if (pImp->maxWidgetMessages != 0 && na->count() > pImp->maxWidgetMessages) { na->deleteLastItem(); } na->push_front(item); // If the non-intrusive notifications are disabled then stop here (messages added to the widget // only) if (pImp->notificationsDisabled) { item->notifying = false;// avoid mass of old notifications if feature is activated afterwards setText(QString::number( static_cast(pImp->notificationaction)->getUnreadCount())); return; } // start or restart rate control (the timer is rearmed if not yet expired, expiration triggers // showing of the notification messages) // // NOTE: The inhibition timer is created in the main thread and cannot be restarted from another // QThread. A QTimer can be moved to another QThread (from the QThread in which it is). However, // it can only be create in a QThread, not in any other thread. // // For this reason, the timer is only triggered if this QThread is the QTimer thread. // // But I want my message from my thread to appear in the notification area. Fine, then configure // Console not to use the direct connection mode, but the Queued one: // Base::Console().SetConnectionMode(ConnectionMode::Queued); auto timer_thread = pImp->inhibitTimer.thread(); auto current_thread = QThread::currentThread(); if (timer_thread == current_thread) pImp->inhibitTimer.start(pImp->inhibitNotificationTime); } bool NotificationArea::confirmationRequired(Base::LogStyle level) { auto userInitiatedRestore = Application::Instance->testStatus(Gui::Application::UserInitiatedOpenDocument); return (level == Base::LogStyle::Critical && userInitiatedRestore && pImp->requireConfirmationCriticalMessageDuringRestoring); } void NotificationArea::showConfirmationDialog(const QString& notifiername, const QString& message) { auto confirmMsg = QObject::tr("Notifier: ") + notifiername + QStringLiteral("\n\n") + message + QStringLiteral("\n\n") + QObject::tr("Do you want to skip confirmation of further critical message notifications " "while loading the file?"); auto button = QMessageBox::critical(getMainWindow()->activeWindow(), QObject::tr("Critical Message"), confirmMsg, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (button == QMessageBox::Yes) pImp->requireConfirmationCriticalMessageDuringRestoring = false; } void NotificationArea::showInNotificationArea() { std::lock_guard g( pImp->mutexNotification);// guard to avoid modifying the notification list and indices while // creating the tooltip NotificationsAction* na = static_cast(pImp->notificationaction); if (!NotificationBox::isVisible()) { // The Notification Box may have been closed (by the user by popping it out or by left mouse // button) ensure that old notifications are not shown again, even if the timer has not // lapsed int i = 0; while (i < na->count() && static_cast(na->getItem(i))->notifying) { NotificationItem* item = static_cast(na->getItem(i)); if (item->shown) { item->notifying = false; item->shown = false; } i++; } } auto currentlyshown = na->getShownCount(); // If we cannot show more messages, we do no need to update the non-intrusive notification if (currentlyshown < pImp->maxOpenNotifications) { // There is space for at least one more notification // We update the message with the most recent up to maxOpenNotifications QString msgw = QString::fromLatin1( " \

\ \ \ \ \ \ ") .arg(QObject::tr("Type")) .arg(QObject::tr("Notifier")) .arg(QObject::tr("Message")); auto currentlynotifying = na->getCurrentlyNotifyingCount(); if (currentlynotifying > pImp->maxOpenNotifications) { msgw += QString::fromLatin1( " \ \ \ \ \ ") .arg(QObject::tr("Too many opened non-intrusive notifications. Notifications " "are being omitted!")); } int i = 0; while (i < na->count() && static_cast(na->getItem(i))->notifying) { if (i < pImp->maxOpenNotifications) {// show the first up to maxOpenNotifications NotificationItem* item = static_cast(na->getItem(i)); QString iconstr; if (item->notificationType == Base::LogStyle::Error) { iconstr = QStringLiteral(":/icons/edit_Cancel.svg"); } else if (item->notificationType == Base::LogStyle::Warning) { iconstr = QStringLiteral(":/icons/Warning.svg"); } else if (item->notificationType == Base::LogStyle::Critical) { iconstr = QStringLiteral(":/icons/critical-info.svg"); } else { iconstr = QStringLiteral(":/icons/info.svg"); } QString tmpmessage = convertFromPlainText(item->msg, Qt::WhiteSpaceMode::WhiteSpaceNormal); msgw += QString::fromLatin1( " \ \ \ \ \ ") .arg(iconstr) .arg(item->notifierName) .arg(tmpmessage); // start a timer for each of these notifications that was not previously shown if (!item->shown) { QTimer::singleShot(pImp->notificationExpirationTime, [this, item]() { std::lock_guard g( pImp->mutexNotification);// guard to avoid modifying the notification // start index while creating the tooltip if (item) { item->shown = false; item->notifying = false; if (pImp->autoRemoveUserNotifications) { if (item->notificationType == Base::LogStyle::Notification || item->notificationType == Base::LogStyle::TranslatedNotification) { static_cast(pImp->notificationaction) ->deleteItem(item); } } } }); } // We update the status to shown item->shown = true; } else {// We do not have more space and older notifications will be too old static_cast(na->getItem(i))->notifying = false; static_cast(na->getItem(i))->shown = false; } i++; } msgw += QString::fromLatin1("
%1%2%3
FreeCAD%1
%2%3

"); NotificationBox::Options options = NotificationBox::Options::RestrictAreaToReference; if (pImp->preventNonIntrusiveNotificationsWhenWindowNotActive) { options = options | NotificationBox::Options::OnlyIfReferenceActive; } if (pImp->hideNonIntrusiveNotificationsWhenWindowDeactivated) { options = options | NotificationBox::Options::HideIfReferenceWidgetDeactivated; } bool isshown = NotificationBox::showText(this->mapToGlobal(QPoint()), msgw, getMainWindow(), pImp->notificationExpirationTime, pImp->minimumOnScreenTime, options, pImp->notificationWidth); if (!isshown && !pImp->missedNotifications) { pImp->missedNotifications = true; setIcon(TrayIcon::MissedNotifications); } } } void NotificationArea::slotRestoreFinished(const App::Document&) { // Re-arm on restore critical message modal notifications if another document is loaded pImp->requireConfirmationCriticalMessageDuringRestoring = true; } void NotificationArea::setIcon(TrayIcon trayIcon) { if (trayIcon == TrayIcon::Normal) { QPushButton::setIcon(ResourceManager::NotificationAreaIcon()); } else if (trayIcon == TrayIcon::MissedNotifications) { QPushButton::setIcon(ResourceManager::notificationAreaMissedNotificationsIcon()); } }