"Professional CMake" book suggest the following: "Targets should build successfully with or without compiler support for precompiled headers. It should be considered an optimization, not a requirement. In particular, do not explicitly include a precompile header (e.g. stdafx.h) in the source code, let CMake force-include an automatically generated precompile header on the compiler command line instead. This is more portable across the major compilers and is likely to be easier to maintain. It will also avoid warnings being generated from certain code checking tools like iwyu (include what you use)." Therefore, removed the "#include <PreCompiled.h>" from sources, also there is no need for the "#ifdef _PreComp_" anymore
1277 lines
46 KiB
C++
1277 lines
46 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2022 Abdullah Tahiri <abdullah.tahiri.yo@gmail.com> *
|
|
* *
|
|
* 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 <QAction>
|
|
#include <QActionEvent>
|
|
#include <QApplication>
|
|
#include <QEvent>
|
|
#include <QHBoxLayout>
|
|
#include <QHeaderView>
|
|
#include <QMenu>
|
|
#include <QMessageBox>
|
|
#include <QStringList>
|
|
#include <QTextDocument>
|
|
#include <QThread>
|
|
#include <QTimer>
|
|
#include <QTreeWidget>
|
|
#include <QWidgetAction>
|
|
#include <memory>
|
|
#include <mutex>
|
|
|
|
|
|
#include <App/Application.h>
|
|
#include <Base/Console.h>
|
|
|
|
#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 sp = std::placeholders;
|
|
|
|
using std::lock_guard;
|
|
|
|
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
|
|
|
|
//NOLINTBEGIN
|
|
/** @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 = false;
|
|
//@}
|
|
|
|
/** @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;
|
|
//@}
|
|
|
|
/** @name Data source control */
|
|
//@{
|
|
/* Controls whether debug warnings and errors intended for developers should be processed or not
|
|
*/
|
|
bool developerErrorSubscriptionEnabled = false;
|
|
bool developerWarningSubscriptionEnabled = false;
|
|
//@}
|
|
|
|
bool missedNotifications = false;
|
|
|
|
// Access control
|
|
std::mutex mutexNotification;
|
|
|
|
// Pointers to widgets (no ownership)
|
|
QMenu* menu = nullptr;
|
|
QWidgetAction* notificationaction = nullptr;
|
|
|
|
/** @name Resources */
|
|
//@{
|
|
/// Base::Console Message observer
|
|
std::unique_ptr<NotificationAreaObserver> observer;
|
|
Connection finishRestoreDocumentConnection;
|
|
/// Preference Parameter observer
|
|
std::unique_ptr<NotificationArea::ParameterObserver> parameterObserver;
|
|
//@}
|
|
|
|
//NOLINTEND
|
|
};
|
|
|
|
}// namespace Gui
|
|
|
|
|
|
/******************* Resource Management *****************************************/
|
|
|
|
/** Simple class to manage Notification Area Resources*/
|
|
class ResourceManager
|
|
{
|
|
|
|
private:
|
|
ResourceManager()
|
|
{
|
|
//NOLINTBEGIN
|
|
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));
|
|
//NOLINTEND
|
|
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;
|
|
|
|
NotificationAreaObserver(const NotificationAreaObserver &) = delete;
|
|
NotificationAreaObserver(NotificationAreaObserver &&) = delete;
|
|
NotificationAreaObserver &operator=(const NotificationAreaObserver &) = delete;
|
|
NotificationAreaObserver &operator=(NotificationAreaObserver &&) = delete;
|
|
|
|
/// 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,
|
|
Base::IntendedRecipient recipient, Base::ContentType content) 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
|
|
}
|
|
|
|
NotificationAreaObserver::~NotificationAreaObserver()
|
|
{
|
|
Base::Console().detachObserver(this);
|
|
}
|
|
|
|
void NotificationAreaObserver::sendLog(const std::string& notifiername, const std::string& msg,
|
|
Base::LogStyle level, Base::IntendedRecipient recipient,
|
|
Base::ContentType content)
|
|
{
|
|
// 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.
|
|
|
|
bool violatesBasicPolicy = (recipient == Base::IntendedRecipient::Developer
|
|
|| content == Base::ContentType::Untranslatable);
|
|
|
|
// We allow derogations for debug purposes according to user preferences
|
|
bool meetsDerogationCriteria = false;
|
|
|
|
if (violatesBasicPolicy) {
|
|
meetsDerogationCriteria =
|
|
(level == Base::LogStyle::Warning && notificationArea->areDeveloperWarningsActive())
|
|
|| (level == Base::LogStyle::Error && notificationArea->areDeveloperErrorsActive());
|
|
}
|
|
|
|
if (violatesBasicPolicy && !meetsDerogationCriteria) {
|
|
return;
|
|
}
|
|
|
|
auto simplifiedstring =
|
|
QString::fromStdString(msg)
|
|
.trimmed();// remove any leading and trailing whitespace character ('\n')
|
|
|
|
// avoid processing empty strings
|
|
if (simplifiedstring.isEmpty())
|
|
return;
|
|
|
|
if (content == Base::ContentType::Translated) {
|
|
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 getMessage();
|
|
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 {};
|
|
}
|
|
|
|
void addRepetition() {
|
|
unread = true;
|
|
notifying = true;
|
|
shown = false;
|
|
repetitions++;
|
|
}
|
|
|
|
bool isRepeated(Base::LogStyle notificationtype, const QString & notifiername, const QString & message ) const {
|
|
return (notificationType == notificationtype && notifierName == notifiername && msg == message);
|
|
}
|
|
|
|
bool isType(Base::LogStyle notificationtype) const {
|
|
return notificationType == notificationtype;
|
|
}
|
|
|
|
bool isUnread() const {
|
|
return unread;
|
|
}
|
|
|
|
bool isNotifying() const {
|
|
return notifying;
|
|
}
|
|
|
|
bool isShown() const {
|
|
return shown;
|
|
}
|
|
|
|
int getRepetitions() const{
|
|
return repetitions;
|
|
}
|
|
|
|
void setNotified() {
|
|
notifying = false;
|
|
}
|
|
|
|
void resetNotified() {
|
|
notifying = true;
|
|
}
|
|
|
|
void setShown() {
|
|
shown = true;
|
|
}
|
|
|
|
void resetShown() {
|
|
shown = false;
|
|
}
|
|
|
|
void setRead() {
|
|
unread = false;
|
|
}
|
|
|
|
void setUnread() {
|
|
unread = true;
|
|
}
|
|
|
|
QString getMessage() const {
|
|
if(repetitions == 0) {
|
|
return msg;
|
|
}
|
|
else {
|
|
return msg + QObject::tr(" (%1 times)").arg(repetitions+1);
|
|
}
|
|
}
|
|
|
|
const QString & getNotifier() {
|
|
return notifierName;
|
|
}
|
|
|
|
private:
|
|
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)
|
|
int repetitions = 0; // message appears n times in a row.
|
|
};
|
|
|
|
/** 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(const NotificationsAction &) = delete;
|
|
NotificationsAction(NotificationsAction &&) = delete;
|
|
NotificationsAction & operator=(const NotificationsAction &) = delete;
|
|
NotificationsAction & operator=(NotificationsAction &&) = delete;
|
|
|
|
~NotificationsAction() override
|
|
{
|
|
for (auto* item : std::as_const(pushedItems)) {
|
|
if (item) {
|
|
delete item; // NOLINT
|
|
}
|
|
}
|
|
}
|
|
|
|
public:
|
|
/// deletes only notifications (messages of type Notification)
|
|
void deleteNotifications()
|
|
{
|
|
if (tableWidget) {
|
|
for (int i = tableWidget->topLevelItemCount() - 1; i >= 0; i--) {
|
|
//NOLINTNEXTLINE
|
|
auto* item = static_cast<NotificationItem*>(tableWidget->topLevelItem(i));
|
|
if (item->isType(Base::LogStyle::Notification)) {
|
|
delete item; //NOLINT
|
|
}
|
|
}
|
|
}
|
|
for (int i = pushedItems.size() - 1; i >= 0; i--) {
|
|
//NOLINTNEXTLINE
|
|
auto* item = static_cast<NotificationItem*>(pushedItems.at(i));
|
|
if (item->isType(Base::LogStyle::Notification)) {
|
|
delete pushedItems.takeAt(i); //NOLINT
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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->isUnread();
|
|
});
|
|
}
|
|
|
|
/// returns the amount of notifications, errors and warnings currently being notified
|
|
inline int getCurrentlyNotifyingCount() const
|
|
{
|
|
return getCurrently([](auto* item) {
|
|
return item->isNotifying();
|
|
});
|
|
}
|
|
|
|
/// 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->isShown();
|
|
});
|
|
}
|
|
|
|
/// 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
|
|
//NOLINTNEXTLINE
|
|
auto* item = static_cast<NotificationItem*>(tableWidget->topLevelItem(i));
|
|
item->setRead();
|
|
}
|
|
}
|
|
|
|
/// 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 one 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); //NOLINT
|
|
}
|
|
else {
|
|
delete tableWidget->topLevelItem(index - pushedItems.count()); //NOLINT
|
|
}
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
|
|
/// checks if last notification is the same
|
|
bool isSameNotification(const QString& notifiername, const QString& message,
|
|
Base::LogStyle level) const {
|
|
if(count() > 0) { // if not empty
|
|
//NOLINTNEXTLINE
|
|
auto item = static_cast<NotificationItem*>(getItem(0));
|
|
return item->isRepeated(level,notifiername,message);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void resetLastNotificationStatus() {
|
|
//NOLINTNEXTLINE
|
|
auto item = static_cast<NotificationItem*>(getItem(0));
|
|
item->addRepetition();
|
|
}
|
|
|
|
/// pushes a notification item to the front
|
|
auto push_front(std::unique_ptr<NotificationItem> item)
|
|
{
|
|
auto it = item.release();
|
|
pushedItems.push_front(it);
|
|
return it;
|
|
}
|
|
|
|
QSize size()
|
|
{
|
|
return tableWidget->size();
|
|
}
|
|
|
|
protected:
|
|
/// creates the Notifications Widget
|
|
QWidget* createWidget(QWidget* parent) override
|
|
{
|
|
//NOLINTBEGIN
|
|
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 selectedItems = tableWidget->selectedItems();
|
|
|
|
QMenu menu;
|
|
|
|
QAction* del = menu.addAction(tr("Delete"), this, [&]() {
|
|
for (auto it : std::as_const(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));
|
|
});
|
|
//NOLINTEND
|
|
|
|
return notificationsWidget;
|
|
}
|
|
|
|
private:
|
|
/// utility function to return the number of Notification Items meeting the functor/lambda
|
|
/// criteria
|
|
int getCurrently(std::function<bool(const NotificationItem*)> F) const
|
|
{
|
|
int instate = 0;
|
|
for (auto i = 0; i < tableWidget->topLevelItemCount(); i++) {
|
|
//NOLINTNEXTLINE
|
|
auto* item = static_cast<NotificationItem*>(tableWidget->topLevelItem(i));
|
|
if (F(item)) {
|
|
instate++;
|
|
}
|
|
}
|
|
for (auto i = 0; i < pushedItems.count(); i++) {
|
|
//NOLINTNEXTLINE
|
|
auto* item = static_cast<NotificationItem*>(pushedItems.at(i));
|
|
if (F(item)) {
|
|
instate++;
|
|
}
|
|
}
|
|
return instate;
|
|
}
|
|
|
|
private:
|
|
QTreeWidget* tableWidget = nullptr;
|
|
// 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<QTreeWidgetItem*> pushedItems;
|
|
};
|
|
|
|
/************ Parameter Observer (preferences) **************************************/
|
|
|
|
NotificationArea::ParameterObserver::ParameterObserver(NotificationArea* notificationarea)
|
|
: notificationArea(notificationarea)
|
|
{
|
|
hGrp = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/NotificationArea");
|
|
|
|
//NOLINTBEGIN
|
|
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<unsigned int>(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<unsigned int>(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<unsigned int>(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<unsigned int>(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;
|
|
}},
|
|
{"DeveloperErrorSubscriptionEnabled",
|
|
[this](const std::string& string) {
|
|
auto enabled = hGrp->GetBool(string.c_str(), false);
|
|
notificationArea->pImp->developerErrorSubscriptionEnabled = enabled;
|
|
}},
|
|
{"DeveloperWarningSubscriptionEnabled",
|
|
[this](const std::string& string) {
|
|
auto enabled = hGrp->GetBool(string.c_str(), false);
|
|
notificationArea->pImp->developerWarningSubscriptionEnabled = enabled;
|
|
}},
|
|
};
|
|
//NOLINTEND
|
|
|
|
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<const char*>& 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<NotificationAreaP>();
|
|
|
|
pImp->observer = std::make_unique<NotificationAreaObserver>(this);
|
|
pImp->parameterObserver = std::make_unique<NotificationArea::ParameterObserver>(this);
|
|
|
|
pImp->menu = new QMenu(parent); //NOLINT
|
|
setMenu(pImp->menu);
|
|
|
|
auto na = new NotificationsAction(pImp->menu); //NOLINT
|
|
|
|
pImp->menu->addAction(na);
|
|
|
|
pImp->notificationaction = na;
|
|
|
|
//NOLINTBEGIN
|
|
// Signals for synchronisation of storage before showing/hiding the widget
|
|
QObject::connect(pImp->menu, &QMenu::aboutToHide, [&]() {
|
|
lock_guard<std::mutex> g(pImp->mutexNotification);
|
|
static_cast<NotificationsAction*>(pImp->notificationaction)->clearUnreadFlag();
|
|
static_cast<NotificationsAction*>(pImp->notificationaction)->shiftToCache();
|
|
});
|
|
|
|
QObject::connect(pImp->menu, &QMenu::aboutToShow, [this]() {
|
|
// guard to avoid modifying the notification list and indices while creating the tooltip
|
|
lock_guard<std::mutex> g(pImp->mutexNotification);
|
|
setText(QString::number(0)); // no unread notifications
|
|
if (pImp->missedNotifications) {
|
|
setIcon(TrayIcon::Normal);
|
|
pImp->missedNotifications = false;
|
|
}
|
|
static_cast<NotificationsAction*>(pImp->notificationaction)->synchroniseWidget();
|
|
|
|
|
|
// There is a Qt bug in not respecting a QMenu size when size changes in aboutToShow.
|
|
//
|
|
// https://bugreports.qt.io/browse/QTBUG-54421
|
|
// https://forum.qt.io/topic/68765/how-to-update-geometry-on-qaction-visibility-change-in-qmenu-abouttoshow/3
|
|
//
|
|
// None of this works
|
|
// pImp->menu->updateGeometry();
|
|
// pImp->menu->adjustSize();
|
|
// pImp->menu->ensurePolished();
|
|
// this->updateGeometry();
|
|
// this->adjustSize();
|
|
// this->ensurePolished();
|
|
|
|
// This does correct the size
|
|
QSize size = static_cast<NotificationsAction*>(pImp->notificationaction)->size();
|
|
QResizeEvent re(size, size);
|
|
qApp->sendEvent(pImp->menu, &re);
|
|
|
|
// This corrects the position of the menu
|
|
QTimer::singleShot(0, [&] {
|
|
QWidget* statusbar = static_cast<QWidget*>(this->parent());
|
|
QPoint statusbar_top_right = statusbar->mapToGlobal(statusbar->rect().topRight());
|
|
QSize menusize = pImp->menu->size();
|
|
QWidget* w = this;
|
|
QPoint button_pos = w->mapToGlobal(w->rect().topLeft());
|
|
QPoint widget_pos;
|
|
if ((statusbar_top_right.x() - menusize.width()) > button_pos.x()) {
|
|
widget_pos = QPoint(button_pos.x(), statusbar_top_right.y() - menusize.height());
|
|
}
|
|
else {
|
|
widget_pos = QPoint(statusbar_top_right.x() - menusize.width(),
|
|
statusbar_top_right.y() - menusize.height());
|
|
}
|
|
pImp->menu->move(widget_pos);
|
|
});
|
|
});
|
|
|
|
// Connection to the finish restore signal to rearm Critical messages modal mode when action is
|
|
// user initiated
|
|
pImp->finishRestoreDocumentConnection =
|
|
App::GetApplication().signalFinishRestoreDocument.connect(
|
|
std::bind(&Gui::NotificationArea::slotRestoreFinished, this, sp::_1));
|
|
//NOLINTEND
|
|
|
|
// 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;
|
|
|
|
//NOLINTBEGIN
|
|
NotificationsAction* na = static_cast<NotificationsAction*>(pImp->notificationaction);
|
|
|
|
QAction* delnotifications = menu.addAction(tr("Delete User Notifications"), [&]() {
|
|
// guard to avoid modifying the notification list and indices while creating the tooltip
|
|
lock_guard<std::mutex> g(pImp->mutexNotification);
|
|
na->deleteNotifications();
|
|
setText(QString::number(na->getUnreadCount()));
|
|
});
|
|
|
|
delnotifications->setEnabled(!na->isEmpty());
|
|
|
|
QAction* delall = menu.addAction(tr("Delete All"), [&]() {
|
|
// guard to avoid modifying the notification list and indices while creating the tooltip
|
|
lock_guard<std::mutex> g(pImp->mutexNotification);
|
|
na->deleteAll();
|
|
setText(QString::number(0));
|
|
});
|
|
//NOLINTEND
|
|
|
|
delall->setEnabled(!na->isEmpty());
|
|
|
|
menu.setDefaultAction(delall);
|
|
|
|
menu.exec(this->mapToGlobal(e->pos()));
|
|
}
|
|
QPushButton::mousePressEvent(e);
|
|
}
|
|
|
|
bool NotificationArea::areDeveloperWarningsActive() const
|
|
{
|
|
return pImp->developerWarningSubscriptionEnabled;
|
|
}
|
|
|
|
bool NotificationArea::areDeveloperErrorsActive() const
|
|
{
|
|
return pImp->developerErrorSubscriptionEnabled;
|
|
}
|
|
|
|
void NotificationArea::pushNotification(const QString& notifiername, const QString& message,
|
|
Base::LogStyle level)
|
|
{
|
|
auto confirmation = confirmationRequired(level);
|
|
|
|
if (confirmation) {
|
|
showConfirmationDialog(notifiername, message);
|
|
}
|
|
// guard to avoid modifying the notification list and indices while creating the tooltip
|
|
lock_guard<std::mutex> g(pImp->mutexNotification);
|
|
|
|
//NOLINTNEXTLINE
|
|
NotificationsAction* na = static_cast<NotificationsAction*>(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();
|
|
}
|
|
|
|
auto repeated = na->isSameNotification(notifiername, message, level);
|
|
|
|
if(!repeated) {
|
|
auto itemptr = std::make_unique<NotificationItem>(level, notifiername, message);
|
|
|
|
auto item = na->push_front(std::move(itemptr));
|
|
|
|
// If the non-intrusive notifications are disabled then stop here (messages added to the widget
|
|
// only)
|
|
if (pImp->notificationsDisabled) {
|
|
item->setNotified(); // avoid mass of old notifications if feature is activated afterwards
|
|
//NOLINTBEGIN
|
|
setText(QString::number(
|
|
static_cast<NotificationsAction*>(pImp->notificationaction)->getUnreadCount()));
|
|
return;
|
|
//NOLINTEND
|
|
}
|
|
}
|
|
else {
|
|
na->resetLastNotificationStatus();
|
|
}
|
|
|
|
// 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(static_cast<int>(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:") + QStringLiteral(" ") + notifiername + QStringLiteral("\n\n") + message
|
|
+ QStringLiteral("\n\n")
|
|
+ QObject::tr("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()
|
|
{
|
|
// guard to avoid modifying the notification list and indices while creating the tooltip
|
|
lock_guard<std::mutex> g(pImp->mutexNotification);
|
|
|
|
//NOLINTNEXTLINE
|
|
NotificationsAction* na = static_cast<NotificationsAction*>(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;
|
|
//NOLINTNEXTLINE
|
|
while (i < na->count() && static_cast<NotificationItem*>(na->getItem(i))->isNotifying()) {
|
|
//NOLINTNEXTLINE
|
|
NotificationItem* item = static_cast<NotificationItem*>(na->getItem(i));
|
|
|
|
if (item->isShown()) {
|
|
item->setNotified();
|
|
item->resetShown();
|
|
}
|
|
|
|
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 =
|
|
QStringLiteral(
|
|
"<style>p { margin: 0 0 0 0 } td { padding: 0 15px }</style> \
|
|
<p style='white-space:normal'> \
|
|
<table> \
|
|
<tr> \
|
|
<th><small>%1</small></th> \
|
|
<th><small>%2</small></th> \
|
|
<th><small>%3</small></th> \
|
|
</tr>")
|
|
.arg(QObject::tr("Type"), QObject::tr("Notifier"), QObject::tr("Message"));
|
|
|
|
auto currentlynotifying = na->getCurrentlyNotifyingCount();
|
|
|
|
if (currentlynotifying > pImp->maxOpenNotifications) {
|
|
msgw +=
|
|
QStringLiteral(
|
|
" \
|
|
<tr> \
|
|
<td align='left'><img width=\"16\" height=\"16\" src=':/icons/Warning.svg'></td> \
|
|
<td align='left'>FreeCAD</td> \
|
|
<td align='left'>%1</td> \
|
|
</tr>")
|
|
.arg(QObject::tr("Too many opened non-intrusive notifications. Notifications "
|
|
"are being omitted!"));
|
|
}
|
|
|
|
int i = 0;
|
|
|
|
//NOLINTNEXTLINE
|
|
while (i < na->count() && static_cast<NotificationItem*>(na->getItem(i))->isNotifying()) {
|
|
|
|
if (i < pImp->maxOpenNotifications) {// show the first up to maxOpenNotifications
|
|
//NOLINTNEXTLINE
|
|
NotificationItem* item = static_cast<NotificationItem*>(na->getItem(i));
|
|
|
|
QString iconstr;
|
|
if (item->isType(Base::LogStyle::Error)) {
|
|
iconstr = QStringLiteral(":/icons/edit_Cancel.svg");
|
|
}
|
|
else if (item->isType(Base::LogStyle::Warning)) {
|
|
iconstr = QStringLiteral(":/icons/Warning.svg");
|
|
}
|
|
else if (item->isType(Base::LogStyle::Critical)) {
|
|
iconstr = QStringLiteral(":/icons/critical-info.svg");
|
|
}
|
|
else {
|
|
iconstr = QStringLiteral(":/icons/info.svg");
|
|
}
|
|
|
|
QString tmpmessage =
|
|
convertFromPlainText(item->getMessage(), Qt::WhiteSpaceMode::WhiteSpaceNormal);
|
|
|
|
msgw +=
|
|
QStringLiteral(
|
|
" \
|
|
<tr> \
|
|
<td align='left'><img width=\"16\" height=\"16\" src='%1'></td> \
|
|
<td align='left'>%2</td> \
|
|
<td align='left'>%3</td> \
|
|
</tr>")
|
|
.arg(iconstr, item->getNotifier(), tmpmessage);
|
|
|
|
// start a timer for each of these notifications that was not previously shown
|
|
if (!item->isShown()) {
|
|
QTimer::singleShot(pImp->notificationExpirationTime, [this, item, repetitions = item->getRepetitions()]() {
|
|
// guard to avoid modifying the notification
|
|
// start index while creating the tooltip
|
|
lock_guard<std::mutex> g(pImp->mutexNotification);
|
|
|
|
// if the item exists and the number of repetitions has not changed in the
|
|
// meantime
|
|
if (item && item->getRepetitions() == repetitions) {
|
|
item->resetShown();
|
|
item->setNotified();
|
|
|
|
if (pImp->autoRemoveUserNotifications) {
|
|
if (item->isType(Base::LogStyle::Notification)) {
|
|
//NOLINTNEXTLINE
|
|
static_cast<NotificationsAction*>(pImp->notificationaction)
|
|
->deleteItem(item);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// We update the status to shown
|
|
item->setShown();
|
|
}
|
|
else {// We do not have more space and older notifications will be too old
|
|
//NOLINTBEGIN
|
|
static_cast<NotificationItem*>(na->getItem(i))->setNotified();
|
|
static_cast<NotificationItem*>(na->getItem(i))->resetShown();
|
|
//NOLINTEND
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
msgw += QStringLiteral("</table></p>");
|
|
|
|
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(),
|
|
static_cast<int>(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());
|
|
}
|
|
}
|