Salome HOME
Copyright update 2022
[modules/gui.git] / src / LogWindow / LogWindow.cxx
1 // Copyright (C) 2007-2022  CEA/DEN, EDF R&D, OPEN CASCADE
2 //
3 // Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 // CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU Lesser General Public
8 // License as published by the Free Software Foundation; either
9 // version 2.1 of the License, or (at your option) any later version.
10 //
11 // This library is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 // Lesser General Public License for more details.
15 //
16 // You should have received a copy of the GNU Lesser General Public
17 // License along with this library; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 // See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22
23 //  KERNEL SALOME_Event : Define event posting mechanism
24 // File   : LogWindow.cxx
25 // Author : Vadim SANDLER, Open CASCADE S.A.S. (vadim.sandler@opencascade.com)
26 //
27 #include "LogWindow.h"
28
29 #include <QAction>
30 #include <QApplication>
31 #include <QDate>
32 #include <QFile>
33 #include <QMenu>
34 #include <QTextBlock>
35 #include <QTextEdit>
36 #include <QTextStream>
37 #include <QTime>
38 #include <QVBoxLayout>
39
40 #include <SUIT_MessageBox.h>
41 #include <SUIT_ResourceMgr.h>
42 #include <SUIT_Session.h>
43 #include <SUIT_Tools.h>
44
45 #define DEFAULT_SEPARATOR "***"
46
47 namespace
48 {
49   /*!
50     \brief Convert rich text to plain text.
51     \internal
52     \param richText rich text string
53     \return converted plain text string
54   */
55   QString plainText( const QString& richText )
56   {
57     QString aText = richText;
58     int startTag = aText.indexOf( '<' );
59     while ( true )
60     {
61       if ( startTag < 0 )
62         break;
63       
64       int finishTag = aText.indexOf( '>', startTag );
65       if ( finishTag < 0 )
66         break;
67       
68       aText = aText.remove( startTag, finishTag - startTag + 1 );
69       startTag = aText.indexOf( '<' );
70     }
71     return aText;
72   }
73 }
74
75 /*!
76   \class LogWindow
77   \brief Widget, displaying log messages.
78
79   The log messages window provides operations like:
80   - show messages
81   - display timestamps at the message beginning
82   - color messages according to their purposes (e.g., errors/warning)
83   - clear log output
84   - copy messages to clipvoard
85   - save message log to to the text file
86 */
87
88 /*!
89   \brief Constructor.
90
91   Creates new messages log window widget.
92   \param parent parent widget
93 */
94 LogWindow::LogWindow( QWidget* parent )
95   : QWidget( parent ), SUIT_PopupClient(), QtxMsgHandlerCallback( false )
96 {
97   SUIT_ResourceMgr* resMgr = SUIT_Session::session()->resourceMgr();
98
99   QString fntSet = resMgr ? resMgr->stringValue( "Log Window", "font", QString() ) : QString();
100
101   setFont( SUIT_Tools::stringToFont( fntSet ) );
102
103   myView = new QTextEdit( this );
104   myView->setReadOnly( true );
105   myView->viewport()->installEventFilter( this );
106   myView->setPlaceholderText( "Message Log" );
107
108   QVBoxLayout* main = new QVBoxLayout( this );
109   main->setMargin( 0 );
110   main->addWidget( myView );
111
112   mySeparator = DEFAULT_SEPARATOR;
113
114   clear();
115
116   createActions();
117 }
118
119 /*!
120   \brief Destructor.
121
122   Does nothing for the moment.
123 */
124 LogWindow::~LogWindow()
125 {
126 }
127
128 /*!
129   \brief Get current banner (text shown when log window is empty).
130   \return string representing the current banner
131 */
132 QString LogWindow::banner() const
133 {
134   return myView->placeholderText();
135 }
136
137 /*!
138   \brief Get current separator (text which is printed between messages).
139   \return string representing the current separator
140 */
141 QString LogWindow::separator() const
142 {
143   return mySeparator;
144 }
145
146 /*!
147   \brief Set current banner (text shown when log window is empty).
148   \param banner new banner
149 */
150 void LogWindow::setBanner( const QString& banner )
151 {
152   myView->setPlaceholderText( banner );
153 }
154
155 /*!
156   Set current separator (text which is printed between messages).
157   \param separator new separator
158 */
159 void LogWindow::setSeparator( const QString& separator )
160 {
161   mySeparator = separator;
162
163   clear( false );
164 }
165
166 /*!
167   \brief Custom event handler.
168
169   Process context popup menu request event.
170   
171   \param o object
172   \param e event
173   \return True if the event is processed and further processing should be stopped
174 */
175 bool LogWindow::eventFilter( QObject* o, QEvent* e )
176 {
177   if ( o == myView->viewport() && e->type() == QEvent::ContextMenu )
178   {
179     contextMenuRequest( (QContextMenuEvent*)e );
180     return true;
181   }
182   return QWidget::eventFilter( o, e );
183 }
184
185 /*!
186   \brief Put new message to the log window.
187   \param message text of the message
188   \param flags ORed flags which define how the message should be printed
189 */
190 void LogWindow::putMessage( const QString& message, const int flags )
191 {
192   putMessage( message, QColor(), flags );
193 }
194
195 /*!
196   \brief Put new message to the log window.
197   \param message text of the message
198   \param color text color of the message
199   \param flags ORed flags which define how the message should be printed
200 */
201 void LogWindow::putMessage( const QString& message, const QColor& color, const int flags )
202 {
203   QString msg = message;
204   if ( msg.isEmpty() )
205     return;
206
207   bool noColor = flags & DisplayNoColor;
208
209   if ( color.isValid() )
210     msg = QString( "<font color=\"%1\">%2</font>" ).arg( color.name() ).arg( msg );
211
212   QString dStr;
213   if ( flags & DisplayDate )
214   {
215     dStr = QDate::currentDate().toString( Qt::SystemLocaleDate );
216     if ( !noColor )
217       dStr = QString( "<font color=\"#003380\">%1</font>" ).arg( dStr );
218   }
219
220   QString tStr;
221   if ( flags & DisplayTime )
222   {
223     tStr = QTime::currentTime().toString( Qt::SystemLocaleDate );
224     if ( !noColor )
225       tStr = QString( "<font color=\"#008033\">%1</font>" ).arg( tStr );
226   }
227
228   QString dateTime = QString( "%1 %2" ).arg( dStr ).arg( tStr ).trimmed();
229   if ( !dateTime.isEmpty() )
230     msg = QString( "[%1] %2" ).arg( dateTime ).arg( msg );
231
232   append( msg );
233   myHistory.append( plainText( message ) );
234
235   if ( flags & DisplaySeparator && !mySeparator.isEmpty() )
236   {
237     // add separator
238     append( mySeparator );
239     myHistory.append( plainText( mySeparator ) );
240   }
241   myView->moveCursor( QTextCursor::End );
242 }
243
244 /*!
245   \brief Append text to the log window.
246   \param text Text being added.
247 */
248 void LogWindow::append( const QString text )
249 {
250   if ( !text.isEmpty() )
251   {
252     myView->append( text );
253     QTextBlock block = myView->document()->lastBlock();
254     QTextCursor cursor( block );
255     QTextBlockFormat format = cursor.blockFormat();
256     format.setBottomMargin( 10 );
257     cursor.setBlockFormat( format );
258   }
259 }
260
261 /*!
262   \brief Clear message log.
263   \param clearHistory if True, clear also the messages history
264 */
265 void LogWindow::clear( bool clearHistory )
266 {
267   myView->clear();
268   if ( clearHistory )
269     myHistory.clear();
270 }
271
272 /*!
273   \brief Save messages log to the file.
274   \param fileName name of the file
275   \return \c true on success and \c false on error
276 */
277 bool LogWindow::saveLog( const QString& fileName )
278 {
279   QFile file( fileName );
280   if ( !file.open( QFile::WriteOnly ) )
281     return false;
282
283   QTextStream stream( &file );
284
285   stream << "*****************************************"   << endl;
286   stream << "Message Log"                                 << endl;
287   stream << QDate::currentDate().toString( "dd.MM:yyyy" ) << "  ";
288   stream << QTime::currentTime().toString( "hh:mm:ss" )   << endl;
289   stream << "*****************************************"   << endl;
290
291   for ( int i = 0; i < myHistory.count(); i++ )
292     stream << myHistory[ i ] << endl;
293
294   file.close();
295   return true;
296 }
297
298 /*!
299   \brief Create context popup menu actions.
300 */
301 void LogWindow::createActions()
302 {
303   QAction* a = new QAction( tr( "EDIT_COPY_CMD" ), this );
304   a->setStatusTip( tr( "EDIT_COPY_CMD" ) );
305   connect( a, SIGNAL( triggered( bool ) ), SLOT( onCopy() ) );
306   myActions.insert( CopyId, a );
307
308   a = new QAction( tr( "EDIT_CLEAR_CMD" ), this );
309   a->setStatusTip( tr( "EDIT_CLEAR_CMD" ) );
310   connect( a, SIGNAL( triggered( bool ) ), SLOT( onClear() ) );
311   myActions.insert( ClearId, a );
312
313   a = new QAction( tr( "EDIT_SELECTALL_CMD" ), this );
314   a->setStatusTip( tr( "EDIT_SELECTALL_CMD" ) );
315   connect( a, SIGNAL( triggered( bool ) ), SLOT( onSelectAll() ) );
316   myActions.insert( SelectAllId, a );
317
318   a = new QAction( tr( "EDIT_SAVETOFILE_CMD" ), this );
319   a->setStatusTip( tr( "EDIT_SAVETOFILE_CMD" ) );
320   connect( a, SIGNAL( triggered( bool ) ), SLOT( onSaveToFile() ) );
321   myActions.insert( SaveToFileId, a );
322 }
323
324 /*!
325   \brief Create the context popup menu.
326
327   Fill in the popup menu with the commands.
328
329   \param menu context popup menu
330 */
331 void LogWindow::contextMenuPopup( QMenu* popup )
332 {
333   popup->addAction( myActions[ CopyId ] );
334   popup->addAction( myActions[ ClearId ] );
335   popup->addSeparator();
336   popup->addAction( myActions[ SelectAllId ] );
337   popup->addSeparator();
338   popup->addAction( myActions[ SaveToFileId ] );
339
340   Qtx::simplifySeparators( popup );
341
342   updateActions();
343 }
344
345 /*!
346   \brief Update menu actions.
347
348   Update context popup menu action state.
349 */
350 void LogWindow::updateActions()
351 {
352   myActions[CopyId]->setEnabled( myView->textCursor().hasSelection() );
353   myActions[ ClearId ]->setEnabled( !myView->document()->isEmpty() );
354   myActions[SelectAllId]->setEnabled( !myView->document()->isEmpty() );
355   myActions[ SaveToFileId ]->setEnabled( myHistory.count() > 0 );
356 }
357
358 /*!
359   \brief Called when user selects "Save To File" command in the popup menu.
360 */
361 void LogWindow::onSaveToFile()
362 {
363   SUIT_Application* app = SUIT_Session::session()->activeApplication();
364   if ( !app )
365     return;
366
367   // call application-specific "Save file" dialog box
368   QString aName = app->getFileName( false, QString(), QString( "*.log" ), QString(), 0 );
369   if ( aName.isNull() )
370     return;
371
372   QApplication::setOverrideCursor( Qt::WaitCursor );
373
374   bool bOk = saveLog( aName );
375
376   QApplication::restoreOverrideCursor();
377
378   if ( !bOk )
379     SUIT_MessageBox::critical( this, tr( "ERR_ERROR" ), tr( "ERR_CANT_SAVE_FILE" ) );
380 }
381
382 /*!
383   \brief Called when user selects "Select all" command in the popup menu.
384 */
385 void LogWindow::onSelectAll()
386 {
387   if ( myView )
388     myView->selectAll();
389 }
390
391 /*!
392   \brief Called when user click "Clear" command in the popup menu.
393 */
394 void LogWindow::onClear()
395 {
396   clear( false );
397 }
398
399 /*!
400   \brief Called when user click "Copy" command in the popup menu.
401 */
402 void LogWindow::onCopy()
403 {
404   if ( myView )
405     myView->copy();
406 }
407
408 /*!
409   \brief Set actions to be visible in the context popup menu.
410   
411   Actions, which IDs are set in \a flags parameter, will be shown in the 
412   context popup menu. Other actions will not be shown.
413
414   \param flags ORed together actions flags
415 */
416 void LogWindow::setMenuActions( const int flags )
417 {
418   myActions[CopyId]->setVisible( flags & CopyId );
419   myActions[ClearId]->setVisible( flags & ClearId );
420   myActions[SelectAllId]->setVisible( flags & SelectAllId );
421   myActions[SaveToFileId]->setVisible( flags & SaveToFileId );
422 }
423
424 /*!
425   \brief Get menu actions which are currently visible in the context popup menu.
426   \return ORed together actions flags
427   \sa setMenuActions()
428 */
429 int LogWindow::menuActions() const
430 {
431   int ret = 0;
432   ret = ret | ( myActions[CopyId]->isVisible() ? CopyId : 0 );
433   ret = ret | ( myActions[ClearId]->isVisible() ? ClearId : 0 );
434   ret = ret | ( myActions[SelectAllId]->isVisible() ? SelectAllId : 0 );
435   ret = ret | ( myActions[SaveToFileId]->isVisible() ? SaveToFileId : 0 );
436   return ret;
437 }
438
439 /*!
440   \brief Activate/deactivate Qt messages handling.
441   \param on If \c true, Qt messags are handled by log window.
442 */
443 void LogWindow::handleQtMessages(bool on)
444 {
445   if ( on )
446     activate();
447   else
448     deactivate();
449 }
450
451 /*!
452   \brief Handle Qt messages.
453   \param type Qt message type.
454   \param context Message context.
455   \param message Message text.
456 */
457 void LogWindow::qtMessage( QtMsgType type, const QMessageLogContext&, const QString& message )
458 {
459   QColor color;
460   switch ( type )
461   {
462   case QtInfoMsg:
463     color = QColor("#008000"); // dark green
464     break;
465   case QtCriticalMsg:
466     color = QColor("#ff0000"); // red
467     break;
468   case QtFatalMsg:
469     color = QColor("#800000"); // dark red
470     break;
471   case QtWarningMsg:
472     color = QColor("#ff9000"); // orange
473     break;
474   case QtDebugMsg:
475   default:
476     color = QColor("#000000"); // black
477     break;
478   }
479   putMessage( message, color, DisplayNormal);
480 }
481
482 /*!
483   \fn virtual QString LogWindow::popupClientType() const;
484   \brief Get popup client symbolic name, used in popup menu management system.
485   \return symbolic name
486 */