mirror of
https://github.com/sunnypilot/sunnypilot.git
synced 2026-09-17 06:13:44 +08:00
0b2c431d3a
version: sunnypilot v2026.003.000 (dev)
date: 2026-09-14T15:43:39
master commit: a5f44653d7
237 lines
8.5 KiB
C++
237 lines
8.5 KiB
C++
#include "tools/cabana/utils/qtutil.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <csignal>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
#include <string>
|
|
|
|
#include <QColor>
|
|
#include <QFontDatabase>
|
|
#include <QPixmapCache>
|
|
#include <QPainterPath>
|
|
|
|
// MessageBytesDelegate
|
|
|
|
MessageBytesDelegate::MessageBytesDelegate(QObject *parent, bool multiple_lines)
|
|
: font_metrics(QApplication::font()), multiple_lines(multiple_lines), QStyledItemDelegate(parent) {
|
|
fixed_font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
|
|
byte_size = QFontMetrics(fixed_font).size(Qt::TextSingleLine, "00 ") + QSize(0, 2);
|
|
for (int i = 0; i < 256; ++i) {
|
|
hex_text_table[i].setText(QStringLiteral("%1").arg(i, 2, 16, QLatin1Char('0')).toUpper());
|
|
hex_text_table[i].prepare({}, fixed_font);
|
|
}
|
|
h_margin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1;
|
|
v_margin = QApplication::style()->pixelMetric(QStyle::PM_FocusFrameVMargin) + 1;
|
|
}
|
|
|
|
QSize MessageBytesDelegate::sizeForBytes(int n) const {
|
|
int rows = multiple_lines ? std::max(1, n / 8) : 1;
|
|
return {(n / rows) * byte_size.width() + h_margin * 2, rows * byte_size.height() + v_margin * 2};
|
|
}
|
|
|
|
QSize MessageBytesDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
|
auto data = index.data(BytesRole);
|
|
return sizeForBytes(data.isValid() ? static_cast<std::vector<uint8_t> *>(data.value<void *>())->size() : 0);
|
|
}
|
|
|
|
void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
|
|
if (option.state & QStyle::State_Selected) {
|
|
painter->fillRect(option.rect, option.palette.brush(QPalette::Normal, QPalette::Highlight));
|
|
}
|
|
|
|
QRect item_rect = option.rect.adjusted(h_margin, v_margin, -h_margin, -v_margin);
|
|
QColor highlighted_color = option.palette.color(QPalette::HighlightedText);
|
|
auto text_color = index.data(Qt::ForegroundRole).value<QColor>();
|
|
bool inactive = text_color.isValid();
|
|
if (!inactive) {
|
|
text_color = option.palette.color(QPalette::Text);
|
|
}
|
|
auto data = index.data(BytesRole);
|
|
if (!data.isValid()) {
|
|
painter->setFont(option.font);
|
|
painter->setPen(option.state & QStyle::State_Selected ? highlighted_color : text_color);
|
|
QString text = font_metrics.elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, item_rect.width());
|
|
painter->drawText(item_rect, Qt::AlignLeft | Qt::AlignVCenter, text);
|
|
return;
|
|
}
|
|
|
|
// Paint hex column
|
|
const auto &bytes = *static_cast<std::vector<uint8_t> *>(data.value<void *>());
|
|
const auto &colors = *static_cast<std::vector<CabanaColor> *>(index.data(ColorsRole).value<void *>());
|
|
|
|
painter->setFont(fixed_font);
|
|
const QPen text_pen(option.state & QStyle::State_Selected ? highlighted_color : text_color);
|
|
const QPoint pt = item_rect.topLeft();
|
|
for (int i = 0; i < bytes.size(); ++i) {
|
|
int row = !multiple_lines ? 0 : i / 8;
|
|
int column = !multiple_lines ? i : i % 8;
|
|
QRect r({pt.x() + column * byte_size.width(), pt.y() + row * byte_size.height()}, byte_size);
|
|
|
|
if (!inactive && i < colors.size() && colors[i].alpha() > 0) {
|
|
if (option.state & QStyle::State_Selected) {
|
|
painter->setPen(option.palette.color(QPalette::Text));
|
|
painter->fillRect(r, option.palette.color(QPalette::Window));
|
|
}
|
|
painter->fillRect(r, toQColor(colors[i]));
|
|
} else {
|
|
painter->setPen(text_pen);
|
|
}
|
|
utils::drawStaticText(painter, r, hex_text_table[bytes[i]]);
|
|
}
|
|
}
|
|
|
|
// TabBar
|
|
|
|
int TabBar::addTab(const QString &text) {
|
|
int index = QTabBar::addTab(text);
|
|
QToolButton *btn = new ToolButton("x", tr("Close Tab"));
|
|
int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, nullptr, btn);
|
|
int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, nullptr, btn);
|
|
btn->setFixedSize({width, height});
|
|
setTabButton(index, QTabBar::RightSide, btn);
|
|
QObject::connect(btn, &QToolButton::clicked, this, &TabBar::closeTabClicked);
|
|
return index;
|
|
}
|
|
|
|
void TabBar::closeTabClicked() {
|
|
QObject *object = sender();
|
|
for (int i = 0; i < count(); ++i) {
|
|
if (tabButton(i, QTabBar::RightSide) == object) {
|
|
emit tabCloseRequested(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// validators
|
|
|
|
static QValidator::State toQtState(ValidState s) {
|
|
switch (s) {
|
|
case ValidState::Acceptable: return QValidator::Acceptable;
|
|
case ValidState::Intermediate: return QValidator::Intermediate;
|
|
default: return QValidator::Invalid;
|
|
}
|
|
}
|
|
|
|
QValidator::State NameValidator::validate(QString &input, int &pos) const {
|
|
std::string s = input.toStdString();
|
|
auto state = validateName(s);
|
|
input = QString::fromStdString(s);
|
|
return toQtState(state);
|
|
}
|
|
|
|
QValidator::State NodeValidator::validate(QString &input, int &pos) const {
|
|
return toQtState(validateNodes(input.toStdString()));
|
|
}
|
|
|
|
QValidator::State NonWhitespaceValidator::validate(QString &input, int &pos) const {
|
|
return toQtState(validateNonWhitespace(input.toStdString()));
|
|
}
|
|
|
|
QValidator::State IpAddressValidator::validate(QString &input, int &pos) const {
|
|
return toQtState(validateIpAddress(input.toStdString()));
|
|
}
|
|
|
|
QValidator::State DoubleValidator::validate(QString &input, int &pos) const {
|
|
return toQtState(validateDouble(input.toLatin1().toStdString()));
|
|
}
|
|
|
|
namespace utils {
|
|
|
|
bool isDarkTheme() {
|
|
QColor windowColor = QApplication::palette().color(QPalette::Window);
|
|
return windowColor.lightness() < 128;
|
|
}
|
|
|
|
QPixmap icon(const QString &id) {
|
|
bool dark_theme = isDarkTheme();
|
|
|
|
QPixmap pm;
|
|
QString key = "bootstrap_" % id % (dark_theme ? "1" : "0");
|
|
if (!QPixmapCache::find(key, &pm)) {
|
|
pm = bootstrapPixmap(id);
|
|
if (dark_theme) {
|
|
QPainter p(&pm);
|
|
p.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
|
p.fillRect(pm.rect(), QColor("#bbbbbb"));
|
|
}
|
|
QPixmapCache::insert(key, pm);
|
|
}
|
|
return pm;
|
|
}
|
|
|
|
void setTheme(int theme) {
|
|
auto style = QApplication::style();
|
|
if (!style) return;
|
|
|
|
static int prev_theme = 0;
|
|
if (theme != prev_theme) {
|
|
prev_theme = theme;
|
|
QPalette new_palette;
|
|
if (theme == DARK_THEME) {
|
|
new_palette.setColor(QPalette::Window, toQColor(DarkTheme::window));
|
|
new_palette.setColor(QPalette::WindowText, toQColor(DarkTheme::window_text));
|
|
new_palette.setColor(QPalette::Base, toQColor(DarkTheme::base));
|
|
new_palette.setColor(QPalette::AlternateBase, toQColor(DarkTheme::base));
|
|
new_palette.setColor(QPalette::ToolTipBase, toQColor(DarkTheme::base));
|
|
new_palette.setColor(QPalette::ToolTipText, toQColor(DarkTheme::tooltip_text));
|
|
new_palette.setColor(QPalette::Text, toQColor(DarkTheme::text));
|
|
new_palette.setColor(QPalette::Button, toQColor(DarkTheme::button));
|
|
new_palette.setColor(QPalette::ButtonText, toQColor(DarkTheme::window_text));
|
|
new_palette.setColor(QPalette::Highlight, toQColor(DarkTheme::highlight));
|
|
new_palette.setColor(QPalette::HighlightedText, toQColor(DarkTheme::window_text));
|
|
new_palette.setColor(QPalette::BrightText, toQColor(DarkTheme::bright_text));
|
|
new_palette.setColor(QPalette::Disabled, QPalette::ButtonText, toQColor(DarkTheme::disabled_text));
|
|
new_palette.setColor(QPalette::Disabled, QPalette::WindowText, toQColor(DarkTheme::disabled_text));
|
|
new_palette.setColor(QPalette::Disabled, QPalette::Text, toQColor(DarkTheme::disabled_text));
|
|
new_palette.setColor(QPalette::Light, toQColor(DarkTheme::light));
|
|
new_palette.setColor(QPalette::Dark, toQColor(DarkTheme::dark));
|
|
} else {
|
|
new_palette = style->standardPalette();
|
|
}
|
|
qApp->setPalette(new_palette);
|
|
style->polish(qApp);
|
|
for (auto w : QApplication::allWidgets()) {
|
|
w->setPalette(new_palette);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace utils
|
|
|
|
void sigTermHandler(int s) {
|
|
std::signal(s, SIG_DFL);
|
|
qApp->quit();
|
|
}
|
|
|
|
void initApp(int argc, char *argv[], bool disable_hidpi) {
|
|
// setup signal handlers to exit gracefully
|
|
std::signal(SIGINT, sigTermHandler);
|
|
std::signal(SIGTERM, sigTermHandler);
|
|
|
|
#ifdef __APPLE__
|
|
// Get the devicePixelRatio, and scale accordingly to maintain 1:1 rendering
|
|
QApplication tmp(argc, argv);
|
|
if (disable_hidpi) {
|
|
qputenv("QT_SCALE_FACTOR", QString::number(1.0 / tmp.devicePixelRatio()).toLocal8Bit());
|
|
}
|
|
#endif
|
|
|
|
qputenv("QT_DBL_CLICK_DIST", "150");
|
|
// ensure the current dir matches the exectuable's directory
|
|
std::error_code ec;
|
|
std::filesystem::current_path(executableDir(), ec);
|
|
}
|
|
|
|
QPixmap bootstrapPixmap(const QString &id) {
|
|
QPixmap pixmap;
|
|
const std::string svg = utils::bootstrapSvg(id.toStdString());
|
|
if (!svg.empty()) {
|
|
pixmap.loadFromData((const uchar *)svg.data(), svg.size(), "svg");
|
|
}
|
|
return pixmap;
|
|
}
|