Cabana: show MSB/LSB of signals in the BinaryView (#26103)
	
		
	
				
					
				
			* dispaly msb and lsb in binary view * draw at bottom * move binaryview to seperate files * increase the width of vertical header * re-draw changed cells only * correct lsb/msb position * check bounds * todopull/214/head
							parent
							
								
									409e8f5f89
								
							
						
					
					
						commit
						99b16151fc
					
				
				 7 changed files with 265 additions and 121 deletions
			
			
		@ -0,0 +1,184 @@ | 
				
			|||||||
 | 
					#include "tools/cabana/binaryview.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QApplication> | 
				
			||||||
 | 
					#include <QHeaderView> | 
				
			||||||
 | 
					#include <QPainter> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "tools/cabana/canmessages.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BinaryView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const int CELL_HEIGHT = 35; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BinaryView::BinaryView(QWidget *parent) : QTableView(parent) { | 
				
			||||||
 | 
					  model = new BinaryViewModel(this); | 
				
			||||||
 | 
					  setModel(model); | 
				
			||||||
 | 
					  setItemDelegate(new BinaryItemDelegate(this)); | 
				
			||||||
 | 
					  horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); | 
				
			||||||
 | 
					  horizontalHeader()->hide(); | 
				
			||||||
 | 
					  verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); | 
				
			||||||
 | 
					  setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // replace selection model
 | 
				
			||||||
 | 
					  auto old_model = selectionModel(); | 
				
			||||||
 | 
					  setSelectionModel(new BinarySelectionModel(model)); | 
				
			||||||
 | 
					  delete old_model; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  QObject::connect(model, &QAbstractItemModel::modelReset, [this]() { | 
				
			||||||
 | 
					    setFixedHeight((CELL_HEIGHT + 1) * std::min(model->rowCount(), 8) + 2); | 
				
			||||||
 | 
					  }); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BinaryView::mouseReleaseEvent(QMouseEvent *event) { | 
				
			||||||
 | 
					  QTableView::mouseReleaseEvent(event); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (auto indexes = selectedIndexes(); !indexes.isEmpty()) { | 
				
			||||||
 | 
					    int start_bit = indexes.first().row() * 8 + indexes.first().column(); | 
				
			||||||
 | 
					    int size = indexes.back().row() * 8 + indexes.back().column() - start_bit + 1; | 
				
			||||||
 | 
					    emit cellsSelected(start_bit, size); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BinaryView::setMessage(const QString &message_id) { | 
				
			||||||
 | 
					  msg_id = message_id; | 
				
			||||||
 | 
					  model->setMessage(message_id); | 
				
			||||||
 | 
					  resizeRowsToContents(); | 
				
			||||||
 | 
					  clearSelection(); | 
				
			||||||
 | 
					  updateState(); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BinaryView::updateState() { | 
				
			||||||
 | 
					  model->updateState(); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BinaryViewModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BinaryViewModel::setMessage(const QString &message_id) { | 
				
			||||||
 | 
					  msg_id = message_id; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beginResetModel(); | 
				
			||||||
 | 
					  items.clear(); | 
				
			||||||
 | 
					  row_count = 0; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  dbc_msg = dbc()->msg(msg_id); | 
				
			||||||
 | 
					  if (dbc_msg) { | 
				
			||||||
 | 
					    row_count = dbc_msg->size; | 
				
			||||||
 | 
					    items.resize(row_count * column_count); | 
				
			||||||
 | 
					    for (int i = 0; i < dbc_msg->sigs.size(); ++i) { | 
				
			||||||
 | 
					      const auto &sig = dbc_msg->sigs[i]; | 
				
			||||||
 | 
					      const int start = sig.is_little_endian ? sig.start_bit : bigEndianBitIndex(sig.start_bit); | 
				
			||||||
 | 
					      const int end = start + sig.size - 1; | 
				
			||||||
 | 
					      for (int j = start; j <= end; ++j) { | 
				
			||||||
 | 
					        int idx = column_count * (j / 8) + j % 8; | 
				
			||||||
 | 
					        if (idx >= items.size()) { | 
				
			||||||
 | 
					          qWarning() << "signal " << sig.name.c_str() << "out of bounds.start_bit:" << sig.start_bit << "size:" << sig.size; | 
				
			||||||
 | 
					          break; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        if (j == start) { | 
				
			||||||
 | 
					          sig.is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true; | 
				
			||||||
 | 
					        } else if (j == end) { | 
				
			||||||
 | 
					          sig.is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        items[idx].bg_color = QColor(getColor(i)); | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  endResetModel(); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QModelIndex BinaryViewModel::index(int row, int column, const QModelIndex &parent) const { | 
				
			||||||
 | 
					  return createIndex(row, column, (void *)&items[row * column_count + column]); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Qt::ItemFlags BinaryViewModel::flags(const QModelIndex &index) const { | 
				
			||||||
 | 
					  return (index.column() == column_count - 1) ? Qt::ItemIsEnabled : Qt::ItemIsEnabled | Qt::ItemIsSelectable; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BinaryViewModel::updateState() { | 
				
			||||||
 | 
					  auto prev_items = items; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const auto &binary = can->lastMessage(msg_id).dat; | 
				
			||||||
 | 
					  // data size may changed.
 | 
				
			||||||
 | 
					  if (!dbc_msg && binary.size() != row_count) { | 
				
			||||||
 | 
					    beginResetModel(); | 
				
			||||||
 | 
					    row_count = binary.size(); | 
				
			||||||
 | 
					    items.clear(); | 
				
			||||||
 | 
					    items.resize(row_count * column_count); | 
				
			||||||
 | 
					    endResetModel(); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  char hex[3] = {'\0'}; | 
				
			||||||
 | 
					  for (int i = 0; i < std::min(binary.size(), row_count); ++i) { | 
				
			||||||
 | 
					    for (int j = 0; j < column_count - 1; ++j) { | 
				
			||||||
 | 
					      items[i * column_count + j].val = QChar((binary[i] >> (7 - j)) & 1 ? '1' : '0'); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    hex[0] = toHex(binary[i] >> 4); | 
				
			||||||
 | 
					    hex[1] = toHex(binary[i] & 0xf); | 
				
			||||||
 | 
					    items[i * column_count + 8].val = hex; | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (int i = 0; i < items.size(); ++i) { | 
				
			||||||
 | 
					    if (i >= prev_items.size() || prev_items[i].val != items[i].val) { | 
				
			||||||
 | 
					      auto idx = index(i / column_count, i % column_count); | 
				
			||||||
 | 
					      emit dataChanged(idx, idx); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QVariant BinaryViewModel::headerData(int section, Qt::Orientation orientation, int role) const { | 
				
			||||||
 | 
					  if (orientation == Qt::Vertical) { | 
				
			||||||
 | 
					    switch (role) { | 
				
			||||||
 | 
					      case Qt::DisplayRole: return section + 1; | 
				
			||||||
 | 
					      case Qt::SizeHintRole: return QSize(30, CELL_HEIGHT); | 
				
			||||||
 | 
					      case Qt::TextAlignmentRole: return Qt::AlignCenter; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  return {}; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BinarySelectionModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BinarySelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) { | 
				
			||||||
 | 
					  QItemSelection new_selection = selection; | 
				
			||||||
 | 
					  if (auto indexes = selection.indexes(); !indexes.isEmpty()) { | 
				
			||||||
 | 
					    auto [begin_idx, end_idx] = (QModelIndex[]){indexes.first(), indexes.back()}; | 
				
			||||||
 | 
					    for (int row = begin_idx.row(); row <= end_idx.row(); ++row) { | 
				
			||||||
 | 
					      int left_col = (row == begin_idx.row()) ? begin_idx.column() : 0; | 
				
			||||||
 | 
					      int right_col = (row == end_idx.row()) ? end_idx.column() : 7; | 
				
			||||||
 | 
					      new_selection.merge({model()->index(row, left_col), model()->index(row, right_col)}, command); | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  QItemSelectionModel::select(new_selection, command); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// BinaryItemDelegate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BinaryItemDelegate::BinaryItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { | 
				
			||||||
 | 
					  // cache fonts and color
 | 
				
			||||||
 | 
					  small_font.setPointSize(7); | 
				
			||||||
 | 
					  bold_font.setBold(true); | 
				
			||||||
 | 
					  highlight_color = QApplication::style()->standardPalette().color(QPalette::Active, QPalette::Highlight); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QSize BinaryItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { | 
				
			||||||
 | 
					  QSize sz = QStyledItemDelegate::sizeHint(option, index); | 
				
			||||||
 | 
					  return {sz.width(), CELL_HEIGHT}; | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { | 
				
			||||||
 | 
					  auto item = (const BinaryViewModel::Item *)index.internalPointer(); | 
				
			||||||
 | 
					  painter->save(); | 
				
			||||||
 | 
					  // TODO: highlight signal cells on mouse over
 | 
				
			||||||
 | 
					  painter->fillRect(option.rect, option.state & QStyle::State_Selected ? highlight_color : item->bg_color); | 
				
			||||||
 | 
					  if (index.column() == 8) { | 
				
			||||||
 | 
					    painter->setFont(bold_font); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  painter->drawText(option.rect, Qt::AlignCenter, item->val); | 
				
			||||||
 | 
					  if (item->is_msb || item->is_lsb) { | 
				
			||||||
 | 
					    painter->setFont(small_font); | 
				
			||||||
 | 
					    painter->drawText(option.rect, Qt::AlignHCenter | Qt::AlignBottom, item->is_msb ? "MSB" : "LSB"); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  painter->restore(); | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,72 @@ | 
				
			|||||||
 | 
					#pragma once | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <QStyledItemDelegate> | 
				
			||||||
 | 
					#include <QTableView> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "tools/cabana/dbcmanager.h" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BinaryItemDelegate : public QStyledItemDelegate { | 
				
			||||||
 | 
					  Q_OBJECT | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public: | 
				
			||||||
 | 
					  BinaryItemDelegate(QObject *parent); | 
				
			||||||
 | 
					  void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; | 
				
			||||||
 | 
					  QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private: | 
				
			||||||
 | 
					  QFont small_font, bold_font; | 
				
			||||||
 | 
					  QColor highlight_color; | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BinaryViewModel : public QAbstractTableModel { | 
				
			||||||
 | 
					  Q_OBJECT | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public: | 
				
			||||||
 | 
					  BinaryViewModel(QObject *parent) : QAbstractTableModel(parent) {} | 
				
			||||||
 | 
					  void setMessage(const QString &message_id); | 
				
			||||||
 | 
					  void updateState(); | 
				
			||||||
 | 
					  Qt::ItemFlags flags(const QModelIndex &index) const; | 
				
			||||||
 | 
					  QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; | 
				
			||||||
 | 
					  QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; | 
				
			||||||
 | 
					  QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const { return {}; } | 
				
			||||||
 | 
					  int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; } | 
				
			||||||
 | 
					  int columnCount(const QModelIndex &parent = QModelIndex()) const override { return column_count; } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  struct Item { | 
				
			||||||
 | 
					    QColor bg_color = QColor(Qt::white); | 
				
			||||||
 | 
					    bool is_msb = false; | 
				
			||||||
 | 
					    bool is_lsb = false; | 
				
			||||||
 | 
					    QString val = "0"; | 
				
			||||||
 | 
					  }; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private: | 
				
			||||||
 | 
					  QString msg_id; | 
				
			||||||
 | 
					  const Msg *dbc_msg; | 
				
			||||||
 | 
					  int row_count = 0; | 
				
			||||||
 | 
					  const int column_count = 9; | 
				
			||||||
 | 
					  std::vector<Item> items; | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// the default QItemSelectionModel does not support our selection mode.
 | 
				
			||||||
 | 
					class BinarySelectionModel : public QItemSelectionModel { | 
				
			||||||
 | 
					 public: | 
				
			||||||
 | 
					  BinarySelectionModel(QAbstractItemModel *model = nullptr) : QItemSelectionModel(model) {} | 
				
			||||||
 | 
					  void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) override; | 
				
			||||||
 | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BinaryView : public QTableView { | 
				
			||||||
 | 
					  Q_OBJECT | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public: | 
				
			||||||
 | 
					  BinaryView(QWidget *parent = nullptr); | 
				
			||||||
 | 
					  void mouseReleaseEvent(QMouseEvent *event) override; | 
				
			||||||
 | 
					  void setMessage(const QString &message_id); | 
				
			||||||
 | 
					  void updateState(); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					signals: | 
				
			||||||
 | 
					  void cellsSelected(int start_bit, int size); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private: | 
				
			||||||
 | 
					  QString msg_id; | 
				
			||||||
 | 
					  BinaryViewModel *model; | 
				
			||||||
 | 
					}; | 
				
			||||||
					Loading…
					
					
				
		Reference in new issue