1
/***************************************************************************
2
* Copyright (c) 2022 Abdullah Tahiri <abdullah.tahiri.yo@gmail.com> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 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 *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
23
#include "PreCompiled.h"
27
#include <QActionEvent>
28
#include <QApplication>
35
#include <QTextDocument>
39
#include <QWidgetAction>
44
#include <App/Application.h>
45
#include <Base/Console.h>
47
#include "Application.h"
48
#include "BitmapFactory.h"
50
#include "MainWindow.h"
51
#include "NotificationBox.h"
53
#include "NotificationArea.h"
57
using Connection = boost::signals2::connection;
59
namespace sp = std::placeholders;
63
class NotificationAreaObserver;
67
/** PImpl idiom structure having all data necessary for the notification area */
68
struct NotificationAreaP
70
// Structure holding all variables necessary for the Notification Area.
71
// Preference parameters are updated by NotificationArea::ParameterObserver
74
/** @name Non-intrusive notifications parameters */
76
/// Parameter controlled
77
int maxOpenNotifications = 15;
78
/// Parameter controlled
79
unsigned int notificationExpirationTime = 10000;
80
/// minimum time that the notification will remain unclosed
81
unsigned int minimumOnScreenTime = 5000;
82
/// Parameter controlled
83
bool notificationsDisabled = false;
84
/// Control of confirmation mechanism (for Critical Messages)
85
bool requireConfirmationCriticalMessageDuringRestoring = true;
86
/// Width of the non-intrusive notification
87
int notificationWidth = 800;
88
/// Any open non-intrusive notifications will disappear when another window is activated
89
bool hideNonIntrusiveNotificationsWhenWindowDeactivated = true;
90
/// Prevent non-intrusive notifications from appearing when the FreeCAD Window is not the active
92
bool preventNonIntrusiveNotificationsWhenWindowNotActive = true;
95
/** @name Widget parameters */
97
/// Parameter controlled - maximum number of message allowed in the notification area widget (0
99
int maxWidgetMessages = 1000;
100
/// User notifications get automatically removed from the Widget after the non-intrusive
101
/// notification expiration time
102
bool autoRemoveUserNotifications = false;
105
/** @name Notification rate control */
107
/* Control rate of updates of non-intrusive messages (avoids visual artifacts when messages are
108
* constantly being received) */
109
// Timer to delay notification until a minimum time between two consecutive messages have lapsed
111
// The time between two consecutive messages forced by the inhibitTimer
112
const unsigned int inhibitNotificationTime = 250;
115
/** @name Data source control */
117
/* Controls whether debug warnings and errors intended for developers should be processed or not
119
bool developerErrorSubscriptionEnabled = false;
120
bool developerWarningSubscriptionEnabled = false;
123
bool missedNotifications = false;
126
std::mutex mutexNotification;
128
// Pointers to widgets (no ownership)
129
QMenu* menu = nullptr;
130
QWidgetAction* notificationaction = nullptr;
132
/** @name Resources */
134
/// Base::Console Message observer
135
std::unique_ptr<NotificationAreaObserver> observer;
136
Connection finishRestoreDocumentConnection;
137
/// Preference Parameter observer
138
std::unique_ptr<NotificationArea::ParameterObserver> parameterObserver;
147
/******************* Resource Management *****************************************/
149
/** Simple class to manage Notification Area Resources*/
157
error = BitmapFactory().pixmapFromSvg(":/icons/edit_Cancel.svg", QSize(16, 16));
158
warning = BitmapFactory().pixmapFromSvg(":/icons/Warning.svg", QSize(16, 16));
159
critical = BitmapFactory().pixmapFromSvg(":/icons/critical-info.svg", QSize(16, 16));
160
info = BitmapFactory().pixmapFromSvg(":/icons/info.svg", QSize(16, 16));
162
notificationArea = QIcon(QStringLiteral(":/icons/InTray.svg"));
163
notificationAreaMissedNotifications =
164
QIcon(QStringLiteral(":/icons/InTray_missed_notifications.svg"));
167
inline static const auto& getResourceManager()
169
static ResourceManager manager;
175
inline static auto ErrorPixmap()
177
auto rm = getResourceManager();
181
inline static auto WarningPixmap()
183
auto rm = getResourceManager();
187
inline static auto CriticalPixmap()
189
auto rm = getResourceManager();
193
inline static auto InfoPixmap()
195
auto rm = getResourceManager();
199
inline static auto NotificationAreaIcon()
201
auto rm = getResourceManager();
202
return rm.notificationArea;
205
inline static auto notificationAreaMissedNotificationsIcon()
207
auto rm = getResourceManager();
208
return rm.notificationAreaMissedNotifications;
216
QIcon notificationArea;
217
QIcon notificationAreaMissedNotifications;
220
/******************** Console Messages Observer (Console Interface) ************************/
222
/** This class listens to all messages sent via the console interface and
223
feeds the non-intrusive notification system and the notifications widget */
224
class NotificationAreaObserver: public Base::ILogger
227
NotificationAreaObserver(NotificationArea* notificationarea);
228
~NotificationAreaObserver() override;
230
NotificationAreaObserver(const NotificationAreaObserver &) = delete;
231
NotificationAreaObserver(NotificationAreaObserver &&) = delete;
232
NotificationAreaObserver &operator=(const NotificationAreaObserver &) = delete;
233
NotificationAreaObserver &operator=(NotificationAreaObserver &&) = delete;
235
/// Function that is called by the console interface for this observer with the message
237
void SendLog(const std::string& notifiername, const std::string& msg, Base::LogStyle level,
238
Base::IntendedRecipient recipient, Base::ContentType content) override;
240
/// Name of the observer
241
const char* Name() override
243
return "NotificationAreaObserver";
247
NotificationArea* notificationArea;
250
NotificationAreaObserver::NotificationAreaObserver(NotificationArea* notificationarea)
251
: notificationArea(notificationarea)
253
Base::Console().AttachObserver(this);
254
bLog = false; // ignore log messages
255
bMsg = false; // ignore messages
256
bNotification = true;// activate user notifications
259
NotificationAreaObserver::~NotificationAreaObserver()
261
Base::Console().DetachObserver(this);
264
void NotificationAreaObserver::SendLog(const std::string& notifiername, const std::string& msg,
265
Base::LogStyle level, Base::IntendedRecipient recipient,
266
Base::ContentType content)
268
// 1. As notification system is shared with report view and others, the expectation is that any
269
// individual error and warning message will end in "\n". This means the string must be stripped
270
// of this character for representation in the notification area.
271
// 2. However, any message marked with the QT_TRANSLATE_NOOT macro with the "Notifications"
272
// context, shall not include
273
// "\n", as this generates problems with the translation system. Then the string must be
274
// stripped of "\n" before translation.
276
bool violatesBasicPolicy = (recipient == Base::IntendedRecipient::Developer
277
|| content == Base::ContentType::Untranslatable);
279
// We allow derogations for debug purposes according to user preferences
280
bool meetsDerogationCriteria = false;
282
if (violatesBasicPolicy) {
283
meetsDerogationCriteria =
284
(level == Base::LogStyle::Warning && notificationArea->areDeveloperWarningsActive())
285
|| (level == Base::LogStyle::Error && notificationArea->areDeveloperErrorsActive());
288
if (violatesBasicPolicy && !meetsDerogationCriteria) {
292
auto simplifiedstring =
293
QString::fromStdString(msg)
294
.trimmed();// remove any leading and trailing whitespace character ('\n')
296
// avoid processing empty strings
297
if (simplifiedstring.isEmpty())
300
if (content == Base::ContentType::Translated) {
301
notificationArea->pushNotification(
302
QString::fromStdString(notifiername), simplifiedstring, level);
305
notificationArea->pushNotification(
306
QString::fromStdString(notifiername),
307
QCoreApplication::translate("Notifications", simplifiedstring.toUtf8()),
313
/******************* Notification Widget *******************************************************/
315
/** Specialised Item class for the Widget notifications/errors/warnings
316
It holds all item specific data, including visualisation data and controls how
317
the item should appear in the widget.
319
class NotificationItem: public QTreeWidgetItem
322
NotificationItem(Base::LogStyle notificationtype, QString notifiername, QString message)
323
: notificationType(notificationtype),
324
notifierName(std::move(notifiername)),
325
msg(std::move(message))
328
QVariant data(int column, int role) const override
330
// strings that will be displayed for each column of the widget
331
if (role == Qt::DisplayRole) {
341
else if (column == 0 && role == Qt::DecorationRole) {
342
// Icons to be displayed for the first row
343
if (notificationType == Base::LogStyle::Error) {
344
return std::move(ResourceManager::ErrorPixmap());
346
else if (notificationType == Base::LogStyle::Warning) {
347
return std::move(ResourceManager::WarningPixmap());
349
else if (notificationType == Base::LogStyle::Critical) {
350
return std::move(ResourceManager::CriticalPixmap());
353
return std::move(ResourceManager::InfoPixmap());
356
else if (role == Qt::FontRole) {
357
// Visualisation control of unread messages
359
static QFont boldFont(font.family(), font.pointSize(), QFont::Bold);
371
void addRepetition() {
378
bool isRepeated(Base::LogStyle notificationtype, const QString & notifiername, const QString & message ) const {
379
return (notificationType == notificationtype && notifierName == notifiername && msg == message);
382
bool isType(Base::LogStyle notificationtype) const {
383
return notificationType == notificationtype;
386
bool isUnread() const {
390
bool isNotifying() const {
394
bool isShown() const {
398
int getRepetitions() const{
406
void resetNotified() {
426
QString getMessage() const {
427
if(repetitions == 0) {
431
return msg + QObject::tr(" (%1 times)").arg(repetitions+1);
435
const QString & getNotifier() {
440
Base::LogStyle notificationType;
441
QString notifierName;
444
bool unread = true; // item is unread in the Notification Area Widget
445
bool notifying = true;// item is to be notified or being notified as non-intrusive message
446
bool shown = false; // item is already being notified (it is onScreen)
447
int repetitions = 0; // message appears n times in a row.
450
/** Drop menu Action containing the notifications widget.
451
* It stores all the notification item information in the form
452
* of NotificationItems (QTreeWidgetItem). This information is used
453
* by the Widget and by the non-intrusive messages.
454
* It owns the notification resources and is responsible for the release
455
* of the memory resources, either directly for the intermediate fast cache
456
* or indirectly via QT for the case of the QTreeWidgetItems.
458
class NotificationsAction: public QWidgetAction
461
NotificationsAction(QWidget* parent)
462
: QWidgetAction(parent)
465
NotificationsAction(const NotificationsAction &) = delete;
466
NotificationsAction(NotificationsAction &&) = delete;
467
NotificationsAction & operator=(const NotificationsAction &) = delete;
468
NotificationsAction & operator=(NotificationsAction &&) = delete;
470
~NotificationsAction() override
472
for (auto* item : std::as_const(pushedItems)) {
474
delete item; // NOLINT
480
/// deletes only notifications (messages of type Notification)
481
void deleteNotifications()
484
for (int i = tableWidget->topLevelItemCount() - 1; i >= 0; i--) {
486
auto* item = static_cast<NotificationItem*>(tableWidget->topLevelItem(i));
487
if (item->isType(Base::LogStyle::Notification)) {
488
delete item; //NOLINT
492
for (int i = pushedItems.size() - 1; i >= 0; i--) {
494
auto* item = static_cast<NotificationItem*>(pushedItems.at(i));
495
if (item->isType(Base::LogStyle::Notification)) {
496
delete pushedItems.takeAt(i); //NOLINT
501
/// deletes all notifications, errors and warnings
505
tableWidget->clear();// the parent will delete the items.
507
while (!pushedItems.isEmpty())
508
delete pushedItems.takeFirst();
511
/// returns the amount of unread notifications, errors and warnings
512
inline int getUnreadCount() const
514
return getCurrently([](auto* item) {
515
return item->isUnread();
519
/// returns the amount of notifications, errors and warnings currently being notified
520
inline int getCurrentlyNotifyingCount() const
522
return getCurrently([](auto* item) {
523
return item->isNotifying();
527
/// returns the amount of notifications, errors and warnings currently being shown as
528
/// non-intrusive messages (on-screen)
529
inline int getShownCount() const
531
return getCurrently([](auto* item) {
532
return item->isShown();
536
/// marks all notifications, errors and warnings as read
537
void clearUnreadFlag()
539
for (auto i = 0; i < tableWidget->topLevelItemCount();
540
i++) {// all messages were read, so clear the unread flag
542
auto* item = static_cast<NotificationItem*>(tableWidget->topLevelItem(i));
547
/// pushes all Notification Items to the Widget, so that they can be shown
548
void synchroniseWidget()
550
tableWidget->insertTopLevelItems(0, pushedItems);
554
/** pushes all Notification Items to the fast cache (this also prevents all unnecessary
555
* signaling from parents) and allows to accelerate insertions and deletions
559
tableWidget->blockSignals(true);
560
tableWidget->clearSelection();
561
while (tableWidget->topLevelItemCount() > 0) {
562
auto* item = tableWidget->takeTopLevelItem(0);
563
pushedItems.push_back(item);
565
tableWidget->blockSignals(false);
568
/// returns if there are no notifications, errors and warnings at all
571
return tableWidget->topLevelItemCount() == 0 && pushedItems.isEmpty();
574
/// returns the total amount of notifications, errors and warnings currently stored
577
return tableWidget->topLevelItemCount() + pushedItems.count();
580
/// retrieves a pointer to a given notification from storage.
581
auto getItem(int index) const
583
if (index < pushedItems.count()) {
584
return pushedItems.at(index);
587
return tableWidget->topLevelItem(index - pushedItems.count());
591
/// deletes a given notification, errors or warnings by index
592
void deleteItem(int index)
594
if (index < pushedItems.count()) {
595
delete pushedItems.takeAt(index); //NOLINT
598
delete tableWidget->topLevelItem(index - pushedItems.count()); //NOLINT
602
/// deletes a given notification, errors or warnings by pointer
603
void deleteItem(NotificationItem* item)
605
for (int i = 0; i < count(); i++) {
606
if (getItem(i) == item) {
613
/// deletes the last Notification Item
614
void deleteLastItem()
616
deleteItem(count() - 1);
619
/// checks if last notification is the same
620
bool isSameNotification(const QString& notifiername, const QString& message,
621
Base::LogStyle level) const {
622
if(count() > 0) { // if not empty
624
auto item = static_cast<NotificationItem*>(getItem(0));
625
return item->isRepeated(level,notifiername,message);
631
void resetLastNotificationStatus() {
633
auto item = static_cast<NotificationItem*>(getItem(0));
634
item->addRepetition();
637
/// pushes a notification item to the front
638
auto push_front(std::unique_ptr<NotificationItem> item)
640
auto it = item.release();
641
pushedItems.push_front(it);
647
return tableWidget->size();
651
/// creates the Notifications Widget
652
QWidget* createWidget(QWidget* parent) override
655
QWidget* notificationsWidget = new QWidget(parent);
657
QHBoxLayout* layout = new QHBoxLayout(notificationsWidget);
658
notificationsWidget->setLayout(layout);
660
tableWidget = new QTreeWidget(parent);
661
tableWidget->setColumnCount(3);
664
headers << QObject::tr("Type") << QObject::tr("Notifier") << QObject::tr("Message");
665
tableWidget->setHeaderLabels(headers);
667
layout->addWidget(tableWidget);
669
tableWidget->setMaximumSize(1200, 600);
670
tableWidget->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
671
tableWidget->header()->setStretchLastSection(false);
672
tableWidget->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
674
tableWidget->setSelectionMode(QAbstractItemView::ExtendedSelection);
675
tableWidget->setContextMenuPolicy(Qt::CustomContextMenu);
678
// context menu on any item (row) of the widget
680
tableWidget, &QTreeWidget::customContextMenuRequested, [&](const QPoint& pos) {
681
auto selectedItems = tableWidget->selectedItems();
685
QAction* del = menu.addAction(tr("Delete"), this, [&]() {
686
for (auto it : std::as_const(selectedItems)) {
691
del->setEnabled(!selectedItems.isEmpty());
695
QAction* delnotifications =
696
menu.addAction(tr("Delete user notifications"),
698
&NotificationsAction::deleteNotifications);
700
delnotifications->setEnabled(tableWidget->topLevelItemCount() > 0);
703
menu.addAction(tr("Delete All"), this, &NotificationsAction::deleteAll);
705
delall->setEnabled(tableWidget->topLevelItemCount() > 0);
707
menu.setDefaultAction(del);
709
menu.exec(tableWidget->mapToGlobal(pos));
713
return notificationsWidget;
717
/// utility function to return the number of Notification Items meeting the functor/lambda
719
int getCurrently(std::function<bool(const NotificationItem*)> F) const
722
for (auto i = 0; i < tableWidget->topLevelItemCount(); i++) {
724
auto* item = static_cast<NotificationItem*>(tableWidget->topLevelItem(i));
729
for (auto i = 0; i < pushedItems.count(); i++) {
731
auto* item = static_cast<NotificationItem*>(pushedItems.at(i));
740
QTreeWidget* tableWidget = nullptr;
741
// Intermediate storage
742
// Note: QTreeWidget is helplessly slow to single insertions, QTreeWidget is actually only
743
// necessary when showing the widget. A single QList insertion into a QTreeWidget is actually
744
// not that slow. The use of this intermediate storage substantially accelerates non-intrusive
746
QList<QTreeWidgetItem*> pushedItems;
749
/************ Parameter Observer (preferences) **************************************/
751
NotificationArea::ParameterObserver::ParameterObserver(NotificationArea* notificationarea)
752
: notificationArea(notificationarea)
754
hGrp = App::GetApplication().GetParameterGroupByPath(
755
"User parameter:BaseApp/Preferences/NotificationArea");
759
{"NotificationAreaEnabled",
760
[this](const std::string& string) {
761
auto enabled = hGrp->GetBool(string.c_str(), true);
763
notificationArea->deleteLater();
765
{"NonIntrusiveNotificationsEnabled",
766
[this](const std::string& string) {
767
auto enabled = hGrp->GetBool(string.c_str(), true);
768
notificationArea->pImp->notificationsDisabled = !enabled;
771
[this](const std::string& string) {
772
auto time = hGrp->GetInt(string.c_str(), 20) * 1000;
775
notificationArea->pImp->notificationExpirationTime = static_cast<unsigned int>(time);
777
{"MinimumOnScreenTime",
778
[this](const std::string& string) {
779
auto time = hGrp->GetInt(string.c_str(), 5) * 1000;
782
notificationArea->pImp->minimumOnScreenTime = static_cast<unsigned int>(time);
784
{"MaxOpenNotifications",
785
[this](const std::string& string) {
786
auto limit = hGrp->GetInt(string.c_str(), 15);
789
notificationArea->pImp->maxOpenNotifications = static_cast<unsigned int>(limit);
791
{"MaxWidgetMessages",
792
[this](const std::string& string) {
793
auto limit = hGrp->GetInt(string.c_str(), 1000);
796
notificationArea->pImp->maxWidgetMessages = static_cast<unsigned int>(limit);
798
{"AutoRemoveUserNotifications",
799
[this](const std::string& string) {
800
auto enabled = hGrp->GetBool(string.c_str(), true);
801
notificationArea->pImp->autoRemoveUserNotifications = enabled;
803
{"NotificiationWidth",
804
[this](const std::string& string) {
805
auto width = hGrp->GetInt(string.c_str(), 800);
808
notificationArea->pImp->notificationWidth = width;
810
{"HideNonIntrusiveNotificationsWhenWindowDeactivated",
811
[this](const std::string& string) {
812
auto enabled = hGrp->GetBool(string.c_str(), true);
813
notificationArea->pImp->hideNonIntrusiveNotificationsWhenWindowDeactivated = enabled;
815
{"PreventNonIntrusiveNotificationsWhenWindowNotActive",
816
[this](const std::string& string) {
817
auto enabled = hGrp->GetBool(string.c_str(), true);
818
notificationArea->pImp->preventNonIntrusiveNotificationsWhenWindowNotActive = enabled;
820
{"DeveloperErrorSubscriptionEnabled",
821
[this](const std::string& string) {
822
auto enabled = hGrp->GetBool(string.c_str(), false);
823
notificationArea->pImp->developerErrorSubscriptionEnabled = enabled;
825
{"DeveloperWarningSubscriptionEnabled",
826
[this](const std::string& string) {
827
auto enabled = hGrp->GetBool(string.c_str(), false);
828
notificationArea->pImp->developerWarningSubscriptionEnabled = enabled;
833
for (auto& val : parameterMap) {
834
auto string = val.first;
835
auto update = val.second;
843
NotificationArea::ParameterObserver::~ParameterObserver()
848
void NotificationArea::ParameterObserver::OnChange(Base::Subject<const char*>& rCaller,
853
auto key = parameterMap.find(sReason);
855
if (key != parameterMap.end()) {
856
auto string = key->first;
857
auto update = key->second;
863
/************************* Notification Area *****************************************/
865
NotificationArea::NotificationArea(QWidget* parent)
866
: QPushButton(parent)
868
// QPushButton appearance
872
// Initialisation of pImpl structure
873
pImp = std::make_unique<NotificationAreaP>();
875
pImp->observer = std::make_unique<NotificationAreaObserver>(this);
876
pImp->parameterObserver = std::make_unique<NotificationArea::ParameterObserver>(this);
878
pImp->menu = new QMenu(parent); //NOLINT
881
auto na = new NotificationsAction(pImp->menu); //NOLINT
883
pImp->menu->addAction(na);
885
pImp->notificationaction = na;
888
// Signals for synchronisation of storage before showing/hiding the widget
889
QObject::connect(pImp->menu, &QMenu::aboutToHide, [&]() {
890
lock_guard<std::mutex> g(pImp->mutexNotification);
891
static_cast<NotificationsAction*>(pImp->notificationaction)->clearUnreadFlag();
892
static_cast<NotificationsAction*>(pImp->notificationaction)->shiftToCache();
895
QObject::connect(pImp->menu, &QMenu::aboutToShow, [this]() {
896
// guard to avoid modifying the notification list and indices while creating the tooltip
897
lock_guard<std::mutex> g(pImp->mutexNotification);
898
setText(QString::number(0)); // no unread notifications
899
if (pImp->missedNotifications) {
900
setIcon(TrayIcon::Normal);
901
pImp->missedNotifications = false;
903
static_cast<NotificationsAction*>(pImp->notificationaction)->synchroniseWidget();
906
// There is a Qt bug in not respecting a QMenu size when size changes in aboutToShow.
908
// https://bugreports.qt.io/browse/QTBUG-54421
909
// https://forum.qt.io/topic/68765/how-to-update-geometry-on-qaction-visibility-change-in-qmenu-abouttoshow/3
911
// None of this works
912
// pImp->menu->updateGeometry();
913
// pImp->menu->adjustSize();
914
// pImp->menu->ensurePolished();
915
// this->updateGeometry();
916
// this->adjustSize();
917
// this->ensurePolished();
919
// This does correct the size
920
QSize size = static_cast<NotificationsAction*>(pImp->notificationaction)->size();
921
QResizeEvent re(size, size);
922
qApp->sendEvent(pImp->menu, &re);
924
// This corrects the position of the menu
925
QTimer::singleShot(0, [&] {
926
QWidget* statusbar = static_cast<QWidget*>(this->parent());
927
QPoint statusbar_top_right = statusbar->mapToGlobal(statusbar->rect().topRight());
928
QSize menusize = pImp->menu->size();
930
QPoint button_pos = w->mapToGlobal(w->rect().topLeft());
932
if ((statusbar_top_right.x() - menusize.width()) > button_pos.x()) {
933
widget_pos = QPoint(button_pos.x(), statusbar_top_right.y() - menusize.height());
936
widget_pos = QPoint(statusbar_top_right.x() - menusize.width(),
937
statusbar_top_right.y() - menusize.height());
939
pImp->menu->move(widget_pos);
943
// Connection to the finish restore signal to rearm Critical messages modal mode when action is
945
pImp->finishRestoreDocumentConnection =
946
App::GetApplication().signalFinishRestoreDocument.connect(
947
std::bind(&Gui::NotificationArea::slotRestoreFinished, this, sp::_1));
950
// Initialisation of the timer to inhibit continuous updates of the notification system in case
951
// clusters of messages arrive (so as to delay the actual notification until the whole cluster
953
pImp->inhibitTimer.setSingleShot(true);
955
pImp->inhibitTimer.callOnTimeout([this, na]() {
956
setText(QString::number(na->getUnreadCount()));
957
showInNotificationArea();
960
setIcon(TrayIcon::Normal);
963
NotificationArea::~NotificationArea()
965
pImp->finishRestoreDocumentConnection.disconnect();
968
void NotificationArea::mousePressEvent(QMouseEvent* e)
970
// Contextual menu (e.g. to delete Notifications or all messages (Notifications, Errors,
971
// Warnings and Critical messages)
972
if (e->button() == Qt::RightButton && hitButton(e->pos())) {
976
NotificationsAction* na = static_cast<NotificationsAction*>(pImp->notificationaction);
978
QAction* delnotifications = menu.addAction(tr("Delete user notifications"), [&]() {
979
// guard to avoid modifying the notification list and indices while creating the tooltip
980
lock_guard<std::mutex> g(pImp->mutexNotification);
981
na->deleteNotifications();
982
setText(QString::number(na->getUnreadCount()));
985
delnotifications->setEnabled(!na->isEmpty());
987
QAction* delall = menu.addAction(tr("Delete All"), [&]() {
988
// guard to avoid modifying the notification list and indices while creating the tooltip
989
lock_guard<std::mutex> g(pImp->mutexNotification);
991
setText(QString::number(0));
995
delall->setEnabled(!na->isEmpty());
997
menu.setDefaultAction(delall);
999
menu.exec(this->mapToGlobal(e->pos()));
1001
QPushButton::mousePressEvent(e);
1004
bool NotificationArea::areDeveloperWarningsActive() const
1006
return pImp->developerWarningSubscriptionEnabled;
1009
bool NotificationArea::areDeveloperErrorsActive() const
1011
return pImp->developerErrorSubscriptionEnabled;
1014
void NotificationArea::pushNotification(const QString& notifiername, const QString& message,
1015
Base::LogStyle level)
1017
auto confirmation = confirmationRequired(level);
1020
showConfirmationDialog(notifiername, message);
1022
// guard to avoid modifying the notification list and indices while creating the tooltip
1023
lock_guard<std::mutex> g(pImp->mutexNotification);
1026
NotificationsAction* na = static_cast<NotificationsAction*>(pImp->notificationaction);
1028
// Limit the maximum number of messages stored in the widget (0 means no limit)
1029
if (pImp->maxWidgetMessages != 0 && na->count() > pImp->maxWidgetMessages) {
1030
na->deleteLastItem();
1033
auto repeated = na->isSameNotification(notifiername, message, level);
1036
auto itemptr = std::make_unique<NotificationItem>(level, notifiername, message);
1038
auto item = na->push_front(std::move(itemptr));
1040
// If the non-intrusive notifications are disabled then stop here (messages added to the widget
1042
if (pImp->notificationsDisabled) {
1043
item->setNotified(); // avoid mass of old notifications if feature is activated afterwards
1045
setText(QString::number(
1046
static_cast<NotificationsAction*>(pImp->notificationaction)->getUnreadCount()));
1052
na->resetLastNotificationStatus();
1055
// start or restart rate control (the timer is rearmed if not yet expired, expiration triggers
1056
// showing of the notification messages)
1058
// NOTE: The inhibition timer is created in the main thread and cannot be restarted from another
1059
// QThread. A QTimer can be moved to another QThread (from the QThread in which it is). However,
1060
// it can only be create in a QThread, not in any other thread.
1062
// For this reason, the timer is only triggered if this QThread is the QTimer thread.
1064
// But I want my message from my thread to appear in the notification area. Fine, then configure
1065
// Console not to use the direct connection mode, but the Queued one:
1066
// Base::Console().SetConnectionMode(ConnectionMode::Queued);
1068
auto timer_thread = pImp->inhibitTimer.thread();
1069
auto current_thread = QThread::currentThread();
1071
if (timer_thread == current_thread) {
1072
pImp->inhibitTimer.start(static_cast<int>(pImp->inhibitNotificationTime));
1076
bool NotificationArea::confirmationRequired(Base::LogStyle level)
1078
auto userInitiatedRestore =
1079
Application::Instance->testStatus(Gui::Application::UserInitiatedOpenDocument);
1081
return (level == Base::LogStyle::Critical && userInitiatedRestore
1082
&& pImp->requireConfirmationCriticalMessageDuringRestoring);
1085
void NotificationArea::showConfirmationDialog(const QString& notifiername, const QString& message)
1087
auto confirmMsg = QObject::tr("Notifier:") + QStringLiteral(" ") + notifiername + QStringLiteral("\n\n") + message
1088
+ QStringLiteral("\n\n")
1089
+ QObject::tr("Do you want to skip confirmation of further critical message notifications "
1090
"while loading the file?");
1092
auto button = QMessageBox::critical(getMainWindow()->activeWindow(),
1093
QObject::tr("Critical Message"),
1095
QMessageBox::Yes | QMessageBox::No,
1098
if (button == QMessageBox::Yes)
1099
pImp->requireConfirmationCriticalMessageDuringRestoring = false;
1102
void NotificationArea::showInNotificationArea()
1104
// guard to avoid modifying the notification list and indices while creating the tooltip
1105
lock_guard<std::mutex> g(pImp->mutexNotification);
1108
NotificationsAction* na = static_cast<NotificationsAction*>(pImp->notificationaction);
1110
if (!NotificationBox::isVisible()) {
1111
// The Notification Box may have been closed (by the user by popping it out or by left mouse
1112
// button) ensure that old notifications are not shown again, even if the timer has not
1116
while (i < na->count() && static_cast<NotificationItem*>(na->getItem(i))->isNotifying()) {
1118
NotificationItem* item = static_cast<NotificationItem*>(na->getItem(i));
1120
if (item->isShown()) {
1121
item->setNotified();
1129
auto currentlyshown = na->getShownCount();
1131
// If we cannot show more messages, we do no need to update the non-intrusive notification
1132
if (currentlyshown < pImp->maxOpenNotifications) {
1133
// There is space for at least one more notification
1134
// We update the message with the most recent up to maxOpenNotifications
1137
QString::fromLatin1(
1138
"<style>p { margin: 0 0 0 0 } td { padding: 0 15px }</style> \
1139
<p style='white-space:normal'> \
1142
<th><small>%1</small></th> \
1143
<th><small>%2</small></th> \
1144
<th><small>%3</small></th> \
1146
.arg(QObject::tr("Type"), QObject::tr("Notifier"), QObject::tr("Message"));
1148
auto currentlynotifying = na->getCurrentlyNotifyingCount();
1150
if (currentlynotifying > pImp->maxOpenNotifications) {
1152
QString::fromLatin1(
1155
<td align='left'><img width=\"16\" height=\"16\" src=':/icons/Warning.svg'></td> \
1156
<td align='left'>FreeCAD</td> \
1157
<td align='left'>%1</td> \
1159
.arg(QObject::tr("Too many opened non-intrusive notifications. Notifications "
1160
"are being omitted!"));
1166
while (i < na->count() && static_cast<NotificationItem*>(na->getItem(i))->isNotifying()) {
1168
if (i < pImp->maxOpenNotifications) {// show the first up to maxOpenNotifications
1170
NotificationItem* item = static_cast<NotificationItem*>(na->getItem(i));
1173
if (item->isType(Base::LogStyle::Error)) {
1174
iconstr = QStringLiteral(":/icons/edit_Cancel.svg");
1176
else if (item->isType(Base::LogStyle::Warning)) {
1177
iconstr = QStringLiteral(":/icons/Warning.svg");
1179
else if (item->isType(Base::LogStyle::Critical)) {
1180
iconstr = QStringLiteral(":/icons/critical-info.svg");
1183
iconstr = QStringLiteral(":/icons/info.svg");
1186
QString tmpmessage =
1187
convertFromPlainText(item->getMessage(), Qt::WhiteSpaceMode::WhiteSpaceNormal);
1190
QString::fromLatin1(
1193
<td align='left'><img width=\"16\" height=\"16\" src='%1'></td> \
1194
<td align='left'>%2</td> \
1195
<td align='left'>%3</td> \
1197
.arg(iconstr, item->getNotifier(), tmpmessage);
1199
// start a timer for each of these notifications that was not previously shown
1200
if (!item->isShown()) {
1201
QTimer::singleShot(pImp->notificationExpirationTime, [this, item, repetitions = item->getRepetitions()]() {
1202
// guard to avoid modifying the notification
1203
// start index while creating the tooltip
1204
lock_guard<std::mutex> g(pImp->mutexNotification);
1206
// if the item exists and the number of repetitions has not changed in the
1208
if (item && item->getRepetitions() == repetitions) {
1210
item->setNotified();
1212
if (pImp->autoRemoveUserNotifications) {
1213
if (item->isType(Base::LogStyle::Notification)) {
1215
static_cast<NotificationsAction*>(pImp->notificationaction)
1223
// We update the status to shown
1226
else {// We do not have more space and older notifications will be too old
1228
static_cast<NotificationItem*>(na->getItem(i))->setNotified();
1229
static_cast<NotificationItem*>(na->getItem(i))->resetShown();
1236
msgw += QString::fromLatin1("</table></p>");
1238
NotificationBox::Options options = NotificationBox::Options::RestrictAreaToReference;
1240
if (pImp->preventNonIntrusiveNotificationsWhenWindowNotActive) {
1241
options = options | NotificationBox::Options::OnlyIfReferenceActive;
1244
if (pImp->hideNonIntrusiveNotificationsWhenWindowDeactivated) {
1245
options = options | NotificationBox::Options::HideIfReferenceWidgetDeactivated;
1248
bool isshown = NotificationBox::showText(this->mapToGlobal(QPoint()),
1251
static_cast<int>(pImp->notificationExpirationTime),
1252
pImp->minimumOnScreenTime,
1254
pImp->notificationWidth);
1256
if (!isshown && !pImp->missedNotifications) {
1257
pImp->missedNotifications = true;
1258
setIcon(TrayIcon::MissedNotifications);
1263
void NotificationArea::slotRestoreFinished(const App::Document&)
1265
// Re-arm on restore critical message modal notifications if another document is loaded
1266
pImp->requireConfirmationCriticalMessageDuringRestoring = true;
1269
void NotificationArea::setIcon(TrayIcon trayIcon)
1271
if (trayIcon == TrayIcon::Normal) {
1272
QPushButton::setIcon(ResourceManager::NotificationAreaIcon());
1274
else if (trayIcon == TrayIcon::MissedNotifications) {
1275
QPushButton::setIcon(ResourceManager::notificationAreaMissedNotificationsIcon());