openpilot is an open source driver assistance system. openpilot performs the functions of Automated Lane Centering and Adaptive Cruise Control for over 200 supported car makes and models.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

274 lines
9.3 KiB

#include "tools/cabana/binaryview.h"
#include <QApplication>
#include <QFontDatabase>
#include <QHeaderView>
#include <QMouseEvent>
#include <QPainter>
#include <QToolTip>
#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<const Signal *> BinaryView::getOverlappingSignals() const {
QSet<const Signal *> 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 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 &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();
}