#ifndef UTILITY_H #define UTILITY_H #include #include #include #include #include #include #include #include #include #include #include #include //#include enum TimeStyle { TS_SECONDS, TS_MICROS, TS_MILLIS, TS_CLOCK, TS_START_DATE, }; // 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 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 QDateTime startDate; static bool useStartDate; static QString fullyQualifiedNameSeperator; static void SetComboBoxItemEnabled(QComboBox * comboBox, int index, bool enabled) { auto * model = qobject_cast(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(stamp.time().hour()) * 3600ull) + (static_cast(stamp.time().minute()) * 60ull) + (static_cast(stamp.time().second())) * 1000ull) + static_cast(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_START_DATE: return startDate.addMSecs(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(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(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(kRowPalette.size()); return QColor(kRowPalette[idx]); } }; #endif // UTILITY_H