diff --git a/src/Gui/Document.cpp b/src/Gui/Document.cpp index 05989dae13..6aa26fff0a 100644 --- a/src/Gui/Document.cpp +++ b/src/Gui/Document.cpp @@ -28,6 +28,7 @@ # include # include # include +# include # include # include #endif @@ -70,6 +71,175 @@ 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 { @@ -130,8 +300,9 @@ struct DocumentP using ConnectionBlock = boost::signals2::shared_connection_block; ConnectionBlock connectActObjectBlocker; ConnectionBlock connectChangeDocumentBlocker; -}; + MessageManager messageManager; +}; } // namespace Gui /* TRANSLATOR Gui::Document */ @@ -211,6 +382,8 @@ Document::Document(App::Document* pcDocument,Application * app) (boost::bind(&Gui::Document::slotTransactionAppend, this, bp::_1, bp::_2)); 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) @@ -1074,7 +1247,7 @@ static bool checkCanonicalPath(const std::map &docs) << QObject::tr("Path:") << ' ' << QString::fromUtf8(doc->FileName.getValue()); for (auto d : v.second) { if (d == doc) continue; - ts << "\n" + ts << "\n" << QObject::tr("Document:") << ' ' << docName(d) << "\n " << QObject::tr("Path:") << ' ' << QString::fromUtf8(d->FileName.getValue()); @@ -1690,7 +1863,6 @@ void Document::slotFinishImportObjects(const std::vector & // } } - void Document::addRootObjectsToGroup(const std::vector& obj, App::DocumentObjectGroup* grp) { std::map rootMap;