Browse Source

Add multi-thread support to ErrorLog model

* Each message handler have been updated to use mutex and appropriate
data structure to properly handle threaded situation.

* To ensure that the underlying ItemModel associated with the ErrorLogModel
is updated in the main thread (GUI thread), each message handler transmit
the message to log using either QueuedConnection (asynchronous) or
a BlockingQueuedConnection (synchronous).

* Methods setAsynchronousLogging allow to change how
behaves the model at runtime.

* Convenient testing function associated with the ErrorLogModel have been
moved into a single "cpp" file (ctkErrorLogModelTestHelper.cpp) that can
be included from the different testing source files.

* Rename tests with a more meaningful name

* Control over which messages are displayed on the terminal has been
revisited. The method terminalOutputs/setTerminalOutputs now understand
the flags TerminalOutputs:{None, StandardError, StandardOutput, All}
Jean-Christophe Fillion-Robin 13 years ago
parent
commit
6d676b24cc
28 changed files with 1737 additions and 797 deletions
  1. 11 9
      Libs/Core/Testing/Cpp/CMakeLists.txt
  2. 107 0
      Libs/Core/Testing/Cpp/ctkErrorLogFDMessageHandlerWithThreadsTest1.cpp
  3. 7 59
      Libs/Core/Testing/Cpp/ctkErrorLogModelTest2.cpp
  4. 201 0
      Libs/Core/Testing/Cpp/ctkErrorLogModelTerminalOutputTest1.cpp
  5. 24 86
      Libs/Core/Testing/Cpp/ctkErrorLogModelTest1.cpp
  6. 0 196
      Libs/Core/Testing/Cpp/ctkErrorLogModelTest3.cpp
  7. 1 1
      Libs/Core/Testing/Cpp/ctkErrorLogModelTest4.cpp
  8. 235 0
      Libs/Core/Testing/Cpp/ctkErrorLogModelTestHelper.cpp
  9. 104 0
      Libs/Core/Testing/Cpp/ctkErrorLogQtMessageHandlerWithThreadsTest1.cpp
  10. 102 0
      Libs/Core/Testing/Cpp/ctkErrorLogStreamMessageHandlerWithThreadsTest1.cpp
  11. 92 71
      Libs/Core/ctkErrorLogFDMessageHandler.cpp
  12. 22 17
      Libs/Core/ctkErrorLogFDMessageHandler_p.h
  13. 335 85
      Libs/Core/ctkErrorLogModel.cpp
  14. 122 46
      Libs/Core/ctkErrorLogModel.h
  15. 10 11
      Libs/Core/ctkErrorLogQtMessageHandler.cpp
  16. 55 24
      Libs/Core/ctkErrorLogStreamMessageHandler.cpp
  17. 4 2
      Libs/ImageProcessing/ITK/Core/Testing/Cpp/CMakeLists.txt
  18. 107 0
      Libs/ImageProcessing/ITK/Core/Testing/Cpp/ctkITKErrorLogMessageHandlerWithThreadsTest1.cpp
  19. 8 43
      Libs/ImageProcessing/ITK/Core/Testing/Cpp/ctkITKErrorLogModelTest1.cpp
  20. 24 8
      Libs/ImageProcessing/ITK/Core/ctkITKErrorLogMessageHandler.cpp
  21. 2 0
      Libs/Visualization/VTK/Core/Testing/Cpp/CMakeLists.txt
  22. 107 0
      Libs/Visualization/VTK/Core/Testing/Cpp/ctkVTKErrorLogMessageHandlerWithThreadsTest1.cpp
  23. 6 59
      Libs/Visualization/VTK/Core/Testing/Cpp/ctkVTKErrorLogModelTest1.cpp
  24. 24 8
      Libs/Visualization/VTK/Core/ctkVTKErrorLogMessageHandler.cpp
  25. 7 58
      Libs/Widgets/Testing/Cpp/ctkErrorLogWidgetTest1.cpp
  26. 7 3
      Libs/Widgets/ctkErrorLogStatusMessageHandler.cpp
  27. 1 1
      Libs/Widgets/ctkErrorLogStatusMessageHandler.h
  28. 12 10
      Libs/Widgets/ctkErrorLogWidget.cpp

+ 11 - 9
Libs/Core/Testing/Cpp/CMakeLists.txt

