From 2c6bf5d56a7577c650dfb66b077419c54a904257 Mon Sep 17 00:00:00 2001 From: Ernie Pasveer Date: Mon, 15 Jun 2026 18:08:04 -0500 Subject: [PATCH] Fixed nasty segfault when removing items from scene. --- src/SeerGdbWidget.cpp | 2 +- src/SeerParallelStacksGraphicsView.cpp | 48 +- src/SeerParallelStacksGraphicsView.h | 7 + src/SeerParallelStacksVisualizerWidget.cpp | 4 + src/SeerUtl.cpp-main | 1512 -------------------- 5 files changed, 54 insertions(+), 1519 deletions(-) delete mode 100644 src/SeerUtl.cpp-main diff --git a/src/SeerGdbWidget.cpp b/src/SeerGdbWidget.cpp index 5480e48..af89387 100644 --- a/src/SeerGdbWidget.cpp +++ b/src/SeerGdbWidget.cpp @@ -3073,9 +3073,9 @@ void SeerGdbWidget::handleGdbParallelStacksVisualizer () { w->show(); // Connect things. - QObject::connect(w, &SeerParallelStacksVisualizerWidget::refreshParallelStackFrames, this, &SeerGdbWidget::handleGdbParallelStackFrames); QObject::connect(_gdbMonitor, &GdbMonitor::astrixTextOutput, w, &SeerParallelStacksVisualizerWidget::handleText); QObject::connect(_gdbMonitor, &GdbMonitor::caretTextOutput, w, &SeerParallelStacksVisualizerWidget::handleText); + QObject::connect(w, &SeerParallelStacksVisualizerWidget::refreshParallelStackFrames, this, &SeerGdbWidget::handleGdbParallelStackFrames); } void SeerGdbWidget::handleGdbMonitor () { diff --git a/src/SeerParallelStacksGraphicsView.cpp b/src/SeerParallelStacksGraphicsView.cpp index b18cd2b..5005046 100644 --- a/src/SeerParallelStacksGraphicsView.cpp +++ b/src/SeerParallelStacksGraphicsView.cpp @@ -22,11 +22,22 @@ namespace Seer::PSV { _headerLeft = QString("%1 Thread%2") .arg(stack.threadCount) .arg(stack.threadCount == 1 ? "" : "s"); if (!stack.threadIds.isEmpty()) { - QStringList ids = stack.threadIds; - if (ids.size() > 8) - _headerRight = QString("[%1 … +%2]") .arg(ids.mid(0, 8).join(", ")) .arg(ids.size() - 8); - else - _headerRight = "[" + ids.join(", ") + "]"; + + const QVector &ids = stack.threadIds; + const int shown = std::min(ids.size(), 8); + + QStringList parts; + parts.reserve(shown); + + for (int i = 0; i < shown; ++i) { + parts.append(ids[i]); + } + + if (ids.size() > 8) { + _headerRight = QString("[%1 … +%2]").arg(parts.join(", ")).arg(ids.size() - 8); + }else{ + _headerRight = "[" + parts.join(", ") + "]"; + } } for (int i = stack.functions.size() - 1; i >= 0; --i) { @@ -51,6 +62,21 @@ namespace Seer::PSV { _height = kPadY + kRowH * (1 + (int)_rows.size()) + kPadY; } + StackBoxItem::~StackBoxItem() { + + // Tell every edge that still points at us to forget about it. + // This must run BEFORE this object's memory is freed, regardless of + // whether the scene destroys this box or its edges first — it prevents + // the edges' own destructors (and any pending itemChange callbacks) + // from dereferencing a dangling pointer back to this item. + + for (LiveEdge *e : _edges) { + e->detachEndpoint(this); + } + + _edges.clear(); + } + QRectF StackBoxItem::boundingRect() const { return QRectF(0, 0, _width, _height); } @@ -174,11 +200,21 @@ namespace Seer::PSV { } LiveEdge::~LiveEdge() { - // Guard against half-destroyed scenes + + // If our endpoints are still alive, tell them to forget about us so + // they don't later call update() on a dangling LiveEdge* during + // itemChange(). detachEndpoint() is a no-op if the endpoint has + // already nulled itself out via StackBoxItem::~StackBoxItem(). if (_from) _from->unregisterEdge(this); if (_to) _to->unregisterEdge(this); } + void LiveEdge::detachEndpoint(StackBoxItem *box) { + + if (_from == box) _from = nullptr; + if (_to == box) _to = nullptr; + } + QRectF LiveEdge::boundingRect() const { // Return the bounding rect of the two endpoints plus generous padding diff --git a/src/SeerParallelStacksGraphicsView.h b/src/SeerParallelStacksGraphicsView.h index 70225f3..c5ff672 100644 --- a/src/SeerParallelStacksGraphicsView.h +++ b/src/SeerParallelStacksGraphicsView.h @@ -21,6 +21,7 @@ namespace Seer::PSV { public: explicit StackBoxItem(const Stack& stack, QGraphicsItem* parent = nullptr); + ~StackBoxItem() override; QRectF boundingRect () const override; void paint (QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; @@ -77,6 +78,12 @@ namespace Seer::PSV { QRectF boundingRect () const override; void paint (QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + // Called by StackBoxItem's destructor so this edge stops referencing + // an endpoint that is about to be (or has been) destroyed. After this, + // the edge renders nothing and its own destructor won't touch `box`. + void detachEndpoint (StackBoxItem* box); + + private: StackBoxItem* _from; // child (bottom anchor) StackBoxItem* _to; // parent (top anchor) diff --git a/src/SeerParallelStacksVisualizerWidget.cpp b/src/SeerParallelStacksVisualizerWidget.cpp index c7db4da..b129430 100644 --- a/src/SeerParallelStacksVisualizerWidget.cpp +++ b/src/SeerParallelStacksVisualizerWidget.cpp @@ -42,6 +42,10 @@ SeerParallelStacksVisualizerWidget::SeerParallelStacksVisualizerWidget (QWidget* } SeerParallelStacksVisualizerWidget::~SeerParallelStacksVisualizerWidget () { + + if (QGraphicsScene* scene = graphicsView->scene()) { + scene->clear(); + } } void SeerParallelStacksVisualizerWidget::handleText (const QString& text) { diff --git a/src/SeerUtl.cpp-main b/src/SeerUtl.cpp-main deleted file mode 100644 index 5ec5f9e..0000000 --- a/src/SeerUtl.cpp-main +++ /dev/null @@ -1,1512 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Ernie Pasveer -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "SeerUtl.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Comment out for now. I don't want to include boost because -// that would impact people try to compile Seer. Qt6 offer -// a 'stacktrace' function. OpenSuse may be slow in adoption. -// For now, comment it out and use when needed. -// #include - -#include -#include - -// -// Increment this with every release on GitHub. -// See scripts/change_versionnumber -// -#define SEER_VERSION "2.8beta" - -namespace Seer { - - QString version () { - return SEER_VERSION + QString(" (Qt") + QT_VERSION_STR + ")"; - } - - QString filterBareNewLines (const QString& str) { - - // Remove bare (not inside quotes) '\n' substrings. - // Maybe extra spaces after '\n'. - // "{\n B = 1,\n C = 0,\n D = \"asd\",\n E = std::vector of length 1, capacity 64 = {true}\n}" - // "{B = 1, C = 0, D = \"asd\", E = std::vector of length 1, capacity 64 = {true}}" - - QString tmp; - qsizetype anchor=0; - qsizetype curr=0; - qsizetype end=str.length(); - - // Loop through string a character at a time. - while (curr < end) { - - // Found a '\n' substring. Deal with it. - if (str.mid(curr,2) == "\\n") { - - // Append the good bits before the '\n'. - tmp.append(str.mid(anchor,curr-anchor)); - curr += 2; - - // Skip trailing spaces, if any, after '\n'. - while (curr < end) { - if (str.at(curr) == ' ') { - curr++; - }else{ - break; - } - } - - anchor = curr; - - // Other character. Skip it. - }else{ - curr++; - } - } - - // Anything left to deal with? - if (curr > anchor) { - tmp.append(str.mid(anchor,curr-anchor)); - anchor = curr; - } - - return tmp; - } - - QString filterEscapes (const QString& str) { - - // Remove one level of '\'. - // value="\"'Treasure' by Lucillius\\n\\n\\tbut theirs.\\n\"" - - QString tmp; - bool escaped = false; - - for (int i=0; i 0) { - r.remove(i, len); - r.insert(i, value); - }else{ - f = false; // Not expanded. - break; - } - } - - while (true) { - - QRegularExpressionMatch match = env_re2.match(r); - - if (match.hasMatch() == false) { - break; - } - - qsizetype i = match.capturedStart(); - qsizetype len = match.capturedLength(); - QString capstr = match.captured(); - QString envstr = capstr.mid(1,capstr.length()-1); - - QByteArray value(qgetenv(envstr.toLatin1().data())); - - if (value.size() > 0) { - r.remove(i, len); - r.insert(i, value); - }else{ - f = false; // Not expanded. - break; - } - } - - if (ok) { - *ok = f; - } - - return r; - } - - QStringList parse (const QString& str, const QString& search, QChar startBracket, QChar endBracket, bool includeSearchString) { - - // - // str = "^done,stack-args=[frame={level=\"0\",args=[{name=\"message\",value=\"\\\"Hello, World!\\\"\"}]},frame={level=\"1\",args=[{name=\"argc\",value=\"1\"},{name=\"argv\",value=\"0x7fffffffd5b8\"}]}]" - // search = "frame=" - // startBracket = '{' - // endBracket = '}' - // - - QStringList list; - QString searchWord = search + startBracket; // 'frame={' - int from = 0; - - while (1) { - - // Look for the next occurance of the search word. - int index = str.indexOf(searchWord,from); - if (index < 0) { // If not found, we're done. - break; - } - - // Set things up to look for the matching end bracket. - int start = index + search.size() + 1; // Positioned after 'frame={' - int end = start + 1; - int bracketLevel = 1; // Start with one start bracket already encountered. - - for (end=start; end= 0) { - if (str[end-1] == '\\') { - escaped = true; - } - } - - if (startBracket != endBracket) { // Do this test if brackets are not the same character. - if (str[end] == startBracket && escaped == false) { // Encountered another start bracket, increment the level. - bracketLevel++; - } - } - - if (str[end] == endBracket && escaped == false) { // Encountered an end bracket, decrement the level. - bracketLevel--; - - if (bracketLevel == 0) { // If the level is 0, we're done. - break; - } - } - } - - // Are things valid? - if (bracketLevel != 0) { // No matching bracket. - break; - } - - if (end > str.size()) { // The end is past the end of the string. - break; - } - - // Build the string. - QString tmp; - - if (includeSearchString) { - tmp = search + startBracket + str.mid(start, end-start) + endBracket; - }else{ - tmp = str.mid(start, end-start); - } - - // Add it to the list. - list.append(tmp); - - // Go back for more - from = end + 1; - } - - // Return the list. - return list; - } - - QString parseFirst (const QString& str, const QString& search, QChar startBracket, QChar endBracket, bool includeSearchString) { - - QStringList list = Seer::parse(str, search, startBracket, endBracket, includeSearchString); - - if (list.size() == 0) { - return QString(); - } - - return list.front(); - } - - QString parseFirst (const QString& str, const QString& search, bool includeSearchString) { - - QString match; - - // Look for the next occurance of the search word. - int index = str.indexOf(search,0); - if (index < 0) { // If not found, we're done. - return match; - } - - // Set things up to look for the matching end bracket. - int start = index + search.size() + 0; // Position after 'msg=' - int end = str.size(); - - if (includeSearchString) { - match = search + str.mid(start, end-start); - }else{ - match = str.mid(start, end-start); - } - - return match; - } - - - bool hasBookends (const QString& str, QChar startBracket, QChar endBracket) { - - if (str.startsWith(startBracket) && str.endsWith(endBracket)) { - return true; - } - - return false; - } - - - QString filterBookends (const QString& str, QChar startBracket, QChar endBracket) { - - // If the string starts with and ends with the bracket characters, then return - // the text between the brackets. - if (str.startsWith(startBracket) && str.endsWith(endBracket)) { - return str.mid(1,str.size()-2); - } - - // Otherwise, return the orginal string. - return str; - } - - QStringList filterBookends (const QStringList& strings, QChar startBracket, QChar endBracket) { - - QStringList list; - - // For a list of strings, remove the bookends, if possible. - for (int i=0; i, \"hildTest 2000-", '0' , "1", '0' , "330324377377377177000000004", '0' , "test\", '000' , \"325377377\", child = {childId = -9864, childString = \"\"}} - - enum STATE{ - START = 0, - SCANNING = 1, - FOUND = 2, - IGNORE = 3, - }; - QStringList list; - int index = 0; - int start = 0; - int end = 0; - int previousComma= 0; - STATE state = START; - - while (index < str.length()) - { - // Handle "=" - if (str[index] == '=') { - if (state == SCANNING) // looking for 2nd '=' - { - // state = FOUND; - end = previousComma; - QString field = str.mid(start, end-start); - list.append(field.trimmed()); - start = previousComma + 1; - } - if (state == START) // looking for 2nd '=' - { - state = SCANNING; - - } - } - - if (str[index] == startBracket) - { - state = IGNORE; - } - - if (str[index] == endBracket) - { - state = SCANNING; - } - - if (str[index] == ',' && state != IGNORE) { - previousComma = index; - } - - // The last - if (index == str.length() - 1) - { - QString field = str.mid(start, index); - list.append(field.trimmed()); - } - index++; - } - return list; - } - - // - // Parse an array, turn it to QList - // - - QStringList parseArray (const QString& parentName, const QString& str) - { - // Input: {age = 1, name = "1st_child"}, {age = 2, name = "2nd_child"} - // Output: QStringList : parentName[0] = {age = 1, name = "1st_child"}, parentName[1] = {age = 1, name = "1st_child"} - - QStringList list; - int index = 0; - int cntStartBracket = 0; - int cntEndBracket = 0; - int start = 0; - int cnt = 0; - - while (index < str.length()) - { - if (str[index] == '{') - { - if (cntStartBracket == cntEndBracket) - start = index; - cntStartBracket++; - } - if (str[index] == '}') - { - // Check to see if character after it is ",". Otherwise, it probably trash character. - if (index + 1 < str.length()) - { - if (str[index + 1] == '}' || str[index + 1] == ',' || index + 1 == str.length() ) - { - cntEndBracket++; - if (cntStartBracket == cntEndBracket) - { - QString field = str.mid(start, index + 1 - start); - field = parentName + "[" + QString::number(cnt) + "] = " + field; - list.append(field.trimmed()); - cntStartBracket = 0; cntEndBracket = 0; - cnt++; - } - } - } - else if (index + 1 == str.length()) - { - // The last element - cntEndBracket++; - if (cntStartBracket == cntEndBracket) - { - QString field = str.mid(start, index + 1 - start); - field = parentName + "[" + QString::number(cnt) + "] = " + field; - list.append(field.trimmed()); - cntStartBracket = 0; cntEndBracket = 0; - cnt++; - } - } - } - index ++; - } - return list; - } - - QMap createKeyValueMap (const QStringList& list, QChar separator) { - - QMap map; - - for (const auto& i : list) { - QStringPair pair = parseNameValue(i, separator); - - map[pair.first] = pair.second; - } - - return map; - } - - // - // - // - - QStringPair parseNameValue (const QString& str, QChar separator) { - - // name = "Pasveer, Ernie" - // - // pair.first = name - // pair.second = "Pasveer, Ernie" - // - - QStringPair pair; - int index = 0; - int state = 0; - int start = 0; - int end = 0; - bool inquotes = false; - - while (index < str.length()) { - - // Handle start of field. - if (state == 0) { // Start of field. - start = index; - end = index; - state = 1; // Look for end of field (a command or eol). - - continue; - } - - // Handle end of field. - if (state == 1) { - - // Handle """ - if (str[index] == '"') { - if (inquotes == false) { - inquotes = true; - }else{ - inquotes = false; - } - - index++; continue; - } - - // Handle "=" separator. - if (str[index] == separator) { - - if (inquotes) { - index++; continue; - } - - // Extract the two fields. index is at the "=". - end = index; - - QString one = str.mid(start, end-start); - QString two = str.mid(end+1); - - pair = QStringPair(one.trimmed(),two.trimmed()); - - state = 0; // Look for the next field. - - break; - } - - // Handle any other character. - index++; continue; - } - - qDebug() << "Bad state!"; - index++; continue; - } - - // Handle if no "=" separator. - if (state == 1) { - pair = QStringPair(str.trimmed(),QString()); - } - - return pair; - } - - // - // Quote certain characters in a string. - // - // "hello" => \"hello\" - // - QString quoteChars (const QString& str, const QString& chars) { - - QString string; - - for (int i=0; i= 0x060000 - - QRegularExpression re = QRegularExpression::fromWildcard(pattern, Qt::CaseInsensitive, QRegularExpression::UnanchoredWildcardConversion); - -#else - // With Qt5, 'wildcardToRegularExpression' needs a "/*" at the start of 'pattern' - // if 'string' start with a "/". - //if (string[0] == '/') { - // if (pattern[0] != '/') { - // pattern = "/*/" + pattern; - // } - //} - - // This pattern needs to be converted from a glob to a regex. - // All '*' are replaced with '.*' - // The pattern is terminated with a '$', if it doesn't have one. - pattern.replace("*", ".*"); - - //if (string[0] != '/') { - // if (pattern.back() != '$') { - // pattern += '$'; - // } - //} - - // qDebug() << pattern << string; - - // QRegularExpression re = QRegularExpression(QRegularExpression::wildcardToRegularExpression(pattern)); - QRegularExpression re = QRegularExpression(pattern); -#endif - - if (re.match(string).hasMatch()) { - return true; - } - } - - return false; - } - - bool hasWildcards (const QString& str) { - return (str.indexOf('*') >= 0) || (str.indexOf('?') >= 0); - } - - QString elideText (const QString& str, Qt::TextElideMode mode, int length) { - - QString leftElide("... "); - QString middleElide(" ... "); - QString rightElide(" ..."); - - // The string is fine. Just return it. - if (str.length() <= length) { - return str; - } - - // The string is too long but don't add elide. - if (mode == Qt::ElideNone) { - return str.left(length); - } - - // The string is too long. Add elilde on the left. - if (mode == Qt::ElideLeft) { - return leftElide + str.right(length); - } - - // The string is too long. Add elilde on the right. - if (mode == Qt::ElideRight) { - return str.left(length) + rightElide; - } - - // The string is too long. Add elilde in the middle. - if (mode == Qt::ElideRight) { - int halve = length / 2; - return str.left(halve) + middleElide + str.right(halve); - } - - // Bad mode. Just return the string. - return str; - } - - // Split a string on words. Double "quoted strings" - // act as one word. - QStringList split (const QString& str) { - - QRegularExpression re("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'"); - QStringList list; - - QRegularExpressionMatchIterator i = re.globalMatch(str); - while (i.hasNext()) { - QRegularExpressionMatch match = i.next(); - - QString word; - if (match.captured(2) != "") { - word = match.captured(2); - }else if (match.captured(1) != "") { - word = match.captured(1); - }else if (match.captured(0) != "") { - word = match.captured(0); - } - - if (word != "") { - list << word; - } - } - - return list; - } - - // - // - // - - static int Next_ID = 1; - static std::mutex ID_mutex; - - int createID () { - - std::lock_guard guard(ID_mutex); - - int id = Next_ID; - - Next_ID++; - - return id; - } - - unsigned char ebcdicToAscii (unsigned char byte) { - - static const unsigned char ebcdicToAsciiTable[256] = { - 46, 46, 46, 46, 46, 46, 46, 46, // 0 - 46, 46, 46, 46, 46, 46, 46, 46, // 8 - 46, 46, 46, 46, 46, 46, 46, 46, // 16 - 46, 46, 46, 46, 46, 46, 46, 46, // 24 - 46, 46, 46, 46, 46, 46, 46, 46, // 32 - 46, 46, 46, 46, 46, 46, 46, 46, // 40 - 46, 46, 46, 46, 46, 46, 46, 46, // 48 - 46, 46, 46, 46, 46, 46, 46, 46, // 56 - 32, 46, 46, 46, 46, 46, 46, 46, // 64 - 46, 46, 91, 46, 60, 40, 43, 33, // 72 - 38, 46, 46, 46, 46, 46, 46, 46, // 80 - 46, 46, 33, 36, 42, 41, 59, 94, // 88 - 45, 47, 46, 46, 46, 46, 46, 46, // 96 - 46, 46, 124, 44, 37, 95, 62, 63, // 104 - 46, 46, 46, 46, 46, 46, 46, 46, // 112 - 46, 96, 58, 35, 64, 39, 61, 34, // 120 - 46, 97, 98, 99, 100, 101, 102, 103, // 128 - 104, 105, 46, 46, 46, 46, 46, 46, // 136 - 46, 106, 107, 108, 109, 110, 111, 112, // 144 - 113, 114, 46, 46, 46, 46, 46, 46, // 152 - 46, 126, 115, 116, 117, 118, 119, 120, // 160 - 121, 122, 46, 46, 46, 46, 46, 46, // 168 - 46, 46, 46, 46, 46, 46, 46, 46, // 176 - 46, 46, 46, 46, 46, 46, 46, 46, // 184 - 123, 65, 66, 67, 68, 69, 70, 71, // 192 - 72, 73, 46, 46, 46, 46, 46, 46, // 200 - 125, 74, 75, 76, 77, 78, 79, 80, // 208 - 81, 82, 46, 46, 46, 46, 46, 46, // 216 - 92, 46, 83, 84, 85, 86, 87, 88, // 224 - 89, 90, 46, 46, 46, 46, 46, 46, // 232 - 48, 49, 50, 51, 52, 53, 54, 55, // 240 - 56, 57, 46, 46, 46, 46, 46, 46 // 248 - }; - - return ebcdicToAsciiTable[byte]; - } - - unsigned char ucharToAscii (unsigned char byte) { - - static const unsigned char ucharToAsciiTable[256] = { - 46, 46, 46, 46, 46, 46, 46, 46, // 0 - 46, 46, 46, 46, 46, 46, 46, 46, // 8 - 46, 46, 46, 46, 46, 46, 46, 46, // 16 - 46, 46, 46, 46, 46, 46, 46, 46, // 24 - 32, 33, 34, 35, 36, 37, 38, 39, // 32 - 40, 41, 42, 43, 44, 45, 46, 47, // 40 - 48, 49, 50, 51, 52, 53, 54, 55, // 48 - 56, 57, 58, 59, 60, 61, 62, 63, // 56 - 64, 65, 66, 67, 68, 69, 70, 71, // 64 - 72, 73, 74, 75, 76, 77, 78, 79, // 72 - 80, 81, 82, 83, 84, 85, 86, 87, // 80 - 88, 89, 90, 91, 92, 93, 94, 95, // 88 - 96, 97, 98, 99, 100, 101, 102, 103, // 96 - 104, 105, 106, 107, 108, 109, 110, 111, // 104 - 112, 113, 114, 115, 116, 117, 118, 119, // 112 - 120, 121, 122, 123, 124, 125, 126, 46, // 120 - 46, 46, 46, 46, 46, 46, 46, 46, // 128 - 46, 46, 46, 46, 46, 46, 46, 46, // 136 - 46, 46, 46, 46, 46, 46, 46, 46, // 144 - 46, 46, 46, 46, 46, 46, 46, 46, // 152 - 46, 46, 46, 46, 46, 46, 46, 46, // 160 - 46, 46, 46, 46, 46, 46, 46, 46, // 168 - 46, 46, 46, 46, 46, 46, 46, 46, // 176 - 46, 46, 46, 46, 46, 46, 46, 46, // 184 - 46, 46, 46, 46, 46, 46, 46, 46, // 192 - 46, 46, 46, 46, 46, 46, 46, 46, // 200 - 46, 46, 46, 46, 46, 46, 46, 46, // 208 - 46, 46, 46, 46, 46, 46, 46, 46, // 216 - 46, 46, 46, 46, 46, 46, 46, 46, // 224 - 46, 46, 46, 46, 46, 46, 46, 46, // 232 - 46, 46, 46, 46, 46, 46, 46, 46, // 240 - 46, 46, 46, 46, 46, 46, 46, 46 // 248 - }; - - return ucharToAsciiTable[byte]; - } - - QString ucharToHex (const QVector& bytes, int from, int count) { - - QString str; - - str.reserve(count*2+4); - - for (int i=from; i= bytes.size()) { - break; - } - - quint8 decimal = bytes[i]; - QString hex = QString("%1").arg(decimal, 2, 16, QLatin1Char('0')); - - str += hex; - } - - return str; - } - - QString ucharToOctal (const QVector& bytes, int from, int count) { - - QString str; - - str.reserve(count*3+4); - - for (int i=from; i= bytes.size()) { - break; - } - - quint8 decimal = bytes[i]; - QString octal = QString("%1").arg(decimal, 3, 8, QLatin1Char('0')); - - str += octal; - } - - return str; - } - - QString ucharToAscii (const QVector& bytes, int from, int count) { - - QString str; - - str.reserve(count); - - for (int i=from; i= bytes.size()) { - break; - } - - quint8 decimal = bytes[i]; - QString ascii = QChar(Seer::ucharToAscii(decimal)); - - str += ascii; - } - - return str; - } - - QString ucharToUShort (const QVector& bytes, int from, int count) { - - QString str; - - for (int i=0; i= bytes.size()) { - break; - } - - quint16 decimal = *(quint16*)(bytes.data()+from); - QString val = QString("%1").arg(decimal); - - if (i != 0) { - str += " "; - } - - str += val; - from += sizeof(quint16); - } - - return str; - } - - QString ucharToShort (const QVector& bytes, int from, int count) { - - QString str; - - for (int i=0; i= bytes.size()) { - break; - } - - qint16 decimal = *(qint16*)(bytes.data()+from); - QString val = QString("%1").arg(decimal); - - if (i != 0) { - str += " "; - } - - str += val; - from += sizeof(qint16); - } - - return str; - } - - QString ucharToUInt (const QVector& bytes, int from, int count) { - - QString str; - - for (int i=0; i= bytes.size()) { - break; - } - - quint32 decimal = *(quint32*)(bytes.data()+from); - QString val = QString("%1").arg(decimal); - - if (i != 0) { - str += " "; - } - - str += val; - from += sizeof(quint32); - } - - return str; - } - - QString ucharToInt (const QVector& bytes, int from, int count) { - - QString str; - - for (int i=0; i= bytes.size()) { - break; - } - - qint32 decimal = *(qint32*)(bytes.data()+from); - QString val = QString("%1").arg(decimal); - - if (i != 0) { - str += " "; - } - - str += val; - from += sizeof(qint32); - } - - return str; - } - - QString ucharToULong (const QVector& bytes, int from, int count) { - - QString str; - - for (int i=0; i= bytes.size()) { - break; - } - - quint64 decimal = *(quint64*)(bytes.data()+from); - QString val = QString("%1").arg(decimal); - - if (i != 0) { - str += " "; - } - - str += val; - from += sizeof(quint64); - } - - return str; - } - - QString ucharToLong (const QVector& bytes, int from, int count) { - - QString str; - - for (int i=0; i= bytes.size()) { - break; - } - - qint64 decimal = *(qint64*)(bytes.data()+from); - QString val = QString("%1").arg(decimal); - - if (i != 0) { - str += " "; - } - - str += val; - from += sizeof(qint64); - } - - return str; - } - - QString ucharToFloat (const QVector& bytes, int from, int count) { - - QString str; - - for (int i=0; i= bytes.size()) { - break; - } - - float real = *(float*)(bytes.data()+from); - QString val = QString("%1").arg(real); - - if (i != 0) { - str += " "; - } - - str += val; - from += sizeof(float); - } - - return str; - } - - QString ucharToDouble (const QVector& bytes, int from, int count) { - - QString str; - - for (int i=0; i= bytes.size()) { - break; - } - - double real = *(double*)(bytes.data()+from); - QString val = QString("%1").arg(real); - - if (i != 0) { - str += " "; - } - - str += val; - from += sizeof(double); - } - - return str; - } - - int typeBytes (const QString& type) { - - if (type == "int8" || type == "uint8") { - return 1; - }else if (type == "int16" || type == "uint16") { - return 2; - }else if (type == "int32" || type == "uint32") { - return 4; - }else if (type == "int64" || type == "uint64") { - return 8; - }else if (type == "float32") { - return 4; - }else if (type == "float64") { - return 8; - }else{ - qWarning() << "Bad data type:" << type; - return 0; - } - } - - bool readFile (const QString& filename, QStringList& lines) { - - // Empty the list - lines = QStringList(); - - // Open the file. - QFile file(filename); - - if (file.open(QIODevice::ReadOnly) == false) { - qDebug() << "Can't open:" << filename; - return false; - } - - // Read the file line-by-line and build up the string list. - while (file.atEnd() == false) { - - QString line = file.readLine(); - line.chop(1); - lines.append(line); - } - - file.close(); - - // All good. - return true; - } - - QString unescape (const QString& str) { - - QString result; - bool quoted = false; - - // Maybe reserve `str.length()` bytes in result, so that it will not allocate during `push_back()` - result.reserve(str.length()); - - // Loop through each character in 'str'. Look for a '\'. - for (int i=0; i= str.length()) { - // Nope, just add it. - result.push_back(str[i]); - break; - } - - // If the following character is another '\', skip the first one. - // This removes one level of '\'. - if (str[i+1] == QChar('\\')) { - continue; - } - - // Treat the following character as an escaped character. - // Unescape it. (Is that even a word?) - switch(str[i+1].unicode()) { - // We don't want to unescape things that are in double quotes. - // So keep track of that. (Not the more fool proof implementation). - case QChar('\"').unicode(): - result.push_back(QChar('\"')); - if (quoted) { - quoted = false; - }else{ - quoted = true; - } - break; - case QChar('n').unicode(): - if (quoted == false) { - result.push_back(QChar('\n')); - }else{ - result.push_back(QChar('\\')); - result.push_back(str[i+1]); - } - break; - case QChar('\'').unicode(): - if (quoted == false) { - result.push_back(QChar('\'')); - }else{ - result.push_back(QChar('\\')); - result.push_back(str[i+1]); - } - break; - case QChar('\\').unicode(): - if (quoted == false) { - result.push_back(QChar('\\')); - }else{ - result.push_back(QChar('\\')); - result.push_back(str[i+1]); - } - break; - case QChar('t').unicode(): - if (quoted == false) { - result.push_back(QChar('\t')); - }else{ - result.push_back(QChar('\\')); - result.push_back(str[i+1]); - } - break; - // and so on for \a, \b, \e, \f, \r, \t, \v - // maybe handle octal, hexadecimal ASCII and Unicode forms, but probably in the more distant future - // ... - default: - // unknown escape sequence — do not escape it — I think it is reasonable default - result.push_back(QChar('\\')); - result.push_back(str[i+1]); - break; - } - i++; - - // Normal character. Just add it. - }else{ - result.push_back(str[i]); - } - } - - return result; - } - - void printStackTrace () { - // Capture and print the stack trace using Boost.Stacktrace. - // See comments at top. - // std::cout << "Stack trace:\n" << boost::stacktrace::stacktrace() << std::endl; - } - - // Check if directory and file exist, otherwise, raise a notification - bool isFileExistNotify (const QString& filename) { - if (QFile::exists(filename)) { - return true; - } else { - QMessageBox::warning(nullptr, "Seer", QString("File %1 doesn't exist.").arg(filename), QMessageBox::Ok); - return false; - } - } - - bool isDirExistNotify (const QString& dirname) { - if (QDir(dirname).exists()) { - return true; - } else { - QMessageBox::warning(nullptr, "Seer", QString("Directory %1 doesn't exist.").arg(dirname), QMessageBox::Ok); - return false; - } - } - - void colorizeAllIcons (QWidget* parent, const QSize& size) { - - if (parent == nullptr) { - return; - } - - // Get the current color scheme - Qt::ColorScheme colorScheme = QGuiApplication::styleHints()->colorScheme(); - - // Look for all button types. - const auto buttons = parent->findChildren(); - - for (QAbstractButton* button : buttons) { - - if (colorScheme == Qt::ColorScheme::Dark) { - button->setIcon(Seer::colorizeIcon(button->icon(), QColor("white"), size)); - }else{ - button->setIcon(Seer::colorizeIcon(button->icon(), QColor("black"), size)); - } - } - - // Look for all menu types. - const auto menus = parent->findChildren(); - - for (QMenu* menu : menus) { - - if (colorScheme == Qt::ColorScheme::Dark) { - menu->setIcon(Seer::colorizeIcon(menu->icon(), QColor("white"), size)); - }else{ - menu->setIcon(Seer::colorizeIcon(menu->icon(), QColor("black"), size)); - } - } - - // Look for all action types. - const auto actions = parent->findChildren(); - - for (QAction* action : actions) { - - if (colorScheme == Qt::ColorScheme::Dark) { - action->setIcon(Seer::colorizeIcon(action->icon(), QColor("white"), size)); - }else{ - action->setIcon(Seer::colorizeIcon(action->icon(), QColor("black"), size)); - } - } - - // Look for the app's titlebar icon. - // XXX Doesn't work! - // XXX Titlebar icon remains the same. It doesn't change. Don't know why. - if (colorScheme == Qt::ColorScheme::Dark) { - QApplication::setWindowIcon(Seer::colorizeIcon(QIcon(":/seer/resources/icons/hicolor/64x64/seergdb.png"), QColor("white"), size)); - }else{ - QApplication::setWindowIcon(Seer::colorizeIcon(QIcon(":/seer/resources/icons/hicolor/64x64/seergdb.png"), QColor("black"), size)); - } - } - - void colorizeListWidgetItemIcon (QListWidgetItem* item, const QSize& size) { - - if (item == nullptr) { - return; - } - - // Get the current color scheme - Qt::ColorScheme colorScheme = QGuiApplication::styleHints()->colorScheme(); - - if (colorScheme == Qt::ColorScheme::Dark) { - item->setIcon(Seer::colorizeIcon(item->icon(), QColor("white"), size)); - }else{ - item->setIcon(Seer::colorizeIcon(item->icon(), QColor("black"), size)); - } - } - - void colorizeChartViewItem (QChartView* item) { - - if (item == nullptr) { - return; - } - - QChart* chart = item->chart(); - - if (chart == nullptr) { - return; - } - - // Get the current color scheme - Qt::ColorScheme colorScheme = QGuiApplication::styleHints()->colorScheme(); - - if (colorScheme == Qt::ColorScheme::Dark) { - chart->setTheme(QChart::ChartThemeDark); - }else{ - chart->setTheme(QChart::ChartThemeLight); - } - } - - QIcon colorizeIcon (const QIcon& icon, const QSize& size) { - - // Get the current color scheme - Qt::ColorScheme colorScheme = QGuiApplication::styleHints()->colorScheme(); - - if (colorScheme == Qt::ColorScheme::Dark) { - return Seer::colorizeIcon(icon, QColor("white"), size); - }else{ - return Seer::colorizeIcon(icon, QColor("black"), size); - } - } - - QIcon colorizeIcon (const QIcon& icon, const QColor& color, const QSize& size) { - - // Get the pixmap from the icon - QPixmap pixmap = icon.pixmap(size); - - // Create a new pixmap with the same size and format (preserves alpha) - QPixmap whitePixmap(pixmap.size()); - whitePixmap.fill(Qt::transparent); - - QPainter painter(&whitePixmap); - - // Draw the original pixmap - painter.drawPixmap(0, 0, pixmap); - - // Use CompositionMode_SourceIn to colorize while keeping alpha - painter.setCompositionMode(QPainter::CompositionMode_SourceIn); - painter.fillRect(whitePixmap.rect(), color); - - painter.end(); - - return QIcon(whitePixmap); - } -} -