ccConsole.cpp 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. //##########################################################################
  2. //# #
  3. //# CLOUDCOMPARE #
  4. //# #
  5. //# This program is free software; you can redistribute it and/or modify #
  6. //# it under the terms of the GNU General Public License as published by #
  7. //# the Free Software Foundation; version 2 or later of the License. #
  8. //# #
  9. //# This program is distributed in the hope that it will be useful, #
  10. //# but WITHOUT ANY WARRANTY; without even the implied warranty of #
  11. //# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
  12. //# GNU General Public License for more details. #
  13. //# #
  14. //# COPYRIGHT: EDF R&D / TELECOM ParisTech (ENST-TSI) #
  15. //# #
  16. //##########################################################################
  17. #include "ccConsole.h"
  18. //Local
  19. #include "ccPersistentSettings.h"
  20. #include "mainwindow.h"
  21. //qCC_db
  22. #include <ccSingleton.h>
  23. //Qt
  24. #include <QApplication>
  25. #include <QClipboard>
  26. #include <QColor>
  27. #include <QKeyEvent>
  28. #include <QMessageBox>
  29. #include <QSettings>
  30. #include <QTextStream>
  31. #include <QThread>
  32. #include <QTime>
  33. //system
  34. #include <cassert>
  35. #ifdef QT_DEBUG
  36. #include <iostream>
  37. #endif
  38. /***************
  39. *** Globals ***
  40. ***************/
  41. //unique console instance
  42. static ccSingleton<ccConsole> s_console;
  43. bool ccConsole::s_showQtMessagesInConsole = false;
  44. bool ccConsole::s_redirectToStdOut = false;
  45. static int s_refreshCycle_ms = 1000;
  46. /*** ccCustomQListWidget ***/
  47. ccCustomQListWidget::ccCustomQListWidget(QWidget *parent)
  48. : QListWidget(parent)
  49. {
  50. }
  51. void ccCustomQListWidget::keyPressEvent(QKeyEvent *event)
  52. {
  53. if (event->matches(QKeySequence::Copy))
  54. {
  55. int itemsCount = count();
  56. QStringList strings;
  57. for (int i = 0; i < itemsCount; ++i)
  58. {
  59. if (item(i)->isSelected())
  60. {
  61. strings << item(i)->text();
  62. }
  63. }
  64. QApplication::clipboard()->setText(strings.join("\n"));
  65. }
  66. else
  67. {
  68. QListWidget::keyPressEvent(event);
  69. }
  70. }
  71. /*** ccConsole ***/
  72. void ccConsole::SetRefreshCycle(int cycle_ms/*=1000*/)
  73. {
  74. if (cycle_ms <= 0)
  75. {
  76. //invalid
  77. Warning("Invalid refresh cycle (can't be zero of negative)");
  78. return;
  79. }
  80. if (cycle_ms != s_refreshCycle_ms)
  81. {
  82. s_refreshCycle_ms = cycle_ms;
  83. if (s_console.instance && s_console.instance->autoRefresh())
  84. {
  85. // force the internal timer update
  86. s_console.instance->setAutoRefresh(false);
  87. s_console.instance->setAutoRefresh(true);
  88. }
  89. }
  90. }
  91. ccConsole* ccConsole::TheInstance(bool autoInit/*=true*/)
  92. {
  93. if (!s_console.instance && autoInit)
  94. {
  95. s_console.instance = new ccConsole;
  96. ccLog::RegisterInstance(s_console.instance);
  97. }
  98. return s_console.instance;
  99. }
  100. void ccConsole::ReleaseInstance(bool flush/*=true*/)
  101. {
  102. if (flush && s_console.instance)
  103. {
  104. //DGM: just in case some messages are still in the queue
  105. s_console.instance->refresh();
  106. }
  107. ccLog::RegisterInstance(nullptr);
  108. s_console.release();
  109. }
  110. ccConsole::ccConsole()
  111. : m_textDisplay(nullptr)
  112. , m_parentWidget(nullptr)
  113. , m_parentWindow(nullptr)
  114. , m_logStream(nullptr)
  115. {
  116. }
  117. ccConsole::~ccConsole()
  118. {
  119. setLogFile(QString()); //to close/delete any active stream
  120. }
  121. static void MyMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
  122. {
  123. #ifndef QT_DEBUG
  124. if (!ccConsole::QtMessagesEnabled())
  125. {
  126. return;
  127. }
  128. if (type == QtDebugMsg)
  129. {
  130. return;
  131. }
  132. #endif
  133. QString message = QString("[%1] ").arg(context.function) + msg; // QString("%1 (%1:%1, %1)").arg(msg).arg(context.file).arg(context.line).arg(context.function);
  134. //in this function, you can write the message to any stream!
  135. switch (type)
  136. {
  137. case QtDebugMsg:
  138. ccLog::PrintDebug(msg);
  139. break;
  140. case QtWarningMsg:
  141. message.prepend("[Qt WARNING] ");
  142. ccLog::Warning(message);
  143. break;
  144. case QtCriticalMsg:
  145. message.prepend("[Qt CRITICAL] ");
  146. ccLog::Warning(message);
  147. break;
  148. case QtFatalMsg:
  149. message.prepend("[Qt FATAL] ");
  150. ccLog::Warning(message);
  151. break;
  152. case QtInfoMsg:
  153. message.prepend("[Qt INFO] ");
  154. ccLog::Warning(message);
  155. break;
  156. }
  157. #ifdef QT_DEBUG
  158. // Also send the message to the console so we can look at the output when CC has quit
  159. // (in Qt Creator's Application Output for example)
  160. switch (type)
  161. {
  162. case QtDebugMsg:
  163. case QtWarningMsg:
  164. case QtInfoMsg:
  165. std::cout << message.toStdString() << std::endl;
  166. break;
  167. case QtCriticalMsg:
  168. case QtFatalMsg:
  169. std::cerr << message.toStdString() << std::endl;
  170. break;
  171. }
  172. #endif
  173. }
  174. void ccConsole::EnableQtMessages(bool state)
  175. {
  176. s_showQtMessagesInConsole = state;
  177. //save to persistent settings
  178. QSettings settings;
  179. settings.beginGroup(ccPS::Console());
  180. settings.setValue("QtMessagesEnabled", s_showQtMessagesInConsole);
  181. settings.endGroup();
  182. }
  183. void ccConsole::Init( QListWidget* textDisplay/*=nullptr*/,
  184. QWidget* parentWidget/*=nullptr*/,
  185. MainWindow* parentWindow/*=nullptr*/,
  186. bool redirectToStdOut/*=false*/)
  187. {
  188. //should be called only once!
  189. if (s_console.instance)
  190. {
  191. assert(false);
  192. return;
  193. }
  194. s_console.instance = new ccConsole;
  195. s_console.instance->m_textDisplay = textDisplay;
  196. s_console.instance->m_parentWidget = parentWidget;
  197. s_console.instance->m_parentWindow = parentWindow;
  198. s_redirectToStdOut = redirectToStdOut;
  199. if (s_redirectToStdOut)
  200. {
  201. // make the system console/terminal more responsive by removing any buffering
  202. setbuf(stdout, NULL);
  203. }
  204. //auto-start
  205. if (textDisplay)
  206. {
  207. //load from persistent settings
  208. QSettings settings;
  209. settings.beginGroup(ccPS::Console());
  210. s_showQtMessagesInConsole = settings.value("QtMessagesEnabled", false).toBool();
  211. settings.endGroup();
  212. //install : set the callback for Qt messages
  213. qInstallMessageHandler(MyMessageOutput);
  214. s_console.instance->setAutoRefresh(true);
  215. }
  216. ccLog::RegisterInstance(s_console.instance);
  217. }
  218. bool ccConsole::autoRefresh() const
  219. {
  220. return m_timer.isActive();
  221. }
  222. void ccConsole::setAutoRefresh(bool state)
  223. {
  224. if (state)
  225. {
  226. connect(&m_timer, &QTimer::timeout, this, &ccConsole::refresh);
  227. m_timer.start(s_refreshCycle_ms);
  228. }
  229. else
  230. {
  231. m_timer.stop();
  232. disconnect(&m_timer, &QTimer::timeout, this, &ccConsole::refresh);
  233. }
  234. }
  235. void ccConsole::refresh()
  236. {
  237. m_mutex.lock();
  238. if (!m_queue.isEmpty())
  239. {
  240. if (m_textDisplay || m_logStream)
  241. {
  242. for (auto messagePair : m_queue)
  243. {
  244. //destination: log file
  245. if (m_logStream)
  246. {
  247. *m_logStream << messagePair.first << endl;
  248. }
  249. //destination: console widget
  250. if (m_textDisplay)
  251. {
  252. //messagePair.first = message text
  253. QListWidgetItem* item = new QListWidgetItem(messagePair.first);
  254. //set color based on the message severity
  255. if ((messagePair.second & LOG_ERROR) == LOG_ERROR) // Error
  256. {
  257. item->setForeground(Qt::red);
  258. }
  259. else if ((messagePair.second & LOG_WARNING) == LOG_WARNING) // Warning
  260. {
  261. item->setForeground(Qt::darkRed);
  262. //we also force the console visibility if a warning message arrives!
  263. if (m_parentWindow)
  264. {
  265. m_parentWindow->forceConsoleDisplay();
  266. }
  267. }
  268. #ifdef QT_DEBUG
  269. else if (messagePair.second & DEBUG_FLAG) // Debug
  270. {
  271. item->setForeground(Qt::blue);
  272. }
  273. #endif
  274. m_textDisplay->addItem(item);
  275. }
  276. }
  277. if (m_logStream)
  278. {
  279. m_logFile.flush();
  280. }
  281. if (m_textDisplay)
  282. {
  283. m_textDisplay->scrollToBottom();
  284. }
  285. }
  286. m_queue.clear();
  287. }
  288. m_mutex.unlock();
  289. }
  290. void ccConsole::logMessage(const QString& message, int level)
  291. {
  292. //skip messages below the current 'verbosity' level
  293. if ((level & 7) < ccLog::VerbosityLevel())
  294. {
  295. return;
  296. }
  297. QString formatedMessage = QStringLiteral("[") + QTime::currentTime().toString() + QStringLiteral("] ") + message;
  298. if (s_redirectToStdOut)
  299. {
  300. printf("%s\n", qPrintable(formatedMessage));
  301. }
  302. if (m_textDisplay || m_logStream)
  303. {
  304. m_mutex.lock();
  305. m_queue.push_back(ConsoleItemType(formatedMessage, level));
  306. m_mutex.unlock();
  307. }
  308. #ifdef QT_DEBUG
  309. else if (!s_redirectToStdOut)
  310. {
  311. //Error
  312. if (level & LOG_ERROR)
  313. {
  314. if (level & DEBUG_FLAG)
  315. printf("ERR-DBG: ");
  316. else
  317. printf("ERR: ");
  318. }
  319. //Warning
  320. else if (level & LOG_WARNING)
  321. {
  322. if (level & DEBUG_FLAG)
  323. printf("WARN-DBG: ");
  324. else
  325. printf("WARN: ");
  326. }
  327. //Standard
  328. else
  329. {
  330. if (level & DEBUG_FLAG)
  331. printf("MSG-DBG: ");
  332. else
  333. printf("MSG: ");
  334. }
  335. printf(" %s\n", qPrintable(formatedMessage));
  336. }
  337. #endif
  338. //we display the error messages in a popup dialog
  339. if ( (level & LOG_ERROR)
  340. && qApp
  341. && m_parentWidget
  342. && QThread::currentThread() == qApp->thread()
  343. )
  344. {
  345. QMessageBox::warning(m_parentWidget, "Error", message);
  346. }
  347. }
  348. bool ccConsole::setLogFile(const QString& filename)
  349. {
  350. //close previous stream (if any)
  351. if (m_logStream)
  352. {
  353. m_mutex.lock();
  354. delete m_logStream;
  355. m_logStream = nullptr;
  356. m_mutex.unlock();
  357. if (m_logFile.isOpen())
  358. {
  359. m_logFile.close();
  360. }
  361. }
  362. if (!filename.isEmpty())
  363. {
  364. m_logFile.setFileName(filename);
  365. if (!m_logFile.open(QFile::Text| QFile::WriteOnly))
  366. {
  367. return Error(QString("[Console] Failed to open/create log file '%1'").arg(filename));
  368. }
  369. m_mutex.lock();
  370. m_logStream = new QTextStream(&m_logFile);
  371. m_mutex.unlock();
  372. setAutoRefresh(true);
  373. }
  374. return true;
  375. }