# include "tools/cabana/tools/findsignal.h"
# include <QFormLayout>
# include <QHBoxLayout>
# include <QHeaderView>
# include <QMenu>
# include <QtConcurrent>
# include <QTimer>
# include <QVBoxLayout>
// FindSignalModel
QVariant FindSignalModel : : headerData ( int section , Qt : : Orientation orientation , int role ) const {
static QString titles [ ] = { " Id " , " Start Bit, size " , " (time, value) " } ;
if ( role ! = Qt : : DisplayRole ) return { } ;
return orientation = = Qt : : Horizontal ? titles [ section ] : QString : : number ( section + 1 ) ;
}
QVariant FindSignalModel : : data ( const QModelIndex & index , int role ) const {
if ( role = = Qt : : DisplayRole ) {
const auto & s = filtered_signals [ index . row ( ) ] ;
switch ( index . column ( ) ) {
case 0 : return s . id . toString ( ) ;
case 1 : return QString ( " %1, %2 " ) . arg ( s . sig . start_bit ) . arg ( s . sig . size ) ;
case 2 : return s . values . join ( " " ) ;
}
}
return { } ;
}
void FindSignalModel : : search ( std : : function < bool ( double ) > cmp ) {
beginResetModel ( ) ;
std : : mutex lock ;
const auto prev_sigs = ! histories . isEmpty ( ) ? histories . back ( ) : initial_signals ;
filtered_signals . clear ( ) ;
filtered_signals . reserve ( prev_sigs . size ( ) ) ;
QtConcurrent : : blockingMap ( prev_sigs , [ & ] ( auto & s ) {
const auto & events = can - > events ( s . id ) ;
auto first = std : : upper_bound ( events . cbegin ( ) , events . cend ( ) , s . mono_time , CompareCanEvent ( ) ) ;
auto last = events . cend ( ) ;
if ( last_time < std : : numeric_limits < uint64_t > : : max ( ) ) {
last = std : : upper_bound ( events . cbegin ( ) , events . cend ( ) , last_time , CompareCanEvent ( ) ) ;
}
auto it = std : : find_if ( first , last , [ & ] ( const CanEvent * e ) { return cmp ( get_raw_value ( e - > dat , e - > size , s . sig ) ) ; } ) ;
if ( it ! = last ) {
auto values = s . values ;
values + = QString ( " (%1, %2) " ) . arg ( ( * it ) - > mono_time / 1e9 - can - > routeStartTime ( ) , 0 , ' f ' , 2 ) . arg ( get_raw_value ( ( * it ) - > dat , ( * it ) - > size , s . sig ) ) ;
std : : lock_guard lk ( lock ) ;
filtered_signals . push_back ( { . id = s . id , . mono_time = ( * it ) - > mono_time , . sig = s . sig , . values = values } ) ;
}
} ) ;
histories . push_back ( filtered_signals ) ;
endResetModel ( ) ;
}
void FindSignalModel : : undo ( ) {
if ( ! histories . isEmpty ( ) ) {
beginResetModel ( ) ;
histories . pop_back ( ) ;
filtered_signals . clear ( ) ;
if ( ! histories . isEmpty ( ) ) filtered_signals = histories . back ( ) ;
endResetModel ( ) ;
}
}
void FindSignalModel : : reset ( ) {
beginResetModel ( ) ;
histories . clear ( ) ;
filtered_signals . clear ( ) ;
initial_signals . clear ( ) ;
endResetModel ( ) ;
}
// FindSignalDlg
FindSignalDlg : : FindSignalDlg ( QWidget * parent ) : QDialog ( parent , Qt : : WindowFlags ( ) | Qt : : Window ) {
setWindowTitle ( tr ( " Find Signal " ) ) ;
setAttribute ( Qt : : WA_DeleteOnClose ) ;
QVBoxLayout * main_layout = new QVBoxLayout ( this ) ;
// Messages group
message_group = new QGroupBox ( tr ( " Messages " ) , this ) ;
QFormLayout * message_layout = new QFormLayout ( message_group ) ;
message_layout - > addRow ( tr ( " Bus " ) , bus_edit = new QLineEdit ( ) ) ;
bus_edit - > setPlaceholderText ( tr ( " comma-seperated values. Leave blank for all " ) ) ;
message_layout - > addRow ( tr ( " Address " ) , address_edit = new QLineEdit ( ) ) ;
address_edit - > setPlaceholderText ( tr ( " comma-seperated hex values. Leave blank for all " ) ) ;
QHBoxLayout * hlayout = new QHBoxLayout ( ) ;
hlayout - > addWidget ( first_time_edit = new QLineEdit ( " 0 " ) ) ;
hlayout - > addWidget ( new QLabel ( " - " ) ) ;
hlayout - > addWidget ( last_time_edit = new QLineEdit ( " MAX " ) ) ;
hlayout - > addWidget ( new QLabel ( " seconds " ) ) ;
hlayout - > addStretch ( 0 ) ;
message_layout - > addRow ( tr ( " Time " ) , hlayout ) ;
// Signal group
properties_group = new QGroupBox ( tr ( " Signal " ) ) ;
QFormLayout * property_layout = new QFormLayout ( properties_group ) ;
property_layout - > setFieldGrowthPolicy ( QFormLayout : : FieldsStayAtSizeHint ) ;
hlayout = new QHBoxLayout ( ) ;
hlayout - > addWidget ( min_size = new QSpinBox ) ;
hlayout - > addWidget ( new QLabel ( " - " ) ) ;
hlayout - > addWidget ( max_size = new QSpinBox ) ;
hlayout - > addWidget ( litter_endian = new QCheckBox ( tr ( " Little endian " ) ) ) ;
hlayout - > addWidget ( is_signed = new QCheckBox ( tr ( " Signed " ) ) ) ;
hlayout - > addStretch ( 0 ) ;
min_size - > setRange ( 1 , 64 ) ;
max_size - > setRange ( 1 , 64 ) ;
min_size - > setValue ( 8 ) ;
max_size - > setValue ( 8 ) ;
litter_endian - > setChecked ( true ) ;
property_layout - > addRow ( tr ( " Size " ) , hlayout ) ;
property_layout - > addRow ( tr ( " Factor " ) , factor_edit = new QLineEdit ( " 1.0 " ) ) ;
property_layout - > addRow ( tr ( " Offset " ) , offset_edit = new QLineEdit ( " 0.0 " ) ) ;
// find group
QGroupBox * find_group = new QGroupBox ( tr ( " Find signal " ) , this ) ;
QVBoxLayout * vlayout = new QVBoxLayout ( find_group ) ;
hlayout = new QHBoxLayout ( ) ;
hlayout - > addWidget ( new QLabel ( tr ( " Value " ) ) ) ;
hlayout - > addWidget ( compare_cb = new QComboBox ( this ) ) ;
hlayout - > addWidget ( value1 = new QLineEdit ) ;
hlayout - > addWidget ( to_label = new QLabel ( " - " ) ) ;
hlayout - > addWidget ( value2 = new QLineEdit ) ;
hlayout - > addWidget ( undo_btn = new QPushButton ( tr ( " Undo prev find " ) , this ) ) ;
hlayout - > addWidget ( search_btn = new QPushButton ( tr ( " Find " ) ) ) ;
hlayout - > addWidget ( reset_btn = new QPushButton ( tr ( " Reset " ) , this ) ) ;
vlayout - > addLayout ( hlayout ) ;
compare_cb - > addItems ( { " = " , " > " , " >= " , " != " , " < " , " <= " , " between " } ) ;
value1 - > setFocus ( Qt : : OtherFocusReason ) ;
value2 - > setVisible ( false ) ;
to_label - > setVisible ( false ) ;
undo_btn - > setEnabled ( false ) ;
reset_btn - > setEnabled ( false ) ;
auto double_validator = new DoubleValidator ( this ) ;
for ( auto edit : { value1 , value2 , factor_edit , offset_edit , first_time_edit , last_time_edit } ) {
edit - > setValidator ( double_validator ) ;
}
vlayout - > addWidget ( view = new QTableView ( this ) ) ;
view - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
view - > horizontalHeader ( ) - > setStretchLastSection ( true ) ;
view - > horizontalHeader ( ) - > setSelectionMode ( QAbstractItemView : : NoSelection ) ;
view - > setSelectionBehavior ( QAbstractItemView : : SelectRows ) ;
view - > setModel ( model = new FindSignalModel ( this ) ) ;
hlayout = new QHBoxLayout ( ) ;
hlayout - > addWidget ( message_group ) ;
hlayout - > addWidget ( properties_group ) ;
main_layout - > addLayout ( hlayout ) ;
main_layout - > addWidget ( find_group ) ;
main_layout - > addWidget ( stats_label = new QLabel ( ) ) ;
setMinimumSize ( { 700 , 650 } ) ;
QObject : : connect ( search_btn , & QPushButton : : clicked , this , & FindSignalDlg : : search ) ;
QObject : : connect ( undo_btn , & QPushButton : : clicked , model , & FindSignalModel : : undo ) ;
QObject : : connect ( model , & QAbstractItemModel : : modelReset , this , & FindSignalDlg : : modelReset ) ;
QObject : : connect ( reset_btn , & QPushButton : : clicked , model , & FindSignalModel : : reset ) ;
QObject : : connect ( view , & QTableView : : customContextMenuRequested , this , & FindSignalDlg : : customMenuRequested ) ;
QObject : : connect ( view , & QTableView : : doubleClicked , [ this ] ( const QModelIndex & index ) {
if ( index . isValid ( ) ) emit openMessage ( model - > filtered_signals [ index . row ( ) ] . id ) ;
} ) ;
QObject : : connect ( compare_cb , qOverload < int > ( & QComboBox : : currentIndexChanged ) , [ = ] ( int index ) {
to_label - > setVisible ( index = = compare_cb - > count ( ) - 1 ) ;
value2 - > setVisible ( index = = compare_cb - > count ( ) - 1 ) ;
} ) ;
}
void FindSignalDlg : : search ( ) {
if ( model - > histories . isEmpty ( ) ) {
setInitialSignals ( ) ;
}
auto v1 = value1 - > text ( ) . toDouble ( ) ;
auto v2 = value2 - > text ( ) . toDouble ( ) ;
std : : function < bool ( double ) > cmp = nullptr ;
switch ( compare_cb - > currentIndex ( ) ) {
case 0 : cmp = [ v1 ] ( double v ) { return v = = v1 ; } ; break ;
case 1 : cmp = [ v1 ] ( double v ) { return v > v1 ; } ; break ;
case 2 : cmp = [ v1 ] ( double v ) { return v > = v1 ; } ; break ;
case 3 : cmp = [ v1 ] ( double v ) { return v ! = v1 ; } ; break ;
case 4 : cmp = [ v1 ] ( double v ) { return v < v1 ; } ; break ;
case 5 : cmp = [ v1 ] ( double v ) { return v < = v1 ; } ; break ;
case 6 : cmp = [ v1 , v2 ] ( double v ) { return v > = v1 & & v < = v2 ; } ; break ;
}
properties_group - > setEnabled ( false ) ;
message_group - > setEnabled ( false ) ;
search_btn - > setEnabled ( false ) ;
stats_label - > setVisible ( false ) ;
search_btn - > setText ( " Finding .... " ) ;
QTimer : : singleShot ( 0 , this , [ = ] ( ) { model - > search ( cmp ) ; } ) ;
}
void FindSignalDlg : : setInitialSignals ( ) {
QSet < ushort > buses ;
for ( auto bus : bus_edit - > text ( ) . trimmed ( ) . split ( " , " ) ) {
bus = bus . trimmed ( ) ;
if ( ! bus . isEmpty ( ) ) buses . insert ( bus . toUShort ( ) ) ;
}
QSet < uint32_t > addresses ;
for ( auto addr : address_edit - > text ( ) . trimmed ( ) . split ( " , " ) ) {
addr = addr . trimmed ( ) ;
if ( ! addr . isEmpty ( ) ) addresses . insert ( addr . toULong ( nullptr , 16 ) ) ;
}
cabana : : Signal sig { } ;
sig . is_little_endian = litter_endian - > isChecked ( ) ;
sig . is_signed = is_signed - > isChecked ( ) ;
sig . factor = factor_edit - > text ( ) . toDouble ( ) ;
sig . offset = offset_edit - > text ( ) . toDouble ( ) ;
double first_time_val = first_time_edit - > text ( ) . toDouble ( ) ;
double last_time_val = last_time_edit - > text ( ) . toDouble ( ) ;
auto [ first_sec , last_sec ] = std : : minmax ( first_time_val , last_time_val ) ;
uint64_t first_time = ( can - > routeStartTime ( ) + first_sec ) * 1e9 ;
model - > last_time = std : : numeric_limits < uint64_t > : : max ( ) ;
if ( last_sec > 0 ) {
model - > last_time = ( can - > routeStartTime ( ) + last_sec ) * 1e9 ;
}
model - > initial_signals . clear ( ) ;
for ( const auto & [ id , m ] : can - > lastMessages ( ) ) {
if ( buses . isEmpty ( ) | | buses . contains ( id . source ) & & ( addresses . isEmpty ( ) | | addresses . contains ( id . address ) ) ) {
const auto & events = can - > events ( id ) ;
auto e = std : : lower_bound ( events . cbegin ( ) , events . cend ( ) , first_time , CompareCanEvent ( ) ) ;
if ( e ! = events . cend ( ) ) {
const int total_size = m . dat . size ( ) * 8 ;
for ( int size = min_size - > value ( ) ; size < = max_size - > value ( ) ; + + size ) {
for ( int start = 0 ; start < = total_size - size ; + + start ) {
FindSignalModel : : SearchSignal s { . id = id , . mono_time = first_time , . sig = sig } ;
s . sig . start_bit = start ;
s . sig . size = size ;
updateMsbLsb ( s . sig ) ;
s . value = get_raw_value ( ( * e ) - > dat , ( * e ) - > size , s . sig ) ;
model - > initial_signals . push_back ( s ) ;
}
}
}
}
}
}
void FindSignalDlg : : modelReset ( ) {
properties_group - > setEnabled ( model - > histories . isEmpty ( ) ) ;
message_group - > setEnabled ( model - > histories . isEmpty ( ) ) ;
search_btn - > setText ( model - > histories . isEmpty ( ) ? tr ( " Find " ) : tr ( " Find Next " ) ) ;
reset_btn - > setEnabled ( ! model - > histories . isEmpty ( ) ) ;
undo_btn - > setEnabled ( model - > histories . size ( ) > 1 ) ;
search_btn - > setEnabled ( model - > rowCount ( ) > 0 | | model - > histories . isEmpty ( ) ) ;
stats_label - > setVisible ( true ) ;
stats_label - > setText ( tr ( " %1 matches. right click on an item to create signal. double click to open message " ) . arg ( model - > filtered_signals . size ( ) ) ) ;
}
void FindSignalDlg : : customMenuRequested ( const QPoint & pos ) {
if ( auto index = view - > indexAt ( pos ) ; index . isValid ( ) ) {
QMenu menu ( this ) ;
menu . addAction ( tr ( " Create Signal " ) ) ;
if ( menu . exec ( view - > mapToGlobal ( pos ) ) ) {
auto & s = model - > filtered_signals [ index . row ( ) ] ;
UndoStack : : push ( new AddSigCommand ( s . id , s . sig ) ) ;
emit openMessage ( s . id ) ;
}
}
}