Merge branch 'master' of github.com:collin80/SavvyCAN
This commit is contained in:
@@ -54,10 +54,13 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
qmake CONFIG+=release CONFIG+=sdk_no_version_check SavvyCAN.pro
|
qmake CONFIG+=release CONFIG+=sdk_no_version_check SavvyCAN.pro
|
||||||
make -j`sysctl kern.aioprocmax | awk '{print $2}'`
|
make -j`sysctl kern.aioprocmax | awk '{print $2}'`
|
||||||
|
lrelease translations/*.ts
|
||||||
|
|
||||||
- name: Package
|
- name: Package
|
||||||
run: |
|
run: |
|
||||||
|
mkdir -p SavvyCAN.app/Contents/MacOS/translations
|
||||||
mkdir -p SavvyCAN.app/Contents/MacOS/help
|
mkdir -p SavvyCAN.app/Contents/MacOS/help
|
||||||
|
cp -R translations/*.qm SavvyCAN.app/Contents/MacOS/translations
|
||||||
cp -R help/* SavvyCAN.app/Contents/MacOS/help
|
cp -R help/* SavvyCAN.app/Contents/MacOS/help
|
||||||
macdeployqt SavvyCAN.app -dmg
|
macdeployqt SavvyCAN.app -dmg
|
||||||
mv SavvyCAN.dmg SavvyCAN_x64.dmg
|
mv SavvyCAN.dmg SavvyCAN_x64.dmg
|
||||||
@@ -84,10 +87,13 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
qmake CONFIG+=release CONFIG+=sdk_no_version_check SavvyCAN.pro
|
qmake CONFIG+=release CONFIG+=sdk_no_version_check SavvyCAN.pro
|
||||||
make -j`sysctl kern.aioprocmax | awk '{print $2}'`
|
make -j`sysctl kern.aioprocmax | awk '{print $2}'`
|
||||||
|
lrelease translations/*.ts
|
||||||
|
|
||||||
- name: Package
|
- name: Package
|
||||||
run: |
|
run: |
|
||||||
|
mkdir -p SavvyCAN.app/Contents/MacOS/translations
|
||||||
mkdir -p SavvyCAN.app/Contents/MacOS/help
|
mkdir -p SavvyCAN.app/Contents/MacOS/help
|
||||||
|
cp -R translations/*.qm SavvyCAN.app/Contents/MacOS/translations
|
||||||
cp -R help/* SavvyCAN.app/Contents/MacOS/help
|
cp -R help/* SavvyCAN.app/Contents/MacOS/help
|
||||||
macdeployqt SavvyCAN.app -dmg
|
macdeployqt SavvyCAN.app -dmg
|
||||||
mv SavvyCAN.dmg SavvyCAN_arm64.dmg
|
mv SavvyCAN.dmg SavvyCAN_arm64.dmg
|
||||||
@@ -120,6 +126,7 @@ jobs:
|
|||||||
mkdir package
|
mkdir package
|
||||||
copy "release/SavvyCAN.exe" package/
|
copy "release/SavvyCAN.exe" package/
|
||||||
mkdir package/help
|
mkdir package/help
|
||||||
|
copy translations/*.qm package/translations/
|
||||||
copy help/*.md package/help/
|
copy help/*.md package/help/
|
||||||
mkdir package/help/images
|
mkdir package/help/images
|
||||||
copy help/images/*.* package/help/images/
|
copy help/images/*.* package/help/images/
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ build/
|
|||||||
*.o
|
*.o
|
||||||
*.obj
|
*.obj
|
||||||
|
|
||||||
|
# Compiled translations
|
||||||
|
*.qm
|
||||||
|
|
||||||
# Precompiled Headers
|
# Precompiled Headers
|
||||||
*.gch
|
*.gch
|
||||||
*.pch
|
*.pch
|
||||||
@@ -36,6 +39,7 @@ SavvyCAN
|
|||||||
**/moc_*
|
**/moc_*
|
||||||
**/ui_*.h
|
**/ui_*.h
|
||||||
**/qrc_*.cpp
|
**/qrc_*.cpp
|
||||||
|
.qtc_clangd
|
||||||
SavvyCAN.pro.user.*
|
SavvyCAN.pro.user.*
|
||||||
.qmake.stash
|
.qmake.stash
|
||||||
Makefile
|
Makefile
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ to download it separately.
|
|||||||
|
|
||||||
This project requires 5.14.0 or higher because of a dependency on QtSerialBus and other new additions to Qt.
|
This project requires 5.14.0 or higher because of a dependency on QtSerialBus and other new additions to Qt.
|
||||||
|
|
||||||
NOTE: As the code in this master branch sits, it does compile with QT6. Support for QT6 is approximately "beta" quality. Most all functions should work, please send issues if found.
|
NOTE: There is a work-in-progress branch QT6WIP that can compile with QT6 (Tested with Qt 6.9 on 2026-03-11). Support for QT6 is approximately "beta" quality. Most all functions should work, please send issues if found.
|
||||||
|
|
||||||
It appears that the current binary build for MacOS requires at least MacOS 10.15
|
It appears that the current binary build for MacOS requires at least MacOS 10.15
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
[Desktop Entry]
|
[Desktop Entry]
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=SavvyCAN
|
Name=SavvyCAN
|
||||||
|
Name[pt_BR]=SavvyCAN
|
||||||
GenericName=CANBus reverse engineering tool
|
GenericName=CANBus reverse engineering tool
|
||||||
|
GenericName[pt_BR]=Ferramenta Rede CAN de engenharia reversa
|
||||||
Comment=Facilitates reverse engineering of canbus captures
|
Comment=Facilitates reverse engineering of canbus captures
|
||||||
|
Comment[pt_BR]=Facilitar engenharia reversa de capturas de Rede CAN
|
||||||
Exec=SavvyCAN
|
Exec=SavvyCAN
|
||||||
Icon=SavvyCAN
|
Icon=SavvyCAN
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Categories=Development;Electronics;IDE;
|
Categories=Development;Electronics;IDE;
|
||||||
MimeType=text/x-application;
|
MimeType=text/x-application;
|
||||||
Keywords=embedded electronics;electronics;canbus;reverse engineering;
|
Keywords=embedded electronics;electronics;canbus;reverse engineering;
|
||||||
|
Keywords[pt_BR]=eletronica embarcada;eletronicos;canbus;engenharia reversa;
|
||||||
|
|||||||
@@ -279,3 +279,9 @@ INSTALLS += helpfiles
|
|||||||
|
|
||||||
INSTALLS += target
|
INSTALLS += target
|
||||||
|
|
||||||
|
TRANSLATIONS += \
|
||||||
|
translations/SavvyCAN_en.ts \
|
||||||
|
translations/SavvyCAN_pt_BR.ts
|
||||||
|
|
||||||
|
DISTFILES += \
|
||||||
|
translations/pt_BR.qph
|
||||||
|
|||||||
+38
-17
@@ -3,6 +3,8 @@
|
|||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QPalette>
|
#include <QPalette>
|
||||||
|
#include <QColor>
|
||||||
|
#include <QBrush>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include "utility.h"
|
#include "utility.h"
|
||||||
@@ -76,6 +78,7 @@ CANFrameModel::CANFrameModel(QObject *parent)
|
|||||||
overwriteDups = false;
|
overwriteDups = false;
|
||||||
filtersPersistDuringClear = false;
|
filtersPersistDuringClear = false;
|
||||||
useHexMode = true;
|
useHexMode = true;
|
||||||
|
useColorsByCanId = false;
|
||||||
timeStyle = TS_MICROS;
|
timeStyle = TS_MICROS;
|
||||||
timeOffset = 0;
|
timeOffset = 0;
|
||||||
needFilterRefresh = false;
|
needFilterRefresh = false;
|
||||||
@@ -101,6 +104,16 @@ void CANFrameModel::setHexMode(bool mode)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CANFrameModel::setUseColorsByCanId(bool mode)
|
||||||
|
{
|
||||||
|
if (useColorsByCanId != mode)
|
||||||
|
{
|
||||||
|
this->beginResetModel();
|
||||||
|
useColorsByCanId = mode;
|
||||||
|
this->endResetModel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void CANFrameModel::setTimeStyle(TimeStyle newStyle)
|
void CANFrameModel::setTimeStyle(TimeStyle newStyle)
|
||||||
{
|
{
|
||||||
if (timeStyle != newStyle)
|
if (timeStyle != newStyle)
|
||||||
@@ -401,7 +414,6 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const
|
|||||||
{
|
{
|
||||||
QString tempString;
|
QString tempString;
|
||||||
CANFrame thisFrame;
|
CANFrame thisFrame;
|
||||||
static bool rowFlip = false;
|
|
||||||
QVariant ts;
|
QVariant ts;
|
||||||
|
|
||||||
if (!index.isValid())
|
if (!index.isValid())
|
||||||
@@ -425,9 +437,31 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const
|
|||||||
return msg->bgColor;
|
return msg->bgColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rowFlip = (index.row() % 2);
|
|
||||||
if (rowFlip) return QApplication::palette().color(QPalette::Base);
|
if (useColorsByCanId)
|
||||||
else return QApplication::palette().color(QPalette::AlternateBase);
|
{
|
||||||
|
QString frameStr = Utility::formatCANID(thisFrame.frameId(), thisFrame.hasExtendedFrameFormat());
|
||||||
|
// Color rows by the value in the "category" column
|
||||||
|
return QBrush(Utility::colorForString(frameStr));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (index.row() % 2) ?
|
||||||
|
QApplication::palette().color(QPalette::Base) :
|
||||||
|
QApplication::palette().color(QPalette::AlternateBase);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == Qt::ForegroundRole)
|
||||||
|
{
|
||||||
|
if (dbcHandler != nullptr && interpretFrames && !ignoreDBCColors)
|
||||||
|
{
|
||||||
|
DBC_MESSAGE *msg = dbcHandler->findMessage(thisFrame);
|
||||||
|
if (msg != nullptr)
|
||||||
|
{
|
||||||
|
return msg->fgColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QApplication::palette().color(QPalette::WindowText);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == Qt::TextAlignmentRole)
|
if (role == Qt::TextAlignmentRole)
|
||||||
@@ -448,19 +482,6 @@ QVariant CANFrameModel::data(const QModelIndex &index, int role) const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == Qt::ForegroundRole)
|
|
||||||
{
|
|
||||||
if (dbcHandler != nullptr && interpretFrames && !ignoreDBCColors)
|
|
||||||
{
|
|
||||||
DBC_MESSAGE *msg = dbcHandler->findMessage(thisFrame);
|
|
||||||
if (msg != nullptr)
|
|
||||||
{
|
|
||||||
return msg->fgColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return QApplication::palette().color(QPalette::WindowText);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == Qt::DisplayRole) {
|
if (role == Qt::DisplayRole) {
|
||||||
switch (Column(index.column()))
|
switch (Column(index.column()))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ public:
|
|||||||
bool getInterpretMode();
|
bool getInterpretMode();
|
||||||
void setOverwriteMode(bool);
|
void setOverwriteMode(bool);
|
||||||
void setHexMode(bool);
|
void setHexMode(bool);
|
||||||
|
void setUseColorsByCanId(bool);
|
||||||
void setClearMode(bool mode);
|
void setClearMode(bool mode);
|
||||||
void setTimeStyle(TimeStyle newStyle);
|
void setTimeStyle(TimeStyle newStyle);
|
||||||
void setIgnoreDBCColors(bool mode);
|
void setIgnoreDBCColors(bool mode);
|
||||||
@@ -94,6 +95,7 @@ private:
|
|||||||
QString timeFormat;
|
QString timeFormat;
|
||||||
TimeStyle timeStyle;
|
TimeStyle timeStyle;
|
||||||
bool useHexMode;
|
bool useHexMode;
|
||||||
|
bool useColorsByCanId;
|
||||||
bool needFilterRefresh;
|
bool needFilterRefresh;
|
||||||
bool ignoreDBCColors;
|
bool ignoreDBCColors;
|
||||||
int64_t timeOffset;
|
int64_t timeOffset;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ LAWICELSerial::LAWICELSerial(QString portName, int serialSpeed, int lawicelSpeed
|
|||||||
|
|
||||||
serial = nullptr;
|
serial = nullptr;
|
||||||
isAutoRestart = false;
|
isAutoRestart = false;
|
||||||
|
rebuildLocalTimeBasis();
|
||||||
|
|
||||||
readSettings();
|
readSettings();
|
||||||
}
|
}
|
||||||
@@ -203,6 +204,13 @@ bool LAWICELSerial::piSendFrame(const CANFrame& frame)
|
|||||||
|
|
||||||
/****************************************************************/
|
/****************************************************************/
|
||||||
|
|
||||||
|
void LAWICELSerial::rebuildLocalTimeBasis()
|
||||||
|
{
|
||||||
|
timeBasis = 0;
|
||||||
|
lastHWTimestamp = -1;
|
||||||
|
wrapAdder = 0;
|
||||||
|
}
|
||||||
|
|
||||||
void LAWICELSerial::readSettings()
|
void LAWICELSerial::readSettings()
|
||||||
{
|
{
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
@@ -254,6 +262,8 @@ void LAWICELSerial::deviceConnected()
|
|||||||
{
|
{
|
||||||
sendDebug("Connecting to LAWICEL Device!");
|
sendDebug("Connecting to LAWICEL Device!");
|
||||||
|
|
||||||
|
rebuildLocalTimeBasis();
|
||||||
|
|
||||||
QByteArray output;
|
QByteArray output;
|
||||||
|
|
||||||
output.clear();
|
output.clear();
|
||||||
@@ -474,8 +484,6 @@ void LAWICELSerial::readSerialData()
|
|||||||
QByteArray data;
|
QByteArray data;
|
||||||
unsigned char c;
|
unsigned char c;
|
||||||
QString debugBuild;
|
QString debugBuild;
|
||||||
CANFrame buildFrame;
|
|
||||||
QByteArray buildData;
|
|
||||||
|
|
||||||
if (serial) data = serial->readAll();
|
if (serial) data = serial->readAll();
|
||||||
|
|
||||||
@@ -491,22 +499,29 @@ void LAWICELSerial::readSerialData()
|
|||||||
{
|
{
|
||||||
qDebug() << "Got CR!";
|
qDebug() << "Got CR!";
|
||||||
|
|
||||||
if (useSystemTime)
|
CANFrame buildFrame;
|
||||||
{
|
QByteArray buildData;
|
||||||
buildFrame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(QDateTime::currentMSecsSinceEpoch() * 1000ul));
|
|
||||||
}
|
buildFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, QDateTime::currentMSecsSinceEpoch() * 1000ll));
|
||||||
else
|
if (!useSystemTime)
|
||||||
{
|
{
|
||||||
|
bool isFD = false;
|
||||||
char offset;
|
char offset;
|
||||||
char len_index;
|
char len_index;
|
||||||
switch(mBuildLine[0].toLatin1()) {
|
switch(mBuildLine[0].toLatin1()) {
|
||||||
case 't':
|
case 'b':
|
||||||
case 'd':
|
case 'd':
|
||||||
|
isFD = true;
|
||||||
|
[[fallthrough]];
|
||||||
|
case 't':
|
||||||
offset = 5;
|
offset = 5;
|
||||||
len_index = 4;
|
len_index = 4;
|
||||||
break;
|
break;
|
||||||
case 'T':
|
case 'B':
|
||||||
case 'D':
|
case 'D':
|
||||||
|
isFD = true;
|
||||||
|
[[fallthrough]];
|
||||||
|
case 'T':
|
||||||
offset = 10;
|
offset = 10;
|
||||||
len_index = 9;
|
len_index = 9;
|
||||||
break;
|
break;
|
||||||
@@ -515,17 +530,18 @@ void LAWICELSerial::readSerialData()
|
|||||||
len_index = 4;
|
len_index = 4;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
//If total length is greater than command, header and data, timestamps must be enabled.
|
|
||||||
if (data.length() > (offset + mBuildLine.mid(len_index, 1).toInt() * 2 + 1))
|
int dlc = mBuildLine.mid(len_index, 1).toInt();
|
||||||
|
int byteCount = isFD ? (int)dlc_code_to_bytes(dlc) : dlc;
|
||||||
|
int frameStringLen = offset + byteCount * 2;
|
||||||
|
if (mBuildLine.length() > frameStringLen + 1)
|
||||||
{
|
{
|
||||||
//Four bytes after the end of the data bytes.
|
qint64 hardwareMs = mBuildLine.mid(frameStringLen, 4).toInt(nullptr, 16);
|
||||||
buildTimestamp = mBuildLine.mid(offset + mBuildLine.mid(len_index, 1).toInt() * 2, 4).toInt(nullptr, 16);
|
if (lastHWTimestamp >= 0 && hardwareMs < lastHWTimestamp) { wrapAdder += 60000; }
|
||||||
buildFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, buildTimestamp));
|
lastHWTimestamp = hardwareMs;
|
||||||
}
|
qint64 unwrappedMs = wrapAdder + hardwareMs;
|
||||||
else
|
if (timeBasis == 0) { timeBasis = QDateTime::currentMSecsSinceEpoch() - unwrappedMs; }
|
||||||
{
|
buildFrame.setTimeStamp(QCanBusFrame::TimeStamp(0, (timeBasis + unwrappedMs) * 1000ll));
|
||||||
//Default to system time if timestamps are disabled.
|
|
||||||
buildFrame.setTimeStamp(QCanBusFrame::TimeStamp::fromMicroSeconds(QDateTime::currentMSecsSinceEpoch() * 1000ul));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,8 +60,9 @@ protected:
|
|||||||
bool isAutoRestart;
|
bool isAutoRestart;
|
||||||
QSerialPort *serial;
|
QSerialPort *serial;
|
||||||
int framesRapid;
|
int framesRapid;
|
||||||
CANFrame buildFrame;
|
qint64 timeBasis;
|
||||||
qint64 buildTimestamp;
|
qint64 lastHWTimestamp;
|
||||||
|
qint64 wrapAdder;
|
||||||
bool can0Enabled;
|
bool can0Enabled;
|
||||||
bool can0ListenOnly;
|
bool can0ListenOnly;
|
||||||
bool canFd;
|
bool canFd;
|
||||||
|
|||||||
+7
-1
@@ -66,6 +66,13 @@ public:
|
|||||||
QString descript;
|
QString descript;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class DBC_VAL_TABLE
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString name;
|
||||||
|
QList<DBC_VAL_ENUM_ENTRY> valList;
|
||||||
|
};
|
||||||
|
|
||||||
class DBC_NODE
|
class DBC_NODE
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -176,4 +183,3 @@ public:
|
|||||||
|
|
||||||
|
|
||||||
#endif // DBC_CLASSES_H
|
#endif // DBC_CLASSES_H
|
||||||
|
|
||||||
|
|||||||
+126
-27
@@ -16,6 +16,28 @@
|
|||||||
|
|
||||||
DBCHandler* DBCHandler::instance = nullptr;
|
DBCHandler* DBCHandler::instance = nullptr;
|
||||||
|
|
||||||
|
static bool parseValueDescriptions(QString tokenString, QList<DBC_VAL_ENUM_ENTRY> &values)
|
||||||
|
{
|
||||||
|
QRegularExpression regex;
|
||||||
|
QRegularExpressionMatch match;
|
||||||
|
DBC_VAL_ENUM_ENTRY val;
|
||||||
|
|
||||||
|
values.clear();
|
||||||
|
tokenString = tokenString.trimmed();
|
||||||
|
while (tokenString.length() > 2)
|
||||||
|
{
|
||||||
|
regex.setPattern("^([+-]?\\d+) \\\"(.*?)\\\"(.*)");
|
||||||
|
match = regex.match(tokenString);
|
||||||
|
if (!match.hasMatch()) return false;
|
||||||
|
|
||||||
|
val.value = match.captured(1).toInt();
|
||||||
|
val.descript = match.captured(2);
|
||||||
|
values.append(val);
|
||||||
|
tokenString = match.captured(3).trimmed();
|
||||||
|
}
|
||||||
|
return values.count() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
DBC_SIGNAL* DBCSignalHandler::findSignalByIdx(int idx)
|
DBC_SIGNAL* DBCSignalHandler::findSignalByIdx(int idx)
|
||||||
{
|
{
|
||||||
if (sigs.count() == 0) return nullptr;
|
if (sigs.count() == 0) return nullptr;
|
||||||
@@ -316,6 +338,8 @@ DBCFile::DBCFile(const DBCFile& cpy) : QObject()
|
|||||||
assocBuses = cpy.assocBuses;
|
assocBuses = cpy.assocBuses;
|
||||||
dbc_nodes.clear();
|
dbc_nodes.clear();
|
||||||
dbc_nodes.append(cpy.dbc_nodes);
|
dbc_nodes.append(cpy.dbc_nodes);
|
||||||
|
dbc_value_tables.clear();
|
||||||
|
dbc_value_tables.append(cpy.dbc_value_tables);
|
||||||
dbc_attributes.clear();
|
dbc_attributes.clear();
|
||||||
dbc_attributes.append(cpy.dbc_attributes);
|
dbc_attributes.append(cpy.dbc_attributes);
|
||||||
isDirty = cpy.isDirty;
|
isDirty = cpy.isDirty;
|
||||||
@@ -331,6 +355,8 @@ DBCFile& DBCFile::operator=(const DBCFile& cpy)
|
|||||||
assocBuses = cpy.assocBuses;
|
assocBuses = cpy.assocBuses;
|
||||||
dbc_nodes.clear();
|
dbc_nodes.clear();
|
||||||
dbc_nodes.append(cpy.dbc_nodes);
|
dbc_nodes.append(cpy.dbc_nodes);
|
||||||
|
dbc_value_tables.clear();
|
||||||
|
dbc_value_tables.append(cpy.dbc_value_tables);
|
||||||
dbc_attributes.clear();
|
dbc_attributes.clear();
|
||||||
dbc_attributes.append(cpy.dbc_attributes);
|
dbc_attributes.append(cpy.dbc_attributes);
|
||||||
}
|
}
|
||||||
@@ -382,6 +408,19 @@ DBC_NODE* DBCFile::findNodeByNameAndComment(QString fullname)
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DBC_VAL_TABLE* DBCFile::findValueTableByName(QString name)
|
||||||
|
{
|
||||||
|
if (dbc_value_tables.length() == 0) return nullptr;
|
||||||
|
for (int i = 0; i < dbc_value_tables.length(); i++)
|
||||||
|
{
|
||||||
|
if (name.compare(dbc_value_tables[i].name, Qt::CaseInsensitive) == 0)
|
||||||
|
{
|
||||||
|
return &dbc_value_tables[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
QString DBCFile::getFullFilename()
|
QString DBCFile::getFullFilename()
|
||||||
{
|
{
|
||||||
return filePath + fileName;
|
return filePath + fileName;
|
||||||
@@ -712,6 +751,29 @@ bool DBCFile::parseSignalValueTypeLine(QString line)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool DBCFile::parseValueTableLine(QString line)
|
||||||
|
{
|
||||||
|
QRegularExpression regex;
|
||||||
|
QRegularExpressionMatch match;
|
||||||
|
|
||||||
|
regex.setPattern("^VAL\\_TABLE\\_ ([-\\w]+) (.*);");
|
||||||
|
match = regex.match(line);
|
||||||
|
//captured 1 is the table name
|
||||||
|
//captured 2 is a series of values in the form (number "text")
|
||||||
|
if (match.hasMatch())
|
||||||
|
{
|
||||||
|
DBC_VAL_TABLE table;
|
||||||
|
table.name = match.captured(1);
|
||||||
|
if (!parseValueDescriptions(match.captured(2), table.valList)) return false;
|
||||||
|
|
||||||
|
DBC_VAL_TABLE *existingTable = findValueTableByName(table.name);
|
||||||
|
if (existingTable) existingTable->valList = table.valList;
|
||||||
|
else dbc_value_tables.append(table);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool DBCFile::parseValueLine(QString line)
|
bool DBCFile::parseValueLine(QString line)
|
||||||
{
|
{
|
||||||
QRegularExpression regex;
|
QRegularExpression regex;
|
||||||
@@ -733,26 +795,19 @@ bool DBCFile::parseValueLine(QString line)
|
|||||||
if (sig != nullptr)
|
if (sig != nullptr)
|
||||||
{
|
{
|
||||||
QString tokenString = match.captured(3);
|
QString tokenString = match.captured(3);
|
||||||
DBC_VAL_ENUM_ENTRY val;
|
QList<DBC_VAL_ENUM_ENTRY> values;
|
||||||
while (tokenString.length() > 2)
|
DBC_VAL_TABLE *valueTable = findValueTableByName(tokenString.trimmed());
|
||||||
|
if (valueTable)
|
||||||
{
|
{
|
||||||
regex.setPattern("(\\d+) \\\"(.*?)\\\"(.*)");
|
sig->valList = valueTable->valList;
|
||||||
match = regex.match(tokenString);
|
|
||||||
if (match.hasMatch())
|
|
||||||
{
|
|
||||||
val.value = match.captured(1).toULong() & 0x1FFFFFFFul;
|
|
||||||
val.descript = match.captured(2);
|
|
||||||
//qDebug() << "sig val " << val.value << " desc " <<val.descript;
|
|
||||||
sig->valList.append(val);
|
|
||||||
int rightSize = tokenString.length() - match.captured(1).length() - match.captured(2).length() - 4;
|
|
||||||
if (rightSize > 0) tokenString = tokenString.right(rightSize);
|
|
||||||
else tokenString = "";
|
|
||||||
//qDebug() << "New token string: " << tokenString;
|
|
||||||
}
|
|
||||||
else tokenString = "";
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (parseValueDescriptions(tokenString, values))
|
||||||
|
{
|
||||||
|
sig->valList.append(values);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -919,6 +974,8 @@ bool DBCFile::loadFile(QString fileName)
|
|||||||
QString fileBaseName = QFileInfo(fileName).baseName();
|
QString fileBaseName = QFileInfo(fileName).baseName();
|
||||||
|
|
||||||
bool inMultilineBU = false;
|
bool inMultilineBU = false;
|
||||||
|
bool inMultilineValTable = false;
|
||||||
|
QString multilineValTable;
|
||||||
|
|
||||||
qDebug() << "DBC File: " << fileName;
|
qDebug() << "DBC File: " << fileName;
|
||||||
|
|
||||||
@@ -931,6 +988,7 @@ bool DBCFile::loadFile(QString fileName)
|
|||||||
|
|
||||||
qDebug() << "Starting DBC load";
|
qDebug() << "Starting DBC load";
|
||||||
dbc_nodes.clear();
|
dbc_nodes.clear();
|
||||||
|
dbc_value_tables.clear();
|
||||||
messageHandler->removeAllMessages();
|
messageHandler->removeAllMessages();
|
||||||
messageHandler->setMatchingCriteria(EXACT);
|
messageHandler->setMatchingCriteria(EXACT);
|
||||||
messageHandler->setFilterLabeling(false);
|
messageHandler->setFilterLabeling(false);
|
||||||
@@ -962,6 +1020,18 @@ bool DBCFile::loadFile(QString fileName)
|
|||||||
else inMultilineBU = false;
|
else inMultilineBU = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inMultilineValTable)
|
||||||
|
{
|
||||||
|
multilineValTable.append(" " + line);
|
||||||
|
if (line.endsWith(";"))
|
||||||
|
{
|
||||||
|
parseValueTableLine(multilineValTable);
|
||||||
|
multilineValTable.clear();
|
||||||
|
inMultilineValTable = false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (!inMultilineBU)
|
if (!inMultilineBU)
|
||||||
{
|
{
|
||||||
if (line.startsWith("BO_ ")) //defines a message
|
if (line.startsWith("BO_ ")) //defines a message
|
||||||
@@ -1064,6 +1134,19 @@ bool DBCFile::loadFile(QString fileName)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (line.startsWith("VAL_TABLE_ "))
|
||||||
|
{
|
||||||
|
if (line.endsWith(";"))
|
||||||
|
{
|
||||||
|
parseValueTableLine(line);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
multilineValTable = line;
|
||||||
|
inMultilineValTable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//VAL_ (1090) (VCUPresentParkLightOC) (1 "Error present" 0 "Error not present") ;
|
//VAL_ (1090) (VCUPresentParkLightOC) (1 "Error present" 0 "Error not present") ;
|
||||||
if (line.startsWith("VAL_ "))
|
if (line.startsWith("VAL_ "))
|
||||||
{
|
{
|
||||||
@@ -1347,8 +1430,8 @@ bool DBCFile::saveFile(QString fileName)
|
|||||||
int msgNumber = 1;
|
int msgNumber = 1;
|
||||||
int sigNumber = 1;
|
int sigNumber = 1;
|
||||||
QFile *outFile = new QFile(fileName);
|
QFile *outFile = new QFile(fileName);
|
||||||
QString nodesOutput, msgOutput, commentsOutput, valuesOutput, extMultiplexOutput;
|
QString nodesOutput, msgOutput, commentsOutput, valuesOutput, valueTableOutput, extMultiplexOutput;
|
||||||
QString defaultsOutput, attrValOutput;
|
QString defaultsOutput, attrValOutput, sigValTypeOutput;
|
||||||
bool hasExtendedMultiplexing = false;
|
bool hasExtendedMultiplexing = false;
|
||||||
|
|
||||||
if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text))
|
if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text))
|
||||||
@@ -1433,6 +1516,23 @@ bool DBCFile::saveFile(QString fileName)
|
|||||||
nodesOutput.append("\n");
|
nodesOutput.append("\n");
|
||||||
outFile->write(nodesOutput.toUtf8());
|
outFile->write(nodesOutput.toUtf8());
|
||||||
|
|
||||||
|
for (int x = 0; x < dbc_value_tables.count(); x++)
|
||||||
|
{
|
||||||
|
DBC_VAL_TABLE table = dbc_value_tables[x];
|
||||||
|
valueTableOutput.append("VAL_TABLE_ " + table.name);
|
||||||
|
for (int v = 0; v < table.valList.count(); v++)
|
||||||
|
{
|
||||||
|
DBC_VAL_ENUM_ENTRY val = table.valList[v];
|
||||||
|
valueTableOutput.append(" " + QString::number(val.value) + " \"" + val.descript + "\"");
|
||||||
|
}
|
||||||
|
valueTableOutput.append(";\n");
|
||||||
|
}
|
||||||
|
if (!valueTableOutput.isEmpty())
|
||||||
|
{
|
||||||
|
outFile->write(valueTableOutput.toUtf8());
|
||||||
|
valueTableOutput.clear();
|
||||||
|
}
|
||||||
|
|
||||||
//Go through all messages one at at time issuing the message line then all signals in there too.
|
//Go through all messages one at at time issuing the message line then all signals in there too.
|
||||||
for (int x = 0; x < messageHandler->getCount(); x++)
|
for (int x = 0; x < messageHandler->getCount(); x++)
|
||||||
{
|
{
|
||||||
@@ -1512,17 +1612,11 @@ bool DBCFile::saveFile(QString fileName)
|
|||||||
else msgOutput.append("0+");
|
else msgOutput.append("0+");
|
||||||
break;
|
break;
|
||||||
case SIGNED_INT:
|
case SIGNED_INT:
|
||||||
|
case SP_FLOAT:
|
||||||
|
case DP_FLOAT:
|
||||||
if (sig->intelByteOrder) msgOutput.append("1-");
|
if (sig->intelByteOrder) msgOutput.append("1-");
|
||||||
else msgOutput.append("0-");
|
else msgOutput.append("0-");
|
||||||
break;
|
break;
|
||||||
case SP_FLOAT:
|
|
||||||
if (sig->intelByteOrder) msgOutput.append("5-");
|
|
||||||
else msgOutput.append("2-");
|
|
||||||
break;
|
|
||||||
case DP_FLOAT:
|
|
||||||
if (sig->intelByteOrder) msgOutput.append("6-");
|
|
||||||
else msgOutput.append("3-");
|
|
||||||
break;
|
|
||||||
case STRING:
|
case STRING:
|
||||||
msgOutput.append("4-");
|
msgOutput.append("4-");
|
||||||
break;
|
break;
|
||||||
@@ -1533,6 +1627,8 @@ bool DBCFile::saveFile(QString fileName)
|
|||||||
msgOutput.append(" (" + QString::number(sig->factor) + "," + QString::number(sig->bias) + ") [" +
|
msgOutput.append(" (" + QString::number(sig->factor) + "," + QString::number(sig->bias) + ") [" +
|
||||||
QString::number(sig->min) + "|" + QString::number(sig->max) + "] \"" + sig->unitName
|
QString::number(sig->min) + "|" + QString::number(sig->max) + "] \"" + sig->unitName
|
||||||
+ "\" " + sig->receiver->name + "\n");
|
+ "\" " + sig->receiver->name + "\n");
|
||||||
|
if (sig->valType == SP_FLOAT || sig->valType == DP_FLOAT)
|
||||||
|
sigValTypeOutput.append("SIG_VALTYPE_ " + QString::number(ID) + " " + sig->name + " : " + QString::number(sig->valType == SP_FLOAT ? 1 : 2) + ";\n");
|
||||||
if (sig->comment.length() > 0)
|
if (sig->comment.length() > 0)
|
||||||
{
|
{
|
||||||
commentsOutput.append("CM_ SG_ " + QString::number(ID) + " " + sig->name + " \"" + sig->comment + "\";\n");
|
commentsOutput.append("CM_ SG_ " + QString::number(ID) + " " + sig->name + " \"" + sig->comment + "\";\n");
|
||||||
@@ -1685,11 +1781,14 @@ bool DBCFile::saveFile(QString fileName)
|
|||||||
outFile->write(attrValOutput.toUtf8());
|
outFile->write(attrValOutput.toUtf8());
|
||||||
outFile->write(valuesOutput.toUtf8());
|
outFile->write(valuesOutput.toUtf8());
|
||||||
outFile->write(extMultiplexOutput.toUtf8());
|
outFile->write(extMultiplexOutput.toUtf8());
|
||||||
|
if (!sigValTypeOutput.isEmpty())
|
||||||
|
outFile->write(sigValTypeOutput.toUtf8());
|
||||||
|
|
||||||
attrValOutput.clear();
|
attrValOutput.clear();
|
||||||
defaultsOutput.clear();
|
defaultsOutput.clear();
|
||||||
valuesOutput.clear();
|
valuesOutput.clear();
|
||||||
extMultiplexOutput.clear();
|
extMultiplexOutput.clear();
|
||||||
|
sigValTypeOutput.clear();
|
||||||
|
|
||||||
outFile->close();
|
outFile->close();
|
||||||
delete outFile;
|
delete outFile;
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ public:
|
|||||||
DBC_NODE *findNodeByName(QString name);
|
DBC_NODE *findNodeByName(QString name);
|
||||||
DBC_NODE *findNodeByNameAndComment(QString fullname);
|
DBC_NODE *findNodeByNameAndComment(QString fullname);
|
||||||
DBC_NODE *findNodeByIdx(int idx);
|
DBC_NODE *findNodeByIdx(int idx);
|
||||||
|
DBC_VAL_TABLE *findValueTableByName(QString name);
|
||||||
DBC_ATTRIBUTE *findAttributeByName(QString name, DBC_ATTRIBUTE_TYPE type = ATTR_TYPE_ANY);
|
DBC_ATTRIBUTE *findAttributeByName(QString name, DBC_ATTRIBUTE_TYPE type = ATTR_TYPE_ANY);
|
||||||
DBC_ATTRIBUTE *findAttributeByIdx(int idx);
|
DBC_ATTRIBUTE *findAttributeByIdx(int idx);
|
||||||
void findAttributesByType(DBC_ATTRIBUTE_TYPE typ, QList<DBC_ATTRIBUTE> *list);
|
void findAttributesByType(DBC_ATTRIBUTE_TYPE typ, QList<DBC_ATTRIBUTE> *list);
|
||||||
@@ -93,6 +94,7 @@ public:
|
|||||||
|
|
||||||
DBCMessageHandler *messageHandler;
|
DBCMessageHandler *messageHandler;
|
||||||
QList<DBC_NODE> dbc_nodes;
|
QList<DBC_NODE> dbc_nodes;
|
||||||
|
QList<DBC_VAL_TABLE> dbc_value_tables;
|
||||||
QList<DBC_ATTRIBUTE> dbc_attributes;
|
QList<DBC_ATTRIBUTE> dbc_attributes;
|
||||||
private:
|
private:
|
||||||
QString fileName;
|
QString fileName;
|
||||||
@@ -105,6 +107,7 @@ private:
|
|||||||
DBC_SIGNAL* parseSignalLine(QString line, DBC_MESSAGE *msg);
|
DBC_SIGNAL* parseSignalLine(QString line, DBC_MESSAGE *msg);
|
||||||
bool parseSignalMultiplexValueLine(QString line);
|
bool parseSignalMultiplexValueLine(QString line);
|
||||||
DBC_MESSAGE* parseMessageLine(QString line);
|
DBC_MESSAGE* parseMessageLine(QString line);
|
||||||
|
bool parseValueTableLine(QString line);
|
||||||
bool parseValueLine(QString line);
|
bool parseValueLine(QString line);
|
||||||
bool parseSignalValueTypeLine(QString line);
|
bool parseSignalValueTypeLine(QString line);
|
||||||
bool parseAttributeLine(QString line);
|
bool parseAttributeLine(QString line);
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QTranslator>
|
||||||
|
#include <QLocale>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
class SavvyCANApplication : public QApplication
|
class SavvyCANApplication : public QApplication
|
||||||
{
|
{
|
||||||
@@ -39,11 +42,31 @@ int main(int argc, char *argv[])
|
|||||||
a.setOrganizationName("EVTV");
|
a.setOrganizationName("EVTV");
|
||||||
a.setApplicationName("SavvyCAN");
|
a.setApplicationName("SavvyCAN");
|
||||||
a.setOrganizationDomain("evtv.me");
|
a.setOrganizationDomain("evtv.me");
|
||||||
|
|
||||||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||||
|
QSettings settings;
|
||||||
|
|
||||||
|
QString localeString = settings.value("Main/Language").toString();
|
||||||
|
if (localeString.isEmpty()) {
|
||||||
|
QLocale sysLocale = QLocale::system();
|
||||||
|
localeString = sysLocale.name();
|
||||||
|
settings.setValue("Main/Language", localeString);
|
||||||
|
}
|
||||||
|
|
||||||
|
QTranslator translator;
|
||||||
|
QLocale locale(localeString);
|
||||||
|
QString lang = locale.name();
|
||||||
|
QString shortLang = locale.name().left(2);
|
||||||
|
|
||||||
|
if (QString translationDir = QCoreApplication::applicationDirPath() + "/translations"; !translator.load("SavvyCAN_" + lang, translationDir)) {
|
||||||
|
translator.load("SavvyCAN_" + shortLang, translationDir);
|
||||||
|
}
|
||||||
|
a.installTranslator(&translator);
|
||||||
|
|
||||||
|
qInfo() << "Locale Value is:" << locale.name();
|
||||||
|
|
||||||
a.mainWindow = new MainWindow();
|
a.mainWindow = new MainWindow();
|
||||||
|
|
||||||
QSettings settings;
|
|
||||||
int fontSize = settings.value("Main/FontSize", 9).toUInt();
|
int fontSize = settings.value("Main/FontSize", 9).toUInt();
|
||||||
QFont sysFont = QFont(); //get default font
|
QFont sysFont = QFont(); //get default font
|
||||||
sysFont.setPointSize(fontSize);
|
sysFont.setPointSize(fontSize);
|
||||||
|
|||||||
@@ -3,6 +3,10 @@
|
|||||||
#include "helpwindow.h"
|
#include "helpwindow.h"
|
||||||
#include <qevent.h>
|
#include <qevent.h>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QRegExp>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDebug>
|
||||||
#include "simplecrypt.h"
|
#include "simplecrypt.h"
|
||||||
|
|
||||||
//using this simple encryption library to obfuscate stored password a bit. It's not super secure but better than
|
//using this simple encryption library to obfuscate stored password a bit. It's not super secure but better than
|
||||||
@@ -10,6 +14,42 @@
|
|||||||
//whatever key, you'd see it. Just behave yourselves
|
//whatever key, you'd see it. Just behave yourselves
|
||||||
SimpleCrypt crypto(Q_UINT64_C(0xdeadbeefface6285));
|
SimpleCrypt crypto(Q_UINT64_C(0xdeadbeefface6285));
|
||||||
|
|
||||||
|
static QStringList availableLanguageCodes()
|
||||||
|
{
|
||||||
|
QStringList codes;
|
||||||
|
QDir transDir(QCoreApplication::applicationDirPath() + "/translations");
|
||||||
|
QStringList list = transDir.entryList(QStringList() << "SavvyCAN_*.qm");
|
||||||
|
//* just for test purposes
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
// maybe only ts files exist during development
|
||||||
|
list = transDir.entryList(QStringList() << "SavvyCAN_*.ts");
|
||||||
|
}
|
||||||
|
for (QString file : list) {
|
||||||
|
QRegExp re("SavvyCAN_(.*)\\.(qm|ts)");
|
||||||
|
if (re.indexIn(file) != -1) {
|
||||||
|
codes << re.cap(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return codes;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainSettingsDialog::populateLanguageCombo()
|
||||||
|
{
|
||||||
|
ui->comboLanguage->clear();
|
||||||
|
QStringList codes = availableLanguageCodes();
|
||||||
|
if (codes.isEmpty()) {
|
||||||
|
codes << "en"; // fallback
|
||||||
|
}
|
||||||
|
for (const QString &code : codes) {
|
||||||
|
QLocale locale(code);
|
||||||
|
QString name = locale.nativeLanguageName();
|
||||||
|
qInfo() << code;
|
||||||
|
if (name.isEmpty())
|
||||||
|
name = code;
|
||||||
|
ui->comboLanguage->addItem(name, code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MainSettingsDialog::MainSettingsDialog(QWidget *parent) :
|
MainSettingsDialog::MainSettingsDialog(QWidget *parent) :
|
||||||
QDialog(parent),
|
QDialog(parent),
|
||||||
ui(new Ui::MainSettingsDialog)
|
ui(new Ui::MainSettingsDialog)
|
||||||
@@ -17,6 +57,8 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) :
|
|||||||
QSettings settings;
|
QSettings settings;
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
populateLanguageCombo();
|
||||||
|
|
||||||
//TODO: This is still hard coded to support only two buses. Sometimes there is none, sometimes 1, sometimes much more than 2. Fix this.
|
//TODO: This is still hard coded to support only two buses. Sometimes there is none, sometimes 1, sometimes much more than 2. Fix this.
|
||||||
ui->comboSendingBus->addItem(tr("None"));
|
ui->comboSendingBus->addItem(tr("None"));
|
||||||
ui->comboSendingBus->addItem(tr("0"));
|
ui->comboSendingBus->addItem(tr("0"));
|
||||||
@@ -90,7 +132,15 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) :
|
|||||||
ui->cbUseFiltered->setChecked(settings.value("Main/UseFiltered", false).toBool());
|
ui->cbUseFiltered->setChecked(settings.value("Main/UseFiltered", false).toBool());
|
||||||
ui->cbUseOpenGL->setChecked(settings.value("Main/UseOpenGL", false).toBool());
|
ui->cbUseOpenGL->setChecked(settings.value("Main/UseOpenGL", false).toBool());
|
||||||
ui->cbFilterLabeling->setChecked(settings.value("Main/FilterLabeling", true).toBool());
|
ui->cbFilterLabeling->setChecked(settings.value("Main/FilterLabeling", true).toBool());
|
||||||
|
ui->cbFilterLabeling->setChecked(settings.value("Main/FilterLabeling", true).toBool());
|
||||||
ui->cbIgnoreDBCColors->setChecked(settings.value("Main/IgnoreDBCColors", false).toBool());
|
ui->cbIgnoreDBCColors->setChecked(settings.value("Main/IgnoreDBCColors", false).toBool());
|
||||||
|
ui->cbColorsByCanId->setChecked(settings.value("Main/ColorsByCanId", false).toBool());
|
||||||
|
|
||||||
|
if (QString savedLang = settings.value("Main/Language").toString(); !savedLang.isEmpty()) {
|
||||||
|
int idx = ui->comboLanguage->findData(savedLang);
|
||||||
|
if (idx >= 0)
|
||||||
|
ui->comboLanguage->setCurrentIndex(idx);
|
||||||
|
}
|
||||||
|
|
||||||
int maxFramesDefault;
|
int maxFramesDefault;
|
||||||
if (QSysInfo::WordSize > 32)
|
if (QSysInfo::WordSize > 32)
|
||||||
@@ -108,6 +158,7 @@ MainSettingsDialog::MainSettingsDialog(QWidget *parent) :
|
|||||||
ui->spinBytesPerLine->setValue(settings.value("Main/BytesPerLine", 8).toInt());
|
ui->spinBytesPerLine->setValue(settings.value("Main/BytesPerLine", 8).toInt());
|
||||||
|
|
||||||
//just for simplicity they all call the same function and that function updates all settings at once
|
//just for simplicity they all call the same function and that function updates all settings at once
|
||||||
|
connect(ui->comboLanguage, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSettings()));
|
||||||
connect(ui->cbDisplayHex, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
connect(ui->cbDisplayHex, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
||||||
connect(ui->cbFlowAutoRef, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
connect(ui->cbFlowAutoRef, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
||||||
connect(ui->cbFlowUseTimestamp, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
connect(ui->cbFlowUseTimestamp, SIGNAL(toggled(bool)), this, SLOT(updateSettings()));
|
||||||
@@ -177,6 +228,7 @@ void MainSettingsDialog::updateSettings()
|
|||||||
{
|
{
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
|
|
||||||
|
settings.setValue("Main/Language", ui->comboLanguage->currentData().toString());
|
||||||
settings.setValue("Main/UseHex", ui->cbDisplayHex->isChecked());
|
settings.setValue("Main/UseHex", ui->cbDisplayHex->isChecked());
|
||||||
settings.setValue("FlowView/AutoRef", ui->cbFlowAutoRef->isChecked());
|
settings.setValue("FlowView/AutoRef", ui->cbFlowAutoRef->isChecked());
|
||||||
settings.setValue("FlowView/UseTimestamp", ui->cbFlowUseTimestamp->isChecked());
|
settings.setValue("FlowView/UseTimestamp", ui->cbFlowUseTimestamp->isChecked());
|
||||||
@@ -208,6 +260,7 @@ void MainSettingsDialog::updateSettings()
|
|||||||
settings.setValue("Main/MaximumFrames", ui->spinMaximumFrames->value());
|
settings.setValue("Main/MaximumFrames", ui->spinMaximumFrames->value());
|
||||||
settings.setValue("Main/BytesPerLine", ui->spinBytesPerLine->value());
|
settings.setValue("Main/BytesPerLine", ui->spinBytesPerLine->value());
|
||||||
settings.setValue("Main/FontFixedWidth", ui->cbFontFixedWidth->isChecked());
|
settings.setValue("Main/FontFixedWidth", ui->cbFontFixedWidth->isChecked());
|
||||||
|
settings.setValue("Main/ColorsByCanId", ui->cbColorsByCanId->isChecked());
|
||||||
|
|
||||||
settings.sync();
|
settings.sync();
|
||||||
emit updatedSettings();
|
emit updatedSettings();
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ public slots:
|
|||||||
private:
|
private:
|
||||||
Ui::MainSettingsDialog *ui;
|
Ui::MainSettingsDialog *ui;
|
||||||
|
|
||||||
|
void populateLanguageCombo();
|
||||||
void closeEvent(QCloseEvent *event);
|
void closeEvent(QCloseEvent *event);
|
||||||
bool eventFilter(QObject *obj, QEvent *event);
|
bool eventFilter(QObject *obj, QEvent *event);
|
||||||
};
|
};
|
||||||
|
|||||||
+73
-9
@@ -10,6 +10,7 @@
|
|||||||
#include "utility.h"
|
#include "utility.h"
|
||||||
#include "filterutility.h"
|
#include "filterutility.h"
|
||||||
|
|
||||||
|
#include <QClipboard>
|
||||||
/*
|
/*
|
||||||
Some notes on things I'd like to put into the program but haven't put on github (yet)
|
Some notes on things I'd like to put into the program but haven't put on github (yet)
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
useHex = true;
|
useHex = true;
|
||||||
|
useColorsByCanId = false;
|
||||||
selfRef = this;
|
selfRef = this;
|
||||||
|
|
||||||
this->setWindowTitle("Savvy CAN V" + QString::number(VERSION) + " [Built " + QString(__DATE__) +"]");
|
this->setWindowTitle("Savvy CAN V" + QString::number(VERSION) + " [Built " + QString(__DATE__) +"]");
|
||||||
@@ -143,6 +144,11 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||||||
connect(ui->canFramesView, &QAbstractItemView::clicked, this, &MainWindow::gridClicked);
|
connect(ui->canFramesView, &QAbstractItemView::clicked, this, &MainWindow::gridClicked);
|
||||||
connect(ui->canFramesView, &QAbstractItemView::doubleClicked, this, &MainWindow::gridDoubleClicked);
|
connect(ui->canFramesView, &QAbstractItemView::doubleClicked, this, &MainWindow::gridDoubleClicked);
|
||||||
ui->canFramesView->setContextMenuPolicy(Qt::CustomContextMenu);
|
ui->canFramesView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
|
||||||
|
copyAct = new QAction(tr("Copy"), this);
|
||||||
|
copyAct->setShortcut(QKeySequence::Copy);
|
||||||
|
connect(copyAct, &QAction::triggered, this, &MainWindow::copyFromTable);
|
||||||
|
ui->canFramesView->addAction(copyAct);
|
||||||
connect(ui->canFramesView, &QAbstractItemView::customContextMenuRequested, this, &MainWindow::gridContextMenuRequest);
|
connect(ui->canFramesView, &QAbstractItemView::customContextMenuRequested, this, &MainWindow::gridContextMenuRequest);
|
||||||
|
|
||||||
connect(model, &CANFrameModel::updatedFiltersList, this, &MainWindow::updateFilterList);
|
connect(model, &CANFrameModel::updatedFiltersList, this, &MainWindow::updateFilterList);
|
||||||
@@ -415,6 +421,9 @@ void MainWindow::readUpdateableSettings()
|
|||||||
model->setHexMode(useHex);
|
model->setHexMode(useHex);
|
||||||
Utility::decimalMode = !useHex;
|
Utility::decimalMode = !useHex;
|
||||||
|
|
||||||
|
useColorsByCanId = settings.value("Main/ColorsByCanId", false).toBool();
|
||||||
|
model->setUseColorsByCanId(useColorsByCanId);
|
||||||
|
|
||||||
bool tempBool;
|
bool tempBool;
|
||||||
TimeStyle ts = TS_MICROS;
|
TimeStyle ts = TS_MICROS;
|
||||||
tempBool = settings.value("Main/TimeSeconds", false).toBool();
|
tempBool = settings.value("Main/TimeSeconds", false).toBool();
|
||||||
@@ -741,20 +750,75 @@ void MainWindow::gridContextMenuRequest(QPoint pos)
|
|||||||
{
|
{
|
||||||
QModelIndex idx = ui->canFramesView->indexAt(pos); //figure out where in the view we clicked (row, column)
|
QModelIndex idx = ui->canFramesView->indexAt(pos); //figure out where in the view we clicked (row, column)
|
||||||
qDebug() << "Pos: " << pos << " Row :" << idx.row() << " Col: " << idx.column();
|
qDebug() << "Pos: " << pos << " Row :" << idx.row() << " Col: " << idx.column();
|
||||||
qDebug() << "Data: " << idx.data();
|
|
||||||
if (idx.column() == 8) //we're over the DATA column
|
if (!idx.isValid()) return;
|
||||||
{
|
|
||||||
contextMenuPosition = pos;
|
|
||||||
|
|
||||||
QMenu *menu = new QMenu(this);
|
QMenu *menu = new QMenu(this);
|
||||||
menu->setAttribute(Qt::WA_DeleteOnClose);
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
|
||||||
menu->addAction(tr("Add to a new graphing window"), this, SLOT(setupAddToNewGraph()));
|
menu->addAction(copyAct);
|
||||||
menu->addSeparator();
|
|
||||||
menu->addAction(tr("Add to latest graphing window"), this, SLOT(setupSendToLatestGraphWindow()));
|
|
||||||
|
|
||||||
menu->popup(ui->canFramesView->mapToGlobal(pos));
|
if (idx.column() == 8) //we're over the DATA column
|
||||||
|
{
|
||||||
|
contextMenuPosition = pos;
|
||||||
|
menu->addSeparator();
|
||||||
|
menu->addAction(tr("Add to a new graphing window"), this, SLOT(setupAddToNewGraph()));
|
||||||
|
menu->addAction(tr("Add to latest graphing window"), this, SLOT(setupSendToLatestGraphWindow()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menu->popup(ui->canFramesView->viewport()->mapToGlobal(pos));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::copyFromTable()
|
||||||
|
{
|
||||||
|
copySelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::copySelection()
|
||||||
|
{
|
||||||
|
QItemSelectionModel *selectionModel = ui->canFramesView->selectionModel();
|
||||||
|
QModelIndexList selectedIndexes = selectionModel->selectedIndexes();
|
||||||
|
|
||||||
|
if(selectedIndexes.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// QModelIndex::operator< sorts by row and then by column.
|
||||||
|
std::sort(selectedIndexes.begin(), selectedIndexes.end());
|
||||||
|
|
||||||
|
QString selectedText;
|
||||||
|
int currentRow = -1;
|
||||||
|
int lastRow = selectedIndexes.last().row();
|
||||||
|
|
||||||
|
for(const QModelIndex ¤t : selectedIndexes)
|
||||||
|
{
|
||||||
|
if (currentRow != -1 && current.row() != currentRow)
|
||||||
|
{
|
||||||
|
// remove last tab
|
||||||
|
if (selectedText.endsWith(QLatin1Char('\t')))
|
||||||
|
selectedText.chop(1);
|
||||||
|
selectedText.append(QLatin1Char('\n'));
|
||||||
|
}
|
||||||
|
currentRow = current.row();
|
||||||
|
|
||||||
|
QString cellText = current.data(Qt::DisplayRole).toString();
|
||||||
|
|
||||||
|
// Replace newlines within a cell to avoid breaking the table structure in Excel
|
||||||
|
cellText.replace(QLatin1Char('\n'), QLatin1String(" "));
|
||||||
|
|
||||||
|
selectedText.append(cellText);
|
||||||
|
|
||||||
|
if (current.row() != lastRow || current != selectedIndexes.last())
|
||||||
|
{
|
||||||
|
selectedText.append(QLatin1Char('\t'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove last tab if it exists
|
||||||
|
if (selectedText.endsWith(QLatin1Char('\t')))
|
||||||
|
selectedText.chop(1);
|
||||||
|
|
||||||
|
QClipboard *clipboard = QApplication::clipboard();
|
||||||
|
clipboard->setText(selectedText);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MainWindow::getSignalNameFromPosition(QPoint pos)
|
QString MainWindow::getSignalNameFromPosition(QPoint pos)
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ private slots:
|
|||||||
void gridClicked(QModelIndex);
|
void gridClicked(QModelIndex);
|
||||||
void gridDoubleClicked(QModelIndex);
|
void gridDoubleClicked(QModelIndex);
|
||||||
void gridContextMenuRequest(QPoint pos);
|
void gridContextMenuRequest(QPoint pos);
|
||||||
|
void copyFromTable();
|
||||||
void setupAddToNewGraph();
|
void setupAddToNewGraph();
|
||||||
void setupSendToLatestGraphWindow();
|
void setupSendToLatestGraphWindow();
|
||||||
void interpretToggled(bool);
|
void interpretToggled(bool);
|
||||||
@@ -147,6 +148,7 @@ signals:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::MainWindow *ui;
|
Ui::MainWindow *ui;
|
||||||
|
QAction *copyAct;
|
||||||
static MainWindow *selfRef;
|
static MainWindow *selfRef;
|
||||||
|
|
||||||
//canbus related data
|
//canbus related data
|
||||||
@@ -160,6 +162,7 @@ private:
|
|||||||
int rxFrames;
|
int rxFrames;
|
||||||
bool inhibitFilterUpdate;
|
bool inhibitFilterUpdate;
|
||||||
bool useHex;
|
bool useHex;
|
||||||
|
bool useColorsByCanId;
|
||||||
bool allowCapture;
|
bool allowCapture;
|
||||||
bool ignoreDBCColors;
|
bool ignoreDBCColors;
|
||||||
bool CSVAbsTime;
|
bool CSVAbsTime;
|
||||||
@@ -213,6 +216,7 @@ private:
|
|||||||
//private methods
|
//private methods
|
||||||
QString getSignalNameFromPosition(QPoint pos);
|
QString getSignalNameFromPosition(QPoint pos);
|
||||||
uint32_t getMessageIDFromPosition(QPoint pos);
|
uint32_t getMessageIDFromPosition(QPoint pos);
|
||||||
|
void copySelection();
|
||||||
void handleSaveDecodedMethod(bool csv);
|
void handleSaveDecodedMethod(bool csv);
|
||||||
void saveDecodedTextFile(QString);
|
void saveDecodedTextFile(QString);
|
||||||
void saveDecodedTextFileAsColumns(QString);
|
void saveDecodedTextFileAsColumns(QString);
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Scripts
|
||||||
|
|
||||||
|
To run this scripts, first you must authorize your terminal to run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x script/*.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can run by using this command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/the/script/you/want/to/run
|
||||||
|
```
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
PROJECT_ROOT="$(pwd)"
|
||||||
|
|
||||||
|
# To add this tool in Qt Creator:
|
||||||
|
# Category: Linguist
|
||||||
|
# Name: Release Translations (lrelease)
|
||||||
|
# Executable: lrelease
|
||||||
|
# Arguments: SavvyCAN.pro
|
||||||
|
# Working Directory: %{ActiveProject:Path}
|
||||||
|
|
||||||
|
echo "Generating .qm files..."
|
||||||
|
|
||||||
|
lrelease -verbose "$PROJECT_ROOT"/SavvyCAN.pro
|
||||||
|
|
||||||
|
echo ".qm files generated."
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
PROJECT_ROOT="$(pwd)"
|
||||||
|
|
||||||
|
# To add this tool in Qt Creator:
|
||||||
|
# Category: Linguist
|
||||||
|
# Name: Update Translations (lupdate)
|
||||||
|
# Executable: lupdate
|
||||||
|
# Arguments: -project SavvyCAN.pro
|
||||||
|
# Working Directory: %{ActiveProject:Path}
|
||||||
|
|
||||||
|
echo "Updating translation files..."
|
||||||
|
|
||||||
|
lupdate -verbose "$PROJECT_ROOT"/SavvyCAN.pro
|
||||||
|
|
||||||
|
echo "Translation files updated."
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,47 @@
|
|||||||
|
<!DOCTYPE QPH>
|
||||||
|
<QPH sourcelanguage="en" language="pt_BR">
|
||||||
|
<phrase>
|
||||||
|
<source>New</source>
|
||||||
|
<target>Novo</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Save</source>
|
||||||
|
<target>Salvar</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Cancel</source>
|
||||||
|
<target>Cancelar</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Connect</source>
|
||||||
|
<target>Conectar</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Disconnect</source>
|
||||||
|
<target>Desconectar</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Yes</source>
|
||||||
|
<target>Sim</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>No</source>
|
||||||
|
<target>Não</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Bus</source>
|
||||||
|
<target>Barramento</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Frame</source>
|
||||||
|
<target>Frame</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Frames</source>
|
||||||
|
<target>Frames</target>
|
||||||
|
</phrase>
|
||||||
|
<phrase>
|
||||||
|
<source>Scan</source>
|
||||||
|
<target>Varredura</target>
|
||||||
|
</phrase>
|
||||||
|
</QPH>
|
||||||
@@ -53,6 +53,20 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_8">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_13">
|
||||||
|
<property name="text">
|
||||||
|
<string>Language</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="comboLanguage"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
@@ -97,6 +111,13 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QCheckBox" name="cbColorsByCanId">
|
||||||
|
<property name="text">
|
||||||
|
<string>Color rows by CAN ID</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="cbUseOpenGL">
|
<widget class="QCheckBox" name="cbUseOpenGL">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|||||||
@@ -10,6 +10,9 @@
|
|||||||
#include <QRect>
|
#include <QRect>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
|
#include <QColor>
|
||||||
|
#include <QString>
|
||||||
|
#include <array>
|
||||||
//#include <QDesktopWidget>
|
//#include <QDesktopWidget>
|
||||||
|
|
||||||
enum TimeStyle
|
enum TimeStyle
|
||||||
@@ -20,6 +23,44 @@ enum TimeStyle
|
|||||||
TS_CLOCK
|
TS_CLOCK
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// 32 dark, saturated colors — all designed for readability on white backgrounds.
|
||||||
|
// Minimum contrast ratio ≥ 4.5:1 against white (WCAG AA).
|
||||||
|
static constexpr std::array<QRgb, 32> kRowPalette = {{
|
||||||
|
0xFFC62828, // 0 Red 800
|
||||||
|
0xFFE53935, // 1 Red 600
|
||||||
|
0xFFD84315, // 2 Deep Orange 800
|
||||||
|
0xFFEF6C00, // 3 Orange 800
|
||||||
|
0xFFF9A825, // 4 Amber 800
|
||||||
|
0xFF558B2F, // 5 Green 800
|
||||||
|
0xFF2E7D32, // 6 Green 900
|
||||||
|
0xFF00695C, // 7 Teal 800
|
||||||
|
0xFF00838F, // 8 Cyan 800
|
||||||
|
0xFF0277BD, // 9 Blue 800
|
||||||
|
0xFF1565C0, // 10 Blue 900
|
||||||
|
0xFF283593, // 11 Indigo 900
|
||||||
|
0xFF4527A0, // 12 Deep Purple 800
|
||||||
|
0xFF6A1B9A, // 13 Purple 900
|
||||||
|
0xFF880E4F, // 14 Pink 900
|
||||||
|
0xFFAD1457, // 15 Pink 800
|
||||||
|
0xFF37474F, // 16 Blue Grey 800
|
||||||
|
0xFF4E342E, // 17 Brown 800
|
||||||
|
0xFF827717, // 18 Lime 900
|
||||||
|
0xFF1B5E20, // 19 Green 900 alt
|
||||||
|
0xFF006064, // 20 Cyan 900
|
||||||
|
0xFF01579B, // 21 Light Blue 900
|
||||||
|
0xFF0D47A1, // 22 Blue 900 alt
|
||||||
|
0xFF311B92, // 23 Deep Purple 900
|
||||||
|
0xFF4A148C, // 24 Purple 900 alt
|
||||||
|
0xFFB71C1C, // 25 Red 900
|
||||||
|
0xFFBF360C, // 26 Deep Orange 900
|
||||||
|
0xFFE65100, // 27 Orange 900
|
||||||
|
0xFF33691E, // 28 Light Green 900
|
||||||
|
0xFF004D40, // 29 Teal 900
|
||||||
|
0xFF263238, // 30 Blue Grey 900
|
||||||
|
0xFF5D4037, // 31 Brown 700
|
||||||
|
}};
|
||||||
|
|
||||||
class Utility
|
class Utility
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -344,6 +385,35 @@ public:
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FNV-1a 32-bit hash — fast, good distribution, no dependencies.
|
||||||
|
static quint32 hashString(const QString &s)
|
||||||
|
{
|
||||||
|
quint32 hash = 2166136261u;
|
||||||
|
for (const QChar ch : s) {
|
||||||
|
hash ^= static_cast<quint32>(ch.unicode());
|
||||||
|
hash *= 16777619u;
|
||||||
|
}
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a background QColor deterministically chosen from the palette.
|
||||||
|
// The same string always maps to the same color.
|
||||||
|
static QColor colorForString(const QString &s)
|
||||||
|
{
|
||||||
|
const quint32 idx = hashString(s) % static_cast<quint32>(kRowPalette.size());
|
||||||
|
// Use a light tint (alpha ~18%) as the row background so text stays readable.
|
||||||
|
QColor base(kRowPalette[idx]);
|
||||||
|
base.setAlpha(46); // ~18% — comfortable on white, still clearly tinted
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opaque variant — useful for badges, tags, or legend swatches.
|
||||||
|
static QColor solidColorForString(const QString &s)
|
||||||
|
{
|
||||||
|
const quint32 idx = hashString(s) % static_cast<quint32>(kRowPalette.size());
|
||||||
|
return QColor(kRowPalette[idx]);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // UTILITY_H
|
#endif // UTILITY_H
|
||||||
|
|||||||
Reference in New Issue
Block a user