#include "tools/cabana/binaryview.h" #include #include #include #include #include #include #include "tools/cabana/canmessages.h" // BinaryView const int CELL_HEIGHT = 26; inline int get_bit_index(const QModelIndex &index, bool little_endian) { return index.row() * 8 + (little_endian ? 7 - index.column() : index.column()); } BinaryView::BinaryView(QWidget *parent) : QTableView(parent) { model = new BinaryViewModel(this); setModel(model); delegate = new BinaryItemDelegate(this); setItemDelegate(delegate); horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); horizontalHeader()->hide(); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); setFrameShape(QFrame::NoFrame); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); setMouseTracking(true); } void BinaryView::highlight(const Signal *sig) { if (sig != hovered_sig) { hovered_sig = sig; model->dataChanged(model->index(0, 0), model->index(model->rowCount() - 1, model->columnCount() - 1)); emit signalHovered(hovered_sig); } } void BinaryView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) { auto index = indexAt(viewport()->mapFromGlobal(QCursor::pos())); if (!anchor_index.isValid() || !index.isValid()) return; QItemSelection selection; auto [tl, br] = std::minmax(anchor_index, index); if ((resize_sig && resize_sig->is_little_endian) || (!resize_sig && index < anchor_index)) { // little_endian selection if (tl.row() == br.row()) { selection.merge({model->index(tl.row(), tl.column()), model->index(tl.row(), br.column())}, flags); } else { for (int row = tl.row(); row <= br.row(); ++row) { int left_col = (row == br.row()) ? br.column() : 0; int right_col = (row == tl.row()) ? tl.column() : 7; selection.merge({model->index(row, left_col), model->index(row, right_col)}, flags); } } } else { // big endian selection for (int row = tl.row(); row <= br.row(); ++row) { int left_col = (row == tl.row()) ? tl.column() : 0; int right_col = (row == br.row()) ? br.column() : 7; selection.merge({model->index(row, left_col), model->index(row, right_col)}, flags); } } selectionModel()->select(selection, flags); } void BinaryView::mousePressEvent(QMouseEvent *event) { delegate->setSelectionColor(style()->standardPalette().color(QPalette::Active, QPalette::Highlight)); if (auto index = indexAt(event->pos()); index.isValid() && index.column() != 8) { anchor_index = index; auto item = (const BinaryViewModel::Item *)anchor_index.internalPointer(); if (item && item->sigs.size() > 0) { int bit_idx = get_bit_index(anchor_index, true); for (auto s : item->sigs) { if (bit_idx == s->lsb || bit_idx == s->msb) { resize_sig = s; delegate->setSelectionColor(item->bg_color); break; } } } } QTableView::mousePressEvent(event); } void BinaryView::mouseMoveEvent(QMouseEvent *event) { if (auto index = indexAt(event->pos()); index.isValid()) { auto item = (BinaryViewModel::Item *)index.internalPointer(); const Signal *sig = item->sigs.isEmpty() ? nullptr : item->sigs.back(); highlight(sig); sig ? QToolTip::showText(event->globalPos(), sig->name.c_str(), this, rect()) : QToolTip::hideText(); } QTableView::mouseMoveEvent(event); } void BinaryView::mouseReleaseEvent(QMouseEvent *event) { QTableView::mouseReleaseEvent(event); auto release_index = indexAt(event->pos()); if (release_index.isValid() && anchor_index.isValid()) { if (release_index.column() == 8) { release_index = model->index(release_index.row(), 7); } bool little_endian = (resize_sig && resize_sig->is_little_endian) || (!resize_sig && release_index < anchor_index); int release_bit_idx = get_bit_index(release_index, little_endian); int archor_bit_idx = get_bit_index(anchor_index, little_endian); if (resize_sig) { auto [sig_from, sig_to] = getSignalRange(resize_sig); int start_bit, end_bit; if (archor_bit_idx == sig_from) { std::tie(start_bit, end_bit) = std::minmax(release_bit_idx, sig_to); if (start_bit >= sig_from && start_bit <= sig_to) start_bit = std::min(start_bit + 1, sig_to); } else { std::tie(start_bit, end_bit) = std::minmax(release_bit_idx, sig_from); if (end_bit >= sig_from && end_bit <= sig_to) end_bit = std::max(end_bit - 1, sig_from); } emit resizeSignal(resize_sig, start_bit, end_bit - start_bit + 1); } else { auto [sart_bit, end_bit] = std::minmax(archor_bit_idx, release_bit_idx); emit addSignal(sart_bit, end_bit - sart_bit + 1, little_endian); } } clearSelection(); anchor_index = QModelIndex(); resize_sig = nullptr; } void BinaryView::leaveEvent(QEvent *event) { highlight(nullptr); QTableView::leaveEvent(event); } void BinaryView::setMessage(const QString &message_id) { model->setMessage(message_id); clearSelection(); anchor_index = QModelIndex(); resize_sig = nullptr; hovered_sig = nullptr; updateState(); } QSet BinaryView::getOverlappingSignals() const { QSet overlapping; for (auto &item : model->items) { if (item.sigs.size() > 1) for (auto s : item.sigs) overlapping += s; } return overlapping; } // BinaryViewModel void BinaryViewModel::setMessage(const QString &message_id) { beginResetModel(); msg_id = message_id; items.clear(); if ((dbc_msg = dbc()->msg(msg_id))) { row_count = dbc_msg->size; items.resize(row_count * column_count); int i = 0; for (auto &[name, sig] : dbc_msg->sigs) { auto [start, end] = getSignalRange(&sig); for (int j = start; j <= end; ++j) { int bit_index = sig.is_little_endian ? bigEndianBitIndex(j) : j; int idx = column_count * (bit_index / 8) + bit_index % 8; if (idx >= items.size()) { qWarning() << "signal " << name << "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; if (j == end) sig.is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true; items[idx].bg_color = getColor(i); items[idx].sigs.push_back(&sig); } ++i; } } else { row_count = can->lastMessage(msg_id).dat.size(); items.resize(row_count * column_count); } endResetModel(); } 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; case Qt::SizeHintRole: return QSize(30, CELL_HEIGHT); case Qt::TextAlignmentRole: return Qt::AlignCenter; } } return {}; } // BinaryItemDelegate BinaryItemDelegate::BinaryItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { // cache fonts and color small_font.setPointSize(6); hex_font = QFontDatabase::systemFont(QFontDatabase::FixedFont); hex_font.setBold(true); selection_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(); BinaryView *bin_view = (BinaryView *)parent(); painter->save(); // background bool hover = item->sigs.contains(bin_view->hoveredSignal()); QColor bg_color = hover ? hoverColor(item->bg_color) : item->bg_color; if (option.state & QStyle::State_Selected) { bg_color = selection_color; } painter->fillRect(option.rect, bg_color); // text if (index.column() == 8) { // hex column painter->setFont(hex_font); } else if (hover) { painter->setPen(Qt::white); } 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(); }