@ -1,42 +1,46 @@
# include "tools/cabana/messageswidget.h"
# include <QHBoxLayout>
# include <QPainter>
# include <QPushButton>
# include <QScrollBar>
# include <QVBoxLayout>
MessagesWidget : : MessagesWidget ( QWidget * parent ) : QWidget ( parent ) {
QVBoxLayout * main_layout = new QVBoxLayout ( this ) ;
main_layout - > setContentsMargins ( 0 , 0 , 0 , 0 ) ;
// message filter
QHBoxLayout * title_layout = new QHBoxLayout ( ) ;
title_layout - > addWidget ( filter = new QLineEdit ( this ) ) ;
QRegularExpression re ( " \\ S+ " ) ;
filter - > setValidator ( new QRegularExpressionValidator ( re , this ) ) ;
filter - > setClearButtonEnabled ( true ) ;
filter - > setPlaceholderText ( tr ( " filter messages " ) ) ;
num_msg_label = new QLabel ( this ) ;
title_layout - > addSpacing ( 10 ) ;
title_layout - > addWidget ( num_msg_label ) ;
title_layout - > addStretch ( ) ;
title_layout - > addWidget ( multiple_lines_bytes = new QCheckBox ( tr ( " Multiple Lines Bytes " ) , this ) ) ;
multiple_lines_bytes - > setToolTip ( tr ( " Display bytes in multiple lines " ) ) ;
multiple_lines_bytes - > setChecked ( settings . multiple_lines_bytes ) ;
QPushButton * clear_filters = new QPushButton ( tr ( " Clear Filters " ) ) ;
title_layout - > addWidget ( clear_filters ) ;
main_layout - > addLayout ( title_layout ) ;
// message table
view = new MessageView ( this ) ;
model = new MessageListModel ( this ) ;
header = new MessageViewHeader ( this , model ) ;
auto delegate = new MessageBytesDelegate ( view , settings . multiple_lines_bytes ) ;
view - > setItemDelegate ( delegate ) ;
view - > setModel ( model ) ;
view - > setSortingEnabled ( true ) ;
view - > sortByColumn ( 0 , Qt : : AscendingOrder ) ;
view - > sortByColumn ( MessageListModel : : Column : : NAME , Qt : : AscendingOrder ) ;
view - > setAllColumnsShowFocus ( true ) ;
view - > setEditTriggers ( QAbstractItemView : : NoEditTriggers ) ;
view - > setItemsExpandable ( false ) ;
view - > setIndentation ( 0 ) ;
view - > setRootIsDecorated ( false ) ;
view - > setHeader ( header ) ;
// Must be called before setting any header parameters to avoid overriding
restoreHeaderState ( settings . message_header_state ) ;
view - > header ( ) - > setSectionsMovable ( true ) ;
// Header context menu
@ -57,21 +61,28 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
main_layout - > addLayout ( suppress_layout ) ;
// signals/slots
QObject : : connect ( filter , & QLineEdit : : textEdited , model , & MessageListModel : : setFilterString ) ;
QObject : : connect ( header , & MessageViewHeader : : filtersUpdated , model , & MessageListModel : : setFilterStrings ) ;
QObject : : connect ( view - > horizontalScrollBar ( ) , & QScrollBar : : valueChanged , header , & MessageViewHeader : : updateHeaderPositions ) ;
QObject : : connect ( clear_filters , & QPushButton : : clicked , header , & MessageViewHeader : : clearFilters ) ;
QObject : : connect ( multiple_lines_bytes , & QCheckBox : : stateChanged , [ = ] ( int state ) {
settings . multiple_lines_bytes = ( state = = Qt : : Checked ) ;
delegate - > setMultipleLines ( settings . multiple_lines_bytes ) ;
view - > setUniformRowHeights ( ! settings . multiple_lines_bytes ) ;
model - > sortMessages ( ) ;
// Reset model to force recalculation of the width of the bytes column
model - > forceResetModel ( ) ;
} ) ;
QObject : : connect ( suppress_defined_signals , & QCheckBox : : stateChanged , [ = ] ( int state ) {
settings . suppress_defined_signals = ( state = = Qt : : Checked ) ;
} ) ;
QObject : : connect ( can , & AbstractStream : : msgsReceived , model , & MessageListModel : : msgsReceived ) ;
QObject : : connect ( can , & AbstractStream : : streamStarted , this , & MessagesWidget : : reset ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : DBCFileChanged , model , & MessageListModel : : sortMessages ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : msgUpdated , model , & MessageListModel : : sortMessages ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : msgRemoved , model , & MessageListModel : : sortMessages ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : DBCFileChanged , this , & MessagesWidget : : dbcModified ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : msgUpdated , this , & MessagesWidget : : dbcModified ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : msgRemoved , this , & MessagesWidget : : dbcModified ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : signalAdded , this , & MessagesWidget : : dbcModified ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : signalRemoved , this , & MessagesWidget : : dbcModified ) ;
QObject : : connect ( dbc ( ) , & DBCManager : : signalUpdated , this , & MessagesWidget : : dbcModified ) ;
QObject : : connect ( model , & MessageListModel : : modelReset , [ this ] ( ) {
if ( current_msg_id ) {
selectMessage ( * current_msg_id ) ;
@ -97,6 +108,7 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
} ) ;
updateSuppressedButtons ( ) ;
dbcModified ( ) ;
setWhatsThis ( tr ( R " (
< b > Message View < / b > < br / >
@ -108,6 +120,11 @@ MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) {
) " ));
}
void MessagesWidget : : dbcModified ( ) {
num_msg_label - > setText ( tr ( " %1 Messages, %2 Signals " ) . arg ( dbc ( ) - > msgCount ( ) ) . arg ( dbc ( ) - > signalCount ( ) ) ) ;
model - > fetchData ( ) ;
}
void MessagesWidget : : selectMessage ( const MessageId & msg_id ) {
if ( int row = model - > msgs . indexOf ( msg_id ) ; row ! = - 1 ) {
view - > selectionModel ( ) - > setCurrentIndex ( model - > index ( row , 0 ) , QItemSelectionModel : : Rows | QItemSelectionModel : : ClearAndSelect ) ;
@ -128,7 +145,6 @@ void MessagesWidget::reset() {
current_msg_id = std : : nullopt ;
view - > selectionModel ( ) - > clear ( ) ;
model - > reset ( ) ;
filter - > clear ( ) ;
updateSuppressedButtons ( ) ;
}
@ -137,8 +153,14 @@ void MessagesWidget::reset() {
QVariant MessageListModel : : headerData ( int section , Qt : : Orientation orientation , int role ) const {
if ( orientation = = Qt : : Horizontal & & role = = Qt : : DisplayRole ) {
static const QString titles [ ] = { " Name " , " Bus " , " ID " , " Freq " , " Count " , " Bytes " } ;
return titles [ section ] ;
switch ( section ) {
case Column : : NAME : return tr ( " Name " ) ;
case Column : : SOURCE : return tr ( " Bus " ) ;
case Column : : ADDRESS : return tr ( " ID " ) ;
case Column : : FREQ : return tr ( " Freq " ) ;
case Column : : COUNT : return tr ( " Count " ) ;
case Column : : DATA : return tr ( " Bytes " ) ;
}
}
return { } ;
}
@ -157,12 +179,12 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const {
if ( role = = Qt : : DisplayRole ) {
switch ( index . column ( ) ) {
case 0 : return msgName ( id ) ;
case 1 : return id . source ;
case 2 : return QString : : number ( id . address , 16 ) ;
case 3 : return getFreq ( can_data ) ;
case 4 : return can_data . count ;
case 5 : return toHex ( can_data . dat ) ;
case Column : : NAME : return msgName ( id ) ;
case Column : : SOURCE : return id . source ;
case Column : : ADDRESS : return QString : : number ( id . address , 16 ) ;
case Column : : FREQ : return getFreq ( can_data ) ;
case Column : : COUNT : return can_data . count ;
case Column : : DATA : return toHex ( can_data . dat ) ;
}
} else if ( role = = ColorsRole ) {
QVector < QColor > colors = can_data . colors ;
@ -174,83 +196,173 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const {
}
}
return QVariant : : fromValue ( colors ) ;
} else if ( role = = BytesRole & & index . column ( ) = = 5 ) {
} else if ( role = = BytesRole & & index . column ( ) = = Column : : DATA ) {
return can_data . dat ;
}
return { } ;
}
void MessageListModel : : setFilterString ( const QString & string ) {
auto contains = [ ] ( const MessageId & id , const QString & txt ) {
auto cs = Qt : : CaseInsensitive ;
if ( id . toString ( ) . contains ( txt , cs ) | | msgName ( id ) . contains ( txt , cs ) ) return true ;
// Search by signal name
if ( const auto msg = dbc ( ) - > msg ( id ) ) {
for ( auto s : msg - > getSignals ( ) ) {
if ( s - > name . contains ( txt , cs ) ) return true ;
}
}
return false ;
} ;
filter_str = string ;
msgs . clear ( ) ;
for ( auto it = can - > last_msgs . begin ( ) ; it ! = can - > last_msgs . end ( ) ; + + it ) {
if ( filter_str . isEmpty ( ) | | contains ( it . key ( ) , filter_str ) ) {
msgs . push_back ( it . key ( ) ) ;
}
}
sortMessages ( ) ;
void MessageListModel : : setFilterStrings ( const QMap < int , QString > & filters ) {
filter_str = filters ;
fetchData ( ) ;
}
void MessageListModel : : sortMessages ( ) {
beginResetModel ( ) ;
if ( sort_column = = 0 ) {
std : : sort ( msgs . begin ( ) , msgs . end ( ) , [ this ] ( auto & l , auto & r ) {
void MessageListModel : : sortMessages ( Qt : : SortOrder sort_order , int sort_column , QList < MessageId > & new_msgs ) {
if ( sort_column = = Column : : NAME ) {
std : : sort ( new_msgs . begin ( ) , new_msgs . end ( ) , [ = ] ( auto & l , auto & r ) {
auto ll = std : : pair { msgName ( l ) , l } ;
auto rr = std : : pair { msgName ( r ) , r } ;
return sort_order = = Qt : : AscendingOrder ? ll < rr : ll > rr ;
} ) ;
} else if ( sort_column = = 1 ) {
std : : sort ( msgs . begin ( ) , msgs . end ( ) , [ this ] ( auto & l , auto & r ) {
} else if ( sort_column = = Column : : SOURCE ) {
std : : sort ( new_msgs . begin ( ) , new_msgs . end ( ) , [ = ] ( auto & l , auto & r ) {
auto ll = std : : pair { l . source , l } ;
auto rr = std : : pair { r . source , r } ;
return sort_order = = Qt : : AscendingOrder ? ll < rr : ll > rr ;
} ) ;
} else if ( sort_column = = 2 ) {
std : : sort ( msgs . begin ( ) , msgs . end ( ) , [ this ] ( auto & l , auto & r ) {
} else if ( sort_column = = Column : : ADDRESS ) {
std : : sort ( new_msgs . begin ( ) , new_msgs . end ( ) , [ = ] ( auto & l , auto & r ) {
auto ll = std : : pair { l . address , l } ;
auto rr = std : : pair { r . address , r } ;
return sort_order = = Qt : : AscendingOrder ? ll < rr : ll > rr ;
} ) ;
} else if ( sort_column = = 3 ) {
std : : sort ( msgs . begin ( ) , msgs . end ( ) , [ this ] ( auto & l , auto & r ) {
} else if ( sort_column = = Column : : FREQ ) {
std : : sort ( new_ msgs. begin ( ) , new_ msgs. end ( ) , [ = ] ( auto & l , auto & r ) {
auto ll = std : : pair { can - > lastMessage ( l ) . freq , l } ;
auto rr = std : : pair { can - > lastMessage ( r ) . freq , r } ;
return sort_order = = Qt : : AscendingOrder ? ll < rr : ll > rr ;
} ) ;
} else if ( sort_column = = 4 ) {
std : : sort ( msgs . begin ( ) , msgs . end ( ) , [ this ] ( auto & l , auto & r ) {
} else if ( sort_column = = Column : : COUNT ) {
std : : sort ( new_ msgs. begin ( ) , new_ msgs. end ( ) , [ = ] ( auto & l , auto & r ) {
auto ll = std : : pair { can - > lastMessage ( l ) . count , l } ;
auto rr = std : : pair { can - > lastMessage ( r ) . count , r } ;
return sort_order = = Qt : : AscendingOrder ? ll < rr : ll > rr ;
} ) ;
}
endResetModel ( ) ;
}
void MessageListModel : : msgsReceived ( const QHash < MessageId , CanData > * new_msgs ) {
int prev_row_count = msgs . size ( ) ;
if ( filter_str . isEmpty ( ) & & msgs . size ( ) ! = can - > last_msgs . size ( ) ) {
msgs = can - > last_msgs . keys ( ) ;
static std : : pair < unsigned int , unsigned int > parseRange ( QString & filter , bool * ok = nullptr , int base = 10 ) {
// Parse out filter string into a range (e.g. "1" -> {1, 1}, "1-3" -> {1, 3}, "1-" -> {1, inf})
bool ok1 = true , ok2 = true ;
unsigned int parsed1 = std : : numeric_limits < unsigned int > : : min ( ) ;
unsigned int parsed2 = std : : numeric_limits < unsigned int > : : max ( ) ;
auto s = filter . split ( ' - ' ) ;
if ( s . size ( ) = = 1 ) {
parsed1 = s [ 0 ] . toUInt ( ok , base ) ;
return { parsed1 , parsed1 } ;
} else if ( s . size ( ) = = 2 ) {
if ( ! s [ 0 ] . isEmpty ( ) ) parsed1 = s [ 0 ] . toUInt ( & ok1 , base ) ;
if ( ! s [ 1 ] . isEmpty ( ) ) parsed2 = s [ 1 ] . toUInt ( & ok2 , base ) ;
* ok = ok1 & ok2 ;
return { parsed1 , parsed2 } ;
} else {
* ok = false ;
return { 0 , 0 } ;
}
}
bool MessageListModel : : matchMessage ( const MessageId & id , const CanData & data , QMap < int , QString > & filters ) {
auto cs = Qt : : CaseInsensitive ;
bool match = true ;
bool convert_ok ;
for ( int column = Column : : NAME ; column < = Column : : DATA ; column + + ) {
if ( ! filters . contains ( column ) ) continue ;
QString txt = filters [ column ] ;
QRegularExpression re ( txt , QRegularExpression : : CaseInsensitiveOption | QRegularExpression : : DotMatchesEverythingOption ) ;
switch ( column ) {
case Column : : NAME :
{
bool name_match = re . match ( msgName ( id ) ) . hasMatch ( ) ;
// Message signals
if ( const auto msg = dbc ( ) - > msg ( id ) ) {
for ( auto s : msg - > getSignals ( ) ) {
if ( re . match ( s - > name ) . hasMatch ( ) ) {
name_match = true ;
break ;
}
}
}
if ( ! name_match ) match = false ;
}
break ;
case Column : : SOURCE :
{
auto source = parseRange ( txt , & convert_ok ) ;
bool source_match = convert_ok & & ( id . source > = source . first & & id . source < = source . second ) ;
if ( ! source_match ) match = false ;
}
break ;
case Column : : ADDRESS :
{
QString address_str = QString : : number ( id . address , 16 ) ;
bool address_re_match = re . match ( address_str ) . capturedLength ( ) = = address_str . length ( ) ;
auto address = parseRange ( txt , & convert_ok , 16 ) ;
bool address_match = convert_ok & & ( id . address > = address . first & & id . address < = address . second ) ;
if ( ! address_re_match & & ! address_match ) match = false ;
}
break ;
case Column : : FREQ :
{
// TODO: Hide stale messages?
auto freq = parseRange ( txt , & convert_ok ) ;
bool freq_match = convert_ok & & ( data . freq > = freq . first & & data . freq < = freq . second ) ;
if ( ! freq_match ) match = false ;
}
break ;
case Column : : COUNT :
{
auto count = parseRange ( txt , & convert_ok ) ;
bool count_match = convert_ok & & ( data . count > = count . first & & data . count < = count . second ) ;
if ( ! count_match ) match = false ;
}
break ;
case Column : : DATA :
{
bool data_match = false ;
data_match | = QString ( data . dat . toHex ( ) ) . contains ( txt , cs ) ;
data_match | = re . match ( QString ( data . dat . toHex ( ) ) ) . hasMatch ( ) ;
data_match | = re . match ( QString ( data . dat . toHex ( ' ' ) ) ) . hasMatch ( ) ;
if ( ! data_match ) match = false ;
}
break ;
}
}
return match ;
}
void MessageListModel : : fetchData ( ) {
QList < MessageId > new_msgs ;
for ( auto it = can - > last_msgs . begin ( ) ; it ! = can - > last_msgs . end ( ) ; + + it ) {
if ( matchMessage ( it . key ( ) , it . value ( ) , filter_str ) ) {
new_msgs . push_back ( it . key ( ) ) ;
}
}
if ( msgs . size ( ) ! = prev_row_count ) {
sortMessages ( ) ;
return ;
sortMessages ( sort_order , sort_column , new_msgs ) ;
if ( msgs ! = new_msgs ) {
beginResetModel ( ) ;
msgs = new_msgs ;
endResetModel ( ) ;
}
}
void MessageListModel : : msgsReceived ( const QHash < MessageId , CanData > * new_msgs ) {
QList < MessageId > prev_msgs = msgs ;
fetchData ( ) ;
for ( int i = 0 ; i < msgs . size ( ) ; + + i ) {
if ( new_msgs - > contains ( msgs [ i ] ) ) {
for ( int col = 3 ; col < columnCount ( ) ; + + col )
for ( int col = Column : : FREQ ; col < columnCount ( ) ; + + col )
emit dataChanged ( index ( i , col ) , index ( i , col ) , { Qt : : DisplayRole } ) ;
}
}
@ -260,7 +372,7 @@ void MessageListModel::sort(int column, Qt::SortOrder order) {
if ( column ! = columnCount ( ) - 1 ) {
sort_column = column ;
sort_order = order ;
sortMessages ( ) ;
fetchData ( ) ;
}
}
@ -284,12 +396,17 @@ void MessageListModel::clearSuppress() {
void MessageListModel : : reset ( ) {
beginResetModel ( ) ;
filter_str = " " ;
filter_str . clear ( ) ;
msgs . clear ( ) ;
clearSuppress ( ) ;
endResetModel ( ) ;
}
void MessageListModel : : forceResetModel ( ) {
beginResetModel ( ) ;
endResetModel ( ) ;
}
// MessageView
void MessageView : : drawRow ( QPainter * painter , const QStyleOptionViewItem & option , const QModelIndex & index ) const {
@ -360,3 +477,74 @@ void MessageView::headerContextMenuEvent(const QPoint &pos) {
menu - > popup ( header ( ) - > mapToGlobal ( pos ) ) ;
}
MessageViewHeader : : MessageViewHeader ( QWidget * parent , MessageListModel * model ) : model ( model ) , QHeaderView ( Qt : : Horizontal , parent ) {
QObject : : connect ( this , & QHeaderView : : sectionResized , this , & MessageViewHeader : : updateHeaderPositions ) ;
QObject : : connect ( this , & QHeaderView : : sectionMoved , this , & MessageViewHeader : : updateHeaderPositions ) ;
}
void MessageViewHeader : : showEvent ( QShowEvent * e ) {
for ( int i = 0 ; i < count ( ) ; i + + ) {
if ( ! editors [ i ] ) {
QString column_name = model - > headerData ( i , Qt : : Horizontal , Qt : : DisplayRole ) . toString ( ) ;
editors [ i ] = new QLineEdit ( this ) ;
editors [ i ] - > setClearButtonEnabled ( true ) ;
editors [ i ] - > setPlaceholderText ( tr ( " Filter %1 " ) . arg ( column_name ) ) ;
QObject : : connect ( editors [ i ] , & QLineEdit : : textChanged , this , & MessageViewHeader : : updateFilters ) ;
}
editors [ i ] - > show ( ) ;
}
QHeaderView : : showEvent ( e ) ;
}
void MessageViewHeader : : updateFilters ( ) {
QMap < int , QString > filters ;
for ( int i = 0 ; i < count ( ) ; i + + ) {
if ( editors [ i ] ) {
QString filter = editors [ i ] - > text ( ) ;
if ( ! filter . isEmpty ( ) ) {
filters [ i ] = filter ;
}
}
}
emit filtersUpdated ( filters ) ;
}
void MessageViewHeader : : clearFilters ( ) {
for ( QLineEdit * editor : editors ) {
editor - > clear ( ) ;
}
}
void MessageViewHeader : : updateHeaderPositions ( ) {
QSize sz = QHeaderView : : sizeHint ( ) ;
for ( int i = 0 ; i < count ( ) ; i + + ) {
if ( editors [ i ] ) {
int h = editors [ i ] - > sizeHint ( ) . height ( ) ;
editors [ i ] - > move ( sectionViewportPosition ( i ) , sz . height ( ) ) ;
editors [ i ] - > resize ( sectionSize ( i ) , h ) ;
editors [ i ] - > setHidden ( isSectionHidden ( i ) ) ;
}
}
}
void MessageViewHeader : : updateGeometries ( ) {
if ( editors [ 0 ] ) {
setViewportMargins ( 0 , 0 , 0 , editors [ 0 ] - > sizeHint ( ) . height ( ) ) ;
} else {
setViewportMargins ( 0 , 0 , 0 , 0 ) ;
}
QHeaderView : : updateGeometries ( ) ;
updateHeaderPositions ( ) ;
}
QSize MessageViewHeader : : sizeHint ( ) const {
QSize sz = QHeaderView : : sizeHint ( ) ;
if ( editors [ 0 ] ) {
sz . setHeight ( sz . height ( ) + editors [ 0 ] - > minimumSizeHint ( ) . height ( ) ) ;
}
return sz ;
}