diff --git a/cmake/version.cmake b/cmake/version.cmake index 1fc9177..3458889 100644 --- a/cmake/version.cmake +++ b/cmake/version.cmake @@ -3,8 +3,8 @@ # set (QT_UTIL_MAJOR_VERSION "6") -set (QT_UTIL_MINOR_VERSION "4") -set (QT_UTIL_RELEASE_VERSION "2") +set (QT_UTIL_MINOR_VERSION "5") +set (QT_UTIL_RELEASE_VERSION "0") set (QT_UTIL_VERSION ${QT_UTIL_MAJOR_VERSION}.${QT_UTIL_MINOR_VERSION}.${QT_UTIL_RELEASE_VERSION}) diff --git a/src/QtUtil/QtMessageBox.cpp b/src/QtUtil/QtMessageBox.cpp index 3df38cd..8be3765 100644 --- a/src/QtUtil/QtMessageBox.cpp +++ b/src/QtUtil/QtMessageBox.cpp @@ -2,8 +2,10 @@ #include "QtUtil/QtUnicodeHelper.h" #include +#include #include +#include #include #include @@ -26,12 +28,14 @@ USE_ENCODING_AUTODETECTION #define BEGIN_TRY_CATCH_BLOCK \ + bool hasError = false; \ try { #define COMPLETE_TRY_CATCH_BLOCK \ } \ catch (const IN_UTIL Exception& exc) \ { \ + hasError = true; \ QMessageBox::critical (0, \ "ERREUR APPLICATIVE LORS DE L'AFFICHAGE D'UN MESSAGE",\ UTF8TOQSTRING(exc.getFullMessage( )),\ @@ -39,6 +43,7 @@ USE_ENCODING_AUTODETECTION } \ catch (const IN_STD exception& exc) \ { \ + hasError = true; \ QMessageBox::critical (0, \ "ERREUR APPLICATIVE LORS DE L'AFFICHAGE D'UN MESSAGE",\ QSTR (exc.what ( )), \ @@ -46,6 +51,7 @@ USE_ENCODING_AUTODETECTION } \ catch (...) \ { \ + hasError = true; \ QMessageBox::critical (0, \ "ERREUR APPLICATIVE LORS DE L'AFFICHAGE D'UN MESSAGE",\ QSTR ("Erreur non documentée"), \ @@ -67,8 +73,8 @@ USE_ENCODING_AUTODETECTION QtMessageDialog::QtMessageDialog (QMessageBox::Icon icon, QWidget* parent, const QString& title, const QString& text, int columns, const char* button1, const char* button2, const char* button3, int defaultButton) - : QDialog (parent) -{ + : QDialog (parent), _processing (false), _parentState (true) +{ setWindowTitle (title); QVBoxLayout* mainLayout = new QVBoxLayout (this); @@ -167,19 +173,40 @@ QtMessageDialog::~QtMessageDialog ( ) } // QtMessageDialog::~QtMessageDialog +void QtMessageDialog::setProcessing (bool processing) // v 6.5.0 +{ + if (processing != _processing) + { + _processing = processing; + if (0 != parentWidget ( )) + { + if (true == processing) + { + _parentState = parentWidget ( )->isEnabled ( ); + parentWidget ( )->setEnabled (false); + // L'inactivation du parent entraîne celui de la boite de dialogue, donc on la réactive : + setEnabled (true); + } + else if (true == _parentState) + parentWidget ( )->setEnabled (_parentState); + } + } // if (processing != _processing) +} // QtMessageDialog::setProcessing + + void QtMessageDialog::buttonClicked ( ) { const QObject* s = sender ( ); for (int i = 0; i < 3; i++) if (s == _buttons [i]) { + setProcessing (false); // v 6.5.0 done (i); return; } // if (s == _buttons [i]) } // QtMessageDialog::buttonClicked - // =========================================================================== // LA CLASSE QtMessageBox // =========================================================================== @@ -239,6 +266,28 @@ void QtMessageBox::displayWarningMessage (QWidget* parent, const UTF8String& tit } // QtMessageBox::displayWarningMessage +void QtMessageBox::displayWarningMessageInAppWorkspace (QWidget* parent, const UTF8String& title, const UTF8String& message, size_t columnNum) // v 6.5.0 +{ + QApplication::setOverrideCursor (QCursor (Qt::ArrowCursor)); + const bool security = 0 == parent ? true : parent->isEnabled ( ); + + BEGIN_TRY_CATCH_BLOCK + + QtMessageDialog dialog (QMessageBox::Warning, parent, UTF8TOQSTRING (title),UTF8TOQSTRING (message), columnNum, "OK", 0, 0); + dialog.setProcessing (true); + dialog.show ( ); + while (true == dialog.isProcessing ( )) + QApplication::processEvents (QEventLoop::AllEvents, 1000); + + COMPLETE_TRY_CATCH_BLOCK + + if ((true == hasError) && (0 != parent) && (true == security)) + parent->setEnabled (true); + + QApplication::restoreOverrideCursor ( ); +} // QtMessageBox::displayWarningMessageInAppWorkspace + + int QtMessageBox::displayWarningMessage (QWidget* parent, const UTF8String& title, const UTF8String& message, size_t columnNum, const char* button0Text, const char* button1Text, const char* button2Text, int defaultButtonNumber) { QApplication::setOverrideCursor (QCursor (Qt::ArrowCursor)); @@ -264,6 +313,28 @@ void QtMessageBox::displayErrorMessage (QWidget* parent, const UTF8String& title } // QtMessageBox::displayErrorMessage +void QtMessageBox::displayErrorMessageInAppWorkspace (QWidget* parent, const UTF8String& title, const UTF8String& message, size_t columnNum) // v 6.5.0 +{ + QApplication::setOverrideCursor (QCursor (Qt::ArrowCursor)); + const bool security = 0 == parent ? true : parent->isEnabled ( ); + + BEGIN_TRY_CATCH_BLOCK + + QtMessageDialog dialog (QMessageBox::Critical, parent, UTF8TOQSTRING (title), UTF8TOQSTRING (message), columnNum, "OK", 0, 0); + dialog.setProcessing (true); + dialog.show ( ); + while (true == dialog.isProcessing ( )) + QApplication::processEvents (QEventLoop::AllEvents, 1000); + + COMPLETE_TRY_CATCH_BLOCK + + if ((true == hasError) && (0 != parent) && (true == security)) + parent->setEnabled (true); + + QApplication::restoreOverrideCursor ( ); +} // QtMessageBox::displayErrorMessageInAppWorkspace + + int QtMessageBox::displayErrorMessage (QWidget* parent, const UTF8String& title, const UTF8String& message, size_t columnNum, const char* button0Text, const char* button1Text, const char* button2Text, int defaultButtonNumber) { QApplication::setOverrideCursor (QCursor (Qt::ArrowCursor)); @@ -302,3 +373,39 @@ int QtMessageBox::displayQuestionMessage (QWidget* parent, const UTF8String& tit } // QtMessageBox::displayQuestionMessage +int QtMessageBox::systemNotification (const UTF8String& appTitle, const UTF8String& message, URGENCY_LEVEL level, size_t duration) // v 6.5.0 +{ + static bool available = true; + if (false == available) + return -1; + + unique_ptr notifySend (new Process ("notify-send")); + + BEGIN_TRY_CATCH_BLOCK + + notifySend->getOptions ( ).addOption ("-u"); + switch (level) + { + case QtMessageBox::URGENCY_LOW : notifySend->getOptions ( ).addOption ("low"); break; + case QtMessageBox::URGENCY_CRITICAL : notifySend->getOptions ( ).addOption ("critical");break; + default : notifySend->getOptions ( ).addOption ("normal"); + } // switch (level) + + char time [1024]; + sprintf (time, "%lu", duration); + notifySend->getOptions ( ).addOption ("-t"); + notifySend->getOptions ( ).addOption (time); + if (false == appTitle.empty ( )) + { + notifySend->getOptions ( ).addOption ("-a"); + notifySend->getOptions ( ).addOption (appTitle.utf8 ( )); + } // if (false == appTitle.empty ( )) + notifySend->getOptions ( ).addOption (message.utf8 ( )); + notifySend->execute (false); + notifySend->wait ( ); + available = 0 == notifySend->getCompletionCode ( ) ? true : false; + + COMPLETE_TRY_CATCH_BLOCK + + return notifySend->getCompletionCode ( ); +} // QtMessageBox::systemNotification diff --git a/src/QtUtil/public/QtUtil/QtMessageBox.h b/src/QtUtil/public/QtUtil/QtMessageBox.h index f1888d5..9c643e4 100644 --- a/src/QtUtil/public/QtUtil/QtMessageBox.h +++ b/src/QtUtil/public/QtUtil/QtMessageBox.h @@ -7,20 +7,19 @@ #include #include +#include /** - * Fonctions permettant d'afficher une boite de dialogue contenant un - * message. + * Fonctions permettant d'afficher une boite de dialogue contenant un message. */ class QtMessageBox { public : /** - * Boite de dialogue modale affichant un message d'information. Formate le - * message avant affichage. + * Boite de dialogue modale affichant un message d'information. Formate le message avant affichage. * @param Widget parent * @param Titre de la boite de dialogue. * @param Message à afficher. @@ -33,16 +32,14 @@ class QtMessageBox size_t columnNum = 100); /** - * Boite de dialogue modale affichant un message d'information. Formate le - * message avant affichage. + * Boite de dialogue modale affichant un message d'information. Formate le message avant affichage. * @param Widget parent * @param Titre de la boite de dialogue. * @param Message à afficher. * @param Nombre de colonnes du message. * @param Libellés des boutons * @param Bouton actif par défaut - * @return L'identifiant du bouton ayant provoqué la femeture de la - * boite de dialogue. + * @return L'identifiant du bouton ayant provoqué la femeture de la boite de dialogue. */ static int displayInformationMessage ( QWidget* parent, @@ -55,8 +52,7 @@ class QtMessageBox ); /** - * Boite de dialogue modale affichant un message d'avertissement. Formate le - * message avant affichage. + * Boite de dialogue modale affichant un message d'avertissement. Formate le message avant affichage. * @param Widget parent * @param Titre de la boite de dialogue. * @param Message à afficher. @@ -69,16 +65,25 @@ class QtMessageBox size_t columnNum = 100); /** - * Boite de dialogue modale affichant un message d'avertissement. Formate le - * message avant affichage. + * Boite de dialogue affichant un message d'avertissement. Formate le message avant affichage. Désactive l'éventuel parent le temps du message. Non modale, + * elle évite que la fenêtre parente ne passe dans le bureau virtuel courant et désorganise celui-ci. + * @param Widget parent + * @param Titre de la boite de dialogue. + * @param Message à afficher. + * @param Nombre de colonnes du message. + * @since 6.5.0 + */ + static void displayWarningMessageInAppWorkspace (QWidget* parent, const IN_UTIL UTF8String& title, const IN_UTIL UTF8String& message, size_t columnNum = 100); + + /** + * Boite de dialogue modale affichant un message d'avertissement. Formate le message avant affichage. * @param Widget parent * @param Titre de la boite de dialogue. * @param Message à afficher. * @param Libellés des boutons * @param Bouton actif par défaut * @param Nombre de colonnes du message. - * @return L'identifiant du bouton ayant provoqué la femeture de la - * boite de dialogue. + * @return L'identifiant du bouton ayant provoqué la femeture de la boite de dialogue. */ static int displayWarningMessage ( QWidget* parent, @@ -91,14 +96,12 @@ class QtMessageBox ); /** - * Boite de dialogue modale affichant un message d'erreur. Formate le - * message avant affichage. + * Boite de dialogue modale affichant un message d'erreur. Formate le message avant affichage. * @param Widget parent * @param Titre de la boite de dialogue. * @param Message à afficher. * @param Nombre de colonnes du message. - * @return L'identifiant du bouton ayant provoqué la femeture de la - * boite de dialogue. + * @return L'identifiant du bouton ayant provoqué la femeture de la boite de dialogue. */ static void displayErrorMessage ( QWidget* parent, @@ -106,6 +109,17 @@ class QtMessageBox const IN_UTIL UTF8String& message, size_t columnNum = 100); + /** + * Boite de dialogue affichant un message d'avertissement. Formate le message avant affichage. Désactive l'éventuel parent le temps du message. Non modale, + * elle évite que la fenêtre parente ne passe dans le bureau virtuel courant et désorganise celui-ci. + * @param Widget parent + * @param Titre de la boite de dialogue. + * @param Message à afficher. + * @param Nombre de colonnes du message. + * @since 6.5.0 + */ + static void displayErrorMessageInAppWorkspace (QWidget* parent, const IN_UTIL UTF8String& title, const IN_UTIL UTF8String& message, size_t columnNum = 100); + /** * Boite de dialogue modale affichant un message d'erreur. Formate le * message avant affichage. @@ -115,8 +129,7 @@ class QtMessageBox * @param Nombre de colonnes du message. * @param Libellés des boutons * @param Bouton actif par défaut - * @return L'identifiant du bouton ayant provoqué la femeture de la - * boite de dialogue. + * @return L'identifiant du bouton ayant provoqué la femeture de la boite de dialogue. */ static int displayErrorMessage ( QWidget* parent, @@ -129,15 +142,14 @@ class QtMessageBox ); /** - * Boite de dialogue modale affichant une question. Formate le - * message avant affichage. + * Boite de dialogue modale affichant une question. Formate le message avant affichage. * @param Widget parent * @param Titre de la boite de dialogue. * @param Message à afficher. * @param Nombre de colonnes du message. * @param Libellés des boutons * @param Bouton actif par défaut - * @return L'identifiant du bouton ayant provoqué la femeture de la + * @return * boite de dialogue. */ static int displayQuestionMessage ( @@ -150,6 +162,19 @@ class QtMessageBox int defaultButtonNumber = 0 ); + enum URGENCY_LEVEL { URGENCY_LOW, URGENCY_NORMAL, URGENCY_CRITICAL }; + + /** + * Envoie la notification système transmise en argument. Repose sur notify-send. Attention, les caractères accentués semblent ne pas passer. + * @param Titre de l'application + * @param Message à afficher + * @param Niveau d'urgence + * @param Durée (en millisecondes) de la notification. + * @return 0 si la notification s'est bien passée, ou un code d'erreur. + * @since 6.5.0 + */ + static int systemNotification (const IN_UTIL UTF8String& appTitle, const IN_UTIL UTF8String& message, URGENCY_LEVEL level = URGENCY_NORMAL, size_t duration = 5000); + private : @@ -181,7 +206,12 @@ class QtMessageDialog : public QDialog int defaultButton = 0); virtual ~QtMessageDialog ( ); - + + // v 6.5.0 : possibilité de marquer la boite de dialogue comme en cours de traitement. + virtual void setProcessing (bool processing); + virtual bool isProcessing ( ) const + { return _processing; } + protected slots : @@ -194,6 +224,8 @@ class QtMessageDialog : public QDialog QtMessageDialog& operator = (const QtMessageDialog&); QPushButton* _buttons [3]; + bool _processing; // v 6.5.0 + bool _parentState; // v 6.5.0 le parent était-il actif ? }; // class QtMessageDialog diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index a6b043c..90feacd 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -16,3 +16,11 @@ target_link_libraries (qt_tests PUBLIC QtUtil) # INSTALL_RPATH modifie le rpath pour les libs internes au projet : set_target_properties (qt_tests PROPERTIES INSTALL_RPATH_USE_LINK_PATH 1 INSTALL_RPATH ${CMAKE_PACKAGE_RPATH_DIR}) + +# Mini-étude sur les espaces de travail : +add_executable (qworkspaces qworkspaces.cpp) +set_property (TARGET qworkspaces PROPERTY AUTOMOC ON) +target_include_directories (qworkspaces PRIVATE ../QtUtil/public ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_options (qworkspaces PRIVATE ${SHARED_CFLAGS}) +target_link_libraries (qworkspaces PUBLIC QtUtil) +set_target_properties (qworkspaces PROPERTIES INSTALL_RPATH_USE_LINK_PATH 1 INSTALL_RPATH ${CMAKE_PACKAGE_RPATH_DIR}) diff --git a/src/tests/qworkspaces.cpp b/src/tests/qworkspaces.cpp new file mode 100644 index 0000000..d1eed9b --- /dev/null +++ b/src/tests/qworkspaces.cpp @@ -0,0 +1,182 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; +using namespace TkUtil; + + +class QtWSAboutDialog : public QtAboutDialog +{ + public : + + QtWSAboutDialog (QWidget* parent, const TkUtil::UTF8String& title, const std::string& version, + const std::string& url, const TkUtil::UTF8String& description, int workspace) + : QtAboutDialog (parent, title, version, url, description), _workspace (workspace) + { } + virtual ~QtWSAboutDialog ( ) + { } + + + protected : + + void showEvent (QShowEvent *event); + + + private : + + int _workspace; +}; // class QtWSAboutDialog + + +void QtWSAboutDialog::showEvent (QShowEvent* event) +{ + cout << "QtWSAboutDialog::showEvent called for workspace " << _workspace << endl; + QtAboutDialog::showEvent (event); +} // QtWSAboutDialog::showEvent + + +QtWorkspacesMainWindow::QtWorkspacesMainWindow (QWidget* parent) + : QMainWindow (parent) +{ + QMenu* menu = new QMenu ("Application", this); + menuBar ( )->addMenu (menu); + menu->setToolTipsVisible (true); + menu->addAction ("1 boite de dialogue au centre de chaque espaces de travail ...", this, SLOT (dialogForEachWorkspaceCallback ( ))); + menu->addAction ("1 boite de dialogue d'avertissement qui apparait dans 3 secondes ...", this, SLOT (timeoutWarningDialogCallback ( ))); + menu->addAction ("1 boite de dialogue d'erreur qui apparait dans 5 secondes ...", this, SLOT (timeoutErrorDialogCallback ( ))); + menu->addAction ("Quitter", this, SLOT (exitCallback ( ))); +} // QtWorkspacesMainWindow::QtAboutMainWindow + + +void QtWorkspacesMainWindow::dialogForEachWorkspaceCallback ( ) +{ + try + { + QList screens = QGuiApplication::screens ( ); + for (int i = 0; i < screens.count ( ); i++) + { + QScreen* screen = screens [i]; + const QRect geometry = screen->geometry ( ); + UTF8String message; + message << "Screen " << (long)i << " geometry : (" << (long)geometry.x ( ) << ", " << (long)geometry.y ( ) + << ", " << (long)geometry.width ( ) << ", " << (long)geometry.height ( ) << ")"; + QtWSAboutDialog* dialog = new QtWSAboutDialog (0, "App. test", "1.0.0", "http://www.myapp.com", message, i); + dialog->updateGeometry( ); + dialog->setGeometry (geometry.x ( ) + (geometry.width ( ) - dialog->width ( )) / 2, geometry.y ( ) + (geometry.height ( ) - dialog->height ( )) / 2, + dialog->width ( ), dialog->height ( )); + dialog->show ( ); + } // for (int i = 0; i < screens.count ( ); i++) + } + catch (const Exception& exc) + { + cerr << "Exception : " << exc.getFullMessage ( ) << endl; + } + catch (const exception& e) + { + cerr << "Exception : " << e.what ( ) << endl; + } + catch (...) + { + cerr << "Exception non documentée." << endl; + } +} // QtWorkspacesMainWindow::dialogForEachWorkspaceCallback + + +void QtWorkspacesMainWindow::timeoutWarningDialogCallback ( ) +{ + try + { + sleep (3); + QtMessageBox::systemNotification (UTF8String ("Dialogs and Workspaces"), UTF8String ("Un message d'avertissement a été affiché."), QtMessageBox::URGENCY_NORMAL, 10000); + QtMessageBox::displayWarningMessageInAppWorkspace (this, "Dialogs and Workspaces", "Ceci est un message d'avertissement"); +// QtWSAboutDialog* dialog = new QtWSAboutDialog (this, "App. test", "1.0.0", "http://www.myapp.com", "Boite de dialogue affichée après un délai de 5 secondes.", 0); +// dialog->show ( ); // S'affiche dans le même bureau que l'application, sans changement de bureau +//dialog->exec ( ); // Change la boite de dialogue et l'application de bureau pour rejoindre celui qui a le focus. + } + catch (const Exception& exc) + { + cerr << "Exception : " << exc.getFullMessage ( ) << endl; + } + catch (const exception& e) + { + cerr << "Exception : " << e.what ( ) << endl; + } + catch (...) + { + cerr << "Exception non documentée." << endl; + } +} // QtWorkspacesMainWindow::timeoutWarningDialogCallback + + +void QtWorkspacesMainWindow::timeoutErrorDialogCallback ( ) +{ + try + { + sleep (5); + QtMessageBox::systemNotification (UTF8String ("Dialogs and Workspaces"), UTF8String ("Un message d'erreur a été affiché."), QtMessageBox::URGENCY_CRITICAL, 3000); + QtMessageBox::displayErrorMessageInAppWorkspace (this, "Dialogs and Workspaces", "Ceci est un message d'erreur"); +// QtWSAboutDialog* dialog = new QtWSAboutDialog (this, "App. test", "1.0.0", "http://www.myapp.com", "Boite de dialogue affichée après un délai de 5 secondes.", 0); +// dialog->show ( ); // S'affiche dans le même bureau que l'application, sans changement de bureau +//dialog->exec ( ); // Change la boite de dialogue et l'application de bureau pour rejoindre celui qui a le focus. + } + catch (const Exception& exc) + { + cerr << "Exception : " << exc.getFullMessage ( ) << endl; + } + catch (const exception& e) + { + cerr << "Exception : " << e.what ( ) << endl; + } + catch (...) + { + cerr << "Exception non documentée." << endl; + } +} // QtWorkspacesMainWindow::timeoutErrorDialogCallback + + +void QtWorkspacesMainWindow::exitCallback ( ) +{ + switch (QtMessageBox::displayQuestionMessage (this, "QUITTER", "Souhaitez-vous réellement quitter l'application ?", 100, "OK", "Annuler", 0, 0)) + { + case 0 : + QApplication::exit (0); + break; + } +} // QtWorkspacesMainWindow::exitCallback + + +int main (int argc, char* argv[], char* envp []) +{ + try + { + Process::initialize (argc, argv, envp); + QApplication app (argc, argv); + QtWorkspacesMainWindow* window = new QtWorkspacesMainWindow (0); + window->show ( ); + + return app.exec ( ); + } + catch (const Exception& exc) + { + cerr << "Erreur : " << exc.getFullMessage ( ) << endl; + return -1; + } + catch (...) + { + cerr << "Erreur non documentée." << endl; + return -1; + } + + return 0; +} // main diff --git a/src/tests/qworkspaces.h b/src/tests/qworkspaces.h new file mode 100644 index 0000000..5b43815 --- /dev/null +++ b/src/tests/qworkspaces.h @@ -0,0 +1,30 @@ +#ifndef QT_WORKSPACES_H +#define QT_WORKSPACES_H + +#include "QtUtil/QtAboutDialog.h" + +#include + + +class QtWorkspacesMainWindow : public QMainWindow +{ + Q_OBJECT + + public : + + QtWorkspacesMainWindow (QWidget* parent); + + + protected slots : + + void dialogForEachWorkspaceCallback ( ); + void timeoutWarningDialogCallback ( ); + void timeoutErrorDialogCallback ( ); + void exitCallback ( ); + + + private : + +}; // class QtWorkspacesMainWindow + +#endif // QT_WORKSPACES_H diff --git a/versions.txt b/versions.txt index 060889d..1e26cef 100644 --- a/versions.txt +++ b/versions.txt @@ -1,3 +1,20 @@ +Version 6.5.0 : 16/09/24 +=============== + +Méthodes QtMessageBox::displayWarningMessageInAppWorkspace et QtMessageBox::displayErrorMessageInAppWorkspace. +L'objectif est que l'affichage de ces boites de dialogues ne provoque pas l'affichage de l'application dans +un autre bureau de travail que celui initial. Ceci se produit lorsque la boite de dialogue est modale, que +l'utilisateur a lancé une opération longue et changer de bureau pour faire autre chose, et que l'opération +se termine par l'affichage d'une boite de dialogue. Le changement de bureau désorganise le bureau courrant +(Issue Magix3D #112). + +Ces boites de dialogues désactivent la fenêtre parent (parent->setEnabled (false)) le temps de l'affichage de +la boite de dialogue. Selon le gestionnaire de fenêtre la boite de dialogue s'ouvrira dans le bureau courrant +ou dans le bureau initial de l'application, mais la fenêtre principale ne changera pas de bureau. + +Méthode QtMessageBox::systemNotification d'envoi d'un message au système de notification. + + Version 6.4.2 : 28/04/24 ===============