cabana: string helpers in utils return std::string (#38720)

This commit is contained in:
Trey Moen
2026-08-28 07:25:11 -07:00
committed by GitHub
parent 7cc48b5bc9
commit 9b9e3ea604
11 changed files with 159 additions and 65 deletions
+2 -1
View File
@@ -100,7 +100,7 @@ cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, "assets/assets.o"]
cabana_srcs = ['mainwin.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc',
'routesdialog.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc',
'utils/export.cc', 'utils/util.cc', 'utils/elidedlabel.cc',
'utils/export.cc', 'utils/util.cc', 'utils/strings.cc', 'utils/elidedlabel.cc',
'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc',
'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'panda.cc',
'cameraview.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc', 'tools/routeinfo.cc']
@@ -119,6 +119,7 @@ if GetOption('extras'):
dbc_core_test_env.Object('tests/dbc_core_model', 'dbc/dbc.cc'),
dbc_core_test_env.Object('tests/dbc_core_file', 'dbc/dbcfile.cc'),
dbc_core_test_env.Object('tests/dbc_core_manager', 'dbc/dbcmanager.cc'),
dbc_core_test_env.Object('tests/dbc_core_strings', 'utils/strings.cc'),
]
dbc_core_test_env.Program('tests/test_dbc_core', dbc_core_test_objects)
+1 -1
View File
@@ -385,7 +385,7 @@ QVariant BinaryViewModel::headerData(int section, Qt::Orientation orientation, i
QVariant BinaryViewModel::data(const QModelIndex &index, int role) const {
auto item = (const BinaryViewModel::Item *)index.internalPointer();
return role == Qt::ToolTipRole && item && !item->sigs.empty() ? signalToolTip(item->sigs.back()) : QVariant();
return role == Qt::ToolTipRole && item && !item->sigs.empty() ? QString::fromStdString(utils::signalToolTip(item->sigs.back())) : QVariant();
}
// BinaryItemDelegate
+1 -1
View File
@@ -245,7 +245,7 @@ void ChartsWidget::setIsDocked(bool docked) {
void ChartsWidget::updateToolBar() {
title_label->setText(tr("Charts: %1").arg(charts.size()));
columns_action->setText(tr("Columns: %1").arg(column_count));
range_lb->setText(utils::formatSeconds(max_chart_range));
range_lb->setText(QString::fromStdString(utils::formatSeconds(max_chart_range)));
bool is_zoomed = can->timeRange().has_value();
range_lb_action->setVisible(!is_zoomed);
+3 -3
View File
@@ -195,7 +195,7 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const {
switch (index.column()) {
case Column::NAME: return item.name;
case Column::SOURCE: return item.id.source != INVALID_SOURCE ? QString::number(item.id.source) : NA;
case Column::ADDRESS: return toHexString(item.id.address);
case Column::ADDRESS: return QString::fromStdString(utils::toHexString(item.id.address));
case Column::NODE: return item.node;
case Column::FREQ: return item.id.source != INVALID_SOURCE ? getFreq(can->lastMessage(item.id).freq) : NA;
case Column::COUNT: return item.id.source != INVALID_SOURCE ? QString::number(can->lastMessage(item.id).count) : NA;
@@ -288,7 +288,7 @@ bool MessageListModel::match(const MessageListModel::Item &item) {
match = parseRange(txt, item.id.source);
break;
case Column::ADDRESS:
match = toHexString(item.id.address).contains(txt, Qt::CaseInsensitive);
match = QString::fromStdString(utils::toHexString(item.id.address)).contains(txt, Qt::CaseInsensitive);
match = match || parseRange(txt, item.id.address, 16);
break;
case Column::NODE:
@@ -301,7 +301,7 @@ bool MessageListModel::match(const MessageListModel::Item &item) {
match = parseRange(txt, data.count);
break;
case Column::DATA:
match = utils::toHex(data.dat).contains(txt, Qt::CaseInsensitive);
match = QString::fromStdString(utils::toHex(data.dat)).contains(txt, Qt::CaseInsensitive);
break;
}
}
+2 -2
View File
@@ -153,7 +153,7 @@ QVariant SignalModel::data(const QModelIndex &index, int role) const {
if (item->type == Item::Endian) return item->sig->is_little_endian ? Qt::Checked : Qt::Unchecked;
if (item->type == Item::Signed) return item->sig->is_signed ? Qt::Checked : Qt::Unchecked;
} else if (role == Qt::ToolTipRole && item->type == Item::Sig) {
return (index.column() == 0) ? signalToolTip(item->sig) : QString();
return (index.column() == 0) ? QString::fromStdString(utils::signalToolTip(item->sig)) : QString();
}
}
return {};
@@ -570,7 +570,7 @@ void SignalView::signalHovered(const cabana::Signal *sig) {
void SignalView::updateToolBar() {
signal_count_lb->setText(tr("Signals: %1").arg(model->rowCount()));
sparkline_label->setText(utils::formatSeconds(settings.sparkline_range));
sparkline_label->setText(QString::fromStdString(utils::formatSeconds(settings.sparkline_range)));
}
void SignalView::setSparklineRange(int value) {
@@ -1,10 +1,13 @@
#include <cstdlib>
#include <ctime>
#include <filesystem>
#include <sstream>
#include "common/tests/native_test.h"
#include "tools/cabana/dbc/dbcfile.h"
#include "tools/cabana/dbc/dbcmanager.h"
#include "tools/cabana/utils/strings.h"
const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2";
@@ -185,7 +188,62 @@ void test_dbc_manager() {
REQUIRE(manager.msg({.source = 0, .address = 160})->sig("speed") != nullptr);
}
void test_format_seconds() {
REQUIRE(utils::formatSeconds(0) == "00:00");
REQUIRE(utils::formatSeconds(59.4) == "00:59");
REQUIRE(utils::formatSeconds(-1) == "00:00");
REQUIRE(utils::formatSeconds(61.234, true) == "01:01.234");
REQUIRE(utils::formatSeconds(3599.9) == "59:59");
REQUIRE(utils::formatSeconds(3601) == "01:00:01");
REQUIRE(utils::formatSeconds(3601.5, true) == "01:00:01.500");
const char *tz = getenv("TZ");
const bool had_tz = tz != nullptr;
const std::string saved_tz = had_tz ? tz : "";
setenv("TZ", "UTC", 1);
tzset();
REQUIRE(utils::formatSeconds(0, false, true) == "1970-01-01 00:00:00");
REQUIRE(utils::formatSeconds(1700000000.123, true, true) == "2023-11-14 22:13:20.123");
if (had_tz) {
setenv("TZ", saved_tz.c_str(), 1);
} else {
unsetenv("TZ");
}
tzset();
}
void test_to_hex() {
REQUIRE(utils::toHex({}) == "");
REQUIRE(utils::toHex({0x00, 0x0f, 0xab, 0xff}) == "000FABFF");
REQUIRE(utils::toHex({0x01, 0x02, 0x03}, ' ') == "01 02 03");
REQUIRE(utils::toHexString(0) == "0x00");
REQUIRE(utils::toHexString(0xf) == "0x0F");
REQUIRE(utils::toHexString(0x1ab) == "0x1AB");
REQUIRE(utils::toHexString(0x1fffffff) == "0x1FFFFFFF");
}
void test_signal_tooltip() {
cabana::Signal sig{};
sig.name = "speed";
sig.start_bit = 3;
sig.size = 12;
sig.msb = 14;
sig.lsb = 3;
sig.is_little_endian = true;
sig.is_signed = false;
REQUIRE(utils::signalToolTip(&sig) == R"(
speed<br /><span font-size:small">
Start Bit: 3 Size: 12<br />
MSB: 14 LSB: 3<br />
Little Endian: Y Signed: N</span>
)");
}
void test_cabana_core() {
test_format_seconds();
test_to_hex();
test_signal_tooltip();
test_generate_dbc();
test_comment_order();
test_preserve_original_header();
+57
View File
@@ -0,0 +1,57 @@
#include "tools/cabana/utils/strings.h"
#include <algorithm>
#include <cmath>
#include <ctime>
#include <sstream>
#include "tools/cabana/dbc/dbc.h"
namespace utils {
std::string formatSeconds(double sec, bool include_milliseconds, bool absolute_time) {
char out[80] = {};
if (absolute_time) {
const auto ms_total = static_cast<int64_t>(std::llround(sec * 1000.0));
const std::time_t secs = static_cast<std::time_t>(ms_total / 1000);
int millis = static_cast<int>(ms_total % 1000);
if (millis < 0) millis = -millis;
std::tm tm{};
localtime_r(&secs, &tm);
char buf[64] = {};
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
if (!include_milliseconds) return buf;
snprintf(out, sizeof(out), "%s.%03d", buf, millis);
return out;
}
// Relative duration (not wall-clock).
const bool show_hours = sec > 60 * 60;
int total_ms = static_cast<int>(std::llround(std::max(0.0, sec) * 1000.0));
const int hours = total_ms / (3600 * 1000);
const int minutes = (total_ms / (60 * 1000)) % 60;
const int seconds = (total_ms / 1000) % 60;
const int millis = total_ms % 1000;
if (show_hours && include_milliseconds) {
snprintf(out, sizeof(out), "%02d:%02d:%02d.%03d", hours, minutes, seconds, millis);
} else if (show_hours) {
snprintf(out, sizeof(out), "%02d:%02d:%02d", hours, minutes, seconds);
} else if (include_milliseconds) {
snprintf(out, sizeof(out), "%02d:%02d.%03d", minutes, seconds, millis);
} else {
snprintf(out, sizeof(out), "%02d:%02d", minutes, seconds);
}
return out;
}
std::string signalToolTip(const cabana::Signal *sig) {
std::ostringstream s;
s << "\n " << sig->name << "<br /><span font-size:small\">\n"
<< " Start Bit: " << sig->start_bit << " Size: " << sig->size << "<br />\n"
<< " MSB: " << sig->msb << " LSB: " << sig->lsb << "<br />\n"
<< " Little Endian: " << (sig->is_little_endian ? "Y" : "N")
<< " Signed: " << (sig->is_signed ? "Y" : "N") << "</span>\n ";
return s.str();
}
} // namespace utils
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include <cstdint>
#include <cstdio>
#include <string>
#include <vector>
namespace cabana { class Signal; }
namespace utils {
std::string formatSeconds(double sec, bool include_milliseconds = false, bool absolute_time = false);
std::string signalToolTip(const cabana::Signal *sig);
inline std::string toHex(const std::vector<uint8_t> &dat, char separator = '\0') {
static const char digits[] = "0123456789ABCDEF";
std::string hex;
hex.reserve(dat.size() * (separator ? 3 : 2));
for (size_t i = 0; i < dat.size(); ++i) {
if (separator && i) hex += separator;
hex += digits[dat[i] >> 4];
hex += digits[dat[i] & 0xf];
}
return hex;
}
inline std::string toHexString(int value) {
char buf[16] = {};
snprintf(buf, sizeof(buf), "0x%02X", value);
return buf;
}
} // namespace utils
-42
View File
@@ -7,7 +7,6 @@
#include <cstdio>
#include <cstdlib>
#include <csignal>
#include <ctime>
#include <filesystem>
#include <limits>
#include <memory>
@@ -442,37 +441,6 @@ void setTheme(int theme) {
}
}
QString formatSeconds(double sec, bool include_milliseconds, bool absolute_time) {
if (absolute_time) {
const auto ms_total = static_cast<int64_t>(std::llround(sec * 1000.0));
const std::time_t secs = static_cast<std::time_t>(ms_total / 1000);
int millis = static_cast<int>(ms_total % 1000);
if (millis < 0) millis = -millis;
std::tm tm{};
localtime_r(&secs, &tm);
char buf[64];
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
if (include_milliseconds) {
return QString::asprintf("%s.%03d", buf, millis);
}
return QString::fromUtf8(buf);
}
// Relative duration (not wall-clock).
const bool show_hours = sec > 60 * 60;
int total_ms = static_cast<int>(std::llround(std::max(0.0, sec) * 1000.0));
const int hours = total_ms / (3600 * 1000);
const int minutes = (total_ms / (60 * 1000)) % 60;
const int seconds = (total_ms / 1000) % 60;
const int millis = total_ms % 1000;
if (show_hours) {
return include_milliseconds ? QString::asprintf("%02d:%02d:%02d.%03d", hours, minutes, seconds, millis)
: QString::asprintf("%02d:%02d:%02d", hours, minutes, seconds);
}
return include_milliseconds ? QString::asprintf("%02d:%02d.%03d", minutes, seconds, millis)
: QString::asprintf("%02d:%02d", minutes, seconds);
}
} // namespace utils
int num_decimals(double num) {
@@ -481,16 +449,6 @@ int num_decimals(double num) {
return dot_pos == -1 ? 0 : string.size() - dot_pos - 1;
}
QString signalToolTip(const cabana::Signal *sig) {
return QObject::tr(R"(
%1<br /><span font-size:small">
Start Bit: %2 Size: %3<br />
MSB: %4 LSB: %5<br />
Little Endian: %6 Signed: %7</span>
)").arg(QString::fromStdString(sig->name)).arg(sig->start_bit).arg(sig->size).arg(sig->msb).arg(sig->lsb)
.arg(sig->is_little_endian ? "Y" : "N").arg(sig->is_signed ? "Y" : "N");
}
void sigTermHandler(int s) {
std::signal(s, SIG_DFL);
qApp->quit();
+1 -14
View File
@@ -24,6 +24,7 @@
#include "tools/cabana/core/observable.h"
#include "tools/cabana/dbc/dbc.h"
#include "tools/cabana/settings.h"
#include "tools/cabana/utils/strings.h"
// needed by QVariant::fromValue() in the Qt views; goes away with QVariant
Q_DECLARE_METATYPE(MessageId)
@@ -149,22 +150,10 @@ bool getClipboardText(std::string *text); // false if no clipboard tool is avai
bool setClipboardText(const std::string &text);
bool isDarkTheme();
void setTheme(int theme);
QString formatSeconds(double sec, bool include_milliseconds = false, bool absolute_time = false);
inline void drawStaticText(QPainter *p, const QRect &r, const QStaticText &text) {
auto size = (r.size() - text.size()) / 2;
p->drawStaticText(r.left() + size.width(), r.top() + size.height(), text);
}
inline QString toHex(const std::vector<uint8_t> &dat, char separator = '\0') {
static const char digits[] = "0123456789ABCDEF";
QString hex;
hex.reserve(dat.size() * (separator ? 3 : 2));
for (size_t i = 0; i < dat.size(); ++i) {
if (separator && i) hex += QLatin1Char(separator);
hex += QLatin1Char(digits[dat[i] >> 4]);
hex += QLatin1Char(digits[dat[i] & 0xf]);
}
return hex;
}
// boundary conversions for the remaining Qt byte-array based state APIs
template <typename T>
@@ -225,7 +214,5 @@ private:
};
int num_decimals(double num);
QString signalToolTip(const cabana::Signal *sig);
inline QString toHexString(int value) { return QString("0x%1").arg(QString::number(value, 16).toUpper(), 2, '0'); }
void initApp(int argc, char *argv[], bool disable_hidpi = true);
QPixmap bootstrapPixmap(const QString &id);
+1 -1
View File
@@ -204,7 +204,7 @@ void VideoWidget::timeRangeChanged() {
QString VideoWidget::formatTime(double sec, bool include_milliseconds) {
if (settings.absolute_time)
sec += std::chrono::duration<double>(can->beginDateTime().time_since_epoch()).count();
return utils::formatSeconds(sec, include_milliseconds, settings.absolute_time);
return QString::fromStdString(utils::formatSeconds(sec, include_milliseconds, settings.absolute_time));
}
void VideoWidget::updateState() {