@@ -26,9 +26,12 @@ SET(KITTests_SRCS
   ctkCheckableModelHelperTest1.cpp
   ctkCommandLineParserTest1.cpp
   ctkErrorLogModelTest1.cpp
-  ctkErrorLogModelTest2.cpp
-  ctkErrorLogModelTest3.cpp
+  ctkErrorLogModelEntryGroupingTest1.cpp
+  ctkErrorLogModelTerminalOutputTest1.cpp
   ctkErrorLogModelTest4.cpp
+  ctkErrorLogFDMessageHandlerWithThreadsTest1.cpp
+  ctkErrorLogQtMessageHandlerWithThreadsTest1.cpp
+  ctkErrorLogStreamMessageHandlerWithThreadsTest1.cpp
   ctkHistogramTest1.cpp
   ctkLoggerTest1.cpp
   ctkModelTesterTest1.cpp
@@ -104,10 +107,8 @@ ENDIF()
 ADD_EXECUTABLE(${KIT}CppTests ${Tests} ${Tests_Helpers_SRCS} ${Tests_Helpers_MOC_CPP})
 TARGET_LINK_LIBRARIES(${KIT}CppTests ${LIBRARY_NAME} ${CTK_BASE_LIBRARIES} CTKDummyPlugin)
 
-SET( KIT_TESTS ${CPP_TEST_PATH}/${KIT}CppTests)
-
 MACRO( SIMPLE_TEST  TESTNAME )
-  ADD_TEST( ${TESTNAME} ${KIT_TESTS} ${TESTNAME} )
+  ADD_TEST(NAME ${TESTNAME} COMMAND $<TARGET_FILE:${KIT}CppTests> ${TESTNAME} ${ARGN})
   SET_PROPERTY(TEST ${TESTNAME} PROPERTY LABELS ${PROJECT_NAME})
 ENDMACRO( SIMPLE_TEST  )
 
@@ -132,13 +133,14 @@ SIMPLE_TEST( ctkCommandLineParserTest1 )
 SIMPLE_TEST( ctkDependencyGraphTest1 )
 SIMPLE_TEST( ctkDependencyGraphTest2 )
 SIMPLE_TEST( ctkErrorLogModelTest1 )
-SIMPLE_TEST( ctkErrorLogModelTest2 )
-SIMPLE_TEST( ctkErrorLogModelTest3 )
+SIMPLE_TEST( ctkErrorLogModelEntryGroupingTest1 )
+SIMPLE_TEST( ctkErrorLogModelTerminalOutputTest1 --test-launcher $<TARGET_FILE:${KIT}CppTests>)
 SIMPLE_TEST( ctkErrorLogModelTest4 )
+SIMPLE_TEST( ctkErrorLogFDMessageHandlerWithThreadsTest1 )
+SIMPLE_TEST( ctkErrorLogQtMessageHandlerWithThreadsTest1 )
+SIMPLE_TEST( ctkErrorLogStreamMessageHandlerWithThreadsTest1 )
 SIMPLE_TEST( ctkHistogramTest1 )
 SIMPLE_TEST( ctkLoggerTest1 )
-SET_TESTS_PROPERTIES(ctkErrorLogModelTest3 PROPERTIES PASS_REGULAR_EXPRESSION
-"This is a qDebug message\nThis is a std::cerr message\nThis is a qWarning message\nThis is a std::cout message\nThis is a qCritical message\n")
 SIMPLE_TEST( ctkModelTesterTest1 )
 SIMPLE_TEST( ctkModelTesterTest2 )
 SIMPLE_TEST( ctkPimplTest1 )

+ 107 - 0
Libs/Core/Testing/Cpp/ctkErrorLogFDMessageHandlerWithThreadsTest1.cpp

@@ -0,0 +1,107 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QCoreApplication>
+#include <QDebug>
+
+// CTK includes
+#include "ctkErrorLogFDMessageHandler.h"
+#include "ctkModelTester.h"
+
+// STL includesQList
+#include <cstdlib>
+#include <iostream>
+
+// Helper functions
+#include "Testing/Cpp/ctkErrorLogModelTestHelper.cpp"
+
+namespace
+{
+//-----------------------------------------------------------------------------
+class LogFDMessageThread : public LogMessageThread
+{
+public:
+  LogFDMessageThread(int id, int maxIteration) : LogMessageThread(id, maxIteration){}
+
+  virtual void logMessage(const QDateTime& dateTime, int threadId, int counterIdx)
+  {
+    QString msg = QString("counterIdx:%1 - %2 - Message from thread: %3\n")
+        .arg(counterIdx).arg(dateTime.toString()).arg(threadId);
+
+    fprintf(stdout, "%s", qPrintable(msg));
+    fflush(stdout);
+
+    fprintf(stderr, "%s", qPrintable(msg));
+    fflush(stderr);
+  }
+};
+
+}
+
+//-----------------------------------------------------------------------------
+int ctkErrorLogFDMessageHandlerWithThreadsTest1(int argc, char * argv [])
+{
+  QCoreApplication app(argc, argv);
+  Q_UNUSED(app);
+
+  ctkErrorLogModel model;
+  ctkModelTester modelTester;
+  modelTester.setVerbose(false);
+
+  try
+    {
+    modelTester.setModel(&model);
+
+    // --------------------------------------------------------------------------
+    // Monitor FD messages
+
+    model.registerMsgHandler(new ctkErrorLogFDMessageHandler);
+    model.setMsgHandlerEnabled(ctkErrorLogFDMessageHandler::HandlerName, true);
+
+    int threadCount = 15;
+    int maxIteration = 5;
+    int messagesPerIteration = 2;
+
+    startLogMessageThreads<LogFDMessageThread>(threadCount, maxIteration);
+
+    // Give enough time for the threads to send their messages
+    QTimer::singleShot(1500, qApp, SLOT(quit()));
+    app.exec();
+
+    int expectedMessageCount = threadCount * maxIteration * messagesPerIteration;
+    QString errorMsg = checkRowCount(__LINE__, model.rowCount(), /* expected = */ expectedMessageCount);
+    if (!errorMsg.isEmpty())
+      {
+      model.disableAllMsgHandler();
+      printErrorMessage(errorMsg);
+      printTextMessages(model);
+      return EXIT_FAILURE;
+      }
+    }
+  catch (const char* error)
+    {
+    model.disableAllMsgHandler();
+    std::cerr << error << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  return EXIT_SUCCESS;
+}

+ 7 - 59
Libs/Core/Testing/Cpp/ctkErrorLogModelTest2.cpp

@@ -21,10 +21,8 @@
 // Qt includes
 #include <QCoreApplication>
 #include <QDebug>
-#include <QStringList>
 
 // CTK includes
-#include "ctkErrorLogModel.h"
 #include "ctkErrorLogQtMessageHandler.h"
 #include "ctkModelTester.h"
 
@@ -32,65 +30,11 @@
 #include <cstdlib>
 #include <iostream>
 
-namespace
-{
-//-----------------------------------------------------------------------------
-// Utility function
-
-//-----------------------------------------------------------------------------
-QString checkRowCount(int line, int currentRowCount, int expectedRowCount)
-{
-  if (currentRowCount != expectedRowCount)
-    {
-    QString errorMsg("Line %1 - Expected rowCount: %2 - Current rowCount: %3\n");
-    return errorMsg.arg(line).arg(expectedRowCount).arg(currentRowCount);
-    }
-  return QString();
-}
+// Helper functions
+#include "Testing/Cpp/ctkErrorLogModelTestHelper.cpp"
 
 //-----------------------------------------------------------------------------
-QString checkTextMessages(int line, const ctkErrorLogModel& model, const QStringList& expectedMessages)
-{
-  for(int i=0; i < expectedMessages.count(); ++i)
-    {
-    QModelIndex descriptionIndex = model.index(i, ctkErrorLogModel::DescriptionColumn);
-    QString currentMessage = descriptionIndex.data(ctkErrorLogModel::DescriptionTextRole).toString();
-    if (currentMessage.compare(expectedMessages.value(i)) != 0)
-      {
-      QString errorMsg("Line %1 - Problem with row%2 !\n"
-                       "\tExpected message [%3]\n"
-                       "\tCurrent message [%4]\n");
-      return errorMsg.arg(line).arg(i).arg(expectedMessages.value(i)).arg(currentMessage);
-      }
-    }
-  return QString();
-}
-
-//-----------------------------------------------------------------------------
-void printTextMessages(const ctkErrorLogModel& model)
-{
-  fprintf(stdout, "%s", "ErrorLogModel rows:\n");
-  QString text("\trow %1 => %2\n");
-  for (int i=0; i < model.rowCount(); ++i)
-    {
-    QString description =
-        model.index(0, ctkErrorLogModel::DescriptionColumn).data().toString();
-    fprintf(stdout, "%s", qPrintable(text.arg(i).arg(description)));
-    }
-  fflush(stdout);
-}
-
-//-----------------------------------------------------------------------------
-void printErrorMessage(const QString& errorMessage)
-{
-  fprintf(stderr, "%s", qPrintable(errorMessage));
-  fflush(stderr);
-}
-
-} // end namespace
-
-//-----------------------------------------------------------------------------
-int ctkErrorLogModelTest2(int argc, char * argv [])
+int ctkErrorLogModelEntryGroupingTest1(int argc, char * argv [])
 {
   QCoreApplication app(argc, argv);
   Q_UNUSED(app);
@@ -128,6 +72,9 @@ int ctkErrorLogModelTest2(int argc, char * argv [])
     QString qtMessage2b("This is a qCritical message - 2");
     qCritical().nospace() << qPrintable(qtMessage2b);
 
+    // Give enough time to the ErrorLogModel to consider the queued messages.
+    processEvents(1000);
+
     QStringList expectedQtMessages;
     expectedQtMessages << qtMessage0.append("\n").append(qtMessage0b)
                        << qtMessage1
@@ -147,6 +94,7 @@ int ctkErrorLogModelTest2(int argc, char * argv [])
       {
       model.disableAllMsgHandler();
       printErrorMessage(errorMsg);
+      printTextMessages(model);
       return EXIT_FAILURE;
       }
 

+ 201 - 0
Libs/Core/Testing/Cpp/ctkErrorLogModelTerminalOutputTest1.cpp

@@ -0,0 +1,201 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QCoreApplication>
+#include <QDebug>
+#include <QProcess>
+
+// CTK includes
+#include "ctkCommandLineParser.h"
+#include "ctkErrorLogFDMessageHandler.h"
+#include "ctkErrorLogQtMessageHandler.h"
+#include "ctkErrorLogStreamMessageHandler.h"
+#include "ctkModelTester.h"
+
+// STL includes
+#include <cstdio>
+#include <cstdlib>
+#include <iostream>
+
+// Helper functions
+#include "Testing/Cpp/ctkErrorLogModelTestHelper.cpp"
+
+namespace
+{
+//-----------------------------------------------------------------------------
+bool checkTerminalOutput(const QStringList& expectedMessages)
+{
+  ctkCommandLineParser parser;
+  parser.setArgumentPrefix("--", "-");
+  parser.addArgument("test-launcher", "", QVariant::String, "Path to test launcher");
+  bool ok = false;
+  QHash<QString, QVariant> parsedArgs = parser.parseArguments(QCoreApplication::arguments(), &ok);
+  if(!ok)
+    {
+    std::cerr << "Line " << __LINE__ << " - Failed to parse arguments !" << std::endl;
+    return false;
+    }
+  if (parsedArgs.contains("test-launcher"))
+    {
+    QString testLauncher = parsedArgs.value("test-launcher").toString();
+    if (!QFile::exists(testLauncher))
+      {
+      std::cerr << "Line " << __LINE__ << " - Couldn't find test launcher !\n"
+                << "\ttest-launcher:" << qPrintable(testLauncher) << std::endl;
+      return false;
+      }
+    QProcess process;
+    process.setProcessChannelMode(QProcess::MergedChannels);
+    process.start(testLauncher, QStringList() << QCoreApplication::arguments().at(0));
+    process.waitForFinished(1000);
+    QString output = process.readAll();
+    QString errorMsg = checkTextMessages(__LINE__, output.split("\n"), expectedMessages);
+    if (!errorMsg.isEmpty())
+      {
+      printErrorMessage(errorMsg);
+      return false;
+      }
+    process.waitForFinished(1000);
+    }
+  return true;
+}
+}
+
+//-----------------------------------------------------------------------------
+int ctkErrorLogModelTerminalOutputTest1(int argc, char * argv [])
+{
+  QCoreApplication app(argc, argv);
+  Q_UNUSED(app);
+
+  QString fdMessage0("This is stdout message");
+  QString fdMessage1("This is stderr message");
+  QString qtMessage0("This is a qDebug message");
+  QString qtMessage1("This is a qWarning message");
+  QString qtMessage2("This is a qCritical message");
+  QString stdMessage0("This is a std::cerr message");
+  QString stdMessage1("This is a std::cout message");
+
+  QStringList expectedMessages;
+  expectedMessages << fdMessage0 << fdMessage1
+                   << qtMessage0 << qtMessage1 << qtMessage2
+                   << stdMessage0 << stdMessage1;
+
+  // Since the order of the messages outputed on the terminal is not deterministic,
+  // let's just make sure that all messages have been displayed on the terminal
+  // independently of their order.
+  if (!checkTerminalOutput(expectedMessages))
+    {
+    return EXIT_FAILURE;
+    }
+
+  QString errorMsg;
+
+  ctkErrorLogModel model;
+  ctkErrorLogModel::TerminalOutputs currentTerminalOutputEnabled = model.terminalOutputs();
+  errorMsg = checkBoolean(__LINE__, "TerminalOutputEnabled",
+                          currentTerminalOutputEnabled, ctkErrorLogModel::None);
+  if (!errorMsg.isEmpty())
+    {
+    model.disableAllMsgHandler();
+    printErrorMessage(errorMsg);
+    printTextMessages(model);
+    return EXIT_FAILURE;
+    }
+
+  model.setTerminalOutputs(ctkErrorLogModel::All);
+
+  currentTerminalOutputEnabled = model.terminalOutputs();
+  errorMsg = checkBoolean(__LINE__, "TerminalOutputEnabled",
+                          currentTerminalOutputEnabled, ctkErrorLogModel::All);
+  if (!errorMsg.isEmpty())
+    {
+    model.disableAllMsgHandler();
+    printErrorMessage(errorMsg);
+    printTextMessages(model);
+    return EXIT_FAILURE;
+    }
+
+  ctkModelTester modelTester;
+  modelTester.setVerbose(false);
+
+  try
+    {
+    modelTester.setModel(&model);
+
+    // Monitor Qt messages
+    model.registerMsgHandler(new ctkErrorLogQtMessageHandler);
+    model.setMsgHandlerEnabled(ctkErrorLogQtMessageHandler::HandlerName, true);
+
+    // Monitor Stream messages
+    model.registerMsgHandler(new ctkErrorLogStreamMessageHandler);
+    model.setMsgHandlerEnabled(ctkErrorLogStreamMessageHandler::HandlerName, true);
+
+    // Monitor FD messages
+    model.registerMsgHandler(new ctkErrorLogFDMessageHandler);
+    model.setMsgHandlerEnabled(ctkErrorLogFDMessageHandler::HandlerName, true);
+
+    fprintf(stdout, "%s\n", qPrintable(fdMessage0));
+    fflush(stdout);
+
+    qDebug().nospace() << qPrintable(qtMessage0);
+
+    std::cerr << qPrintable(stdMessage0) << std::endl;
+
+    qWarning().nospace() << qPrintable(qtMessage1);
+
+    fprintf(stderr, "%s\n", qPrintable(fdMessage1));
+    fflush(stderr);
+
+    std::cout << qPrintable(stdMessage1) << std::endl;
+
+    qCritical().nospace() << qPrintable(qtMessage2);
+
+    // Give enough time to the ErrorLogModel to consider the queued messages.
+    processEvents(1000);
+
+    errorMsg = checkRowCount(__LINE__, model.rowCount(), /* expected = */ expectedMessages.count());
+    if (!errorMsg.isEmpty())
+      {
+      model.disableAllMsgHandler();
+      printErrorMessage(errorMsg);
+      printTextMessages(model);
+      return EXIT_FAILURE;
+      }
+
+    errorMsg = checkTextMessages(__LINE__, model, expectedMessages);
+    if (!errorMsg.isEmpty())
+      {
+      model.disableAllMsgHandler();
+      printErrorMessage(errorMsg);
+      printTextMessages(model);
+      return EXIT_FAILURE;
+      }
+
+    }
+  catch (const char* error)
+    {
+    model.disableAllMsgHandler();
+    std::cerr << error << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  return EXIT_SUCCESS;
+}

+ 24 - 86
Libs/Core/Testing/Cpp/ctkErrorLogModelTest1.cpp

@@ -21,11 +21,8 @@
 // Qt includes
 #include <QCoreApplication>
 #include <QDebug>
-#include <QStringList>
-#include <QTimer>
 
 // CTK includes
-#include "ctkErrorLogModel.h"
 #include "ctkErrorLogFDMessageHandler.h"
 #include "ctkErrorLogQtMessageHandler.h"
 #include "ctkErrorLogStreamMessageHandler.h"
@@ -35,74 +32,8 @@
 #include <cstdlib>
 #include <iostream>
 
-namespace
-{
-//-----------------------------------------------------------------------------
-// Utility function
-
-//-----------------------------------------------------------------------------
-QString checkRowCount(int line, int currentRowCount, int expectedRowCount)
-{
-  if (currentRowCount != expectedRowCount)
-    {
-    QString errorMsg("Line %1 - Expected rowCount: %2 - Current rowCount: %3\n");
-    return errorMsg.arg(line).arg(expectedRowCount).arg(currentRowCount);
-    }
-  return QString();
-}
-
-//-----------------------------------------------------------------------------
-QString checkTextMessages(int line, const ctkErrorLogModel& model, const QStringList& expectedMessages)
-{
-  for(int i=0; i < expectedMessages.count(); ++i)
-    {
-    QModelIndex descriptionIndex = model.index(i, ctkErrorLogModel::DescriptionColumn);
-    QString currentMessage = descriptionIndex.data(ctkErrorLogModel::DescriptionTextRole).toString();
-    if (currentMessage.compare(expectedMessages.value(i)) != 0)
-      {
-      QString errorMsg("Line %1 - Problem with row%2 !\n"
-                       "\tExpected message [%3]\n"
-                       "\tCurrent message [%4]\n");
-      return errorMsg.arg(line).arg(i).arg(expectedMessages.value(i)).arg(currentMessage);
-      }
-    }
-  return QString();
-}
-
-//-----------------------------------------------------------------------------
-void printTextMessages(const ctkErrorLogModel& model)
-{
-  fprintf(stdout, "%s", "ErrorLogModel rows:\n");
-  QString text("\trow %1 => %2\n");
-  for (int i=0; i < model.rowCount(); ++i)
-    {
-    QString description =
-        model.index(0, ctkErrorLogModel::DescriptionColumn).data().toString();
-    fprintf(stdout, "%s", qPrintable(text.arg(i).arg(description)));
-    }
-  fflush(stdout);
-}
-
-//-----------------------------------------------------------------------------
-void printErrorMessage(const QString& errorMessage)
-{
-  fprintf(stderr, "%s", qPrintable(errorMessage));
-  fflush(stderr);
-}
-
-//-----------------------------------------------------------------------------
-QString checkInteger(int line, const char* valueName, int current, int expected)
-{
-  if (current != expected)
-    {
-    QString errorMsg("Line %1 - Expected %2: %3 - Current %4: %5\n");
-    return errorMsg.arg(line).arg(valueName).
-        arg(expected).arg(valueName).arg(current);
-    }
-  return QString();
-}
-
-} // end namespace
+// Helper functions
+#include "Testing/Cpp/ctkErrorLogModelTestHelper.cpp"
 
 //-----------------------------------------------------------------------------
 int ctkErrorLogModelTest1(int argc, char * argv [])
@@ -139,6 +70,7 @@ int ctkErrorLogModelTest1(int argc, char * argv [])
         {
         model.disableAllMsgHandler();
         printErrorMessage(errorMsg);
+        printTextMessages(model);
         return EXIT_FAILURE;
         }
 
@@ -151,6 +83,9 @@ int ctkErrorLogModelTest1(int argc, char * argv [])
       QString qtMessage2("This is a qCritical message");
       qCritical().nospace() << qPrintable(qtMessage2);
 
+      // Give enough time to the ErrorLogModel to consider the queued messages.
+      processEvents(1000);
+
       QStringList expectedQtMessages;
       expectedQtMessages << qtMessage0 << qtMessage1 << qtMessage2;
 
@@ -168,6 +103,7 @@ int ctkErrorLogModelTest1(int argc, char * argv [])
         {
         model.disableAllMsgHandler();
         printErrorMessage(errorMsg);
+        printTextMessages(model);
         return EXIT_FAILURE;
         }
 
@@ -179,6 +115,7 @@ int ctkErrorLogModelTest1(int argc, char * argv [])
         {
         model.disableAllMsgHandler();
         printErrorMessage(errorMsg);
+        printTextMessages(model);
         return EXIT_FAILURE;
         }
 
@@ -214,6 +151,7 @@ int ctkErrorLogModelTest1(int argc, char * argv [])
         model.disableAllMsgHandler();
         errorMsg = QLatin1String("Line %1 - Qt message handler should be disabled");
         printErrorMessage(errorMsg.arg(__LINE__));
+        printTextMessages(model);
         return EXIT_FAILURE;
         }
 
@@ -232,6 +170,9 @@ int ctkErrorLogModelTest1(int argc, char * argv [])
       QString streamMessage1("This is a Cerr message");
       std::cerr << qPrintable(streamMessage1) << std::endl;
 
+      // Give enough time to the ErrorLogModel to consider the queued messages.
+      processEvents(1000);
+
       QStringList expectedStreamMessages;
       expectedStreamMessages << streamMessage0 << streamMessage1;
 
@@ -249,6 +190,7 @@ int ctkErrorLogModelTest1(int argc, char * argv [])
         {
         model.disableAllMsgHandler();
         printErrorMessage(errorMsg);
+        printTextMessages(model);
         return EXIT_FAILURE;
         }
 
@@ -293,9 +235,9 @@ int ctkErrorLogModelTest1(int argc, char * argv [])
       fdMessage0.append(fdMessage0b);
       fflush(stdout);
 
-//      QString fdMessage1("This is a 2nd stdout message");
-//      fprintf(stdout, "%s\n", qPrintable(fdMessage1));
-//      fflush(stdout);
+      QString fdMessage1("This is a 2nd stdout message");
+      fprintf(stdout, "%s\n", qPrintable(fdMessage1));
+      fflush(stdout);
 
       QString fdMessage2("This is a stderr");
       fprintf(stderr, "%s", qPrintable(fdMessage2));
@@ -304,22 +246,17 @@ int ctkErrorLogModelTest1(int argc, char * argv [])
       fdMessage2.append(fdMessage2b);
       fflush(stderr);
 
-//      QString fdMessage3("This is a 2nd stderr message");
-//      fprintf(stderr, "%s\n", qPrintable(fdMessage3));
-//      fflush(stderr);
+      QString fdMessage3("This is a 2nd stderr message");
+      fprintf(stderr, "%s\n", qPrintable(fdMessage3));
+      fflush(stderr);
 
       QStringList expectedFDMessages;
-      expectedFDMessages << fdMessage0 /*<< fdMessage1*/ << fdMessage2 /*<< fdMessage3*/;
+      expectedFDMessages << fdMessage0 << fdMessage1 << fdMessage2 << fdMessage3;
 
-      // Give enough time to the QFileSystemWatcher used internally by ctkErrorLogFDMessageHandler
+      // Give enough time to the ErrorLogModel to consider the queued messages.
+      // and also to the QFileSystemWatcher used internally by ctkErrorLogFDMessageHandler
       // to consider the updated files.
-      QTimer timer;
-      timer.setSingleShot(true);
-      timer.start(1000);
-      while(timer.isActive())
-        {
-        QCoreApplication::processEvents();
-        }
+      processEvents(1500);
 
       errorMsg = checkRowCount(__LINE__, model.rowCount(), /* expected = */ expectedFDMessages.count());
       if (!errorMsg.isEmpty())
@@ -335,6 +272,7 @@ int ctkErrorLogModelTest1(int argc, char * argv [])
         {
         model.disableAllMsgHandler();
         printErrorMessage(errorMsg);
+        printTextMessages(model);
         return EXIT_FAILURE;
         }
 

+ 0 - 196
Libs/Core/Testing/Cpp/ctkErrorLogModelTest3.cpp

@@ -1,196 +0,0 @@
-/*=========================================================================
-
-  Library:   CTK
-
-  Copyright (c) Kitware Inc.
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-      http://www.apache.org/licenses/LICENSE-2.0.txt
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-
-=========================================================================*/
-
-// Qt includes
-#include <QCoreApplication>
-#include <QDebug>
-#include <QStringList>
-
-// CTK includes
-#include "ctkErrorLogModel.h"
-#include "ctkErrorLogQtMessageHandler.h"
-#include "ctkErrorLogStreamMessageHandler.h"
-#include "ctkModelTester.h"
-
-// STL includes
-#include <cstdlib>
-#include <iostream>
-
-namespace
-{
-//-----------------------------------------------------------------------------
-// Utility function
-
-//-----------------------------------------------------------------------------
-QString checkRowCount(int line, int currentRowCount, int expectedRowCount)
-{
-  if (currentRowCount != expectedRowCount)
-    {
-    QString errorMsg("Line %1 - Expected rowCount: %2 - Current rowCount: %3\n");
-    return errorMsg.arg(line).arg(expectedRowCount).arg(currentRowCount);
-    }
-  return QString();
-}
-
-//-----------------------------------------------------------------------------
-QString checkTextMessages(int line, const ctkErrorLogModel& model, const QStringList& expectedMessages)
-{
-  for(int i=0; i < expectedMessages.count(); ++i)
-    {
-    QModelIndex descriptionIndex = model.index(i, ctkErrorLogModel::DescriptionColumn);
-    QString currentMessage = descriptionIndex.data(ctkErrorLogModel::DescriptionTextRole).toString();
-    if (currentMessage.compare(expectedMessages.value(i)) != 0)
-      {
-      QString errorMsg("Line %1 - Problem with row%2 !\n"
-                       "\tExpected message [%3]\n"
-                       "\tCurrent message [%4]\n");
-      return errorMsg.arg(line).arg(i).arg(expectedMessages.value(i)).arg(currentMessage);
-      }
-    }
-  return QString();
-}
-
-//-----------------------------------------------------------------------------
-void printTextMessages(const ctkErrorLogModel& model)
-{
-  fprintf(stdout, "%s", "ErrorLogModel rows:\n");
-  QString text("\trow %1 => %2\n");
-  for (int i=0; i < model.rowCount(); ++i)
-    {
-    QString description =
-        model.index(0, ctkErrorLogModel::DescriptionColumn).data().toString();
-    fprintf(stdout, "%s", qPrintable(text.arg(i).arg(description)));
-    }
-  fflush(stdout);
-}
-
-//-----------------------------------------------------------------------------
-void printErrorMessage(const QString& errorMessage)
-{
-  fprintf(stderr, "%s", qPrintable(errorMessage));
-  fflush(stderr);
-}
-
-//-----------------------------------------------------------------------------
-QString checkBoolean(int line, const char* valueName, bool current, bool expected)
-{
-  if (current != expected)
-    {
-    QString errorMsg("Line %1 - Expected %2: %3 - Current %4: %5\n");
-    return errorMsg.arg(line).arg(valueName).
-        arg(static_cast<int>(expected)).arg(valueName).arg(static_cast<int>(current));
-    }
-  return QString();
-}
-
-} // end namespace
-
-//-----------------------------------------------------------------------------
-int ctkErrorLogModelTest3(int argc, char * argv [])
-{
-  QCoreApplication app(argc, argv);
-  Q_UNUSED(app);
-  QString errorMsg;
-    
-  ctkErrorLogModel model;
-  bool currentTerminalOutputEnabled = model.terminalOutputEnabled();
-  errorMsg = checkBoolean(__LINE__, "TerminalOutputEnabled", currentTerminalOutputEnabled, false);
-  if (!errorMsg.isEmpty())
-    {
-    model.disableAllMsgHandler();
-    printErrorMessage(errorMsg);
-    printTextMessages(model);
-    return EXIT_FAILURE;
-    }
-
-  model.setTerminalOutputEnabled(true);
-
-  currentTerminalOutputEnabled = model.terminalOutputEnabled();
-  errorMsg = checkBoolean(__LINE__, "TerminalOutputEnabled", currentTerminalOutputEnabled, true);
-  if (!errorMsg.isEmpty())
-    {
-    model.disableAllMsgHandler();
-    printErrorMessage(errorMsg);
-    printTextMessages(model);
-    return EXIT_FAILURE;
-    }
-
-  ctkModelTester modelTester;
-  modelTester.setVerbose(false);
-
-  try
-    {
-    modelTester.setModel(&model);
-
-    // Monitor Qt messages
-    model.registerMsgHandler(new ctkErrorLogQtMessageHandler);
-    model.setMsgHandlerEnabled(ctkErrorLogQtMessageHandler::HandlerName, true);
-
-    // Monitor Stream messages
-    model.registerMsgHandler(new ctkErrorLogStreamMessageHandler);
-    model.setMsgHandlerEnabled(ctkErrorLogStreamMessageHandler::HandlerName, true);
-
-    QString message0("This is a qDebug message");
-    qDebug().nospace() << qPrintable(message0);
-
-    QString message1("This is a std::cerr message");
-    std::cerr << qPrintable(message1) << std::endl;
-
-    QString message2("This is a qWarning message");
-    qWarning().nospace() << qPrintable(message2);
-
-    QString message3("This is a std::cout message");
-    std::cout << qPrintable(message3) << std::endl;
-
-    QString message4("This is a qCritical message");
-    qCritical().nospace() << qPrintable(message4);
-
-    QStringList expectedMessages;
-    expectedMessages << message0 << message1
-                     << message2 << message3
-                     << message4;
-
-    errorMsg = checkRowCount(__LINE__, model.rowCount(), /* expected = */ expectedMessages.count());
-    if (!errorMsg.isEmpty())
-      {
-      model.disableAllMsgHandler();
-      printErrorMessage(errorMsg);
-      printTextMessages(model);
-      return EXIT_FAILURE;
-      }
-
-    errorMsg = checkTextMessages(__LINE__, model, expectedMessages);
-    if (!errorMsg.isEmpty())
-      {
-      model.disableAllMsgHandler();
-      printErrorMessage(errorMsg);
-      return EXIT_FAILURE;
-      }
-
-    }
-  catch (const char* error)
-    {
-    model.disableAllMsgHandler();
-    std::cerr << error << std::endl;
-    return EXIT_FAILURE;
-    }
-
-  return EXIT_SUCCESS;
-}

+ 1 - 1
Libs/Core/Testing/Cpp/ctkErrorLogModelTest4.cpp

@@ -45,7 +45,7 @@ int ctkErrorLogModelTest4(int argc, char * argv [])
     model.enableAllMsgHandler();
     model.msgHandlerNames();
     model.filterEntry();
-    model.filterEntry(ctkErrorLogModel::Fatal, true);
+    model.filterEntry(ctkErrorLogLevel::Fatal, true);
 
     model.logLevelFilter();
     model.logEntryGrouping();

+ 235 - 0
Libs/Core/Testing/Cpp/ctkErrorLogModelTestHelper.cpp

@@ -0,0 +1,235 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QDateTime>
+#include <QFile>
+#include <QMutexLocker>
+#include <QSharedPointer>
+#include <QStringList>
+#include <QTimer>
+#include <QThread>
+
+// CTK includes
+#include "ctkErrorLogModel.h"
+
+namespace
+{
+//-----------------------------------------------------------------------------
+// Utility function
+
+//-----------------------------------------------------------------------------
+QString checkRowCount(int line, int currentRowCount, int expectedRowCount)
+{
+  if (currentRowCount != expectedRowCount)
+    {
+    QString errorMsg("Line %1 - Expected rowCount: %2 - Current rowCount: %3\n");
+    return errorMsg.arg(line).arg(expectedRowCount).arg(currentRowCount);
+    }
+  return QString();
+}
+
+//-----------------------------------------------------------------------------
+QString checkTextMessages(int line, const QStringList& currentMessages, const QStringList& expectedMessages)
+{
+  for(int i = 0; i < expectedMessages.count(); ++i)
+    {
+    if (!expectedMessages.contains(currentMessages.at(i)))
+      {
+      QString errorMsg("Line %1 - Problem with logged messages !\n"
+                       "\tMessage [%2] hasn't been logged !\n");
+      return errorMsg.arg(line).arg(expectedMessages.value(i));
+      }
+    }
+  return QString();
+}
+
+//-----------------------------------------------------------------------------
+QString checkTextMessages(int line, const ctkErrorLogModel& model, const QStringList& expectedMessages)
+{
+  QStringList currentMessages;
+  for(int i = 0; i < expectedMessages.count(); ++i)
+    {
+    QModelIndex descriptionIndex = model.index(i, ctkErrorLogModel::DescriptionColumn);
+    currentMessages << descriptionIndex.data(ctkErrorLogModel::DescriptionTextRole).toString();
+    }
+  return checkTextMessages(line, currentMessages, expectedMessages);
+}
+
+//-----------------------------------------------------------------------------
+void printTextMessages(const ctkErrorLogModel& model)
+{
+  fprintf(stdout, "%s", "ErrorLogModel rows:\n");
+  QString text("\trow %1 => [%2]\n");
+  for (int i=0; i < model.rowCount(); ++i)
+    {
+    QString description =
+        model.index(i, ctkErrorLogModel::DescriptionColumn).data().toString();
+    fprintf(stdout, "%s", qPrintable(text.arg(i).arg(description)));
+    }
+  fflush(stdout);
+}
+
+//-----------------------------------------------------------------------------
+void printErrorMessage(const QString& errorMessage)
+{
+  fprintf(stderr, "%s", qPrintable(errorMessage));
+  fflush(stderr);
+}
+
+//-----------------------------------------------------------------------------
+QString checkInteger(int line, const char* valueName, int current, int expected)
+{
+  if (current != expected)
+    {
+    QString errorMsg("Line %1 - Expected %2: %3 - Current %4: %5\n");
+    return errorMsg.arg(line).arg(valueName).
+        arg(expected).arg(valueName).arg(current);
+    }
+  return QString();
+}
+
+//-----------------------------------------------------------------------------
+QString checkBoolean(int line, const char* valueName, bool current, bool expected)
+{
+  if (current != expected)
+    {
+    QString errorMsg("Line %1 - Expected %2: %3 - Current %4: %5\n");
+    return errorMsg.arg(line).arg(valueName).
+        arg(static_cast<int>(expected)).arg(valueName).arg(static_cast<int>(current));
+    }
+  return QString();
+}
+
+//-----------------------------------------------------------------------------
+void processEvents(int durationInMSecs)
+{
+  QTimer timer;
+  timer.setSingleShot(true);
+  timer.start(durationInMSecs);
+  while(timer.isActive())
+    {
+    QCoreApplication::processEvents();
+    }
+}
+
+//-----------------------------------------------------------------------------
+QMutex AppendToFileMutex;
+
+//-----------------------------------------------------------------------------
+void appendToFile(const QString& fileName, const QString& text)
+{
+  QMutexLocker locker(&AppendToFileMutex);
+  QFile f(fileName);
+  f.open(QFile::Append);
+  QTextStream s(&f);
+  s << QDateTime::currentDateTime().toString() << " - " << text << "\n";
+  f.close();
+}
+
+//-----------------------------------------------------------------------------
+class LogMessageThread : public QThread
+{
+public:
+  LogMessageThread(int id, int maxIteration) :
+    Id(id), MaxIteration(maxIteration), Counter(0){}
+
+protected:
+  void run();
+
+  virtual void logMessage(const QDateTime& dateTime, int threadId, int counterIdx) = 0;
+private:
+  int Id;
+  int MaxIteration;
+  int Counter;
+};
+
+//-----------------------------------------------------------------------------
+void LogMessageThread::run()
+{
+  while(this->Counter < this->MaxIteration)
+    {
+    this->logMessage(QDateTime::currentDateTime(), this->Id, this->Counter);
+    ++this->Counter;
+    }
+}
+
+////-----------------------------------------------------------------------------
+//template<class LogMessageThreadType>
+//class SynchronousLogMessageStarterThread : public QThread
+//{
+//public:
+//  SynchronousLogMessageStarterThread(ctkErrorLogModel * errorLogModel, int threadCount, int maxIteration) :
+//    ErrorLogModel(errorLogModel), ThreadCount(threadCount), MaxIteration(maxIteration)
+//  {
+//    this->ErrorLogModel->setAsynchronousLogging(false);
+//  }
+
+//protected:
+//  void run();
+
+//private:
+//  QList<QSharedPointer<LogMessageThread> > ThreadList;
+//  ctkErrorLogModel * ErrorLogModel;
+//  int ThreadCount;
+//  int MaxIteration;
+//};
+
+////-----------------------------------------------------------------------------
+//template<class LogMessageThreadType>
+//void SynchronousLogMessageStarterThread<LogMessageThreadType>::run()
+//{
+//  for(int i = 0; i < this->ThreadCount; ++i)
+//    {
+//    this->ThreadList << QSharedPointer<LogMessageThread>(new LogMessageThreadType(i, this->MaxIteration));
+//    this->ThreadList.back()->start();
+//    }
+//  foreach(const QSharedPointer<LogMessageThread>& thread, this->ThreadList)
+//    {
+//    thread->wait();
+//    }
+
+//  int expectedMessageCount = this->ThreadCount * this->MaxIteration * 2;
+//  QString errorMsg = checkRowCount(__LINE__, this->ErrorLogModel->rowCount(),
+//                                   /* expected = */ expectedMessageCount);
+//  if (!errorMsg.isEmpty())
+//    {
+//    this->ErrorLogModel->disableAllMsgHandler();
+//    printErrorMessage(errorMsg);
+//    printTextMessages(*this->ErrorLogModel);
+//    QCoreApplication::exit(EXIT_FAILURE);
+//    }
+//}
+
+//-----------------------------------------------------------------------------
+QList<QSharedPointer<LogMessageThread> > ThreadList;
+
+//-----------------------------------------------------------------------------
+template<class LogMessageThreadType>
+void startLogMessageThreads(int threadCount, int maxIteration)
+{
+  for(int i = 0; i < threadCount; ++i)
+    {
+    ThreadList << QSharedPointer<LogMessageThread>(new LogMessageThreadType(i, maxIteration));
+    ThreadList.back()->start();
+    }
+}
+
+} // end namespace

+ 104 - 0
Libs/Core/Testing/Cpp/ctkErrorLogQtMessageHandlerWithThreadsTest1.cpp

@@ -0,0 +1,104 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QCoreApplication>
+#include <QDebug>
+
+// CTK includes
+#include "ctkErrorLogQtMessageHandler.h"
+#include "ctkModelTester.h"
+
+// STL includesQList
+#include <cstdlib>
+#include <iostream>
+
+// Helper functions
+#include "Testing/Cpp/ctkErrorLogModelTestHelper.cpp"
+
+namespace
+{
+//-----------------------------------------------------------------------------
+class LogQtMessageThread : public LogMessageThread
+{
+public:
+  LogQtMessageThread(int id, int maxIteration) : LogMessageThread(id, maxIteration){}
+
+  virtual void logMessage(const QDateTime& dateTime, int threadId, int counterIdx)
+  {
+    QString msg = QString("counterIdx:%1 - %2 - Message from thread: %3\n")
+        .arg(counterIdx).arg(dateTime.toString()).arg(threadId);
+
+    qDebug().nospace() << qPrintable(msg);
+    qWarning().nospace() << qPrintable(msg);
+    qCritical().nospace() << qPrintable(msg);
+  }
+};
+
+}
+
+//-----------------------------------------------------------------------------
+int ctkErrorLogQtMessageHandlerWithThreadsTest1(int argc, char * argv [])
+{
+  QCoreApplication app(argc, argv);
+  Q_UNUSED(app);
+
+  ctkErrorLogModel model;
+  ctkModelTester modelTester;
+  modelTester.setVerbose(false);
+
+  try
+    {
+    modelTester.setModel(&model);
+
+    // --------------------------------------------------------------------------
+    // Monitor Qt messages
+
+    model.registerMsgHandler(new ctkErrorLogQtMessageHandler);
+    model.setMsgHandlerEnabled(ctkErrorLogQtMessageHandler::HandlerName, true);
+
+    int threadCount = 15;
+    int maxIteration = 5;
+    int messagesPerIteration = 3;
+    startLogMessageThreads<LogQtMessageThread>(threadCount, maxIteration);
+
+    // Give enough time for the threads to send their messages
+    QTimer::singleShot(1500, qApp, SLOT(quit()));
+    app.exec();
+
+    int expectedMessageCount = threadCount * maxIteration * messagesPerIteration;
+    QString errorMsg = checkRowCount(__LINE__, model.rowCount(), /* expected = */ expectedMessageCount);
+    if (!errorMsg.isEmpty())
+      {
+      model.disableAllMsgHandler();
+      printErrorMessage(errorMsg);
+      printTextMessages(model);
+      return EXIT_FAILURE;
+      }
+    }
+  catch (const char* error)
+    {
+    model.disableAllMsgHandler();
+    std::cerr << error << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  return EXIT_SUCCESS;
+}

+ 102 - 0
Libs/Core/Testing/Cpp/ctkErrorLogStreamMessageHandlerWithThreadsTest1.cpp

@@ -0,0 +1,102 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QCoreApplication>
+#include <QDebug>
+
+// CTK includes
+#include "ctkErrorLogStreamMessageHandler.h"
+#include "ctkModelTester.h"
+
+// STL includesQList
+#include <cstdlib>
+#include <iostream>
+
+// Helper functions
+#include "Testing/Cpp/ctkErrorLogModelTestHelper.cpp"
+
+namespace
+{
+//-----------------------------------------------------------------------------
+class LogStreamMessageThread : public LogMessageThread
+{
+public:
+  LogStreamMessageThread(int id, int maxIteration) : LogMessageThread(id, maxIteration){}
+
+  virtual void logMessage(const QDateTime& dateTime, int threadId, int counterIdx)
+  {
+    QString msg = QString("counterIdx:%1 - %2 - Message from thread: %3\n")
+        .arg(counterIdx).arg(dateTime.toString()).arg(threadId);
+    std::cout << qPrintable(msg) << std::flush;
+    std::cerr << qPrintable(msg) << std::flush;
+  }
+};
+
+}
+
+//-----------------------------------------------------------------------------
+int ctkErrorLogStreamMessageHandlerWithThreadsTest1(int argc, char * argv [])
+{
+  QCoreApplication app(argc, argv);
+  Q_UNUSED(app);
+
+  ctkErrorLogModel model;
+  ctkModelTester modelTester;
+  modelTester.setVerbose(false);
+
+  try
+    {
+    modelTester.setModel(&model);
+
+    // --------------------------------------------------------------------------
+    // Monitor Stream messages
+
+    model.registerMsgHandler(new ctkErrorLogStreamMessageHandler);
+    model.setMsgHandlerEnabled(ctkErrorLogStreamMessageHandler::HandlerName, true);
+
+    int threadCount = 15;
+    int maxIteration = 5;
+    int messagesPerIteration = 2;
+    startLogMessageThreads<LogStreamMessageThread>(threadCount, maxIteration);
+
+    // Give enough time for the threads to send their messages
+    QTimer::singleShot(1500, qApp, SLOT(quit()));
+    app.exec();
+
+    int expectedMessageCount = threadCount * maxIteration * messagesPerIteration;
+    QString errorMsg = checkRowCount(__LINE__, model.rowCount(), /* expected = */ expectedMessageCount);
+    if (!errorMsg.isEmpty())
+      {
+      model.disableAllMsgHandler();
+      printErrorMessage(errorMsg);
+      printTextMessages(model);
+      return EXIT_FAILURE;
+      }
+    }
+  catch (const char* error)
+    {
+    model.disableAllMsgHandler();
+    std::cerr << error << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  return EXIT_SUCCESS;
+}

+ 92 - 71
Libs/Core/ctkErrorLogFDMessageHandler.cpp

@@ -20,67 +20,81 @@
 
 // Qt includes
 #include <QDebug>
-#include <QDir>
-#include <QFile>
 
 // CTK includes
 #include "ctkErrorLogFDMessageHandler.h"
 #include "ctkErrorLogFDMessageHandler_p.h"
+#include "ctkUtils.h"
 
 // STD includes
 #include <cstdio>
 #ifdef Q_OS_WIN32
-# include <io.h>
+# include <fcntl.h>  // For _O_TEXT
+# include <io.h>     // For _pipe, _dup and _dup2
 #else
-# include <unistd.h>
+# include <unistd.h> // For pipe, dup and dup2
 #endif
 
 // --------------------------------------------------------------------------
 // ctkFDHandler methods
+// See http://stackoverflow.com/questions/5419356/redirect-stdout-stderr-to-a-string
+// and http://stackoverflow.com/questions/955962/how-to-buffer-stdout-in-memory-and-write-it-from-a-dedicated-thread
 
 // --------------------------------------------------------------------------
 ctkFDHandler::ctkFDHandler(ctkErrorLogFDMessageHandler* messageHandler,
-                           ctkErrorLogModel::LogLevel logLevel,
-                           int fileDescriptorNumber)
+                           ctkErrorLogLevel::LogLevel logLevel,
+                           ctkErrorLogModel::TerminalOutput terminalOutput)
 {
   this->MessageHandler = messageHandler;
   this->LogLevel = logLevel;
-  this->FDNumber = fileDescriptorNumber;
+  this->TerminalOutput = terminalOutput;
   this->SavedFDNumber = 0;
-  this->FD = 0;
   this->Enabled = false;
-
-  const QString outputFileTemplateName =  QDir::tempPath()
-    + QDir::separator () + "ctkFDHandler-%1.XXXXXX.txt";
-  this->OutputFile.setFileTemplate(outputFileTemplateName.arg(this->FDNumber));
-
-  connect(&this->OutputFileWatcher, SIGNAL(fileChanged(QString)),
-          SLOT(outputFileChanged(QString)));
+  this->Initialized = false;
+  this->init();
 }
 
 // --------------------------------------------------------------------------
-ctkErrorLogFDMessageHandler::~ctkErrorLogFDMessageHandler()
+ctkFDHandler::~ctkFDHandler()
 {
+  if (this->Initialized)
+    {
+    this->RedirectionFile.close();
+    delete this->RedirectionStream;
+    }
 }
 
 // --------------------------------------------------------------------------
-FILE* ctkFDHandler::fileDescriptorFromNumber(int fdNumber)
+void ctkFDHandler::init()
 {
-  Q_ASSERT(fdNumber == 1 /* stdout*/ || fdNumber == 2 /*stderr*/);
-  if (fdNumber == 1)
-    {
-    return stdout;
-    }
-  else if (fdNumber == 2)
+#ifdef Q_OS_WIN32
+  int status = _pipe(this->Pipe, 65536, _O_TEXT);
+#else
+  int status = pipe(this->Pipe);
+#endif
+  if (status != 0)
     {
-    return stderr;
+    qCritical().nospace() << "ctkFDHandler - Failed to create pipe !";
+    return;
     }
-  return 0;
+  this->RedirectionFile.open(this->Pipe[0], QIODevice::ReadOnly);
+  this->RedirectionStream = new QTextStream(&this->RedirectionFile);
+  this->Initialized = true;
+}
+
+// --------------------------------------------------------------------------
+FILE* ctkFDHandler::terminalOutputFile()
+{
+  return this->TerminalOutput == ctkErrorLogModel::StandardOutput ? stdout : stderr;
 }
 
 // --------------------------------------------------------------------------
 void ctkFDHandler::setEnabled(bool value)
 {
+  if (!this->Initialized)
+    {
+    return;
+    }
   if (this->Enabled == value)
     {
     return;
@@ -89,77 +103,79 @@ void ctkFDHandler::setEnabled(bool value)
   if (value)
     {
     // Flush (stdout|stderr) so that any buffered messages are delivered
-    fflush(Self::fileDescriptorFromNumber(this->FDNumber));
+    fflush(this->terminalOutputFile());
 
     // Save position of current standard output
-    fgetpos(Self::fileDescriptorFromNumber(this->FDNumber), &this->SavedFDPos);
+    fgetpos(this->terminalOutputFile(), &this->SavedFDPos);
 #ifdef Q_OS_WIN32
-    this->SavedFDNumber = _dup(_fileno(Self::fileDescriptorFromNumber(this->FDNumber)));
+    this->SavedFDNumber = _dup(_fileno(this->terminalOutputFile()));
+    _dup2(this->Pipe[1], _fileno(this->terminalOutputFile()));
 #else
-    this->SavedFDNumber = dup(fileno(Self::fileDescriptorFromNumber(this->FDNumber)));
+    this->SavedFDNumber = dup(fileno(this->terminalOutputFile()));
+    dup2(this->Pipe[1], fileno(this->terminalOutputFile()));
 #endif
+    close(this->Pipe[1]);
 
-    // Open and close the OutputFile so that the unique filename is created
-    if (!this->OutputFile.exists())
-      {
-      this->OutputFile.open();
-      this->OutputFile.close();
-      }
-
-    //qDebug() << "ctkFDHandler - OutputFile" << this->OutputFile.fileName();
-
-    if ((this->FD = freopen(this->OutputFile.fileName().toLatin1(),
-                            "w",
-                            Self::fileDescriptorFromNumber(this->FDNumber))) == 0)
-      {
-      // this->SavedFDNumber = 0;
-      }
-
-    // Observe the OutputFile for changes
-    this->OutputFileWatcher.addPath(this->OutputFile.fileName());
+    // Start polling thread
+    this->start();
     }
   else
     {
     // Flush stdout or stderr so that any buffered messages are delivered
-    fflush(Self::fileDescriptorFromNumber(this->FDNumber));
+    fflush(this->terminalOutputFile());
 
     // Flush current stream so that any buffered messages are delivered
-    fflush(this->FD);
+    this->RedirectionFile.flush();
 
-    // Un-observe OutputFile
-    this->OutputFileWatcher.removePath(this->OutputFile.fileName());
+    // Stop polling thread
+    this->terminate();
+    this->wait();
 
-    // Close file and restore standard output to stdout or stderr - which should be the terminal
+    // Close files and restore standard output to stdout or stderr - which should be the terminal
 #ifdef Q_OS_WIN32
-    _dup2(this->SavedFDNumber, _fileno(Self::fileDescriptorFromNumber(this->FDNumber)));
+    _dup2(this->SavedFDNumber, _fileno(this->terminalOutputFile()));
     _close(this->SavedFDNumber);
 #else
-    dup2(this->SavedFDNumber, fileno(Self::fileDescriptorFromNumber(this->FDNumber)));
+    dup2(this->SavedFDNumber, fileno(this->terminalOutputFile()));
     close(this->SavedFDNumber);
 #endif
-    clearerr(Self::fileDescriptorFromNumber(this->FDNumber));
-    fsetpos(Self::fileDescriptorFromNumber(this->FDNumber), &this->SavedFDPos);
+    clearerr(this->terminalOutputFile());
+    fsetpos(this->terminalOutputFile(), &this->SavedFDPos);
+
+    this->SavedFDNumber = fileno(this->terminalOutputFile());
+    }
+
+  ctkErrorLogTerminalOutput * terminalOutput =
+      this->MessageHandler->terminalOutput(this->TerminalOutput);
+  if(terminalOutput)
+    {
+    terminalOutput->setFileDescriptor(this->SavedFDNumber);
     }
 
   this->Enabled = value;
 }
 
 // --------------------------------------------------------------------------
-void ctkFDHandler::outputFileChanged(const QString & path)
+void ctkFDHandler::run()
 {
-  QFile file(path);
-  if (!file.open(QFile::ReadOnly))
-    {
-    qCritical() << "ctkFDHandler - Failed to open file" << path;
-    return;
-    }
-
-  QTextStream stream(&file);
-  while (!stream.atEnd())
+  while(true)
     {
-    Q_ASSERT(this->MessageHandler->errorLogModel());
-    this->MessageHandler->errorLogModel()->addEntry(
-          this->LogLevel, this->MessageHandler->handlerPrettyName(), qPrintable(stream.readLine()));
+    char c = '\0';
+    QString line;
+    while(c != '\n')
+      {
+      read(this->Pipe[0], &c, 1); // When used with pipe, read() is blocking
+      if (c != '\n')
+        {
+        line += c;
+        }
+      }
+    Q_ASSERT(this->MessageHandler);
+    this->MessageHandler->handleMessage(
+      ctk::qtHandleToString(QThread::currentThreadId()),
+      this->LogLevel,
+      this->MessageHandler->handlerPrettyName(),
+      line);
     }
 }
 
@@ -203,8 +219,13 @@ ctkErrorLogFDMessageHandler::ctkErrorLogFDMessageHandler() :
   Superclass(), d_ptr(new ctkErrorLogFDMessageHandlerPrivate())
 {
   Q_D(ctkErrorLogFDMessageHandler);
-  d->StdOutFDHandler = new ctkFDHandler(this, ctkErrorLogModel::Info, 1 /* stdout */);
-  d->StdErrFDHandler = new ctkFDHandler(this, ctkErrorLogModel::Critical, 2 /* stderr */);
+  d->StdOutFDHandler = new ctkFDHandler(this, ctkErrorLogLevel::Info, ctkErrorLogModel::StandardOutput);
+  d->StdErrFDHandler = new ctkFDHandler(this, ctkErrorLogLevel::Critical, ctkErrorLogModel::StandardError);
+}
+
+// --------------------------------------------------------------------------
+ctkErrorLogFDMessageHandler::~ctkErrorLogFDMessageHandler()
+{
 }
 
 // --------------------------------------------------------------------------

+ 22 - 17
Libs/Core/ctkErrorLogFDMessageHandler_p.h

@@ -22,9 +22,8 @@
 #define __ctkErrorLogFDMessageHandler_p_h
 
 // Qt includes
-#include <QObject>
-#include <QFileSystemWatcher>
-#include <QTemporaryFile>
+#include <QFile>
+#include <QThread>
 
 // CTK includes
 #include "ctkErrorLogModel.h"
@@ -33,40 +32,46 @@
 #include <cstdio>
 
 class ctkErrorLogFDMessageHandler;
+class QTextStream;
 
 // --------------------------------------------------------------------------
-class ctkFDHandler : public QObject
+// ctkFDHandler
+
+// --------------------------------------------------------------------------
+class ctkFDHandler : public QThread
 {
   Q_OBJECT
 public:
   typedef ctkFDHandler Self;
 
   ctkFDHandler(ctkErrorLogFDMessageHandler* messageHandler,
-               ctkErrorLogModel::LogLevel logLevel,
-               int fileDescriptorNumber);
+               ctkErrorLogLevel::LogLevel logLevel,
+               ctkErrorLogModel::TerminalOutput terminalOutput);
+  virtual ~ctkFDHandler();
 
   void setEnabled(bool value);
 
-  static FILE* fileDescriptorFromNumber(int fdNumber);
+  FILE* terminalOutputFile();
+
+protected:
+  void init();
 
-public slots:
-  void outputFileChanged(const QString & path);
+  void run();
 
 private:
   ctkErrorLogFDMessageHandler * MessageHandler;
-  ctkErrorLogModel::LogLevel LogLevel;
+  ctkErrorLogLevel::LogLevel LogLevel;
 
-  /// OutputFile where either stdout or stderr is redirected
-  QTemporaryFile     OutputFile;
+  ctkErrorLogModel::TerminalOutput TerminalOutput;
 
-  /// The fileWatcher will emit the signal 'fileChanged()' each time the outputFile changed.
-  QFileSystemWatcher OutputFileWatcher;
-
-  int    FDNumber;
   int    SavedFDNumber;
   fpos_t SavedFDPos;
-  FILE*  FD;
 
+  int          Pipe[2]; // 0: Read, 1: Write
+  QFile        RedirectionFile;
+  QTextStream* RedirectionStream;
+
+  bool Initialized;
   bool Enabled;
 };
 

+ 335 - 85
Libs/Core/ctkErrorLogModel.cpp

@@ -22,9 +22,11 @@
 #include <QApplication>
 #include <QDateTime>
 #include <QDebug>
+#include <QFile>
 #include <QMainWindow>
 #include <QMetaEnum>
 #include <QMetaType>
+#include <QMutexLocker>
 #include <QPointer>
 #include <QStandardItem>
 #include <QStatusBar>
@@ -33,68 +35,129 @@
 #include "ctkErrorLogModel.h"
 #include <ctkPimpl.h>
 
+// STD includes
+#include <cstdio> // For _fileno or fileno
+
 // --------------------------------------------------------------------------
-// ctkErrorLogAbstractMessageHandler methods
+// ctkErrorLogLevel methods
 
 // --------------------------------------------------------------------------
-ctkErrorLogAbstractMessageHandler::ctkErrorLogAbstractMessageHandler()
+ctkErrorLogLevel::ctkErrorLogLevel()
+{
+  qRegisterMetaType<ctkErrorLogLevel::LogLevel>("ctkErrorLogLevel::LogLevel");
+}
+
+// --------------------------------------------------------------------------
+QString ctkErrorLogLevel::operator()(ctkErrorLogLevel::LogLevel logLevel)
+{
+  return this->logLevelAsString(logLevel);
+}
+
+// --------------------------------------------------------------------------
+QString ctkErrorLogLevel::logLevelAsString(ctkErrorLogLevel::LogLevel logLevel)const
+{
+  QMetaEnum logLevelEnum = this->metaObject()->enumerator(0);
+  Q_ASSERT(QString("LogLevel").compare(logLevelEnum.name()) == 0);
+  return QLatin1String(logLevelEnum.valueToKey(logLevel));
+}
+
+// --------------------------------------------------------------------------
+// ctkErrorLogTerminalOutputPrivate
+
+// --------------------------------------------------------------------------
+class ctkErrorLogTerminalOutputPrivate
+{
+public:
+  ctkErrorLogTerminalOutputPrivate();
+  ~ctkErrorLogTerminalOutputPrivate();
+
+  bool Enabled;
+  mutable QMutex EnableMutex;
+
+  int FD;
+  mutable QMutex OutputMutex;
+};
+
+// --------------------------------------------------------------------------
+ctkErrorLogTerminalOutputPrivate::ctkErrorLogTerminalOutputPrivate()
   : Enabled(false)
 {
+#ifdef Q_OS_WIN32
+  this->FD = _fileno(stdout);
+#else
+  this->FD = fileno(stdout);
+#endif
 }
 
 // --------------------------------------------------------------------------
-ctkErrorLogAbstractMessageHandler::~ctkErrorLogAbstractMessageHandler()
+ctkErrorLogTerminalOutputPrivate::~ctkErrorLogTerminalOutputPrivate()
 {
 }
 
 // --------------------------------------------------------------------------
-ctkErrorLogModel* ctkErrorLogAbstractMessageHandler::errorLogModel()const
+// ctkErrorLogTerminalOutput methods
+
+// --------------------------------------------------------------------------
+ctkErrorLogTerminalOutput::ctkErrorLogTerminalOutput()
+  : d_ptr(new ctkErrorLogTerminalOutputPrivate)
 {
-  return this->ErrorLogModel.data();
 }
 
 // --------------------------------------------------------------------------
-void ctkErrorLogAbstractMessageHandler::setErrorLogModel(ctkErrorLogModel * newErrorLogModel)
+ctkErrorLogTerminalOutput::~ctkErrorLogTerminalOutput()
 {
-  this->ErrorLogModel = QPointer<ctkErrorLogModel>(newErrorLogModel);
 }
 
 // --------------------------------------------------------------------------
-QString ctkErrorLogAbstractMessageHandler::handlerPrettyName()const
+bool ctkErrorLogTerminalOutput::enabled()const
 {
-  if (this->HandlerPrettyName.isEmpty())
-    {
-    return this->handlerName();
-    }
-  else
-    {
-    return this->HandlerPrettyName;
-    }
+  Q_D(const ctkErrorLogTerminalOutput);
+  QMutexLocker locker(&d->EnableMutex);
+  return d->Enabled;
 }
 
 // --------------------------------------------------------------------------
-void ctkErrorLogAbstractMessageHandler::setHandlerPrettyName(const QString& newHandlerPrettyName)
+void ctkErrorLogTerminalOutput::setEnabled(bool value)
 {
-  this->HandlerPrettyName = newHandlerPrettyName;
+  Q_D(ctkErrorLogTerminalOutput);
+  QMutexLocker locker(&d->EnableMutex);
+  d->Enabled = value;
 }
 
 // --------------------------------------------------------------------------
-bool ctkErrorLogAbstractMessageHandler::enabled()const
+int ctkErrorLogTerminalOutput::fileDescriptor()const
 {
-  return this->Enabled;
+  Q_D(const ctkErrorLogTerminalOutput);
+  QMutexLocker locker(&d->OutputMutex);
+  return d->FD;
 }
 
 // --------------------------------------------------------------------------
-void ctkErrorLogAbstractMessageHandler::setEnabled(bool value)
+void ctkErrorLogTerminalOutput::setFileDescriptor(int fd)
 {
-  if (value == this->Enabled)
-    {
-    return;
-    }
-  this->setEnabledInternal(value);
-  this->Enabled = value;
+  Q_D(ctkErrorLogTerminalOutput);
+  QMutexLocker locker(&d->OutputMutex);
+  d->FD = fd;
 }
 
+// --------------------------------------------------------------------------
+void ctkErrorLogTerminalOutput::output(const QString& text)
+{
+  Q_D(ctkErrorLogTerminalOutput);
+  {
+    QMutexLocker locker(&d->EnableMutex);
+    if (!d->Enabled)
+      {
+      return;
+      }
+  }
+
+  {
+    QMutexLocker locker(&d->OutputMutex);
+    QString textWithNewLine = text + "\n";
+    write(d->FD, qPrintable(textWithNewLine), textWithNewLine.size());
+  }
+}
 
 // --------------------------------------------------------------------------
 // ctkErrorLogModelPrivate
@@ -111,18 +174,27 @@ public:
 
   void init();
 
+  /// Convenient method that could be used for debugging purposes.
+  void appendToFile(const QString& fileName, const QString& text);
+
+  void setMessageHandlerConnection(ctkErrorLogAbstractMessageHandler * msgHandler, bool asynchronous);
+
   QStandardItemModel StandardItemModel;
 
   QHash<QString, ctkErrorLogAbstractMessageHandler*> RegisteredHandlers;
 
-  ctkErrorLogModel::LogLevels CurrentLogLevelFilter;
+  ctkErrorLogLevel::LogLevels CurrentLogLevelFilter;
 
   bool LogEntryGrouping;
-
+  bool AsynchronousLogging;
   bool AddingEntry;
 
-  int TerminalOutputEnabled;
+  ctkErrorLogLevel ErrorLogLevel;
+
+  ctkErrorLogTerminalOutput StdErrTerminalOutput;
+  ctkErrorLogTerminalOutput StdOutTerminalOutput;
 
+  QMutex AppendToFileMutex;
 };
 
 // --------------------------------------------------------------------------
@@ -132,14 +204,14 @@ public:
 ctkErrorLogModelPrivate::ctkErrorLogModelPrivate(ctkErrorLogModel& object)
   : q_ptr(&object)
 {
+  this->LogEntryGrouping = false;
+  this->AsynchronousLogging = true;
   this->AddingEntry = false;
-  this->TerminalOutputEnabled = false;
 }
 
 // --------------------------------------------------------------------------
 ctkErrorLogModelPrivate::~ctkErrorLogModelPrivate()
 {
-  this->LogEntryGrouping = false;
   foreach(const QString& handlerName, this->RegisteredHandlers.keys())
     {
     ctkErrorLogAbstractMessageHandler * msgHandler =
@@ -163,6 +235,31 @@ void ctkErrorLogModelPrivate::init()
 }
 
 // --------------------------------------------------------------------------
+void ctkErrorLogModelPrivate::appendToFile(const QString& fileName, const QString& text)
+{
+  QMutexLocker locker(&this->AppendToFileMutex);
+  QFile f(fileName);
+  f.open(QFile::Append);
+  QTextStream s(&f);
+  s << QDateTime::currentDateTime().toString() << " - " << text << "\n";
+  f.close();
+}
+
+// --------------------------------------------------------------------------
+void ctkErrorLogModelPrivate::setMessageHandlerConnection(
+    ctkErrorLogAbstractMessageHandler * msgHandler, bool asynchronous)
+{
+  Q_Q(ctkErrorLogModel);
+
+  msgHandler->disconnect();
+
+  QObject::connect(msgHandler,
+        SIGNAL(messageHandled(QDateTime,QString,ctkErrorLogLevel::LogLevel,QString,QString)),
+        q, SLOT(addEntry(QDateTime,QString,ctkErrorLogLevel::LogLevel,QString,QString)),
+        asynchronous ? Qt::QueuedConnection : Qt::BlockingQueuedConnection);
+}
+
+// --------------------------------------------------------------------------
 // ctkErrorLogModel methods
 
 //------------------------------------------------------------------------------
@@ -192,7 +289,12 @@ bool ctkErrorLogModel::registerMsgHandler(ctkErrorLogAbstractMessageHandler * ms
     {
     return false;
     }
-  msgHandler->setErrorLogModel(this);
+
+  d->setMessageHandlerConnection(msgHandler, d->AsynchronousLogging);
+
+  msgHandler->setTerminalOutput(Self::StandardError, &d->StdErrTerminalOutput);
+  msgHandler->setTerminalOutput(Self::StandardOutput, &d->StdOutTerminalOutput);
+
   d->RegisteredHandlers.insert(msgHandler->handlerName(), msgHandler);
   return true;
 }
@@ -275,59 +377,64 @@ void ctkErrorLogModel::setAllMsgHandlerEnabled(bool enabled)
 }
 
 //------------------------------------------------------------------------------
-bool ctkErrorLogModel::terminalOutputEnabled()const
+ctkErrorLogModel::TerminalOutputs ctkErrorLogModel::terminalOutputs()const
 {
   Q_D(const ctkErrorLogModel);
-  return d->TerminalOutputEnabled;
+  ctkErrorLogModel::TerminalOutputs currentTerminalOutputs;
+  currentTerminalOutputs |= d->StdErrTerminalOutput.enabled() ? Self::StandardError : Self::None;
+  currentTerminalOutputs |= d->StdOutTerminalOutput.enabled() ? Self::StandardOutput : Self::None;
+  return currentTerminalOutputs;
 }
 
 //------------------------------------------------------------------------------
-void ctkErrorLogModel::setTerminalOutputEnabled(bool enabled)
+void ctkErrorLogModel::setTerminalOutputs(
+    const ctkErrorLogModel::TerminalOutputs& terminalOutput)
 {
   Q_D(ctkErrorLogModel);
-  d->TerminalOutputEnabled = enabled;
-}
-
-//------------------------------------------------------------------------------
-QString ctkErrorLogModel::logLevelAsString(LogLevel logLevel)const
-{
-  QMetaEnum logLevelEnum = this->metaObject()->enumerator(0);
-  Q_ASSERT(QString("LogLevel").compare(logLevelEnum.name()) == 0);
-  return QLatin1String(logLevelEnum.valueToKey(logLevel));
+  d->StdErrTerminalOutput.setEnabled(terminalOutput & ctkErrorLogModel::StandardOutput);
+  d->StdOutTerminalOutput.setEnabled(terminalOutput & ctkErrorLogModel::StandardError);
 }
 
 //------------------------------------------------------------------------------
-void ctkErrorLogModel::addEntry(ctkErrorLogModel::LogLevel logLevel,
-                                const QString& origin, const char* text)
+void ctkErrorLogModel::addEntry(const QDateTime& currentDateTime, const QString& threadId,
+                                ctkErrorLogLevel::LogLevel logLevel,
+                                const QString& origin, const QString& text)
 {
   Q_D(ctkErrorLogModel);
 
+//  d->appendToFile("/tmp/ctkErrorLogModel-appendToFile.txt",
+//                  QString("addEntry: %1").arg(QThread::currentThreadId()));
+
   if (d->AddingEntry)
     {
-//    QFile f("/tmp/ctkErrorLogModel-AddingEntry-true.txt");
-//    f.open(QFile::Append);
-//    QTextStream s(&f);
+//    QString str;
+//    QTextStream s(&str);
+//    s << "----------------------------------\n";
 //    s << "text=>" << text << "\n";
-//    s << "\tlogLevel:" << qPrintable(this->logLevelAsString(logLevel)) << "\n";
+//    s << "\tlogLevel:" << qPrintable(d->ErrorLogLevel(logLevel)) << "\n";
 //    s << "\torigin:" << qPrintable(origin) << "\n";
-//    s << "\ttext:" << text << "\n";
-//    f.close();
+//    d->appendToFile("/tmp/ctkErrorLogModel-AddingEntry-true.txt", str);
     return;
     }
 
   d->AddingEntry = true;
 
   QString timeFormat("dd.MM.yyyy hh:mm:ss");
-  QDateTime currentDateTime = QDateTime::currentDateTime();
 
   bool groupEntry = false;
   if (d->LogEntryGrouping)
     {
+    // Retrieve threadId associated with last row
+    QModelIndex lastRowThreadIdIndex =
+        d->StandardItemModel.index(d->StandardItemModel.rowCount() - 1, ctkErrorLogModel::ThreadIdColumn);
+
+    bool threadIdMatched = threadId == lastRowThreadIdIndex.data().toString();
+
     // Retrieve logLevel associated with last row
     QModelIndex lastRowLogLevelIndex =
         d->StandardItemModel.index(d->StandardItemModel.rowCount() - 1, ctkErrorLogModel::LogLevelColumn);
 
-    bool logLevelMatched = this->logLevelAsString(logLevel) == lastRowLogLevelIndex.data().toString();
+    bool logLevelMatched = d->ErrorLogLevel(logLevel) == lastRowLogLevelIndex.data().toString();
 
     // Retrieve origin associated with last row
     QModelIndex lastRowOriginIndex =
@@ -343,7 +450,7 @@ void ctkErrorLogModel::addEntry(ctkErrorLogModel::LogLevel logLevel,
     int groupingIntervalInMsecs = 1000;
     bool withinGroupingInterval = lastRowDateTime.time().msecsTo(currentDateTime.time()) <= groupingIntervalInMsecs;
 
-    groupEntry = logLevelMatched && originMatched && withinGroupingInterval;
+    groupEntry = threadIdMatched && logLevelMatched && originMatched && withinGroupingInterval;
     }
 
   if (!groupEntry)
@@ -355,8 +462,13 @@ void ctkErrorLogModel::addEntry(ctkErrorLogModel::LogLevel logLevel,
     timeItem->setEditable(false);
     itemList << timeItem;
 
+    // ThreadId item
+    QStandardItem * threadIdItem = new QStandardItem(threadId);
+    threadIdItem->setEditable(false);
+    itemList << threadIdItem;
+
     // LogLevel item
-    QStandardItem * logLevelItem = new QStandardItem(this->logLevelAsString(logLevel));
+    QStandardItem * logLevelItem = new QStandardItem(d->ErrorLogLevel(logLevel));
     logLevelItem->setEditable(false);
     itemList << logLevelItem;
 
@@ -383,7 +495,7 @@ void ctkErrorLogModel::addEntry(ctkErrorLogModel::LogLevel logLevel,
 
     QStringList updatedDescription;
     updatedDescription << lastRowDescriptionIndex.data(ctkErrorLogModel::DescriptionTextRole).toString();
-    updatedDescription << QLatin1String(text);
+    updatedDescription << text;
 
     d->StandardItemModel.setData(lastRowDescriptionIndex, updatedDescription.join("\n"),
                                  ctkErrorLogModel::DescriptionTextRole);
@@ -396,22 +508,6 @@ void ctkErrorLogModel::addEntry(ctkErrorLogModel::LogLevel logLevel,
       }
     }
 
-  if (d->TerminalOutputEnabled)
-    {
-    QStringList savedMsgHandlerEnabled = this->msgHandlerEnabled();
-
-    this->disableAllMsgHandler();
-    if (logLevel <= ctkErrorLogModel::Info)
-      {
-      std::cout << text << std::endl;
-      }
-    else
-      {
-      std::cerr << text << std::endl;
-      }
-    this->setMsgHandlerEnabled(savedMsgHandlerEnabled);
-    }
-
   d->AddingEntry = false;
 }
 
@@ -423,7 +519,8 @@ void ctkErrorLogModel::clear()
 }
 
 //------------------------------------------------------------------------------
-void ctkErrorLogModel::filterEntry(const LogLevels& logLevel, bool disableFilter)
+void ctkErrorLogModel::filterEntry(const ctkErrorLogLevel::LogLevels& logLevel,
+                                   bool disableFilter)
 {
   Q_D(ctkErrorLogModel);
 
@@ -432,14 +529,14 @@ void ctkErrorLogModel::filterEntry(const LogLevels& logLevel, bool disableFilter
     {
     patterns << this->filterRegExp().pattern().split("|");
     }
-  patterns.removeAll(this->logLevelAsString(Self::None));
+  patterns.removeAll(d->ErrorLogLevel(ctkErrorLogLevel::None));
 
 //  foreach(QString s, patterns)
 //    {
 //    std::cout << "pattern:" << qPrintable(s) << std::endl;
 //    }
 
-  QMetaEnum logLevelEnum = this->metaObject()->enumerator(0);
+  QMetaEnum logLevelEnum = d->ErrorLogLevel.metaObject()->enumerator(0);
   Q_ASSERT(QString("LogLevel").compare(logLevelEnum.name()) == 0);
 
   // Loop over enum values and append associated name to 'patterns' if
@@ -449,16 +546,16 @@ void ctkErrorLogModel::filterEntry(const LogLevels& logLevel, bool disableFilter
     int aLogLevel = logLevelEnum.value(i);
     if (logLevel & aLogLevel)
       {
-      QString logLevelAsString = this->logLevelAsString(static_cast<Self::LogLevel>(aLogLevel));
+      QString logLevelAsString = d->ErrorLogLevel(static_cast<ctkErrorLogLevel::LogLevel>(aLogLevel));
       if (!disableFilter)
         {
         patterns << logLevelAsString;
-        d->CurrentLogLevelFilter |= static_cast<Self::LogLevels>(aLogLevel);
+        d->CurrentLogLevelFilter |= static_cast<ctkErrorLogLevel::LogLevels>(aLogLevel);
         }
       else
         {
         patterns.removeAll(logLevelAsString);
-        d->CurrentLogLevelFilter ^= static_cast<Self::LogLevels>(aLogLevel);
+        d->CurrentLogLevelFilter ^= static_cast<ctkErrorLogLevel::LogLevels>(aLogLevel);
         }
       }
     }
@@ -467,7 +564,7 @@ void ctkErrorLogModel::filterEntry(const LogLevels& logLevel, bool disableFilter
     {
     // If there are no patterns, let's filter with the None level so that
     // all entries are filtered out.
-    patterns << this->logLevelAsString(Self::None);
+    patterns << d->ErrorLogLevel(ctkErrorLogLevel::None);
     }
 
   bool filterChanged = true;
@@ -490,15 +587,16 @@ void ctkErrorLogModel::filterEntry(const LogLevels& logLevel, bool disableFilter
 }
 
 //------------------------------------------------------------------------------
-ctkErrorLogModel::LogLevels ctkErrorLogModel::logLevelFilter()const
+ctkErrorLogLevel::LogLevels ctkErrorLogModel::logLevelFilter()const
 {
-  QMetaEnum logLevelEnum = this->metaObject()->enumerator(0);
+  Q_D(const ctkErrorLogModel);
+  QMetaEnum logLevelEnum = d->ErrorLogLevel.metaObject()->enumerator(0);
   Q_ASSERT(QString("LogLevel").compare(logLevelEnum.name()) == 0);
 
-  Self::LogLevels filter = Self::Unknown;
+  ctkErrorLogLevel::LogLevels filter = ctkErrorLogLevel::Unknown;
   foreach(const QString& filterAsString, this->filterRegExp().pattern().split("|"))
     {
-    filter |= static_cast<Self::LogLevels>(logLevelEnum.keyToValue(filterAsString.toLatin1()));
+    filter |= static_cast<ctkErrorLogLevel::LogLevels>(logLevelEnum.keyToValue(filterAsString.toLatin1()));
     }
   return filter;
 }
@@ -516,3 +614,155 @@ void ctkErrorLogModel::setLogEntryGrouping(bool value)
   Q_D(ctkErrorLogModel);
   d->LogEntryGrouping = value;
 }
+
+//------------------------------------------------------------------------------
+bool ctkErrorLogModel::asynchronousLogging()const
+{
+  Q_D(const ctkErrorLogModel);
+  return d->AsynchronousLogging;
+}
+
+//------------------------------------------------------------------------------
+void ctkErrorLogModel::setAsynchronousLogging(bool value)
+{
+  Q_D(ctkErrorLogModel);
+  if (d->AsynchronousLogging == value)
+    {
+    return;
+    }
+
+  foreach(const QString& handlerName, d->RegisteredHandlers.keys())
+    {
+    d->setMessageHandlerConnection(
+          d->RegisteredHandlers.value(handlerName), value);
+    }
+
+  d->AsynchronousLogging = value;
+}
+
+// --------------------------------------------------------------------------
+// ctkErrorLogAbstractMessageHandlerPrivate
+
+// --------------------------------------------------------------------------
+class ctkErrorLogAbstractMessageHandlerPrivate
+{
+public:
+  ctkErrorLogAbstractMessageHandlerPrivate();
+  ~ctkErrorLogAbstractMessageHandlerPrivate();
+
+  bool                        Enabled;
+  QString                     HandlerPrettyName;
+
+  // Use "int" instead of "ctkErrorLogModel::TerminalOutput" to avoid compilation warning ...
+  // qhash.h:879: warning: passing ‘ctkErrorLogModel::TerminalOutput’ chooses ‘int’ over ‘uint’ [-Wsign-promo]
+  QHash<int, ctkErrorLogTerminalOutput*> TerminalOutputs;
+};
+
+// --------------------------------------------------------------------------
+ctkErrorLogAbstractMessageHandlerPrivate::
+ctkErrorLogAbstractMessageHandlerPrivate()
+  : Enabled(false)
+{
+}
+
+// --------------------------------------------------------------------------
+ctkErrorLogAbstractMessageHandlerPrivate::~ctkErrorLogAbstractMessageHandlerPrivate()
+{
+}
+
+// --------------------------------------------------------------------------
+// ctkErrorLogAbstractMessageHandlerPrivate methods
+
+// --------------------------------------------------------------------------
+ctkErrorLogAbstractMessageHandler::ctkErrorLogAbstractMessageHandler()
+  : Superclass(), d_ptr(new ctkErrorLogAbstractMessageHandlerPrivate)
+{
+}
+
+// --------------------------------------------------------------------------
+ctkErrorLogAbstractMessageHandler::~ctkErrorLogAbstractMessageHandler()
+{
+}
+
+// --------------------------------------------------------------------------
+QString ctkErrorLogAbstractMessageHandler::handlerPrettyName()const
+{
+  Q_D(const ctkErrorLogAbstractMessageHandler);
+  if (d->HandlerPrettyName.isEmpty())
+    {
+    return this->handlerName();
+    }
+  else
+    {
+    return d->HandlerPrettyName;
+    }
+}
+
+// --------------------------------------------------------------------------
+void ctkErrorLogAbstractMessageHandler::setHandlerPrettyName(const QString& newHandlerPrettyName)
+{
+  Q_D(ctkErrorLogAbstractMessageHandler);
+  d->HandlerPrettyName = newHandlerPrettyName;
+}
+
+// --------------------------------------------------------------------------
+bool ctkErrorLogAbstractMessageHandler::enabled()const
+{
+  Q_D(const ctkErrorLogAbstractMessageHandler);
+  return d->Enabled;
+}
+
+// --------------------------------------------------------------------------
+void ctkErrorLogAbstractMessageHandler::setEnabled(bool value)
+{
+  Q_D(ctkErrorLogAbstractMessageHandler);
+  if (value == d->Enabled)
+    {
+    return;
+    }
+  this->setEnabledInternal(value);
+  d->Enabled = value;
+}
+
+// --------------------------------------------------------------------------
+void ctkErrorLogAbstractMessageHandler::handleMessage(const QString& threadId,
+                                                      ctkErrorLogLevel::LogLevel logLevel,
+                                                      const QString& origin, const QString& text)
+{
+  Q_D(ctkErrorLogAbstractMessageHandler);
+  if (logLevel <= ctkErrorLogLevel::Info)
+    {
+    if(d->TerminalOutputs.contains(ctkErrorLogModel::StandardOutput))
+      {
+      d->TerminalOutputs.value(ctkErrorLogModel::StandardOutput)->output(text);
+      }
+    }
+  else
+    {
+    if(d->TerminalOutputs.contains(ctkErrorLogModel::StandardError))
+      {
+      d->TerminalOutputs.value(ctkErrorLogModel::StandardError)->output(text);
+      }
+    }
+  emit this->messageHandled(QDateTime::currentDateTime(), threadId, logLevel, origin, text);
+}
+
+// --------------------------------------------------------------------------
+ctkErrorLogTerminalOutput* ctkErrorLogAbstractMessageHandler::terminalOutput(
+    ctkErrorLogModel::TerminalOutput terminalOutputType)const
+{
+  Q_D(const ctkErrorLogAbstractMessageHandler);
+  if(d->TerminalOutputs.contains(terminalOutputType))
+    {
+    return d->TerminalOutputs.value(terminalOutputType);
+    }
+  return 0;
+}
+
+// --------------------------------------------------------------------------
+void ctkErrorLogAbstractMessageHandler::setTerminalOutput(
+    ctkErrorLogModel::TerminalOutput terminalOutputType, ctkErrorLogTerminalOutput* terminalOutput)
+{
+  Q_D(ctkErrorLogAbstractMessageHandler);
+  d->TerminalOutputs.insert(terminalOutputType, terminalOutput);
+}

+ 122 - 46
Libs/Core/ctkErrorLogModel.h

@@ -28,72 +28,83 @@
 // CTK includes
 #include "ctkCoreExport.h"
 
-class ctkErrorLogModel;
-class ctkErrorLogModelPrivate;
-class QStandardItemModel;
-
-#include <iostream>
-
 //------------------------------------------------------------------------------
-class CTK_CORE_EXPORT ctkErrorLogAbstractMessageHandler
+class CTK_CORE_EXPORT ctkErrorLogLevel : public QObject
 {
+  Q_OBJECT
+  Q_FLAGS(LogLevel)
 public:
-  /// Disabled by default.
-  ctkErrorLogAbstractMessageHandler();
-  virtual ~ctkErrorLogAbstractMessageHandler();
+  ctkErrorLogLevel();
 
-  ctkErrorLogModel * errorLogModel()const;
-  void setErrorLogModel(ctkErrorLogModel * newErrorLogModel);
+  enum LogLevel
+    {
+    None     = 0x0,
+    Unknown  = 0x1,
+    Status   = 0x2,
+    Trace    = 0x4,
+    Debug    = 0x8,
+    Info     = 0x10,
+    Warning  = 0x20,
+    Error    = 0x40,
+    Critical = 0x80,
+    Fatal    = 0x100
+    };
+  Q_DECLARE_FLAGS(LogLevels, LogLevel)
 
-  virtual QString handlerName()const = 0;
+  QString operator ()(LogLevel logLevel);
 
-  QString handlerPrettyName()const;
+  QString logLevelAsString(ctkErrorLogLevel::LogLevel logLevel)const;
+};
+Q_DECLARE_OPERATORS_FOR_FLAGS(ctkErrorLogLevel::LogLevels)
+
+//------------------------------------------------------------------------------
+class ctkErrorLogTerminalOutputPrivate;
+
+//------------------------------------------------------------------------------
+class CTK_CORE_EXPORT ctkErrorLogTerminalOutput
+{
+public:
+  ctkErrorLogTerminalOutput();
+  virtual ~ctkErrorLogTerminalOutput();
 
   bool enabled()const;
   void setEnabled(bool value);
 
-protected:
-  void setHandlerPrettyName(const QString& newHandlerPrettyName);
+  int fileDescriptor()const;
+  void setFileDescriptor(int fd);
 
-  virtual void setEnabledInternal(bool value) = 0;
+  void output(const QString& text);
+
+protected:
+  QScopedPointer<ctkErrorLogTerminalOutputPrivate> d_ptr;
 
 private:
-  QPointer<ctkErrorLogModel> ErrorLogModel;
-  bool Enabled;
-  QString HandlerPrettyName;
+  Q_DECLARE_PRIVATE(ctkErrorLogTerminalOutput);
+  Q_DISABLE_COPY(ctkErrorLogTerminalOutput);
 };
 
 //------------------------------------------------------------------------------
+class ctkErrorLogAbstractMessageHandler;
+class ctkErrorLogModelPrivate;
+
+//------------------------------------------------------------------------------
 class CTK_CORE_EXPORT ctkErrorLogModel : public QSortFilterProxyModel
 {
   Q_OBJECT
-  Q_FLAGS(LogLevel)
+  Q_FLAGS(TerminalOutput)
   Q_PROPERTY(bool logEntryGrouping READ logEntryGrouping WRITE setLogEntryGrouping)
-  Q_PROPERTY(bool terminalOutputEnabled READ terminalOutputEnabled WRITE  setTerminalOutputEnabled)
+  Q_PROPERTY(TerminalOutput terminalOutputs READ terminalOutputs WRITE  setTerminalOutputs)
+  Q_PROPERTY(bool asynchronousLogging READ asynchronousLogging WRITE  setAsynchronousLogging)
 public:
   typedef QSortFilterProxyModel Superclass;
   typedef ctkErrorLogModel Self;
   explicit ctkErrorLogModel(QObject* parentObject = 0);
   virtual ~ctkErrorLogModel();
 
-  enum LogLevel
-    {
-    None     = 0x0,
-    Unknown  = 0x1,
-    Status   = 0x2,
-    Trace    = 0x4,
-    Debug    = 0x8,
-    Info     = 0x10,
-    Warning  = 0x20,
-    Error    = 0x40,
-    Critical = 0x80,
-    Fatal    = 0x100
-    };
-  Q_DECLARE_FLAGS(LogLevels, LogLevel)
-
   enum ColumnsIds
     {
     TimeColumn = 0,
+    ThreadIdColumn,
     LogLevelColumn,
     OriginColumn,
     DescriptionColumn
@@ -103,11 +114,15 @@ public:
     DescriptionTextRole = Qt::UserRole + 1
     };
 
+  /// Register a message handler.
   bool registerMsgHandler(ctkErrorLogAbstractMessageHandler * msgHandler);
 
   QStringList msgHandlerNames()const;
 
+  /// Return True if the handler identified by \a handlerName is enabled
   bool msgHandlerEnabled(const QString& handlerName) const;
+
+  /// Enable a specific handler given its name
   void setMsgHandlerEnabled(const QString& handlerName, bool enabled);
 
   /// Return names of the enabled message handlers
@@ -120,24 +135,41 @@ public:
   void disableAllMsgHandler();
   void setAllMsgHandlerEnabled(bool enabled);
 
-  /// Return True messages are both printed into the terminal and added to ctkErrorLogModel.
-  bool terminalOutputEnabled()const;
-
-  void setTerminalOutputEnabled(bool enabled);
+  enum TerminalOutput
+    {
+    None            = 0x0,
+    StandardError   = 0x1,
+    StandardOutput  = 0x2,
+    All             = StandardError | StandardOutput
+    };
+  Q_DECLARE_FLAGS(TerminalOutputs, TerminalOutput)
 
-  QString logLevelAsString(LogLevel logLevel)const;
+  /// Return if messages are both printed into the terminal and added to ctkErrorLogModel.
+  /// \note If TerminalOutput::None is returned, message will only be added to the model.
+  TerminalOutputs terminalOutputs()const;
 
-  void addEntry(LogLevel logLevel, const QString& origin, const char* text);
+  /// Set terminal output mode
+  /// \sa terminalOutputs()
+  /// \sa TerminalOutput
+  void setTerminalOutputs(const TerminalOutputs& terminalOutput);
 
+  /// Remove all message from model
   void clear();
 
-  void filterEntry(const LogLevels& logLevel = Unknown, bool disableFilter = false);
+  ctkErrorLogLevel::LogLevels logLevelFilter()const;
 
-  LogLevels logLevelFilter()const;
+  void filterEntry(const ctkErrorLogLevel::LogLevels& logLevel = ctkErrorLogLevel::Unknown, bool disableFilter = false);
 
   bool logEntryGrouping()const;
   void setLogEntryGrouping(bool value);
 
+  bool asynchronousLogging()const;
+  void setAsynchronousLogging(bool value);
+
+public slots:
+  void addEntry(const QDateTime& currentDateTime, const QString& threadId,
+                ctkErrorLogLevel::LogLevel logLevel, const QString& origin, const QString& text);
+
 signals:
   void logLevelFilterChanged();
 
@@ -148,6 +180,50 @@ private:
   Q_DECLARE_PRIVATE(ctkErrorLogModel);
   Q_DISABLE_COPY(ctkErrorLogModel);
 };
-Q_DECLARE_OPERATORS_FOR_FLAGS(ctkErrorLogModel::LogLevels)
+Q_DECLARE_OPERATORS_FOR_FLAGS(ctkErrorLogModel::TerminalOutputs)
 
+//------------------------------------------------------------------------------
+class ctkErrorLogAbstractMessageHandlerPrivate;
+
+//------------------------------------------------------------------------------
+class CTK_CORE_EXPORT ctkErrorLogAbstractMessageHandler : public QObject
+{
+  Q_OBJECT
+public:
+  typedef QObject Superclass;
+  /// Disabled by default.
+  ctkErrorLogAbstractMessageHandler();
+  virtual ~ctkErrorLogAbstractMessageHandler();
+
+  virtual QString handlerName()const = 0;
+
+  QString handlerPrettyName()const;
+
+  bool enabled()const;
+  void setEnabled(bool value);
+
+  void handleMessage(const QString& threadId, ctkErrorLogLevel::LogLevel logLevel,
+                     const QString& origin, const QString& text);
+
+  ctkErrorLogTerminalOutput* terminalOutput(ctkErrorLogModel::TerminalOutput terminalOutputType)const;
+  void setTerminalOutput(ctkErrorLogModel::TerminalOutput terminalOutputType,
+                         ctkErrorLogTerminalOutput * terminalOutput);
+
+signals:
+  void messageHandled(const QDateTime& currentDateTime, const QString& threadId,
+                      ctkErrorLogLevel::LogLevel logLevel, const QString& origin,
+                      const QString& text);
+
+protected:
+  void setHandlerPrettyName(const QString& newHandlerPrettyName);
+
+  virtual void setEnabledInternal(bool value) = 0;
+
+protected:
+  QScopedPointer<ctkErrorLogAbstractMessageHandlerPrivate> d_ptr;
+
+private:
+  Q_DECLARE_PRIVATE(ctkErrorLogAbstractMessageHandler);
+  Q_DISABLE_COPY(ctkErrorLogAbstractMessageHandler);
+};
 #endif

+ 10 - 11
Libs/Core/ctkErrorLogQtMessageHandler.cpp

@@ -20,9 +20,11 @@
 
 // Qt includes
 #include <QCoreApplication>
+#include <QThread>
 
 // CTK includes
 #include "ctkErrorLogQtMessageHandler.h"
+#include <ctkUtils.h>
 
 // STD includes
 #include <iostream>
@@ -58,22 +60,22 @@ void ctkErrorLogModelQtMessageOutput(QtMsgType type, const char *msg)
     {
     return;
     }
-  ctkErrorLogModel::LogLevel level = ctkErrorLogModel::Unknown;
+  ctkErrorLogLevel::LogLevel level = ctkErrorLogLevel::Unknown;
   if (type == QtDebugMsg)
     {
-    level = ctkErrorLogModel::Debug;
+    level = ctkErrorLogLevel::Debug;
     }
   else if (type == QtWarningMsg)
     {
-    level = ctkErrorLogModel::Warning;
+    level = ctkErrorLogLevel::Warning;
     }
   else if (type == QtCriticalMsg)
     {
-    level = ctkErrorLogModel::Critical;
+    level = ctkErrorLogLevel::Critical;
     }
   else if (type == QtFatalMsg)
     {
-    level = ctkErrorLogModel::Fatal;
+    level = ctkErrorLogLevel::Fatal;
     }
 
   QCoreApplication * coreApp = QCoreApplication::instance();
@@ -87,12 +89,9 @@ void ctkErrorLogModelQtMessageOutput(QtMsgType type, const char *msg)
 //    //  {
 //    //  continue;
 //    //  }
-    if (!handler->errorLogModel())
-      {
-      std::cout << "ErrorLogModel is Null !" << std::endl;
-      return;
-      }
-    handler->errorLogModel()->addEntry(level, handler->handlerPrettyName(), msg);
+    handler->handleMessage(
+          ctk::qtHandleToString(QThread::currentThreadId()),
+          level, handler->handlerPrettyName(), msg);
     }
 }
 }

+ 55 - 24
Libs/Core/ctkErrorLogStreamMessageHandler.cpp

@@ -18,8 +18,13 @@
 
 =========================================================================*/
 
+// Qt includes
+#include <QMutexLocker>
+#include <QThread>
+
 // CTK includes
 #include "ctkErrorLogStreamMessageHandler.h"
+#include "ctkUtils.h"
 
 // STD includes
 #include <iostream>
@@ -41,7 +46,7 @@ class ctkStreamHandler : public std::streambuf
 {
 public:
   ctkStreamHandler(ctkErrorLogStreamMessageHandler* messageHandler,
-                   ctkErrorLogModel::LogLevel logLevel,
+                   ctkErrorLogLevel::LogLevel logLevel,
                    std::ostream& stream);
 
   void setEnabled(bool value);
@@ -51,14 +56,18 @@ protected:
   virtual std::streamsize xsputn(const char *p, std::streamsize n);
 
 private:
+  void initBuffer();
+  std::string* currentBuffer();
+
   ctkErrorLogStreamMessageHandler * MessageHandler;
-  ctkErrorLogModel::LogLevel LogLevel;
+  ctkErrorLogLevel::LogLevel LogLevel;
 
   bool Enabled;
 
   std::ostream&   Stream;
   std::streambuf* SavedBuffer;
-  std::string     StringBuffer;
+  QHash<Qt::HANDLE, std::string*> StringBuffers;
+  QMutex Mutex;
 };
 
 // --------------------------------------------------------------------------
@@ -66,7 +75,7 @@ private:
 
 // --------------------------------------------------------------------------
 ctkStreamHandler::ctkStreamHandler(ctkErrorLogStreamMessageHandler* messageHandler,
-                                   ctkErrorLogModel::LogLevel logLevel,
+                                   ctkErrorLogLevel::LogLevel logLevel,
                                    std::ostream& stream) :
   MessageHandler(messageHandler), LogLevel(logLevel), Stream(stream)
 {
@@ -89,12 +98,12 @@ void ctkStreamHandler::setEnabled(bool value)
   else
     {
     // Output anything that is left
-    if (!this->StringBuffer.empty())
-      {
-      Q_ASSERT(this->MessageHandler->errorLogModel());
-      this->MessageHandler->errorLogModel()->addEntry(
-            this->LogLevel, this->MessageHandler->handlerPrettyName(), this->StringBuffer.c_str());
-      }
+//    if (!this->StringBuffer.empty())
+//      {
+//      Q_ASSERT(this->MessageHandler);
+//      this->MessageHandler->handleMessage(this->LogLevel,
+//          this->MessageHandler->handlerPrettyName(), this->StringBuffer.c_str());
+//      }
     this->Stream.rdbuf(this->SavedBuffer);
     }
 
@@ -102,18 +111,37 @@ void ctkStreamHandler::setEnabled(bool value)
 }
 
 // --------------------------------------------------------------------------
+void ctkStreamHandler::initBuffer()
+{
+  Qt::HANDLE threadId = QThread::currentThreadId();
+  if (!this->StringBuffers.contains(threadId))
+    {
+    this->StringBuffers.insert(threadId, new std::string);
+    }
+}
+
+// --------------------------------------------------------------------------
+std::string* ctkStreamHandler::currentBuffer()
+{
+  return this->StringBuffers.value(QThread::currentThreadId());
+}
+
+// --------------------------------------------------------------------------
 std::streambuf::int_type ctkStreamHandler::overflow(std::streambuf::int_type v)
 {
+  QMutexLocker locker(&this->Mutex);
+  this->initBuffer();
   if (v == '\n')
     {
-    Q_ASSERT(this->MessageHandler->errorLogModel());
-    this->MessageHandler->errorLogModel()->addEntry(
-          this->LogLevel, this->MessageHandler->handlerPrettyName(), this->StringBuffer.c_str());
-    this->StringBuffer.erase(this->StringBuffer.begin(), this->StringBuffer.end());
+    Q_ASSERT(this->MessageHandler);
+    this->MessageHandler->handleMessage(
+          ctk::qtHandleToString(QThread::currentThreadId()), this->LogLevel,
+          this->MessageHandler->handlerPrettyName(), this->currentBuffer()->c_str());
+    this->currentBuffer()->erase(this->currentBuffer()->begin(), this->currentBuffer()->end());
     }
   else
     {
-    this->StringBuffer += v;
+    *this->currentBuffer() += v;
     }
   return v;
 }
@@ -121,19 +149,22 @@ std::streambuf::int_type ctkStreamHandler::overflow(std::streambuf::int_type v)
 // --------------------------------------------------------------------------
 std::streamsize ctkStreamHandler::xsputn(const char *p, std::streamsize n)
 {
-  this->StringBuffer.append(p, p + n);
+  QMutexLocker locker(&this->Mutex);
+  this->initBuffer();
+  this->currentBuffer()->append(p, p + n);
 
   std::string::size_type pos = 0;
   while (pos != std::string::npos)
     {
-    pos = this->StringBuffer.find('\n');
+    pos = this->currentBuffer()->find('\n');
     if (pos != std::string::npos)
       {
-      std::string tmp(this->StringBuffer.begin(), this->StringBuffer.begin() + pos);
-      Q_ASSERT(this->MessageHandler->errorLogModel());
-      this->MessageHandler->errorLogModel()->addEntry(
-            this->LogLevel, this->MessageHandler->handlerPrettyName(), tmp.c_str());
-      this->StringBuffer.erase(this->StringBuffer.begin(), this->StringBuffer.begin() + pos + 1);
+      std::string tmp(this->currentBuffer()->begin(), this->currentBuffer()->begin() + pos);
+      Q_ASSERT(this->MessageHandler);
+      this->MessageHandler->handleMessage(
+            ctk::qtHandleToString(QThread::currentThreadId()), this->LogLevel,
+            this->MessageHandler->handlerPrettyName(), tmp.c_str());
+      this->currentBuffer()->erase(this->currentBuffer()->begin(), this->currentBuffer()->begin() + pos + 1);
       }
     }
   return n;
@@ -181,8 +212,8 @@ ctkErrorLogStreamMessageHandler::ctkErrorLogStreamMessageHandler() :
   Superclass(), d_ptr(new ctkErrorLogStreamMessageHandlerPrivate())
 {
   Q_D(ctkErrorLogStreamMessageHandler);
-  d->CoutStreamHandler = new ctkStreamHandler(this, ctkErrorLogModel::Info, std::cout);
-  d->CerrStreamHandler = new ctkStreamHandler(this, ctkErrorLogModel::Critical, std::cerr);
+  d->CoutStreamHandler = new ctkStreamHandler(this, ctkErrorLogLevel::Info, std::cout);
+  d->CerrStreamHandler = new ctkStreamHandler(this, ctkErrorLogLevel::Critical, std::cerr);
 }
 
 // --------------------------------------------------------------------------

+ 4 - 2
Libs/ImageProcessing/ITK/Core/Testing/Cpp/CMakeLists.txt

@@ -4,6 +4,7 @@ SET(KIT ${PROJECT_NAME})
 # Tests
 #
 SET(TEST_SOURCES
+  ctkITKErrorLogMessageHandlerWithThreadsTest1.cpp
   ctkITKErrorLogModelTest1.cpp
   )
 
@@ -66,12 +67,13 @@ ENDMACRO( SIMPLE_TEST  )
 # Add Tests
 #
 
-#SIMPLE_TEST( ctkVTKObjectTest1 )
+SIMPLE_TEST( ctkITKErrorLogMessageHandlerWithThreadsTest1 )
+SIMPLE_TEST( ctkITKErrorLogModelTest1 )
 
 #
 # Add Tests expecting CTKData to be set
 #
 IF(EXISTS "${CTKData_DIR}")
-  SIMPLE_TEST( ctkITKErrorLogModelTest1 )
+
 ENDIF()
 

+ 107 - 0
Libs/ImageProcessing/ITK/Core/Testing/Cpp/ctkITKErrorLogMessageHandlerWithThreadsTest1.cpp

@@ -0,0 +1,107 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QCoreApplication>
+#include <QDebug>
+
+// CTK includes
+#include "ctkITKErrorLogMessageHandler.h"
+#include "ctkModelTester.h"
+
+// VTK includes
+#include <itkOutputWindow.h>
+
+// STL includesQList
+#include <cstdlib>
+#include <iostream>
+
+// Helper functions
+#include "Testing/Cpp/ctkErrorLogModelTestHelper.cpp"
+
+namespace
+{
+//-----------------------------------------------------------------------------
+class LogVTKMessageThread : public LogMessageThread
+{
+public:
+  LogVTKMessageThread(int id, int maxIteration) : LogMessageThread(id, maxIteration){}
+
+  virtual void logMessage(const QDateTime& dateTime, int threadId, int counterIdx)
+  {
+    QString msg = QString("counterIdx:%1 - %2 - Message from thread: %3\n")
+        .arg(counterIdx).arg(dateTime.toString()).arg(threadId);
+
+    itk::OutputWindowDisplayDebugText(qPrintable(msg));
+    itk::OutputWindowDisplayWarningText(qPrintable(msg));
+    itk::OutputWindowDisplayErrorText(qPrintable(msg));
+  }
+};
+
+}
+
+//-----------------------------------------------------------------------------
+int ctkITKErrorLogMessageHandlerWithThreadsTest1(int argc, char * argv [])
+{
+  QCoreApplication app(argc, argv);
+  Q_UNUSED(app);
+
+  ctkErrorLogModel model;
+  ctkModelTester modelTester;
+  modelTester.setVerbose(false);
+
+  try
+    {
+    modelTester.setModel(&model);
+
+    // --------------------------------------------------------------------------
+    // Monitor Stream messages
+
+    model.registerMsgHandler(new ctkITKErrorLogMessageHandler);
+    model.setMsgHandlerEnabled(ctkITKErrorLogMessageHandler::HandlerName, true);
+
+    int threadCount = 15;
+    int maxIteration = 5;
+    int messagesPerIteration = 3;
+    startLogMessageThreads<LogVTKMessageThread>(threadCount, maxIteration);
+
+    // Give enough time for the threads to send their messages
+    QTimer::singleShot(1500, qApp, SLOT(quit()));
+    app.exec();
+
+    int expectedMessageCount = threadCount * maxIteration * messagesPerIteration;
+    QString errorMsg = checkRowCount(__LINE__, model.rowCount(), /* expected = */ expectedMessageCount);
+    if (!errorMsg.isEmpty())
+      {
+      model.disableAllMsgHandler();
+      printErrorMessage(errorMsg);
+      printTextMessages(model);
+      return EXIT_FAILURE;
+      }
+    }
+  catch (const char* error)
+    {
+    model.disableAllMsgHandler();
+    std::cerr << error << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  return EXIT_SUCCESS;
+}

+ 8 - 43
Libs/ImageProcessing/ITK/Core/Testing/Cpp/ctkITKErrorLogModelTest1.cpp

@@ -21,7 +21,6 @@
 // Qt includes
 #include <QCoreApplication>
 #include <QDebug>
-#include <QStringList>
 
 #ifdef __GNUC__
 // Disable warnings related to 'itkSmartPointer.h' file
@@ -42,48 +41,8 @@
 #include <cstdlib>
 #include <iostream>
 
-namespace
-{
-//-----------------------------------------------------------------------------
-// Utility function
-
-//-----------------------------------------------------------------------------
-QString checkRowCount(int line, int currentRowCount, int expectedRowCount)
-{
-  if (currentRowCount != expectedRowCount)
-    {
-    QString errorMsg("Line %1 - Expected rowCount: %2 - Current rowCount: %3\n");
-    return errorMsg.arg(line).arg(expectedRowCount).arg(currentRowCount);
-    }
-  return QString();
-}
-
-//-----------------------------------------------------------------------------
-QString checkTextMessages(int line, const ctkErrorLogModel& model, const QStringList& expectedMessages)
-{
-  for(int i=0; i < expectedMessages.count(); ++i)
-    {
-    QModelIndex descriptionIndex = model.index(i, ctkErrorLogModel::DescriptionColumn);
-    QString currentMessage = descriptionIndex.data(ctkErrorLogModel::DescriptionTextRole).toString();
-    if (currentMessage.compare(expectedMessages.value(i)) != 0)
-      {
-      QString errorMsg("Line %1 - Problem with row%2 !\n"
-                       "\tExpected message [%3]\n"
-                       "\tCurrent message [%4]\n");
-      return errorMsg.arg(line).arg(i).arg(expectedMessages.value(i)).arg(currentMessage);
-      }
-    }
-  return QString();
-}
-
-//-----------------------------------------------------------------------------
-void printErrorMessage(const QString& errorMessage)
-{
-  fprintf(stderr, "%s", qPrintable(errorMessage));
-  fflush(stderr);
-}
-
-} // end namespace
+// Helper functions
+#include "Testing/Cpp/ctkErrorLogModelTestHelper.cpp"
 
 //-----------------------------------------------------------------------------
 int ctkITKErrorLogModelTest1(int argc, char * argv [])
@@ -123,6 +82,9 @@ int ctkITKErrorLogModelTest1(int argc, char * argv [])
     QString itkMessage2("This is a ITK error message");
     itk::OutputWindowDisplayErrorText(qPrintable(itkMessage2));
 
+    // Give enough time to the ErrorLogModel to consider the queued messages.
+    processEvents(1000);
+
     QStringList expectedITKMessages;
     expectedITKMessages << itkMessage0 << itkMessage1 << itkMessage2;
 
@@ -131,6 +93,7 @@ int ctkITKErrorLogModelTest1(int argc, char * argv [])
       {
       model.disableAllMsgHandler();
       printErrorMessage(errorMsg);
+      printTextMessages(model);
       return EXIT_FAILURE;
       }
 
@@ -139,6 +102,7 @@ int ctkITKErrorLogModelTest1(int argc, char * argv [])
       {
       model.disableAllMsgHandler();
       printErrorMessage(errorMsg);
+      printTextMessages(model);
       return EXIT_FAILURE;
       }
 
@@ -157,6 +121,7 @@ int ctkITKErrorLogModelTest1(int argc, char * argv [])
       {
       model.disableAllMsgHandler();
       printErrorMessage(errorMsg);
+      printTextMessages(model);
       return EXIT_FAILURE;
       }
     }

+ 24 - 8
Libs/ImageProcessing/ITK/Core/ctkITKErrorLogMessageHandler.cpp

@@ -18,8 +18,12 @@
 
 =========================================================================*/
 
+// Qt includes
+#include <QThread>
+
 // CTK includes
 #include "ctkITKErrorLogMessageHandler.h"
+#include "ctkUtils.h"
 
 #ifdef __GNUC__
 // Disable warnings related to 'itkSmartPointer.h' file
@@ -73,22 +77,31 @@ public:
 //----------------------------------------------------------------------------
 void ctkITKOutputWindow::DisplayText(const char* text)
 {
-  this->MessageHandler->errorLogModel()->addEntry(
-        ctkErrorLogModel::Info, this->MessageHandler->handlerPrettyName(), text);
+  Q_ASSERT(this->MessageHandler);
+  this->MessageHandler->handleMessage(
+        ctk::qtHandleToString(QThread::currentThreadId()),
+        ctkErrorLogLevel::Info,
+        this->MessageHandler->handlerPrettyName(), text);
 }
 
 //----------------------------------------------------------------------------
 void ctkITKOutputWindow::DisplayErrorText(const char* text)
 {
-  this->MessageHandler->errorLogModel()->addEntry(
-        ctkErrorLogModel::Error, this->MessageHandler->handlerPrettyName(), text);
+  Q_ASSERT(this->MessageHandler);
+  this->MessageHandler->handleMessage(
+        ctk::qtHandleToString(QThread::currentThreadId()),
+        ctkErrorLogLevel::Error,
+        this->MessageHandler->handlerPrettyName(), text);
 }
 
 //----------------------------------------------------------------------------
 void ctkITKOutputWindow::DisplayWarningText(const char* text)
 {
-  this->MessageHandler->errorLogModel()->addEntry(
-        ctkErrorLogModel::Warning, this->MessageHandler->handlerPrettyName(), text);
+  Q_ASSERT(this->MessageHandler);
+  this->MessageHandler->handleMessage(
+        ctk::qtHandleToString(QThread::currentThreadId()),
+        ctkErrorLogLevel::Warning,
+        this->MessageHandler->handlerPrettyName(), text);
 }
 
 //----------------------------------------------------------------------------
@@ -100,8 +113,11 @@ void ctkITKOutputWindow::DisplayGenericWarningText(const char* text)
 //----------------------------------------------------------------------------
 void ctkITKOutputWindow::DisplayDebugText(const char* text)
 {
-  this->MessageHandler->errorLogModel()->addEntry(
-        ctkErrorLogModel::Debug, this->MessageHandler->handlerPrettyName(), text);
+  Q_ASSERT(this->MessageHandler);
+  this->MessageHandler->handleMessage(
+        ctk::qtHandleToString(QThread::currentThreadId()),
+        ctkErrorLogLevel::Debug,
+        this->MessageHandler->handlerPrettyName(), text);
 }
 
 } // End of itk namespace

+ 2 - 0
Libs/Visualization/VTK/Core/Testing/Cpp/CMakeLists.txt

@@ -6,6 +6,7 @@ SET(KIT ${PROJECT_NAME})
 SET(TEST_SOURCES
   ctkVTKColorTransferFunctionTest1.cpp
   ctkVTKConnectionTest1.cpp
+  ctkVTKErrorLogMessageHandlerWithThreadsTest1.cpp
   ctkVTKErrorLogModelTest1.cpp
   ctkVTKHistogramTest1.cpp
   ctkVTKHistogramTest2.cpp
@@ -77,6 +78,7 @@ ENDMACRO( SIMPLE_TEST  )
 
 SIMPLE_TEST( ctkVTKColorTransferFunctionTest1 )
 SIMPLE_TEST( ctkVTKConnectionTest1 )
+SIMPLE_TEST( ctkVTKErrorLogMessageHandlerWithThreadsTest1 )
 SIMPLE_TEST( ctkVTKErrorLogModelTest1 )
 SIMPLE_TEST( ctkVTKHistogramTest1 )
 SIMPLE_TEST( ctkVTKHistogramTest2 )

+ 107 - 0
Libs/Visualization/VTK/Core/Testing/Cpp/ctkVTKErrorLogMessageHandlerWithThreadsTest1.cpp

@@ -0,0 +1,107 @@
+/*=========================================================================
+
+  Library:   CTK
+
+  Copyright (c) Kitware Inc.
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0.txt
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=========================================================================*/
+
+// Qt includes
+#include <QCoreApplication>
+#include <QDebug>
+
+// CTK includes
+#include "ctkVTKErrorLogMessageHandler.h"
+#include "ctkModelTester.h"
+
+// VTK includes
+#include <vtkOutputWindow.h>
+
+// STL includesQList
+#include <cstdlib>
+#include <iostream>
+
+// Helper functions
+#include "Testing/Cpp/ctkErrorLogModelTestHelper.cpp"
+
+namespace
+{
+//-----------------------------------------------------------------------------
+class LogVTKMessageThread : public LogMessageThread
+{
+public:
+  LogVTKMessageThread(int id, int maxIteration) : LogMessageThread(id, maxIteration){}
+
+  virtual void logMessage(const QDateTime& dateTime, int threadId, int counterIdx)
+  {
+    QString msg = QString("counterIdx:%1 - %2 - Message from thread: %3\n")
+        .arg(counterIdx).arg(dateTime.toString()).arg(threadId);
+
+    vtkOutputWindowDisplayDebugText(qPrintable(msg));
+    vtkOutputWindowDisplayWarningText(qPrintable(msg));
+    vtkOutputWindowDisplayErrorText(qPrintable(msg));
+  }
+};
+
+}
+
+//-----------------------------------------------------------------------------
+int ctkVTKErrorLogMessageHandlerWithThreadsTest1(int argc, char * argv [])
+{
+  QCoreApplication app(argc, argv);
+  Q_UNUSED(app);
+
+  ctkErrorLogModel model;
+  ctkModelTester modelTester;
+  modelTester.setVerbose(false);
+
+  try
+    {
+    modelTester.setModel(&model);
+
+    // --------------------------------------------------------------------------
+    // Monitor Stream messages
+
+    model.registerMsgHandler(new ctkVTKErrorLogMessageHandler);
+    model.setMsgHandlerEnabled(ctkVTKErrorLogMessageHandler::HandlerName, true);
+
+    int threadCount = 15;
+    int maxIteration = 5;
+    int messagesPerIteration = 3;
+    startLogMessageThreads<LogVTKMessageThread>(threadCount, maxIteration);
+
+    // Give enough time for the threads to send their messages
+    QTimer::singleShot(1500, qApp, SLOT(quit()));
+    app.exec();
+
+    int expectedMessageCount = threadCount * maxIteration * messagesPerIteration;
+    QString errorMsg = checkRowCount(__LINE__, model.rowCount(), /* expected = */ expectedMessageCount);
+    if (!errorMsg.isEmpty())
+      {
+      model.disableAllMsgHandler();
+      printErrorMessage(errorMsg);
+      printTextMessages(model);
+      return EXIT_FAILURE;
+      }
+    }
+  catch (const char* error)
+    {
+    model.disableAllMsgHandler();
+    std::cerr << error << std::endl;
+    return EXIT_FAILURE;
+    }
+
+  return EXIT_SUCCESS;
+}

+ 6 - 59
Libs/Visualization/VTK/Core/Testing/Cpp/ctkVTKErrorLogModelTest1.cpp

@@ -21,76 +21,20 @@
 // Qt includes
 #include <QCoreApplication>
 #include <QDebug>
-#include <QStringList>
 
 // CTK includes
-#include "ctkErrorLogModel.h"
 #include "ctkVTKErrorLogMessageHandler.h"
 #include "ctkModelTester.h"
 
 // VTK includes
 #include <vtkOutputWindow.h>
 
-// STL includes
+// STL includesQList
 #include <cstdlib>
 #include <iostream>
 
-namespace
-{
-//-----------------------------------------------------------------------------
-// Utility function
-
-//-----------------------------------------------------------------------------
-QString checkRowCount(int line, int currentRowCount, int expectedRowCount)
-{
-  if (currentRowCount != expectedRowCount)
-    {
-    QString errorMsg("Line %1 - Expected rowCount: %2 - Current rowCount: %3\n");
-    return errorMsg.arg(line).arg(expectedRowCount).arg(currentRowCount);
-    }
-  return QString();
-}
-
-//-----------------------------------------------------------------------------
-QString checkTextMessages(int line, const ctkErrorLogModel& model, const QStringList& expectedMessages)
-{
-  for(int i=0; i < expectedMessages.count(); ++i)
-    {
-    QModelIndex descriptionIndex = model.index(i, ctkErrorLogModel::DescriptionColumn);
-    QString currentMessage = descriptionIndex.data(ctkErrorLogModel::DescriptionTextRole).toString();
-    if (currentMessage.compare(expectedMessages.value(i)) != 0)
-      {
-      QString errorMsg("Line %1 - Problem with row%2 !\n"
-                       "\tExpected message [%3]\n"
-                       "\tCurrent message [%4]\n");
-      return errorMsg.arg(line).arg(i).arg(expectedMessages.value(i)).arg(currentMessage);
-      }
-    }
-  return QString();
-}
-
-//-----------------------------------------------------------------------------
-void printTextMessages(const ctkErrorLogModel& model)
-{
-  fprintf(stdout, "%s", "ErrorLogModel rows:\n");
-  QString text("\trow %1 => %2\n");
-  for (int i=0; i < model.rowCount(); ++i)
-    {
-    QString description =
-        model.index(0, ctkErrorLogModel::DescriptionColumn).data().toString();
-    fprintf(stdout, "%s", qPrintable(text.arg(i).arg(description)));
-    }
-  fflush(stdout);
-}
-
-//-----------------------------------------------------------------------------
-void printErrorMessage(const QString& errorMessage)
-{
-  fprintf(stderr, "%s", qPrintable(errorMessage));
-  fflush(stderr);
-}
-
-} // end namespace
+// Helper functions
+#include "Testing/Cpp/ctkErrorLogModelTestHelper.cpp"
 
 //-----------------------------------------------------------------------------
 int ctkVTKErrorLogModelTest1(int argc, char * argv [])
@@ -131,6 +75,9 @@ int ctkVTKErrorLogModelTest1(int argc, char * argv [])
     QString vtkMessage2("This is a VTK error message");
     vtkOutputWindowDisplayErrorText(qPrintable(vtkMessage2));
 
+    // Give enough time to the ErrorLogModel to consider the queued messages.
+    processEvents(1000);
+
     QStringList expectedVTKMessages;
     expectedVTKMessages << vtkMessage0 << vtkMessage1 << vtkMessage2;
 

+ 24 - 8
Libs/Visualization/VTK/Core/ctkVTKErrorLogMessageHandler.cpp

@@ -18,8 +18,12 @@
 
 =========================================================================*/
 
+// Qt includes
+#include <QThread>
+
 // CTK includes
 #include "ctkVTKErrorLogMessageHandler.h"
+#include "ctkUtils.h"
 
 // VTK includes
 #include <vtkObjectFactory.h>
@@ -66,22 +70,31 @@ void ctkVTKOutputWindow::PrintSelf(ostream& os, vtkIndent indent)
 //----------------------------------------------------------------------------
 void ctkVTKOutputWindow::DisplayText(const char* text)
 {
-  this->MessageHandler->errorLogModel()->addEntry(
-        ctkErrorLogModel::Info, this->MessageHandler->handlerPrettyName(), text);
+  Q_ASSERT(this->MessageHandler);
+  this->MessageHandler->handleMessage(
+        ctk::qtHandleToString(QThread::currentThreadId()),
+        ctkErrorLogLevel::Info,
+        this->MessageHandler->handlerPrettyName(), text);
 }
 
 //----------------------------------------------------------------------------
 void ctkVTKOutputWindow::DisplayErrorText(const char* text)
 {
-  this->MessageHandler->errorLogModel()->addEntry(
-        ctkErrorLogModel::Error, this->MessageHandler->handlerPrettyName(), text);
+  Q_ASSERT(this->MessageHandler);
+  this->MessageHandler->handleMessage(
+        ctk::qtHandleToString(QThread::currentThreadId()),
+        ctkErrorLogLevel::Error,
+        this->MessageHandler->handlerPrettyName(), text);
 }
 
 //----------------------------------------------------------------------------
 void ctkVTKOutputWindow::DisplayWarningText(const char* text)
 {
-  this->MessageHandler->errorLogModel()->addEntry(
-        ctkErrorLogModel::Warning, this->MessageHandler->handlerPrettyName(), text);
+  Q_ASSERT(this->MessageHandler);
+  this->MessageHandler->handleMessage(
+        ctk::qtHandleToString(QThread::currentThreadId()),
+        ctkErrorLogLevel::Warning,
+        this->MessageHandler->handlerPrettyName(), text);
 }
 
 //----------------------------------------------------------------------------
@@ -93,8 +106,11 @@ void ctkVTKOutputWindow::DisplayGenericWarningText(const char* text)
 //----------------------------------------------------------------------------
 void ctkVTKOutputWindow::DisplayDebugText(const char* text)
 {
-  this->MessageHandler->errorLogModel()->addEntry(
-        ctkErrorLogModel::Debug, this->MessageHandler->handlerPrettyName(), text);
+  Q_ASSERT(this->MessageHandler);
+  this->MessageHandler->handleMessage(
+        ctk::qtHandleToString(QThread::currentThreadId()),
+        ctkErrorLogLevel::Debug,
+        this->MessageHandler->handlerPrettyName(), text);
 }
 
 } // End of anonymous namespace

+ 7 - 58
Libs/Widgets/Testing/Cpp/ctkErrorLogWidgetTest1.cpp

@@ -28,7 +28,6 @@
 #include <QTimer>
 
 // CTK includes
-#include <ctkErrorLogModel.h>
 #include <ctkErrorLogQtMessageHandler.h>
 #include <ctkErrorLogStatusMessageHandler.h>
 #include <ctkErrorLogStreamMessageHandler.h>
@@ -38,62 +37,8 @@
 #include <cstdlib>
 #include <iostream>
 
-namespace
-{
-//-----------------------------------------------------------------------------
-// Utility function
-
-//-----------------------------------------------------------------------------
-QString checkRowCount(int line, int currentRowCount, int expectedRowCount)
-{
-  if (currentRowCount != expectedRowCount)
-    {
-    QString errorMsg("Line %1 - Expected rowCount: %2 - Current rowCount: %3\n");
-    return errorMsg.arg(line).arg(expectedRowCount).arg(currentRowCount);
-    }
-  return QString();
-}
-
-//-----------------------------------------------------------------------------
-QString checkTextMessages(int line, const ctkErrorLogModel& model, const QStringList& expectedMessages)
-{
-  for(int i=0; i < expectedMessages.count(); ++i)
-    {
-    QModelIndex descriptionIndex = model.index(i, ctkErrorLogModel::DescriptionColumn);
-    QString currentMessage = descriptionIndex.data(ctkErrorLogModel::DescriptionTextRole).toString();
-    if (currentMessage.compare(expectedMessages.value(i)) != 0)
-      {
-      QString errorMsg("Line %1 - Problem with row%2 !\n"
-                       "\tExpected message [%3]\n"
-                       "\tCurrent message [%4]\n");
-      return errorMsg.arg(line).arg(i).arg(expectedMessages.value(i)).arg(currentMessage);
-      }
-    }
-  return QString();
-}
-
-//-----------------------------------------------------------------------------
-void printTextMessages(const ctkErrorLogModel& model)
-{
-  fprintf(stdout, "%s", "ErrorLogModel rows:\n");
-  QString text("\trow %1 => %2\n");
-  for (int i=0; i < model.rowCount(); ++i)
-    {
-    QString description =
-        model.index(0, ctkErrorLogModel::DescriptionColumn).data().toString();
-    fprintf(stdout, "%s", qPrintable(text.arg(i).arg(description)));
-    }
-  fflush(stdout);
-}
-
-//-----------------------------------------------------------------------------
-void printErrorMessage(const QString& errorMessage)
-{
-  fprintf(stderr, "%s", qPrintable(errorMessage));
-  fflush(stderr);
-}
-
-} // end namespace
+// Helper functions
+#include "Testing/Cpp/ctkErrorLogModelTestHelper.cpp"
 
 //-----------------------------------------------------------------------------
 int ctkErrorLogWidgetTest1(int argc, char * argv [])
@@ -118,6 +63,9 @@ int ctkErrorLogWidgetTest1(int argc, char * argv [])
     QString expectedStatusText1 = QLatin1String("This is a status message");
     mainWindow.statusBar()->showMessage(expectedStatusText1);
 
+    // Give enough time to the ErrorLogModel to consider the queued messages.
+    processEvents(1000);
+
     QStringList expectedStatusMessages;
     expectedStatusMessages << expectedStatusText1;
 
@@ -135,6 +83,7 @@ int ctkErrorLogWidgetTest1(int argc, char * argv [])
       {
       model.disableAllMsgHandler();
       printErrorMessage(errorMsg);
+      printTextMessages(model);
       return EXIT_FAILURE;
       }
 
@@ -220,7 +169,7 @@ int ctkErrorLogWidgetTest1(int argc, char * argv [])
 
   if (argc < 2 || QString(argv[1]) != "-I" )
     {
-    QTimer::singleShot(200, &app, SLOT(quit()));
+    QTimer::singleShot(1000, &app, SLOT(quit()));
     }
 
   return app.exec();

+ 7 - 3
Libs/Widgets/ctkErrorLogStatusMessageHandler.cpp

@@ -20,19 +20,22 @@
 
 // Qt includes
 #include <QApplication>
+#include <QDateTime>
 #include <QDebug>
 #include <QMainWindow>
 #include <QStatusBar>
+#include <QThread>
 
 // CTK includes
 #include "ctkErrorLogStatusMessageHandler.h"
+#include "ctkUtils.h"
 
 // --------------------------------------------------------------------------
 QString ctkErrorLogStatusMessageHandler::HandlerName = QLatin1String("Status");
 
 // --------------------------------------------------------------------------
 ctkErrorLogStatusMessageHandler::ctkErrorLogStatusMessageHandler(QMainWindow * mainWindow) :
-  QObject(), ctkErrorLogAbstractMessageHandler()
+  ctkErrorLogAbstractMessageHandler()
 {
   this->MainWindow = QPointer<QMainWindow>(mainWindow);
 }
@@ -81,6 +84,7 @@ void ctkErrorLogStatusMessageHandler::statusBarMessageChanged(const QString& tex
     {
     return;
     }
-  this->errorLogModel()->addEntry(
-        ctkErrorLogModel::Status, this->handlerPrettyName(), text.toLatin1());
+  this->handleMessage(
+        ctk::qtHandleToString(QThread::currentThreadId()),
+        ctkErrorLogLevel::Status, this->handlerPrettyName(), text);
 }

+ 1 - 1
Libs/Widgets/ctkErrorLogStatusMessageHandler.h

@@ -29,7 +29,7 @@ class QMainWindow;
 
 //------------------------------------------------------------------------------
 class CTK_WIDGETS_EXPORT ctkErrorLogStatusMessageHandler :
-    public QObject, public ctkErrorLogAbstractMessageHandler
+    public ctkErrorLogAbstractMessageHandler
 {
   Q_OBJECT
 public:

+ 12 - 10
Libs/Widgets/ctkErrorLogWidget.cpp

@@ -37,9 +37,9 @@ public:
   typedef ctkErrorLogWidgetPrivate Self;
   ctkErrorLogWidgetPrivate(ctkErrorLogWidget& object);
 
-  ctkErrorLogModel::LogLevels ErrorButtonFilter;
-  ctkErrorLogModel::LogLevels WarningButtonFilter;
-  ctkErrorLogModel::LogLevels InfoButtonFilter;
+  ctkErrorLogLevel::LogLevels ErrorButtonFilter;
+  ctkErrorLogLevel::LogLevels WarningButtonFilter;
+  ctkErrorLogLevel::LogLevels InfoButtonFilter;
 
   void init();
 
@@ -53,9 +53,9 @@ public:
 ctkErrorLogWidgetPrivate::ctkErrorLogWidgetPrivate(ctkErrorLogWidget& object)
   : q_ptr(&object)
 {
-  this->ErrorButtonFilter = ctkErrorLogModel::Error | ctkErrorLogModel::Critical | ctkErrorLogModel::Fatal;
-  this->WarningButtonFilter = ctkErrorLogModel::Warning;
-  this->InfoButtonFilter = ctkErrorLogModel::Info | ctkErrorLogModel::Debug | ctkErrorLogModel::Trace | ctkErrorLogModel::Status;
+  this->ErrorButtonFilter = ctkErrorLogLevel::Error | ctkErrorLogLevel::Critical | ctkErrorLogLevel::Fatal;
+  this->WarningButtonFilter = ctkErrorLogLevel::Warning;
+  this->InfoButtonFilter = ctkErrorLogLevel::Info | ctkErrorLogLevel::Debug | ctkErrorLogLevel::Trace | ctkErrorLogLevel::Status;
 }
 
 // --------------------------------------------------------------------------
@@ -83,6 +83,8 @@ void ctkErrorLogWidgetPrivate::init()
 
   QObject::connect(this->ClearButton, SIGNAL(clicked()),
                    q, SLOT(removeEntries()));
+
+  this->ErrorLogTableView->setColumnHidden(ctkErrorLogModel::ThreadIdColumn, true);
 }
 
 // --------------------------------------------------------------------------
@@ -150,11 +152,11 @@ void ctkErrorLogWidget::setErrorLogModel(ctkErrorLogModel * newErrorLogModel)
     connect(this->errorLogModel(), SIGNAL(logLevelFilterChanged()),
             this, SLOT(onLogLevelFilterChanged()));
 
-    ctkErrorLogModel::LogLevels logLevelFilter = newErrorLogModel->logLevelFilter();
+    ctkErrorLogLevel::LogLevels logLevelFilter = newErrorLogModel->logLevelFilter();
     this->setErrorEntriesVisible(logLevelFilter & d->ErrorButtonFilter);
     this->setWarningEntriesVisible(logLevelFilter & d->WarningButtonFilter);
     this->setInfoEntriesVisible(logLevelFilter & d->InfoButtonFilter);
-    this->errorLogModel()->filterEntry(logLevelFilter & ctkErrorLogModel::Unknown);
+    this->errorLogModel()->filterEntry(logLevelFilter & ctkErrorLogLevel::Unknown);
 
     // Setup selection model
     d->SelectionModel = QSharedPointer<QItemSelectionModel>(new QItemSelectionModel(this->errorLogModel()));
@@ -226,7 +228,7 @@ void ctkErrorLogWidget::setUnknownEntriesVisible(bool visibility)
     {
     return;
     }
-  this->errorLogModel()->filterEntry(ctkErrorLogModel::Unknown,
+  this->errorLogModel()->filterEntry(ctkErrorLogLevel::Unknown,
       /* disableFilter= */ !visibility);
 }
 
@@ -246,7 +248,7 @@ void ctkErrorLogWidget::onLogLevelFilterChanged()
 {
   Q_D(ctkErrorLogWidget);
   Q_ASSERT(this->errorLogModel());
-  ctkErrorLogModel::LogLevels logLevelFilter = this->errorLogModel()->logLevelFilter();
+  ctkErrorLogLevel::LogLevels logLevelFilter = this->errorLogModel()->logLevelFilter();
   d->ShowErrorEntryButton->setChecked(logLevelFilter & d->ErrorButtonFilter);
   d->ShowWarningEntryButton->setChecked(logLevelFilter & d->WarningButtonFilter);
   d->ShowInfoEntryButton->setChecked(logLevelFilter & d->InfoButtonFilter);