#include "tools/cabana/binaryview.h" #include #include #include #include #include #include "tools/cabana/streams/abstractstream.h" // BinaryView const int CELL_HEIGHT = 36; const int VERTICAL_HEADER_WIDTH = 30; 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); verticalHeader()->setSectionsClickable(false); verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); verticalHeader()->setDefaultSectionSize(CELL_HEIGHT); horizontalHeader()->hide(); setFrameShape(QFrame::NoFrame); setShowGrid(false); setMouseTracking(true); setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } QSize BinaryView::minimumSizeHint() const { return {horizontalHeader()->minimumSectionSize() * 9 + VERTICAL_HEADER_WIDTH + 2, 0}; } void BinaryView::highlight(const Signal *sig) { if (sig != hovered_sig) { for (int i = 0; i < model->items.size(); ++i) { auto &item_sigs = model->items[i].sigs; if ((sig && item_sigs.contains(sig)) || (hovered_sig && item_sigs.contains(hovered_sig))) { auto index = model->index(i / model->columnCount(), i % model->columnCount()); emit model->dataChanged(index, index, {Qt::DisplayRole}); } } hovered_sig = sig; 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 [start, size, is_lb] = getSelection(index); for (int i = start; i < start + size; ++i) { auto idx = model->bitIndex(i, is_lb); selection.merge({idx, idx}, flags); } selectionModel()->select(selection, flags); } void BinaryView::mousePressEvent(QMouseEvent *event) { delegate->selection_color = (palette().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(); int bit_idx = get_bit_index(anchor_index, true); for (auto s : item->sigs) { if (bit_idx == s->lsb || bit_idx == s->msb) { anchor_index = model->bitIndex(bit_idx == s->lsb ? s->msb : s->lsb, true); resize_sig = s; delegate->selection_color = item->bg_color; break; } } } event->accept(); } void BinaryView::highlightPosition(const QPoint &pos) { if (auto index = indexAt(viewport()->mapFromGlobal(pos)); index.isValid()) { auto item = (BinaryViewModel::Item *)index.internalPointer(); const Signal *sig = item->sigs.isEmpty() ? nullptr : item->sigs.back(); highlight(sig); QToolTip::showText(pos, sig ? sig->name.c_str() : "", this, rect()); } } void BinaryView::mouseMoveEvent(QMouseEvent *event) { highlightPosition(event->globalPos()); 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 (selectionModel()->hasSelection()) { auto [start_bit, size, is_lb] = getSelection(release_index); resize_sig ? emit resizeSignal(resize_sig, start_bit, size) : emit addSignal(start_bit, size, is_lb); } else { auto item = (const BinaryViewModel::Item *)anchor_index.internalPointer(); if (item && item->sigs.size() > 0) emit signalClicked(item->sigs.back()); } } 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; highlightPosition(QCursor::pos()); 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; } std::tuple BinaryView::getSelection(QModelIndex index) { if (index.column() == 8) { index = model->index(index.row(), 7); } bool is_lb = (resize_sig && resize_sig->is_little_endian) || (!resize_sig && index < anchor_index); int cur_bit_idx = get_bit_index(index, is_lb); int anchor_bit_idx = get_bit_index(anchor_index, is_lb); auto [start_bit, end_bit] = std::minmax(cur_bit_idx, anchor_bit_idx); return {start_bit, end_bit - start_bit + 1, is_lb}; } // 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 sig : dbc_msg->getSignals()) { 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 " << 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; 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 &last_msg = can->lastMessage(msg_id); const auto &binary = last_msg.dat; // data size may changed. if (binary.size() > row_count) { beginInsertRows({}, row_count, binary.size() - 1); row_count = binary.size(); items.resize(row_count * column_count); endInsertRows(); } char hex[3] = {'\0'}; for (int i = 0; i < binary.size(); ++i) { for (int j = 0; j < column_count - 1; ++j) { items[i * column_count + j].val = ((binary[i] >> (7 - j)) & 1) != 0 ? '1' : '0'; } hex[0] = toHex(binary[i] >> 4); hex[1] = toHex(binary[i] & 0xf); items[i * column_count + 8].val = hex; items[i * column_count + 8].bg_color = last_msg.colors[i]; } for (int i = binary.size(); i < row_count; ++i) { for (int j = 0; j < column_count; ++j) { items[i * column_count + j].val = "-"; } } for (int i = 0; i < row_count * column_count; ++i) { if (i >= prev_items.size() || prev_items[i].val != items[i].val || prev_items[i].bg_color != items[i].bg_color) { 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(VERTICAL_HEADER_WIDTH, 0); case Qt::TextAlignmentRole: return Qt::AlignCenter; } } return {}; } // BinaryItemDelegate BinaryItemDelegate::BinaryItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { small_font.setPixelSize(8); hex_font = QFontDatabase::systemFont(QFontDatabase::FixedFont); hex_font.setBold(true); } 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(); if (index.column() == 8) { painter->setFont(hex_font); painter->fillRect(option.rect, item->bg_color); } else if (option.state & QStyle::State_Selected) { painter->fillRect(option.rect, selection_color); painter->setPen(option.palette.color(QPalette::BrightText)); } else if (!item->sigs.isEmpty() && (!bin_view->selectionModel()->hasSelection() || !item->sigs.contains(bin_view->resize_sig))) { bool sig_hovered = item->sigs.contains(bin_view->hovered_sig); painter->fillRect(option.rect, sig_hovered ? item->bg_color.darker(125) : item->bg_color); // 4/5x brightness painter->setPen(sig_hovered ? option.palette.color(QPalette::BrightText) : Qt::black); } 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(); }