diff --git a/SConstruct b/SConstruct index 74680c0598..033e10a1f0 100644 --- a/SConstruct +++ b/SConstruct @@ -298,12 +298,15 @@ if arch == "Darwin": qt_env["FRAMEWORKS"] += [f"Qt{m}" for m in qt_modules] + ["OpenGL"] qt_env.AppendENVPath('PATH', os.path.join(qt_env['QTDIR'], "bin")) else: - qt_env['QTDIR'] = "/usr" + qt_install_prefix = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_PREFIX'], encoding='utf8').strip() + qt_install_headers = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_HEADERS'], encoding='utf8').strip() + + qt_env['QTDIR'] = qt_install_prefix qt_dirs = [ - f"/usr/include/{real_arch}-linux-gnu/qt5", - f"/usr/include/{real_arch}-linux-gnu/qt5/QtGui/5.12.8/QtGui", + f"{qt_install_headers}", + f"{qt_install_headers}/QtGui/5.12.8/QtGui", ] - qt_dirs += [f"/usr/include/{real_arch}-linux-gnu/qt5/Qt{m}" for m in qt_modules] + qt_dirs += [f"{qt_install_headers}/Qt{m}" for m in qt_modules] qt_libs = [f"Qt5{m}" for m in qt_modules] if arch == "larch64": diff --git a/selfdrive/assets/img_couch.svg b/selfdrive/assets/img_couch.svg index 5b3c048318..2e86012809 100644 --- a/selfdrive/assets/img_couch.svg +++ b/selfdrive/assets/img_couch.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 03392ba0f9..35f87307d6 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -100,7 +100,7 @@ CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = { CAR.BOLT_EUV: GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", "https://youtu.be/xvwzGMUA210", footnotes=[], harness=Harness.gm), CAR.SILVERADO: [ GMCarInfo("Chevrolet Silverado 1500 2020-21", "Safety Package II", footnotes=[], harness=Harness.gm), - GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", footnotes=[], harness=Harness.gm), + GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", "https://youtu.be/5HbNoBLzRwE", footnotes=[], harness=Harness.gm), ], CAR.EQUINOX: GMCarInfo("Chevrolet Equinox 2019-22", footnotes=[], harness=Harness.gm), } diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index 8805b3dce8..c0db988cf6 100644 --- a/selfdrive/modeld/models/supercombo.onnx +++ b/selfdrive/modeld/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db746e3753de84367595fedd089c2bd41b06bd401ea28e085663533d0e63d74b +oid sha256:c73998c9f428380dd2282b451de6011469b717498ae83578cbf7aa95948910f7 size 45962473 diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index f541b6a6d5..cdd6ed7a6b 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -30efb4238327d723e17a3bda7e7c19c18f8a3b18 +ca02aa240e629920ad88a6510213cb0a153af92b diff --git a/selfdrive/ui/qt/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc index f73149cdf2..b99220c1d1 100644 --- a/selfdrive/ui/qt/offroad/experimental_mode.cc +++ b/selfdrive/ui/qt/offroad/experimental_mode.cc @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "selfdrive/ui/ui.h" diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 98d46149dd..bda64c53ab 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -285,11 +285,11 @@ ExperimentalModeButton EXPERIMENTAL MODE ON - + 실험적 모드 사용 CHILL MODE ON - + 안정적 모드 사용 @@ -875,7 +875,7 @@ location set Uninstall - 삭제 + 제거 @@ -1015,15 +1015,15 @@ location set On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. - + 이 차량은 openpilot 롱컨트롤 대신 차량의 내장 ACC로 기본 설정됩니다. openpilot 롱컨트롤을 사용하려면 이 옵션을 활성화하세요. 실험적 openpilot 롱컨트롤을 사용하는 경우 실험적 모드를 활성화 하세요. Experimental mode is currently unavailable on this car, since the car's stock ACC is used for longitudinal control. - + 차량의 기본 ACC가 롱컨트롤에 사용되기 때문에 현재 이 차량에서는 실험적 모드를 사용할수 없습니다. Enable experimental longitudinal control to allow experimental mode. - + 실험적 롱컨트롤을 사용하려면 실험적 모드를 활성화 하세요. openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: @@ -1035,15 +1035,15 @@ location set Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. - + 주행모델이 가속과 감속을 제어하도록 합니다. openpilot은 신호등과 정지표지판을 보고 멈추는 것을 포함하여 운전자가 생각하는것처럼 주행합니다. 주행 모델이 주행할 속도를 결정하므로 설정된 속도는 상한선으로만 작용합니다. 이것은 알파 기능이므로 사용에 주의해야 합니다. New Driving Visualization - + 새로운 주행 시각화 The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. - + 주행 시각화는 저속에서 도로를 향하는 광각 카메라로 전환되어 일부 회전을 더 잘 보여줍니다. 실험적 모드 로고도 우측상단에 표시됩니다. diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index bcd2b88a81..dae4591976 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -23,11 +23,13 @@ BinaryView::BinaryView(QWidget *parent) : QTableView(parent) { delegate = new BinaryItemDelegate(this); setItemDelegate(delegate); horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + verticalHeader()->setSectionsClickable(false); horizontalHeader()->hide(); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents); setFrameShape(QFrame::NoFrame); + setShowGrid(false); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); setMouseTracking(true); } @@ -46,46 +48,30 @@ void BinaryView::setSelection(const QRect &rect, QItemSelectionModel::SelectionF 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); - } + 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->setSelectionColor(style()->standardPalette().color(QPalette::Active, QPalette::Highlight)); - if (auto index = indexAt(event->pos()); index.isValid() && index.column() != 8) { + 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; - } + 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->setSelectionColor(item->bg_color); + break; } } } - QTableView::mousePressEvent(event); + event->accept(); } void BinaryView::mouseMoveEvent(QMouseEvent *event) { @@ -104,28 +90,14 @@ void BinaryView::mouseReleaseEvent(QMouseEvent *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); + 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 [sart_bit, end_bit] = std::minmax(archor_bit_idx, release_bit_idx); - emit addSignal(sart_bit, end_bit - sart_bit + 1, little_endian); + auto item = (const BinaryViewModel::Item *)anchor_index.internalPointer(); + if (item && item->sigs.size() > 0) + emit signalClicked(item->sigs.back()); } } clearSelection(); @@ -156,6 +128,17 @@ QSet BinaryView::getOverlappingSignals() const { 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) { @@ -166,19 +149,19 @@ void BinaryViewModel::setMessage(const QString &message_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 (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 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; + 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; + 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); + items[idx].sigs.push_back(sig); } ++i; } @@ -203,7 +186,7 @@ void BinaryViewModel::updateState() { 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'); + 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); @@ -250,17 +233,16 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op 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, selection_color); + } else if (!bin_view->selectionModel()->hasSelection() || !item->sigs.contains(bin_view->resize_sig)) { + painter->fillRect(option.rect, item->bg_color); } - painter->fillRect(option.rect, bg_color); // text if (index.column() == 8) { // hex column painter->setFont(hex_font); - } else if (hover) { + } else if (option.state & QStyle::State_Selected || (!bin_view->resize_sig && item->sigs.contains(bin_view->hovered_sig))) { painter->setPen(Qt::white); } painter->drawText(option.rect, Qt::AlignCenter, item->val); diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index 2d6fc5c18b..0ea111d917 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -32,6 +32,7 @@ public: 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; } + inline QModelIndex bitIndex(int bit, bool is_lb) const { return index(bit / 8, is_lb ? (7 - bit % 8) : bit % 8); } QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { return createIndex(row, column, (void *)&items[row * column_count + column]); } @@ -63,15 +64,16 @@ public: void setMessage(const QString &message_id); void highlight(const Signal *sig); QSet getOverlappingSignals() const; - inline const Signal *hoveredSignal() const { return hovered_sig; } inline void updateState() { model->updateState(); } signals: + void signalClicked(const Signal *sig); void signalHovered(const Signal *sig); void addSignal(int start_bit, int size, bool little_endian); void resizeSignal(const Signal *sig, int from, int size); private: + std::tuple getSelection(QModelIndex index); void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) override; void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; @@ -83,4 +85,5 @@ private: BinaryItemDelegate *delegate; const Signal *resize_sig = nullptr; const Signal *hovered_sig = nullptr; + friend class BinaryItemDelegate; }; diff --git a/tools/cabana/canmessages.h b/tools/cabana/canmessages.h index ff41edad54..4cb0f403a0 100644 --- a/tools/cabana/canmessages.h +++ b/tools/cabana/canmessages.h @@ -80,11 +80,5 @@ inline const QString &getColor(int i) { return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)]; } -inline QColor hoverColor(const QColor &color) { - QColor c = color.convertTo(QColor::Hsv); - c.setHsv(color.hue(), 180, 180); - return c; -} - // A global pointer referring to the unique CANMessages object extern CANMessages *can; diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 06387b3585..5a129b5f61 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -4,11 +4,9 @@ #include #include #include +#include #include -#include -#include #include -#include // ChartsWidget @@ -19,7 +17,7 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { // toolbar QToolBar *toolbar = new QToolBar(tr("Charts"), this); title_label = new QLabel(); - title_label->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred); + title_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); toolbar->addWidget(title_label); toolbar->addWidget(range_label = new QLabel()); reset_zoom_btn = toolbar->addAction("⟲"); @@ -42,21 +40,10 @@ ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { main_layout->addWidget(charts_scroll); - QObject::connect(dbc(), &DBCManager::DBCFileChanged, [this]() { removeAll(nullptr); }); - QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartsWidget::removeAll); - QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartsWidget::signalUpdated); - QObject::connect(dbc(), &DBCManager::msgRemoved, [this](uint32_t address) { - for (auto c : charts.toVector()) - if (DBCManager::parseId(c->id).second == address) removeChart(c); - }); - QObject::connect(dbc(), &DBCManager::msgUpdated, [this](uint32_t address) { - for (auto c : charts) { - if (DBCManager::parseId(c->id).second == address) c->updateTitle(); - } - }); + QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll); QObject::connect(can, &CANMessages::eventsMerged, this, &ChartsWidget::eventsMerged); QObject::connect(can, &CANMessages::updated, this, &ChartsWidget::updateState); - QObject::connect(remove_all_btn, &QAction::triggered, [this]() { removeAll(); }); + QObject::connect(remove_all_btn, &QAction::triggered, this, &ChartsWidget::removeAll); QObject::connect(reset_zoom_btn, &QAction::triggered, this, &ChartsWidget::zoomReset); QObject::connect(dock_btn, &QAction::triggered, [this]() { emit dock(!docking); @@ -81,8 +68,8 @@ void ChartsWidget::zoomIn(double min, double max) { zoomed_range = {min, max}; is_zoomed = zoomed_range != display_range; updateToolBar(); - emit rangeChanged(min, max, is_zoomed); updateState(); + emit rangeChanged(min, max, is_zoomed); } void ChartsWidget::zoomReset() { @@ -108,14 +95,14 @@ void ChartsWidget::updateState() { if (prev_range != display_range) { QFutureSynchronizer future_synchronizer; for (auto c : charts) - future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, display_range)); + future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::setEventsRange, display_range)); } } const auto &range = is_zoomed ? zoomed_range : display_range; for (auto c : charts) { - c->setRange(range.first, range.second); - c->updateLineMarker(current_sec); + c->setDisplayRange(range.first, range.second); + c->scene()->invalidate({}, QGraphicsScene::ForegroundLayer); } } @@ -128,49 +115,45 @@ void ChartsWidget::updateToolBar() { dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); } -void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show) { - auto it = std::find_if(charts.begin(), charts.end(), [=](auto c) { return c->id == id && c->signal == sig; }); - if (it != charts.end()) { - if (!show) removeChart((*it)); - } else if (show) { - auto chart = new ChartView(id, sig, this); - chart->updateSeries(display_range); - QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); }); - QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn); - QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset); - charts_layout->insertWidget(0, chart); - charts.push_back(chart); - emit chartOpened(chart->id, chart->signal); +ChartView *ChartsWidget::findChart(const QString &id, const Signal *sig) { + for (auto c : charts) + if (c->hasSeries(id, sig)) return c; + return nullptr; +} + +void ChartsWidget::showChart(const QString &id, const Signal *sig, bool show, bool merge) { + if (!show) { + if (ChartView *chart = findChart(id, sig)) { + chart->removeSeries(id, sig); + } + } else { + ChartView *chart = merge && charts.size() > 0 ? charts.back() : nullptr; + if (!chart) { + chart = new ChartView(this); + chart->setEventsRange(display_range); + QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); }); + QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn); + QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset); + QObject::connect(chart, &ChartView::seriesRemoved, this, &ChartsWidget::chartClosed); + charts_layout->insertWidget(0, chart); + charts.push_back(chart); + } + chart->addSeries(id, sig); + emit chartOpened(id, sig); updateState(); } updateToolBar(); } -bool ChartsWidget::isChartOpened(const QString &id, const Signal *sig) { - auto it = std::find_if(charts.begin(), charts.end(), [=](auto c) { return c->id == id && c->signal == sig; }); - return it != charts.end(); -} - void ChartsWidget::removeChart(ChartView *chart) { charts.removeOne(chart); chart->deleteLater(); updateToolBar(); - emit chartClosed(chart->id, chart->signal); } -void ChartsWidget::removeAll(const Signal *sig) { +void ChartsWidget::removeAll() { for (auto c : charts.toVector()) - if (!sig || c->signal == sig) removeChart(c); -} - -void ChartsWidget::signalUpdated(const Signal *sig) { - for (auto c : charts) { - if (c->signal == sig) { - c->updateTitle(); - c->updateSeries(display_range); - c->setRange(display_range.first, display_range.second, true); - } - } + removeChart(c); } bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { @@ -183,21 +166,18 @@ bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { // ChartView -ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent) - : id(id), signal(sig), QChartView(nullptr, parent) { - QLineSeries *series = new QLineSeries(); +ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { QChart *chart = new QChart(); chart->setBackgroundRoundness(0); - chart->addSeries(series); - chart->createDefaultAxes(); - chart->legend()->hide(); + axis_x = new QValueAxis(this); + axis_y = new QValueAxis(this); + chart->addAxis(axis_x, Qt::AlignBottom); + chart->addAxis(axis_y, Qt::AlignLeft); + chart->legend()->setShowToolTips(true); chart->layout()->setContentsMargins(0, 0, 0, 0); // top margin for title chart->setMargins({0, 11, 0, 0}); - line_marker = new QGraphicsLineItem(chart); - line_marker->setZValue(chart->zValue() + 10); - track_line = new QGraphicsLineItem(chart); track_line->setPen(QPen(Qt::darkGray, 1, Qt::DashLine)); track_ellipse = new QGraphicsEllipseItem(chart); @@ -213,45 +193,125 @@ ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent) remove_btn->setToolTip(tr("Remove Chart")); close_btn_proxy = new QGraphicsProxyWidget(chart); close_btn_proxy->setWidget(remove_btn); + close_btn_proxy->setZValue(chart->zValue() + 11); setChart(chart); setRenderHint(QPainter::Antialiasing); setRubberBand(QChartView::HorizontalRubberBand); updateFromSettings(); - updateTitle(); QTimer *timer = new QTimer(this); timer->setInterval(100); timer->setSingleShot(true); timer->callOnTimeout(this, &ChartView::adjustChartMargins); + QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartView::signalRemoved); + QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartView::signalUpdated); + QObject::connect(dbc(), &DBCManager::msgRemoved, this, &ChartView::msgRemoved); + QObject::connect(dbc(), &DBCManager::msgUpdated, this, &ChartView::msgUpdated); QObject::connect(&settings, &Settings::changed, this, &ChartView::updateFromSettings); - QObject::connect(remove_btn, &QToolButton::clicked, [=]() { emit remove(id, sig); }); + QObject::connect(remove_btn, &QToolButton::clicked, this, &ChartView::remove); QObject::connect(chart, &QChart::plotAreaChanged, [=](const QRectF &plotArea) { // use a singleshot timer to avoid recursion call. timer->start(); }); } +ChartView::~ChartView() { + for (auto &s : sigs) + emit seriesRemoved(s.msg_id, s.sig); +} + +void ChartView::addSeries(const QString &msg_id, const Signal *sig) { + QLineSeries *series = new QLineSeries(this); + chart()->addSeries(series); + series->attachAxis(axis_x); + series->attachAxis(axis_y); + auto [source, address] = DBCManager::parseId(msg_id); + sigs.push_back({.msg_id = msg_id, .address = address, .source = source, .sig = sig, .series = series}); + updateTitle(); + updateSeries(sig); +} + +void ChartView::removeSeries(const QString &msg_id, const Signal *sig) { + auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s.msg_id == msg_id && s.sig == sig; }); + if (it != sigs.end()) { + it = removeSeries(it); + } +} + +bool ChartView::hasSeries(const QString &msg_id, const Signal *sig) const { + auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s.msg_id == msg_id && s.sig == sig; }); + return it != sigs.end(); +} + +QList::iterator ChartView::removeSeries(const QList::iterator &it) { + chart()->removeSeries(it->series); + it->series->deleteLater(); + emit seriesRemoved(it->msg_id, it->sig); + + auto ret = sigs.erase(it); + if (!sigs.isEmpty()) { + updateAxisY(); + } else { + emit remove(); + } + return ret; +} + +void ChartView::signalUpdated(const Signal *sig) { + auto it = std::find_if(sigs.begin(), sigs.end(), [=](auto &s) { return s.sig == sig; }); + if (it != sigs.end()) { + updateTitle(); + // TODO: don't update series if only name changed. + updateSeries(sig); + } +} + +void ChartView::signalRemoved(const Signal *sig) { + for (auto it = sigs.begin(); it != sigs.end(); /**/) { + it = (it->sig == sig) ? removeSeries(it) : ++it; + } +} + +void ChartView::msgUpdated(uint32_t address) { + auto it = std::find_if(sigs.begin(), sigs.end(), [=](auto &s) { return s.address == address; }); + if (it != sigs.end()) + updateTitle(); +} + +void ChartView::msgRemoved(uint32_t address) { + for (auto it = sigs.begin(); it != sigs.end(); /**/) { + it = (it->address == address) ? removeSeries(it) : ++it; + } +} + void ChartView::resizeEvent(QResizeEvent *event) { QChartView::resizeEvent(event); close_btn_proxy->setPos(event->size().width() - close_btn_proxy->size().width() - 11, 8); } void ChartView::updateTitle() { - chart()->setTitle(tr("%1 %2 %3").arg(dbc()->msg(id)->name).arg(id).arg(signal->name.c_str())); + for (auto &s : sigs) { + s.series->setName(QString("%1 %2 %3").arg(s.sig->name.c_str()).arg(msgName(s.msg_id)).arg(s.msg_id)); + } } void ChartView::updateFromSettings() { setFixedHeight(settings.chart_height); chart()->setTheme(settings.chart_theme == 0 ? QChart::ChartThemeLight : QChart::QChart::ChartThemeDark); auto color = chart()->titleBrush().color(); - line_marker->setPen(QPen(color, 2)); } -void ChartView::setRange(double min, double max, bool force_update) { - auto axis_x = dynamic_cast(chart()->axisX()); - if (force_update || (min != axis_x->min() || max != axis_x->max())) { +void ChartView::setEventsRange(const std::pair &range) { + if (range != events_range) { + events_range = range; + updateSeries(); + } +} + +void ChartView::setDisplayRange(double min, double max) { + if (min != axis_x->min() || max != axis_x->max()) { axis_x->setRange(min, max); updateAxisY(); } @@ -263,61 +323,75 @@ void ChartView::adjustChartMargins() { if (chart()->plotArea().left() != aligned_pos) { const float left_margin = chart()->margins().left() + aligned_pos - chart()->plotArea().left(); chart()->setMargins(QMargins(left_margin, 11, 0, 0)); - updateLineMarker(can->currentSec()); } } -void ChartView::updateLineMarker(double current_sec) { - auto axis_x = dynamic_cast(chart()->axisX()); - int x = chart()->plotArea().left() + - chart()->plotArea().width() * (current_sec - axis_x->min()) / (axis_x->max() - axis_x->min()); - if (int(line_marker->line().x1()) != x) { - line_marker->setLine(x, chart()->plotArea().top() - chart()->margins().top() + 3, x, height()); - } -} - -void ChartView::updateSeries(const std::pair range) { +void ChartView::updateSeries(const Signal *sig) { auto events = can->events(); if (!events) return; - vals.clear(); - vals.reserve((range.second - range.first) * 1000); // [n]seconds * 1000hz - auto [bus, address] = DBCManager::parseId(id); - double route_start_time = can->routeStartTime(); - Event begin_event(cereal::Event::Which::INIT_DATA, (route_start_time + range.first) * 1e9); - auto begin = std::lower_bound(events->begin(), events->end(), &begin_event, Event::lessThan()); - double end_ns = (route_start_time + range.second) * 1e9; - for (auto it = begin; it != events->end() && (*it)->mono_time <= end_ns; ++it) { - if ((*it)->which == cereal::Event::Which::CAN) { - for (const auto &c : (*it)->event.getCan()) { - if (bus == c.getSrc() && address == c.getAddress()) { - auto dat = c.getDat(); - double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *signal); - double ts = ((*it)->mono_time / (double)1e9) - route_start_time; // seconds - vals.push_back({ts, value}); + for (int i = 0; i < sigs.size(); ++i) { + if (auto &s = sigs[i]; !sig || s.sig == sig) { + s.vals.clear(); + s.vals.reserve((events_range.second - events_range.first) * 1000); // [n]seconds * 1000hz + s.min_y = std::numeric_limits::max(); + s.max_y = std::numeric_limits::lowest(); + + double route_start_time = can->routeStartTime(); + Event begin_event(cereal::Event::Which::INIT_DATA, (route_start_time + events_range.first) * 1e9); + auto begin = std::lower_bound(events->begin(), events->end(), &begin_event, Event::lessThan()); + double end_ns = (route_start_time + events_range.second) * 1e9; + + for (auto it = begin; it != events->end() && (*it)->mono_time <= end_ns; ++it) { + if ((*it)->which == cereal::Event::Which::CAN) { + for (const auto &c : (*it)->event.getCan()) { + if (s.source == c.getSrc() && s.address == c.getAddress()) { + auto dat = c.getDat(); + double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *s.sig); + double ts = ((*it)->mono_time / (double)1e9) - route_start_time; // seconds + s.vals.push_back({ts, value}); + + if (value < s.min_y) s.min_y = value; + if (value > s.max_y) s.max_y = value; + } + } } } + + QLineSeries *series = (QLineSeries *)chart()->series()[i]; + series->replace(s.vals); } } - QLineSeries *series = (QLineSeries *)chart()->series()[0]; - series->replace(vals); + updateAxisY(); } // auto zoom on yaxis void ChartView::updateAxisY() { - const auto axis_x = dynamic_cast(chart()->axisX()); - const auto axis_y = dynamic_cast(chart()->axisY()); - auto begin = std::lower_bound(vals.begin(), vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); - if (begin == vals.end()) - return; - - auto end = std::upper_bound(vals.begin(), vals.end(), axis_x->max(), [](double x, auto &p) { return x < p.x(); }); - const auto [min, max] = std::minmax_element(begin, end, [](auto &p1, auto &p2) { return p1.y() < p2.y(); }); - if (max->y() == min->y()) { - axis_y->setRange(min->y() - 1, max->y() + 1); + double min_y = std::numeric_limits::max(); + double max_y = std::numeric_limits::lowest(); + if (events_range == std::pair{axis_x->min(), axis_x->max()}) { + for (auto &s : sigs) { + if (s.min_y < min_y) min_y = s.min_y; + if (s.max_y > max_y) max_y = s.max_y; + } + } else { + for (auto &s : sigs) { + auto begin = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); + if (begin == s.vals.end()) + return; + + auto end = std::upper_bound(s.vals.begin(), s.vals.end(), axis_x->max(), [](double x, auto &p) { return x < p.x(); }); + const auto [min, max] = std::minmax_element(begin, end, [](auto &p1, auto &p2) { return p1.y() < p2.y(); }); + if (min->y() < min_y) min_y = min->y(); + if (max->y() > max_y) max_y = max->y(); + } + } + + if (max_y == min_y) { + axis_y->setRange(min_y - 1, max_y + 1); } else { - double range = max->y() - min->y(); - axis_y->setRange(min->y() - range * 0.05, max->y() + range * 0.05); + double range = max_y - min_y; + axis_y->setRange(min_y - range * 0.05, max_y + range * 0.05); axis_y->applyNiceNumbers(); } } @@ -333,7 +407,6 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { rubber->hide(); QRectF rect = rubber->geometry().normalized(); rect.translate(-chart()->plotArea().topLeft()); - const auto axis_x = dynamic_cast(chart()->axisX()); double min = axis_x->min() + (rect.left() / chart()->plotArea().width()) * (axis_x->max() - axis_x->min()); double max = axis_x->min() + (rect.right() / chart()->plotArea().width()) * (axis_x->max() - axis_x->min()); if (rubber->width() <= 0) { @@ -343,7 +416,6 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { // zoom in if selected range is greater than 0.5s emit zoomIn(min, max); } - viewport()->update(); event->accept(); } else if (event->button() == Qt::RightButton) { emit zoomReset(); @@ -351,33 +423,52 @@ void ChartView::mouseReleaseEvent(QMouseEvent *event) { } else { QGraphicsView::mouseReleaseEvent(event); } - setViewportUpdateMode(QGraphicsView::MinimalViewportUpdate); } void ChartView::mouseMoveEvent(QMouseEvent *ev) { auto rubber = findChild(); bool is_zooming = rubber && rubber->isVisible(); - if (!is_zooming) { - const auto plot_area = chart()->plotArea(); - auto axis_x = dynamic_cast(chart()->axisX()); + const auto plot_area = chart()->plotArea(); + + if (!is_zooming && plot_area.contains(ev->pos())) { double x = std::clamp((double)ev->pos().x(), plot_area.left(), plot_area.right() - 1); double sec = axis_x->min() + (axis_x->max() - axis_x->min()) * (x - plot_area.left()) / plot_area.width(); - auto value = std::upper_bound(vals.begin(), vals.end(), sec, [](double x, auto &p) { return x < p.x(); }); - if (value != vals.end()) { - QPointF pos = chart()->mapToPosition((*value)); + QStringList text_list; + QPointF pos = plot_area.bottomRight(); + double tm = 0.0; + + for (auto &s : sigs) { + auto value = std::upper_bound(s.vals.begin(), s.vals.end(), sec, [](double x, auto &p) { return x < p.x(); }); + if (value != s.vals.end()) { + text_list.push_back(QString(" %1 : %2 ").arg(sigs.size() > 1 ? s.sig->name.c_str() : "Value").arg(value->y())); + tm = value->x(); + auto y_pos = chart()->mapToPosition(*value); + if (y_pos.y() < pos.y()) pos = y_pos; + } + } + + if (!text_list.isEmpty()) { + value_text->setHtml("
 Time: " + + QString::number(tm, 'f', 3) + " 
" + text_list.join("
") + "
"); track_line->setLine(pos.x(), plot_area.top(), pos.x(), plot_area.bottom()); - track_ellipse->setRect(pos.x() - 5, pos.y() - 5, 10, 10); - value_text->setHtml(tr("
%1, %2)
") - .arg(value->x(), 0, 'f', 3).arg(value->y())); int text_x = pos.x() + 8; - if ((text_x + value_text->boundingRect().width()) > plot_area.right()) { - text_x = pos.x() - value_text->boundingRect().width() - 8; + QRectF text_rect = value_text->boundingRect(); + if ((text_x + text_rect.width()) > plot_area.right()) { + text_x = pos.x() - text_rect.width() - 8; } - value_text->setPos(text_x, pos.y() - 10); + value_text->setPos(text_x, pos.y() - text_rect.height() / 2); + track_ellipse->setRect(pos.x() - 5, pos.y() - 5, 10, 10); } - item_group->setVisible(value != vals.end()); + item_group->setVisible(!text_list.isEmpty()); } else { - setViewportUpdateMode(QGraphicsView::FullViewportUpdate); + item_group->setVisible(false); } QChartView::mouseMoveEvent(ev); } + +void ChartView::drawForeground(QPainter *painter, const QRectF &rect) { + qreal x = chart()->plotArea().left() + + chart()->plotArea().width() * (can->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min()); + painter->setPen(QPen(chart()->titleBrush().color(), 2)); + painter->drawLine(QPointF{x, chart()->plotArea().top() - 2}, QPointF{x, chart()->plotArea().bottom() + 2}); +} diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index 20c673a757..8f0af95b1a 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include "tools/cabana/canmessages.h" #include "tools/cabana/dbcmanager.h" @@ -18,35 +20,59 @@ class ChartView : public QChartView { Q_OBJECT public: - ChartView(const QString &id, const Signal *sig, QWidget *parent = nullptr); - void updateSeries(const std::pair range); - void setRange(double min, double max, bool force_update = false); - void updateLineMarker(double current_sec); - void updateFromSettings(); - void updateTitle(); + ChartView(QWidget *parent = nullptr); + ~ChartView(); + void addSeries(const QString &msg_id, const Signal *sig); + void removeSeries(const QString &msg_id, const Signal *sig); + bool hasSeries(const QString &msg_id, const Signal *sig) const; + void updateSeries(const Signal *sig = nullptr); + void setEventsRange(const std::pair &range); + void setDisplayRange(double min, double max); - QString id; - const Signal *signal; + struct SigItem { + QString msg_id; + uint8_t source = 0; + uint32_t address = 0; + const Signal *sig = nullptr; + QLineSeries *series = nullptr; + double min_y = 0; + double max_y = 0; + QVector vals; + }; signals: + void seriesRemoved(const QString &id, const Signal *sig); void zoomIn(double min, double max); void zoomReset(); - void remove(const QString &msg_id, const Signal *sig); + void remove(); + +private slots: + void msgRemoved(uint32_t address); + void msgUpdated(uint32_t address); + void signalUpdated(const Signal *sig); + void signalRemoved(const Signal *sig); private: + QList::iterator removeSeries(const QList::iterator &it); void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *ev) override; void leaveEvent(QEvent *event) override; void resizeEvent(QResizeEvent *event) override; void adjustChartMargins(); void updateAxisY(); + void updateTitle(); + void updateFromSettings(); + void drawForeground(QPainter *painter, const QRectF &rect) override; + QValueAxis *axis_x; + QValueAxis *axis_y; QGraphicsItemGroup *item_group; - QGraphicsLineItem *line_marker, *track_line; + QGraphicsLineItem *track_line; QGraphicsEllipseItem *track_ellipse; QGraphicsTextItem *value_text; QGraphicsProxyWidget *close_btn_proxy; - QVector vals; + std::pair events_range = {0, 0}; + QList sigs; }; class ChartsWidget : public QWidget { @@ -54,9 +80,9 @@ class ChartsWidget : public QWidget { public: ChartsWidget(QWidget *parent = nullptr); - void showChart(const QString &id, const Signal *sig, bool show); + void showChart(const QString &id, const Signal *sig, bool show, bool merge); void removeChart(ChartView *chart); - bool isChartOpened(const QString &id, const Signal *sig); + inline bool isChartOpened(const QString &id, const Signal *sig) { return findChart(id, sig) != nullptr; } signals: void dock(bool floating); @@ -69,10 +95,10 @@ private: void updateState(); void zoomIn(double min, double max); void zoomReset(); - void signalUpdated(const Signal *sig); void updateToolBar(); - void removeAll(const Signal *sig = nullptr); + void removeAll(); bool eventFilter(QObject *obj, QEvent *event) override; + ChartView *findChart(const QString &id, const Signal *sig); QLabel *title_label; QLabel *range_label; diff --git a/tools/cabana/dbcmanager.cc b/tools/cabana/dbcmanager.cc index 18f103d34c..e7d3ead9c6 100644 --- a/tools/cabana/dbcmanager.cc +++ b/tools/cabana/dbcmanager.cc @@ -107,6 +107,15 @@ DBCManager *dbc() { return &dbc_manager; } +// DBCMsg + +std::vector DBCMsg::getSignals() const { + std::vector ret; + for (auto &[name, sig] : sigs) ret.push_back(&sig); + std::sort(ret.begin(), ret.end(), [](auto l, auto r) { return l->start_bit < r->start_bit; }); + return ret; +} + // helper functions static QVector BIG_ENDIAN_START_BITS = []() { diff --git a/tools/cabana/dbcmanager.h b/tools/cabana/dbcmanager.h index b1d2082969..4e0bc91069 100644 --- a/tools/cabana/dbcmanager.h +++ b/tools/cabana/dbcmanager.h @@ -9,6 +9,7 @@ struct DBCMsg { QString name; uint32_t size; std::map sigs; + std::vector getSignals() const; }; class DBCManager : public QObject { diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 192d1fd66c..52ae530a56 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -17,6 +17,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) { undo_stack = new QUndoStack(this); + setMinimumWidth(500); QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); main_layout->setSpacing(0); @@ -90,6 +91,7 @@ DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(chart tab_widget->addTab(history_log, "Logs"); main_layout->addWidget(tab_widget); + QObject::connect(binary_view, &BinaryView::signalClicked, this, &DetailWidget::showForm); QObject::connect(binary_view, &BinaryView::resizeSignal, this, &DetailWidget::resizeSignal); QObject::connect(binary_view, &BinaryView::addSignal, this, &DetailWidget::addSignal); QObject::connect(tab_widget, &QTabWidget::currentChanged, [this]() { updateState(); }); @@ -149,7 +151,7 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { QStringList warnings; const DBCMsg *msg = dbc()->msg(msg_id); if (msg) { - for (auto &[name, sig] : msg->sigs) { + for (auto sig : msg->getSignals()) { SignalEdit *form = i < signal_list.size() ? signal_list[i] : nullptr; if (!form) { form = new SignalEdit(i); @@ -162,8 +164,8 @@ void DetailWidget::dbcMsgChanged(int show_form_idx) { signals_layout->addWidget(form); signal_list.push_back(form); } - form->setSignal(msg_id, &sig); - form->setChartOpened(charts->isChartOpened(msg_id, &sig)); + form->setSignal(msg_id, sig); + form->setChartOpened(charts->isChartOpened(msg_id, sig)); ++i; } if (msg->size != can->lastMessage(msg_id).dat.size()) @@ -197,9 +199,15 @@ void DetailWidget::updateState(const QHash * msgs) { void DetailWidget::showFormClicked() { auto s = qobject_cast(sender()); + showForm(s->sig); +} + +void DetailWidget::showForm(const Signal *sig) { setUpdatesEnabled(false); - for (auto f : signal_list) - f->updateForm(f == s && !f->isFormVisible()); + for (auto f : signal_list) { + f->updateForm(f->sig == sig && !f->isFormVisible()); + if (f->sig == sig) scroll->ensureWidgetVisible(f); + } setUpdatesEnabled(true); } @@ -235,7 +243,7 @@ void DetailWidget::addSignal(int start_bit, int size, bool little_endian) { } } } - Signal sig = {.is_little_endian = little_endian}; + Signal sig = {.is_little_endian = little_endian, .factor = 1}; for (int i = 1; /**/; ++i) { sig.name = "NEW_SIGNAL_" + std::to_string(i); if (msg->sigs.count(sig.name.c_str()) == 0) break; diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 4346d1c5d5..41fa0edd41 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -28,6 +28,7 @@ public: QUndoStack *undo_stack = nullptr; private: + void showForm(const Signal *sig); void showFormClicked(); void updateChartState(const QString &id, const Signal *sig, bool opened); void showTabBarContextMenu(const QPoint &pt); diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 7781ab3b75..324323ac44 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -55,6 +55,9 @@ MainWindow::MainWindow() : QMainWindow() { charts_widget = new ChartsWidget(this); detail_widget = new DetailWidget(charts_widget, this); splitter->addWidget(detail_widget); + if (!settings.splitter_state.isEmpty()) { + splitter->restoreState(settings.splitter_state); + } main_layout->addWidget(splitter); // right widgets @@ -247,6 +250,7 @@ void MainWindow::closeEvent(QCloseEvent *event) { if (floating_window) floating_window->deleteLater(); + settings.splitter_state = splitter->saveState(); settings.save(); QWidget::closeEvent(event); } diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc index b3a4ed4872..be806aa705 100644 --- a/tools/cabana/settings.cc +++ b/tools/cabana/settings.cc @@ -21,6 +21,7 @@ void Settings::save() { s.setValue("chart_theme", chart_theme); s.setValue("max_chart_x_range", max_chart_x_range); s.setValue("last_dir", last_dir); + s.setValue("splitter_state", splitter_state); } void Settings::load() { @@ -32,6 +33,7 @@ void Settings::load() { chart_theme = s.value("chart_theme", 0).toInt(); max_chart_x_range = s.value("max_chart_x_range", 3 * 60).toInt(); last_dir = s.value("last_dir", QDir::homePath()).toString(); + splitter_state = s.value("splitter_state").toByteArray(); } // SettingsDlg diff --git a/tools/cabana/settings.h b/tools/cabana/settings.h index e08d0ae55e..624a1ce33d 100644 --- a/tools/cabana/settings.h +++ b/tools/cabana/settings.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -19,6 +20,7 @@ public: int chart_theme = 0; int max_chart_x_range = 3 * 60; // 3 minutes QString last_dir; + QByteArray splitter_state; signals: void changed(); diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index eb22b78d5a..93b7aa88f7 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -116,11 +117,18 @@ SignalEdit::SignalEdit(int index, QWidget *parent) : form_idx(index), QWidget(pa hline->setFrameShadow(QFrame::Sunken); main_layout->addWidget(hline); + save_timer = new QTimer(this); + save_timer->setInterval(300); + save_timer->setSingleShot(true); + save_timer->callOnTimeout(this, &SignalEdit::saveSignal); + QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked); - QObject::connect(plot_btn, &QToolButton::clicked, [this](bool checked) { emit showChart(msg_id, sig, checked); }); + QObject::connect(plot_btn, &QToolButton::clicked, [this](bool checked) { + emit showChart(msg_id, sig, checked, QGuiApplication::keyboardModifiers() & Qt::ShiftModifier); + }); QObject::connect(seek_btn, &QToolButton::clicked, [this]() { SignalFindDlg(msg_id, sig, this).exec(); }); QObject::connect(remove_btn, &QToolButton::clicked, [this]() { emit remove(sig); }); - QObject::connect(form, &SignalForm::changed, this, &SignalEdit::saveSignal); + QObject::connect(form, &SignalForm::changed, [this]() { save_timer->start(); }); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); } @@ -167,14 +175,16 @@ void SignalEdit::saveSignal() { } void SignalEdit::setChartOpened(bool opened) { - plot_btn->setToolTip(opened ? tr("Close Plot") : tr("Show Plot")); + plot_btn->setToolTip(opened ? tr("Close Plot") : tr("Show Plot\nSHIFT click to add to previous opened chart")); plot_btn->setChecked(opened); } void SignalEdit::updateForm(bool visible) { if (visible && sig) { form->changed_by_user = false; - form->name->setText(sig->name.c_str()); + if (form->name->text() != sig->name.c_str()) { + form->name->setText(sig->name.c_str()); + } form->endianness->setCurrentIndex(sig->is_little_endian ? 0 : 1); form->sign->setCurrentIndex(sig->is_signed ? 0 : 1); form->factor->setText(QString::number(sig->factor)); @@ -189,9 +199,8 @@ void SignalEdit::updateForm(bool visible) { } void SignalEdit::signalHovered(const Signal *s) { - auto bg_color = sig == s ? hoverColor(getColor(form_idx)) : QColor(getColor(form_idx)); auto color = sig == s ? "white" : "black"; - color_label->setStyleSheet(QString("color:%1; background-color:%2").arg(color).arg(bg_color.name())); + color_label->setStyleSheet(QString("color:%1; background-color:%2").arg(color).arg(getColor(form_idx))); } void SignalEdit::enterEvent(QEvent *event) { diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index da0b9758c7..f035797e72 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "selfdrive/ui/qt/widgets/controls.h" @@ -41,7 +42,7 @@ public: signals: void highlight(const Signal *sig); - void showChart(const QString &name, const Signal *sig, bool show); + void showChart(const QString &name, const Signal *sig, bool show, bool merge); void remove(const Signal *sig); void save(const Signal *sig, const Signal &new_sig); void showFormClicked(); @@ -57,6 +58,7 @@ protected: QLabel *icon; int form_idx = 0; QToolButton *plot_btn; + QTimer *save_timer; }; class SignalFindDlg : public QDialog {