diff --git a/src/App/Document.h b/src/App/Document.h index f09a7310d0..ad202a31d3 100644 --- a/src/App/Document.h +++ b/src/App/Document.h @@ -71,13 +71,6 @@ public: IgnoreErrorOnRecompute = 12, // Don't report errors if the recompute failed }; - enum class NotificationType { - Information, - Warning, - Error, - Critical, - }; - /** @name Properties */ //@{ /// holds the long name of the document (utf-8 coded) @@ -177,8 +170,6 @@ public: boost::signals2::signal&)> signalSkipRecompute; boost::signals2::signal signalFinishRestoreObject; boost::signals2::signal signalChangePropertyEditor; - // signal user message - boost::signals2::signal signalUserMessage; //@} boost::signals2::signal signalLinkXsetValue; diff --git a/src/Gui/Document.cpp b/src/Gui/Document.cpp index 2a4f9e8d22..5ff1c4c583 100644 --- a/src/Gui/Document.cpp +++ b/src/Gui/Document.cpp @@ -30,6 +30,7 @@ # include # include # include +# include # include # include #endif @@ -55,6 +56,7 @@ #include "FileDialog.h" #include "MainWindow.h" #include "MDIView.h" +#include "NotificationArea.h" #include "Selection.h" #include "Thumbnail.h" #include "Tree.h" @@ -72,175 +74,6 @@ namespace bp = boost::placeholders; namespace Gui { -/** This class is an implementation only class to handle user notifications offered by App::Document. - * - * It provides a mechanism requiring confirmation for critical notifications only during User initiated restore/document loading ( it - * does not require confirmation for macro/Python initiated restore, not to interfere with automations). - * - * Additionally, it provides a mechanism to show autoclosing non-modal user notifications in a non-intrusive way. - **/ -class MessageManager { -public: - MessageManager() = default; - ~MessageManager(); - - void setDocument(Gui::Document * pDocument); - void slotUserMessage(const App::DocumentObject&, const QString &, App::Document::NotificationType); - - -private: - void reorderAutoClosingMessages(); - QMessageBox* createNonModalMessage(const QString & msg, App::Document::NotificationType notificationtype); - void pushAutoClosingMessage(const QString & msg, App::Document::NotificationType notificationtype); - void pushAutoClosingMessageTooManyMessages(); - -private: - using Connection = boost::signals2::connection; - Gui::Document * pDoc; - Connection connectUserMessage; - bool requireConfirmationCriticalMessageDuringRestoring = true; - std::vector openAutoClosingMessages; - std::mutex mutexAutoClosingMessages; - const int autoClosingTimeout = 5000; // ms - const int autoClosingMessageStackingOffset = 10; - const unsigned int maxNumberOfOpenAutoClosingMessages = 3; - bool maxNumberOfOpenAutoClosingMessagesLimitReached = false; -}; - -MessageManager::~MessageManager(){ - connectUserMessage.disconnect(); -} - -void MessageManager::setDocument(Gui::Document * pDocument) -{ - - pDoc = pDocument; - - connectUserMessage = pDoc->getDocument()->signalUserMessage.connect - (boost::bind(&Gui::MessageManager::slotUserMessage, this, bp::_1, bp::_2, bp::_3)); - -} - -void MessageManager::slotUserMessage(const App::DocumentObject& obj, const QString & msg, App::Document::NotificationType notificationtype) -{ - (void) obj; - - auto userInitiatedRestore = Application::Instance->testStatus(Gui::Application::UserInitiatedOpenDocument); - - if(notificationtype == App::Document::NotificationType::Critical && userInitiatedRestore && requireConfirmationCriticalMessageDuringRestoring) { - auto confirmMsg = msg + QStringLiteral("\n\n") + QObject::tr("Do you want to skip confirmation of further critical message notifications while loading the file?"); - auto button = QMessageBox::critical(pDoc->getActiveView(), QObject::tr("Critical Message"), confirmMsg, QMessageBox::Yes | QMessageBox::No, QMessageBox::No ); - - if(button == QMessageBox::Yes) - requireConfirmationCriticalMessageDuringRestoring = false; - } - else { // Non-critical errors and warnings - auto-closing non-blocking message box - - auto messageNumber = openAutoClosingMessages.size(); - - // Not opening more than the number of maximum autoclosing messages - // If maximum reached, the mechanism only resets after all present messages are auto-closed - if( messageNumber < maxNumberOfOpenAutoClosingMessages) { - if(messageNumber == 0 && maxNumberOfOpenAutoClosingMessagesLimitReached) { - maxNumberOfOpenAutoClosingMessagesLimitReached = false; - } - - if(!maxNumberOfOpenAutoClosingMessagesLimitReached) { - pushAutoClosingMessage(msg, notificationtype); - } - } - else { - if(!maxNumberOfOpenAutoClosingMessagesLimitReached) - pushAutoClosingMessageTooManyMessages(); - - maxNumberOfOpenAutoClosingMessagesLimitReached = true; - } - } -} - -void MessageManager::pushAutoClosingMessage(const QString & msg, App::Document::NotificationType notificationtype) -{ - std::lock_guard g(mutexAutoClosingMessages); // guard to avoid creating new messages while closing old messages (via timer) - - auto msgBox = createNonModalMessage(msg, notificationtype); - - msgBox->show(); - - int numberOpenAutoClosingMessages = openAutoClosingMessages.size(); - - openAutoClosingMessages.push_back(msgBox); - - reorderAutoClosingMessages(); - - QTimer::singleShot(autoClosingTimeout*numberOpenAutoClosingMessages, [msgBox, this](){ - std::lock_guard g(mutexAutoClosingMessages); // guard to avoid closing old messages while creating new ones - if(msgBox) { - msgBox->done(0); - openAutoClosingMessages.erase( - std::remove(openAutoClosingMessages.begin(), openAutoClosingMessages.end(), msgBox), - openAutoClosingMessages.end()); - - reorderAutoClosingMessages(); - } - }); -} - -void MessageManager::pushAutoClosingMessageTooManyMessages() -{ - pushAutoClosingMessage(QObject::tr("Too many message notifications. Notification temporarily stopped. Look at the report view for more information."), App::Document::NotificationType::Warning); -} - - -QMessageBox* MessageManager::createNonModalMessage(const QString & msg, App::Document::NotificationType notificationtype) -{ - auto parent = pDoc->getActiveView(); - - QMessageBox* msgBox = new QMessageBox(parent); - msgBox->setAttribute(Qt::WA_DeleteOnClose); // msgbox deleted automatically upon closed - msgBox->setStandardButtons(QMessageBox::NoButton); - msgBox->setWindowFlag(Qt::FramelessWindowHint,true); - msgBox->setText(msg); - - if(notificationtype == App::Document::NotificationType::Error) { - msgBox->setWindowTitle(QObject::tr("Error")); - msgBox->setIcon(QMessageBox::Critical); - } - else if(notificationtype == App::Document::NotificationType::Warning) { - msgBox->setWindowTitle(QObject::tr("Warning")); - msgBox->setIcon(QMessageBox::Warning); - } - else if(notificationtype == App::Document::NotificationType::Information) { - msgBox->setWindowTitle(QObject::tr("Information")); - msgBox->setIcon(QMessageBox::Information); - } - else if(notificationtype == App::Document::NotificationType::Critical) { - msgBox->setWindowTitle(QObject::tr("Critical")); - msgBox->setIcon(QMessageBox::Critical); - } - - msgBox->setModal( false ); // if you want it non-modal - - return msgBox; -} - -void MessageManager::reorderAutoClosingMessages() -{ - auto parent = pDoc->getActiveView(); - - int numberOpenAutoClosingMessages = openAutoClosingMessages.size(); - - auto x = parent->width() / 2; - auto y = parent->height() / 7; - - int posindex = numberOpenAutoClosingMessages - 1; - for (auto rit = openAutoClosingMessages.rbegin(); rit != openAutoClosingMessages.rend(); ++rit, posindex--) { - int xw = x - (*rit)->width() / 2 + autoClosingMessageStackingOffset*posindex;; - int yw = y + autoClosingMessageStackingOffset*posindex; - (*rit)->move(xw, yw); - (*rit)->raise(); - } -} - // Pimpl class struct DocumentP { @@ -301,8 +134,6 @@ struct DocumentP using ConnectionBlock = boost::signals2::shared_connection_block; ConnectionBlock connectActObjectBlocker; ConnectionBlock connectChangeDocumentBlocker; - - MessageManager messageManager; }; } // namespace Gui @@ -384,7 +215,6 @@ Document::Document(App::Document* pcDocument,Application * app) d->connectTransactionRemove = pcDocument->signalTransactionRemove.connect (boost::bind(&Gui::Document::slotTransactionRemove, this, bp::_1, bp::_2)); - d->messageManager.setDocument(this); // pointer to the python class // NOTE: As this Python object doesn't get returned to the interpreter we // mustn't increment it (Werner Jan-12-2006)