420 lines
14 KiB
C++
420 lines
14 KiB
C++
#ifndef UTILITY_H
|
|
#define UTILITY_H
|
|
|
|
#include <Qt>
|
|
#include <stdint.h>
|
|
#include <QByteArray>
|
|
#include <QDateTime>
|
|
#include <QDebug>
|
|
#include <QApplication>
|
|
#include <QRect>
|
|
#include <QComboBox>
|
|
#include <QStandardItemModel>
|
|
#include <QColor>
|
|
#include <QString>
|
|
#include <array>
|
|
//#include <QDesktopWidget>
|
|
|
|
enum TimeStyle
|
|
{
|
|
TS_SECONDS,
|
|
TS_MICROS,
|
|
TS_MILLIS,
|
|
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
|
|
{
|
|
public:
|
|
|
|
static bool decimalMode;
|
|
static TimeStyle timeStyle;
|
|
static QString timeFormat;
|
|
|
|
static QString fullyQualifiedNameSeperator;
|
|
|
|
static void SetComboBoxItemEnabled(QComboBox * comboBox, int index, bool enabled)
|
|
{
|
|
auto * model = qobject_cast<QStandardItemModel*>(comboBox->model());
|
|
assert(model);
|
|
if(!model) return;
|
|
|
|
auto * item = model->item(index);
|
|
assert(item);
|
|
if(!item) return;
|
|
item->setEnabled(enabled);
|
|
}
|
|
|
|
//determines whether the window position is within any available screens. If it is not we default
|
|
//back to 0,0 which is going to be on screen. This fixes a problem where some operating systems would
|
|
//otherwise let you put windows on a second monitor, disconnect that monitor, and still put windows on it.
|
|
static QPoint constrainedWindowPos(QPoint originalPos)
|
|
{
|
|
QScreen *screen = QGuiApplication::screenAt(originalPos);
|
|
if (!screen)
|
|
{
|
|
return QPoint(0,0);
|
|
}
|
|
return originalPos;
|
|
}
|
|
|
|
static QString unQuote(QString inStr)
|
|
{
|
|
QStringList temp;
|
|
temp = inStr.split('\"');
|
|
if (temp.length() >= 3)
|
|
return temp[1];
|
|
return inStr;
|
|
}
|
|
|
|
static uint64_t ParseStringToNum(QByteArray input)
|
|
{
|
|
uint64_t temp = 0;
|
|
|
|
input = input.toUpper();
|
|
if (input.startsWith("0X") || input.startsWith("X")) //hex number
|
|
{
|
|
if (input.length() < 3) temp = 0;
|
|
else temp = input.right(input.size() - 2).toLongLong(nullptr, 16);
|
|
}
|
|
else if (input.startsWith("0B") || input.startsWith("B")) //binary number
|
|
{
|
|
input = input.right(input.size() - 1); //remove the B
|
|
for (int i = 0; i < input.length(); i++)
|
|
{
|
|
if (input[i] == '1') temp += (uint64_t)1 << (input.length() - i - 1);
|
|
}
|
|
}
|
|
else //decimal number
|
|
{
|
|
temp = input.toLongLong();
|
|
}
|
|
|
|
return temp;
|
|
}
|
|
|
|
static uint64_t ParseStringToNum(QString input)
|
|
{
|
|
return ParseStringToNum(input.toUtf8());
|
|
}
|
|
|
|
static uint ParseStringToNum2(QString pInput, bool* pOk_p = nullptr)
|
|
{
|
|
if(pInput.startsWith("0b"))
|
|
{
|
|
pInput.remove(0, 2);
|
|
return pInput.toUInt(pOk_p, 2);
|
|
}
|
|
|
|
return pInput.toUInt(pOk_p, 0);
|
|
}
|
|
|
|
static uint64_t GetTimeMS()
|
|
{
|
|
QDateTime stamp = QDateTime::currentDateTime();
|
|
return (((static_cast<uint64_t>(stamp.time().hour()) * 3600ull) + (static_cast<uint64_t>(stamp.time().minute()) * 60ull)
|
|
+ (static_cast<uint64_t>(stamp.time().second())) * 1000ull) + static_cast<uint64_t>(stamp.time().msec()));
|
|
}
|
|
|
|
//prints hex numbers in uppercase with 0's filling out the number depending
|
|
//on the size needed. Promotes hex numbers to either 2, 4, or 8 digits
|
|
static QString formatHexNum(uint64_t input)
|
|
{
|
|
if (input < 256)
|
|
return "0x" + QString::number(input, 16).toUpper().rightJustified(2,'0');
|
|
if (input < 65536)
|
|
return "0x" + QString::number(input, 16).toUpper().rightJustified(4,'0');
|
|
if (input < 4294967296)
|
|
return "0x" + QString::number(input, 16).toUpper().rightJustified(8,'0');
|
|
return "0x" + QString::number(input, 16).toUpper().rightJustified(16,'0');
|
|
}
|
|
|
|
//uses decimalMode to see if it should show value as decimal or hex
|
|
static QString formatNumber(uint64_t value)
|
|
{
|
|
if (decimalMode)
|
|
{
|
|
return QString::number(value, 10);
|
|
}
|
|
else return formatHexNum(value);
|
|
}
|
|
|
|
static QString formatCANID(uint64_t id, bool extended)
|
|
{
|
|
if (decimalMode) return QString::number(id, 10);
|
|
|
|
if (extended)
|
|
{
|
|
return "0x" + QString::number(id, 16).toUpper().rightJustified(8,'0');
|
|
}
|
|
else
|
|
{
|
|
id = id & 0x7FF;
|
|
return "0x" + QString::number(id, 16).toUpper().rightJustified(3,'0');
|
|
}
|
|
}
|
|
|
|
static QString formatCANID(uint64_t id)
|
|
{
|
|
if (id < 0x800) return formatCANID(id, false);
|
|
return formatCANID(id, true);
|
|
}
|
|
|
|
static QString formatByteAsBinary(uint8_t value)
|
|
{
|
|
QString output;
|
|
for (int b = 7; b >= 0; b--)
|
|
{
|
|
if (value & (1 << b)) output += "1";
|
|
else output += "0";
|
|
}
|
|
return output;
|
|
}
|
|
|
|
static QString formatByteAsHex(uint8_t value)
|
|
{
|
|
return QString::number(value, 16).toUpper().rightJustified(2,'0');
|
|
}
|
|
|
|
static QVariant formatTimestamp(uint64_t timestamp)
|
|
{
|
|
switch (timeStyle)
|
|
{
|
|
case TS_CLOCK:
|
|
return QDateTime::fromMSecsSinceEpoch(timestamp / 1000);
|
|
break;
|
|
case TS_MILLIS:
|
|
return (double)timestamp / 1000.0;
|
|
break;
|
|
case TS_MICROS:
|
|
return (unsigned long long)(timestamp);
|
|
break;
|
|
case TS_SECONDS:
|
|
return (double)timestamp / 1000000.0;
|
|
break;
|
|
}
|
|
return QVariant();
|
|
}
|
|
|
|
//parses the input string to grab as much of it as possible while staying alpha numeric
|
|
static QString grabAlphaNumeric(QString &input)
|
|
{
|
|
QString builder;
|
|
QChar thisChar;
|
|
for (int i = 0; i < input.length(); i++)
|
|
{
|
|
thisChar = input[i];
|
|
if (thisChar.isLetterOrNumber() || thisChar == ':' || thisChar == '~') builder.append(input[i]);
|
|
else
|
|
{
|
|
//qDebug() << "i: "<< i << " len: " << input.length();
|
|
if (i < (input.length() - 1)) input = input.right(input.length() - i);
|
|
else input = "";
|
|
return builder;
|
|
}
|
|
}
|
|
//qDebug() << "Reached end of string in grabAlphaNumeric";
|
|
input = "";
|
|
return builder;
|
|
}
|
|
|
|
static QString grabOperation(QString &input)
|
|
{
|
|
QString builder;
|
|
QChar thisChar = input[0];
|
|
|
|
if (thisChar == '+' || thisChar == '-' || thisChar == '*' || thisChar == '/' || thisChar == '^' || thisChar == '&' || thisChar == '|' || thisChar == '=' || thisChar == '%')
|
|
{
|
|
input = input.right(input.length() - 1);
|
|
builder = thisChar;
|
|
}
|
|
return builder;
|
|
}
|
|
|
|
static int getByteFromBitPosition(int bitPos)
|
|
{
|
|
return bitPos / 8;
|
|
}
|
|
|
|
static int getBitFromBitPosition(int bitPos)
|
|
{
|
|
return bitPos & 7;
|
|
}
|
|
|
|
//simple linear interpolation between value1 and value2. sample point is 0.0 to 1.0
|
|
static double Lerp(double value1, double value2, double samplePoint)
|
|
{
|
|
return (value1 * (1.0 - samplePoint)) + (value2 * samplePoint);
|
|
}
|
|
|
|
/* A unified function that can extract a signal from the (up to) 64 bits of data bytes in a CAN frame
|
|
* handles both little and big endian signals (and floats too).
|
|
*/
|
|
static int64_t processIntegerSignal(const QByteArray data, int startBit, int sigSize, bool littleEndian, bool isSigned)
|
|
{
|
|
|
|
uint64_t result = 0;
|
|
int bit = 0;
|
|
|
|
int maxBytes = (startBit + sigSize) / 8;
|
|
if (data.size() < maxBytes) return 0; //if signal extends past the end of data then abort
|
|
|
|
if (littleEndian)
|
|
{
|
|
/*
|
|
int currByte = (startBit) / 8;
|
|
int currOffset = startBit - (currByte * 8);
|
|
int remainingBits = qMax(0, (sigSize - (8 - currOffset)) );
|
|
int prevBits = qMin((8 - currOffset), sigSize);
|
|
result = data[currByte] >> currOffset;
|
|
result &= ( (1 << sigSize) - 1); //doesn't hurt to do this even if sigSize is way larger than the # of bits we've got so far
|
|
|
|
while (remainingBits > 0)
|
|
{
|
|
currByte++;
|
|
if (remainingBits >= 8) //use this entire byte, its easy
|
|
{
|
|
result += data[currByte] << prevBits;
|
|
remainingBits -= 8;
|
|
prevBits += 8;
|
|
}
|
|
else //use only part of this byte. We're going to need to mask it
|
|
{
|
|
result += ((data[currByte] & ((1 << remainingBits) - 1) ) << prevBits);
|
|
remainingBits = 0;
|
|
}
|
|
}*/
|
|
|
|
bit = startBit;
|
|
for (int bitpos = 0; bitpos < sigSize; bitpos++)
|
|
{
|
|
if (bit < 512) {
|
|
int bytePos = bit / 8;
|
|
if (bytePos >= data.count()) return 0; //error!
|
|
if (data[bit / 8] & (1 << (bit % 8)))
|
|
result += (1ULL << bitpos);
|
|
}
|
|
bit++;
|
|
}
|
|
|
|
}
|
|
else //motorola / big endian mode
|
|
{
|
|
bit = startBit;
|
|
for (int bitpos = 0; bitpos < sigSize; bitpos++)
|
|
{
|
|
if (bit < 512) {
|
|
int bytePos = bit / 8;
|
|
if (bytePos >= data.count()) return 0; //error!
|
|
if (data[bit / 8] & (1 << (bit % 8)))
|
|
result += (1ULL << (sigSize - bitpos - 1));
|
|
}
|
|
|
|
if ((bit % 8) == 0)
|
|
bit += 15;
|
|
else bit--;
|
|
|
|
}
|
|
}
|
|
|
|
if (isSigned)
|
|
{
|
|
uint64_t mask = (1ULL << (sigSize - 1));
|
|
if ((result & mask) == mask) //is the highest bit possible for this signal size set?
|
|
{
|
|
/*
|
|
* if so we need to also set every bit higher in the result int too.
|
|
* This leads to the below two lines that are nasty. Here's the theory behind that...
|
|
* If the value is signed and the highest bit is set then it is negative. To create
|
|
* a negative value out of this even though the variable result is 64 bit we have to
|
|
* run 1's all of the way up to bit 63 in result. -1 is all ones for whatever size integer
|
|
* you have. So, it's 64 1's in this case.
|
|
* signedMask is done this way:
|
|
* first you take the signal size and shift 1 up that far. Then subtract one. Lets
|
|
* see that for a 16 bit signal:
|
|
* (1 << 16) - 1 = the first 16 bits set as 1's. So far so good. We then negate the whole
|
|
* thing which flips all bits. Thus signedMask ends up with 1's everwhere that the signal
|
|
* doesn't take up in the 64 bit signed integer result. Then, result has an OR operation on
|
|
* it with the old value and -1 masked so that the the 1 bits from -1 don't overwrite bits from the
|
|
* actual signal. This extends the sign bits out so that the integer result reads as the proper negative
|
|
* value. We dont need to do any of this if the sign bit wasn't set.
|
|
*/
|
|
uint64_t signedMask = ~((1ULL << sigSize) - 1);
|
|
result = (-1LL & signedMask) | result;
|
|
return (int64_t)(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
|