@ -7,11 +7,16 @@
// Custom sink for QPlainTextEdit or QTextEdit and its childs(QTextBrowser...
// etc) Building and using requires Qt library.
//
// Warning: the qt_sink won't be notified if the target widget is destroyed.
// If the widget's lifetime can be shorter than the logger's one, you should provide some permanent QObject,
// and then use a standard signal/slot.
//
# include "spdlog/common.h"
# include "spdlog/details/log_msg.h"
# include "spdlog/details/synchronous_factory.h"
# include "spdlog/sinks/base_sink.h"
# include <array>
# include <QTextEdit>
# include <QPlainTextEdit>
@ -53,15 +58,185 @@ private:
std : : string meta_method_ ;
} ;
// QT color sink to QTextEdit.
// Color location is determined by the sink log pattern like in the rest of spdlog sinks.
// Colors can be modified if needed using sink->set_color(level, qtTextCharFormat).
// max_lines is the maximum number of lines that the sink will hold before removing the oldest lines.
// Note: Only ascii (latin1) is supported by this sink.
template < typename Mutex >
class qt_color_sink : public base_sink < Mutex >
{
public :
qt_color_sink ( QTextEdit * qt_text_edit , int max_lines )
: qt_text_edit_ ( qt_text_edit ) , max_lines_ ( max_lines )
{
if ( ! qt_text_edit_ )
{
throw_spdlog_ex ( " qt_color_text_sink: text_edit is null " ) ;
}
default_color_ = qt_text_edit_ - > currentCharFormat ( ) ;
// set colors
QTextCharFormat format ;
// trace
format . setForeground ( Qt : : gray ) ;
colors_ . at ( level : : trace ) = format ;
// debug
format . setForeground ( Qt : : cyan ) ;
colors_ . at ( level : : debug ) = format ;
// info
format . setForeground ( Qt : : green ) ;
colors_ . at ( level : : info ) = format ;
// warn
format . setForeground ( Qt : : yellow ) ;
colors_ . at ( level : : warn ) = format ;
// err
format . setForeground ( Qt : : red ) ;
colors_ . at ( level : : err ) = format ;
// critical
format . setForeground ( Qt : : white ) ;
format . setBackground ( Qt : : red ) ;
colors_ . at ( level : : critical ) = format ;
}
~ qt_color_sink ( )
{
flush_ ( ) ;
}
void set_default_color ( QTextCharFormat format )
{
// std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
default_color_ = format ;
}
void set_level_color ( level : : level_enum color_level , QTextCharFormat format )
{
// std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_);
colors_ . at ( static_cast < size_t > ( color_level ) ) = format ;
}
QTextCharFormat & get_level_color ( level : : level_enum color_level )
{
std : : lock_guard < Mutex > lock ( base_sink < Mutex > : : mutex_ ) ;
return colors_ . at ( static_cast < size_t > ( color_level ) ) ;
}
QTextCharFormat & get_default_color ( )
{
std : : lock_guard < Mutex > lock ( base_sink < Mutex > : : mutex_ ) ;
return default_color_ ;
}
protected :
struct invoke_params
{
invoke_params ( int max_lines , QTextEdit * q_text_edit , QString payload , QTextCharFormat default_color ,
QTextCharFormat level_color , int color_range_start , int color_range_end )
: max_lines ( max_lines ) ,
q_text_edit ( q_text_edit ) ,
payload ( std : : move ( payload ) ) ,
default_color ( default_color ) ,
level_color ( level_color ) ,
color_range_start ( color_range_start ) ,
color_range_end ( color_range_end )
{
}
int max_lines ;
QTextEdit * q_text_edit ;
QString payload ;
QTextCharFormat default_color ;
QTextCharFormat level_color ;
int color_range_start ;
int color_range_end ;
} ;
void sink_it_ ( const details : : log_msg & msg ) override
{
using this_type = qt_color_sink < Mutex > ;
memory_buf_t formatted ;
base_sink < Mutex > : : formatter_ - > format ( msg , formatted ) ;
const string_view_t str = string_view_t ( formatted . data ( ) , formatted . size ( ) ) ;
// apply the color to the color range in the formatted message.
auto payload = QString : : fromLatin1 ( str . data ( ) , static_cast < int > ( str . size ( ) ) ) ;
invoke_params params {
max_lines_ , // max lines
qt_text_edit_ , // text edit to append to
std : : move ( payload ) , // text to append
default_color_ , // default color
colors_ . at ( msg . level ) , // color to apply
msg . color_range_start , // color range start
msg . color_range_end } ; // color range end
QMetaObject : : invokeMethod (
qt_text_edit_ ,
[ params ] ( ) { invoke_method_ ( params ) ; } ,
Qt : : AutoConnection ) ;
}
void flush_ ( ) override { }
// Add colored text to the text edit widget. This method is invoked in the GUI thread.
// It is a static method to ensure that it is handled correctly even if the sink is destroyed prematurely
// before it is invoked.
static void invoke_method_ ( invoke_params params )
{
auto * document = params . q_text_edit - > document ( ) ;
QTextCursor cursor ( document ) ;
// remove first blocks if number of blocks exceeds max_lines
while ( document - > blockCount ( ) > params . max_lines )
{
cursor . select ( QTextCursor : : BlockUnderCursor ) ;
cursor . removeSelectedText ( ) ;
cursor . deleteChar ( ) ; // delete the newline after the block
}
cursor . movePosition ( QTextCursor : : End ) ;
cursor . setCharFormat ( params . default_color ) ;
// if color range not specified or not not valid, just append the text with default color
if ( params . color_range_end < = params . color_range_start )
{
cursor . insertText ( params . payload ) ;
return ;
}
// insert the text before the color range
cursor . insertText ( params . payload . left ( params . color_range_start ) ) ;
// insert the colorized text
cursor . setCharFormat ( params . level_color ) ;
cursor . insertText ( params . payload . mid ( params . color_range_start , params . color_range_end - params . color_range_start ) ) ;
// insert the text after the color range with default format
cursor . setCharFormat ( params . default_color ) ;
cursor . insertText ( params . payload . mid ( params . color_range_end ) ) ;
}
QTextEdit * qt_text_edit_ ;
int max_lines_ ;
QTextCharFormat default_color_ ;
std : : array < QTextCharFormat , level : : n_levels > colors_ ;
} ;
# include "spdlog/details/null_mutex.h"
# include <mutex>
using qt_sink_mt = qt_sink < std : : mutex > ;
using qt_sink_st = qt_sink < spdlog : : details : : null_mutex > ;
using qt_sink_st = qt_sink < details : : null_mutex > ;
using qt_color_sink_mt = qt_color_sink < std : : mutex > ;
using qt_color_sink_st = qt_color_sink < details : : null_mutex > ;
} // namespace sinks
//
// Factory functions
//
// log to QTextEdit
template < typename Factory = spdlog : : synchronous_factory >
inline std : : shared_ptr < logger > qt_logger_mt ( const std : : string & logger_name , QTextEdit * qt_object , const std : : string & meta_method = " append " )
{
@ -74,6 +249,7 @@ inline std::shared_ptr<logger> qt_logger_st(const std::string &logger_name, QTex
return Factory : : template create < sinks : : qt_sink_st > ( logger_name , qt_object , meta_method ) ;
}
// log to QPlainTextEdit
template < typename Factory = spdlog : : synchronous_factory >
inline std : : shared_ptr < logger > qt_logger_mt (
const std : : string & logger_name , QPlainTextEdit * qt_object , const std : : string & meta_method = " appendPlainText " )
@ -87,7 +263,7 @@ inline std::shared_ptr<logger> qt_logger_st(
{
return Factory : : template create < sinks : : qt_sink_st > ( logger_name , qt_object , meta_method ) ;
}
// log to QObject
template < typename Factory = spdlog : : synchronous_factory >
inline std : : shared_ptr < logger > qt_logger_mt ( const std : : string & logger_name , QObject * qt_object , const std : : string & meta_method )
{
@ -99,4 +275,18 @@ inline std::shared_ptr<logger> qt_logger_st(const std::string &logger_name, QObj
{
return Factory : : template create < sinks : : qt_sink_st > ( logger_name , qt_object , meta_method ) ;
}
// log to QTextEdit with colorize output
template < typename Factory = spdlog : : synchronous_factory >
inline std : : shared_ptr < logger > qt_color_logger_mt ( const std : : string & logger_name , QTextEdit * qt_text_edit , int max_lines )
{
return Factory : : template create < sinks : : qt_color_sink_mt > ( logger_name , qt_text_edit , max_lines ) ;
}
template < typename Factory = spdlog : : synchronous_factory >
inline std : : shared_ptr < logger > qt_color_logger_st ( const std : : string & logger_name , QTextEdit * qt_text_edit , int max_lines )
{
return Factory : : template create < sinks : : qt_color_sink_st > ( logger_name , qt_text_edit , max_lines ) ;
}
} // namespace spdlog