From dea3f0ff3a90d088a417515a00f006eac8128bb7 Mon Sep 17 00:00:00 2001 From: Ernie Pasveer Date: Sun, 19 Nov 2023 17:12:36 -0600 Subject: [PATCH] Add nested struct viewing to Tracker. --- src/SeerVariableTrackerBrowserWidget.cpp | 276 ++++++++++++++++++----- src/SeerVariableTrackerBrowserWidget.h | 4 + src/SeerVariableTrackerBrowserWidget.ui | 17 +- 3 files changed, 236 insertions(+), 61 deletions(-) diff --git a/src/SeerVariableTrackerBrowserWidget.cpp b/src/SeerVariableTrackerBrowserWidget.cpp index b0b616f9..f7cac696 100644 --- a/src/SeerVariableTrackerBrowserWidget.cpp +++ b/src/SeerVariableTrackerBrowserWidget.cpp @@ -20,16 +20,20 @@ SeerVariableTrackerBrowserWidget::SeerVariableTrackerBrowserWidget (QWidget* par variablesTreeWidget->setSortingEnabled(false); variablesTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu); variablesTreeWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); - variablesTreeWidget->resizeColumnToContents(0); // id - variablesTreeWidget->resizeColumnToContents(1); // name - variablesTreeWidget->resizeColumnToContents(2); // value - variablesTreeWidget->setColumnHidden(0, true); // Hide the 'number' column. + variablesTreeWidget->resizeColumnToContents(0); // name + variablesTreeWidget->resizeColumnToContents(1); // value + variablesTreeWidget->resizeColumnToContents(2); // id + variablesTreeWidget->resizeColumnToContents(3); // used + variablesTreeWidget->setColumnHidden(2, true); // Hide the 'id' column. + variablesTreeWidget->setColumnHidden(3, true); // Hide the 'used' column. variablesTreeWidget->clear(); // Connect things. QObject::connect(variableAddLineEdit, &QLineEdit::returnPressed, this, &SeerVariableTrackerBrowserWidget::handleAddLineEdit); QObject::connect(variableDeleteToolButton, &QToolButton::clicked, this, &SeerVariableTrackerBrowserWidget::handleDeleteToolButton); QObject::connect(variableDeleteAllToolButton, &QToolButton::clicked, this, &SeerVariableTrackerBrowserWidget::handleDeleteAllToolButton); + QObject::connect(variablesTreeWidget, &QTreeWidget::itemCollapsed, this, &SeerVariableTrackerBrowserWidget::handleItemCollapsed); + QObject::connect(variablesTreeWidget, &QTreeWidget::itemExpanded, this, &SeerVariableTrackerBrowserWidget::handleItemExpanded); QObject::connect(variablesTreeWidget, &QTreeWidget::itemEntered, this, &SeerVariableTrackerBrowserWidget::handleItemEntered); QObject::connect(variablesTreeWidget, &QTreeWidget::customContextMenuRequested, this, &SeerVariableTrackerBrowserWidget::handleContextMenu); } @@ -48,8 +52,6 @@ void SeerVariableTrackerBrowserWidget::handleText (const QString& text) { if (text.startsWith("^done,DataExpressionTable={") && text.endsWith("}")) { - //qDebug() << text; - // "^done,DataExpressionTable={ // entry={id=\"1\",expression=\"s\"}, // entry={id=\"2\",expression=\"v\"}, @@ -57,56 +59,58 @@ void SeerVariableTrackerBrowserWidget::handleText (const QString& text) { // entry={id=\"5\",expression=\"m\"} // }" - variablesTreeWidget->clear(); - QString frame_text = Seer::parseFirst(text, "DataExpressionTable=", '{', '}', false); QStringList entries_list = Seer::parse(frame_text, "entry=", '{', '}', false); - for (int i=0; isetText(0, id_text); - topItem->setText(1, expression_text); - topItem->setText(2, ""); + QList matches = variablesTreeWidget->findItems(id_text, Qt::MatchExactly, 2); - topItem->setFont(2, QFontDatabase::systemFont(QFontDatabase::FixedFont)); + if (matches.count() == 0) { - variablesTreeWidget->addTopLevelItem(topItem); + QTreeWidgetItem* topItem = new QTreeWidgetItem; + topItem->setText(0, expression_text); + topItem->setText(1, ""); + topItem->setFont(1, QFontDatabase::systemFont(QFontDatabase::FixedFont)); + topItem->setText(2, id_text); + topItem->setText(3, "new"); + + variablesTreeWidget->addTopLevelItem(topItem); + } } }else if (text.startsWith("^done,DataExpressionAdded={") && text.endsWith("}")) { - //qDebug() << "Refresh"; - // "^done,DataExpressionAdded={ // id=\"5\", // expression=\"m\" // }" - QString frame_text = Seer::parseFirst(text, "DataExpressionAdded=", '{', '}', false); + QString frame_text = Seer::parseFirst(text, "DataExpressionAdded=", '{', '}', false); + QString id_text = Seer::parseFirst(frame_text, "id=", '"', '"', false); + QString expression_text = Seer::parseFirst(frame_text, "expression=", '"', '"', false); - QString id_text = Seer::parseFirst(frame_text, "id=", '"', '"', false); - QString expression_text = Seer::parseFirst(frame_text, "expression=", '"', '"', false); + QList matches = variablesTreeWidget->findItems(id_text, Qt::MatchExactly, 2); - QTreeWidgetItem* topItem = new QTreeWidgetItem; - topItem->setText(0, id_text); - topItem->setText(1, expression_text); - topItem->setText(2, ""); + if (matches.count() == 0) { - topItem->setFont(2, QFontDatabase::systemFont(QFontDatabase::FixedFont)); + QTreeWidgetItem* topItem = new QTreeWidgetItem; + topItem->setText(0, expression_text); + topItem->setText(1, ""); + topItem->setFont(1, QFontDatabase::systemFont(QFontDatabase::FixedFont)); + topItem->setText(2, id_text); + topItem->setText(3, "new"); - variablesTreeWidget->addTopLevelItem(topItem); + variablesTreeWidget->addTopLevelItem(topItem); + } }else if (text.startsWith("^done,DataExpressionDeleted={") && text.endsWith("}")) { - //qDebug() << text; - // "^done,DataExpressionDeleted={ // entry={id=\"1\",expression=\"s\"}, // entry={id=\"3\",expression=\"vb\"} @@ -116,46 +120,73 @@ void SeerVariableTrackerBrowserWidget::handleText (const QString& text) { QStringList entries_list = Seer::parse(frame_text, "entry=", '{', '}', false); - for (int i=0; i matches = variablesTreeWidget->findItems(id_text, Qt::MatchExactly, 0); + QList matches = variablesTreeWidget->findItems(id_text, Qt::MatchExactly, 2); qDeleteAll(matches); } }else if (text.contains(QRegularExpression("^([0-9]+)\\^done,value="))) { - //qDebug() << text; - // "6^done,value=\"\\\"abc\\\"\"" QString id_text = text.section('^', 0,0); QString value_text = Seer::parseFirst(text, "value=", '"', '"', false); - QList matches = variablesTreeWidget->findItems(id_text, Qt::MatchExactly, 0); + // Find the ones that match our 'id'. + QList matches = variablesTreeWidget->findItems(id_text, Qt::MatchExactly, 2); + + if (matches.count() == 1) { + + Q_ASSERT(matches.count() == 1); + + // There should be only one. + QTreeWidgetItem* item = matches[0]; + + // Mark each entry initially as "unused". + // Later, some will be marked as "reused" or "new". Then the "unused" ones will + // be deleted. + QTreeWidgetItemIterator itmark(item); + while (*itmark) { + (*itmark)->setText(3, "unused"); + ++itmark; + } + + // Set the value. + handleItemCreate (item, value_text); - if (matches.size() > 0) { - matches.first()->setText(2, Seer::filterEscapes(value_text)); + // At this point, there are some new entries, some reused entries, and some unused ones. + // For now, don't bother deleting 'unused' ones. } }else if (text.contains(QRegularExpression("^([0-9]+)\\^error,msg="))) { - //qDebug() << text; - // "1^error,msg=\"No symbol \\\"j\\\" in current context.\"" QString id_text = text.section('^', 0,0); QString msg_text = Seer::parseFirst(text, "msg=", '"', '"', false); - QList matches = variablesTreeWidget->findItems(id_text, Qt::MatchExactly, 0); + // Find the ones that match our 'id'. + QList matches = variablesTreeWidget->findItems(id_text, Qt::MatchExactly, 2); + + if (matches.count() == 1) { + + // There should be only one. + QTreeWidgetItem* item = matches[0]; + + // Remove any children. + QList children = item->takeChildren(); + + qDeleteAll(children); - if (matches.size() > 0) { - matches.first()->setText(2, Seer::filterEscapes(msg_text)); + // Set the text with the error message. + item->setText(1, Seer::filterEscapes(msg_text)); + item->setText(3, "used"); } }else if (text.startsWith("^error,msg=\"No registers.\"")) { @@ -168,6 +199,7 @@ void SeerVariableTrackerBrowserWidget::handleText (const QString& text) { variablesTreeWidget->resizeColumnToContents(0); variablesTreeWidget->resizeColumnToContents(1); variablesTreeWidget->resizeColumnToContents(2); + variablesTreeWidget->resizeColumnToContents(3); QApplication::restoreOverrideCursor(); } @@ -193,8 +225,6 @@ void SeerVariableTrackerBrowserWidget::refreshValues () { void SeerVariableTrackerBrowserWidget::handleAddLineEdit () { - //qDebug(); - QString variable = variableAddLineEdit->text(); variableAddLineEdit->clear(); @@ -209,8 +239,6 @@ void SeerVariableTrackerBrowserWidget::handleAddLineEdit () { void SeerVariableTrackerBrowserWidget::handleDeleteToolButton () { - //qDebug(); - // Get selected tree items. QList items = variablesTreeWidget->selectedItems(); @@ -224,7 +252,7 @@ void SeerVariableTrackerBrowserWidget::handleDeleteToolButton () { variableids += " "; } - variableids += (*i)->text(0); + variableids += (*i)->text(2); } // Don't do anything if the list of variables is empty. @@ -237,16 +265,35 @@ void SeerVariableTrackerBrowserWidget::handleDeleteToolButton () { } void SeerVariableTrackerBrowserWidget::handleDeleteAllToolButton () { + emit deleteVariableExpressions("*"); } +void SeerVariableTrackerBrowserWidget::handleItemExpanded (QTreeWidgetItem* item) { + + Q_UNUSED(item); + + variablesTreeWidget->resizeColumnToContents(0); + variablesTreeWidget->resizeColumnToContents(1); + variablesTreeWidget->resizeColumnToContents(2); + variablesTreeWidget->resizeColumnToContents(3); +} + +void SeerVariableTrackerBrowserWidget::handleItemCollapsed (QTreeWidgetItem* item) { + + Q_UNUSED(item); + + variablesTreeWidget->resizeColumnToContents(0); + variablesTreeWidget->resizeColumnToContents(1); + variablesTreeWidget->resizeColumnToContents(2); + variablesTreeWidget->resizeColumnToContents(3); +} + void SeerVariableTrackerBrowserWidget::handleItemEntered (QTreeWidgetItem* item, int column) { Q_UNUSED(column); - //qDebug() << item->text(0) << column; - - item->setToolTip(0, item->text(1) + " : " + item->text(2)); + item->setToolTip(0, item->text(0) + " : " + item->text(1)); for (int i=1; icolumnCount(); i++) { // Copy tooltip to other columns. item->setToolTip(i, item->toolTip(0)); @@ -260,6 +307,125 @@ void SeerVariableTrackerBrowserWidget::showEvent (QShowEvent* event) { refresh(); } +void SeerVariableTrackerBrowserWidget::handleItemCreate (QTreeWidgetItem* item, const QString& value_text) { + + /* + { + qDebug() << "BEFORE==================================================" << item->text(0) << item << item->parent(); + int i = 0; + for (i=0; ichildCount(); i++) { + qDebug() << ">" << item->child(i)->text(0) << item->child(i)->text(1) << item->child(i)->text(2) << item->child(i)->text(3) << item->child(i) << item->child(i)->parent(); + } + qDebug() << "========================================================" << i; + } + */ + + handleItemCreate(item, item->text(2), item->text(0), value_text); + + /* + { + qDebug() << "AFTER==================================================" << item->text(0) << item << item->parent(); + int i = 0; + for (i=0; ichildCount(); i++) { + qDebug() << ">" << item->child(i)->text(0) << item->child(i)->text(1) << item->child(i)->text(2) << item->child(i)->text(3) << item->child(i) << item->child(i)->parent(); + } + qDebug() << "========================================================" << i; + } + */ +} + +void SeerVariableTrackerBrowserWidget::handleItemCreate (QTreeWidgetItem* parentItem, const QString& id_text, const QString& name_text, const QString& value_text) { + + // Fill in parent item. Whether is a simple or complex entry. + parentItem->setText(0, name_text); + parentItem->setText(1, Seer::filterEscapes(value_text)); + parentItem->setFont(1, QFontDatabase::systemFont(QFontDatabase::FixedFont)); + parentItem->setText(2, id_text); + parentItem->setText(3, "reused"); + + // Look for bookmarks. This indicates a nested structure. + // There are two types. With and without an address. + + QString capture0; // With bookends. + QString capture1; // Without. + + QRegularExpression withaddress_re("^@0[xX][0-9a-fA-F]+: \\{(.*?)\\}$"); + QRegularExpressionMatch withaddress_match = withaddress_re.match(value_text, 0, QRegularExpression::PartialPreferCompleteMatch); + + if (withaddress_match.hasMatch()) { + capture0 = withaddress_match.captured(0); + capture1 = withaddress_match.captured(1); + + }else{ + QRegularExpression noaddress_re("^\\{(.*?)\\}$"); + QRegularExpressionMatch noaddress_match = noaddress_re.match(value_text, 0, QRegularExpression::PartialPreferCompleteMatch); + + if (noaddress_match.hasMatch()) { + capture0 = noaddress_match.captured(0); + capture1 = noaddress_match.captured(1); + } + } + + // Simple entries don't have children. Delete them. + // Then we're done. + if (capture0 == "" || capture1 == "") { + + while (parentItem->childCount() > 0) { + QTreeWidgetItem* item = parentItem->child(0); + delete item; + } + + return; + } + + // Add the complex entry to the tree. Reuse, if possible. + // Instead of creating a new tree each time, we will reuse existing items, if they are there. + // This allows the expanded items to remain expanded. We start by looking for matches that + // may already be there. If there are matches, the code will reuse it. If not, a new item + // is created by the code. Note, when searching, we only look at the current level. Not any + // children. + + // Use the one without bookends. + QString text = capture1; + + // Convert to a list of name/value pairs. + QStringList nv_pairs = Seer::parseCommaList(text, '{', '}'); + + // Go through each pair and add the name and its value to the tree. + for (const auto& nv : nv_pairs) { + + QStringPair pair = Seer::parseNameValue(nv, '='); + + // Look for the existing child, if any so we can reuse it. + QTreeWidgetItem* childItem = 0; + for (int i=0; ichildCount(); i++) { + if (parentItem->child(i)->text(0) == pair.first) { + childItem = parentItem->child(i); + childItem->setText(0, pair.first); + childItem->setText(1, pair.second); + childItem->setFont(1, QFontDatabase::systemFont(QFontDatabase::FixedFont)); + childItem->setText(2, id_text); + childItem->setText(3, "reused"); + break; + } + } + + // Otherwise, create a new child. + if (childItem == 0) { + childItem = new QTreeWidgetItem; + childItem->setText(0, pair.first); + childItem->setText(1, pair.second); + childItem->setFont(1, QFontDatabase::systemFont(QFontDatabase::FixedFont)); + childItem->setText(2, id_text); + childItem->setText(3, "new"); + + parentItem->addChild(childItem); + } + + handleItemCreate(childItem, id_text, childItem->text(0), childItem->text(1)); + } +} + void SeerVariableTrackerBrowserWidget::handleContextMenu (const QPoint& pos) { // Get the item at the cursor. @@ -296,7 +462,7 @@ void SeerVariableTrackerBrowserWidget::handleContextMenu (const QPoint& pos) { } // Populate the clipboard. - if (items.size() == 0) { + if (items.count() == 0) { return; } @@ -304,13 +470,13 @@ void SeerVariableTrackerBrowserWidget::handleContextMenu (const QPoint& pos) { QString text; - for (int i=0; itext(1) + ":" + items[i]->text(2); + text += items[i]->text(0) + ":" + items[i]->text(1); } clipboard->setText(text, QClipboard::Clipboard); diff --git a/src/SeerVariableTrackerBrowserWidget.h b/src/SeerVariableTrackerBrowserWidget.h index cfaad8c5..3c7c84b0 100644 --- a/src/SeerVariableTrackerBrowserWidget.h +++ b/src/SeerVariableTrackerBrowserWidget.h @@ -23,6 +23,8 @@ class SeerVariableTrackerBrowserWidget : public QWidget, protected Ui::SeerVaria void handleDeleteToolButton (); void handleDeleteAllToolButton (); void handleItemEntered (QTreeWidgetItem* item, int column); + void handleItemExpanded (QTreeWidgetItem* item); + void handleItemCollapsed (QTreeWidgetItem* item); void handleContextMenu (const QPoint& pos); signals: @@ -32,6 +34,8 @@ class SeerVariableTrackerBrowserWidget : public QWidget, protected Ui::SeerVaria void deleteVariableExpressions (QString expressionids); protected: + void handleItemCreate (QTreeWidgetItem* item, const QString& value_text); + void handleItemCreate (QTreeWidgetItem* parentItem, const QString& id_text, const QString& name_text, const QString& value_text); void showEvent (QShowEvent* event); private: diff --git a/src/SeerVariableTrackerBrowserWidget.ui b/src/SeerVariableTrackerBrowserWidget.ui index 9e3e78bc..2f939383 100644 --- a/src/SeerVariableTrackerBrowserWidget.ui +++ b/src/SeerVariableTrackerBrowserWidget.ui @@ -39,7 +39,7 @@ - :/seer/resources/RelaxLightIcons/list-remove.svg:/seer/resources/HighContrast/list-remove.svg + :/seer/resources/RelaxLightIcons/list-remove.svg:/seer/resources/RelaxLightIcons/list-remove.svg @@ -53,7 +53,7 @@ - :/seer/resources/RelaxLightIcons/edit-delete.svg:/seer/resources/HighContrast/edit-delete.svg + :/seer/resources/RelaxLightIcons/edit-delete.svg:/seer/resources/RelaxLightIcons/edit-delete.svg @@ -62,21 +62,26 @@ - 3 + 4 - ID + Name - Name + Value - Value + ID + + + + + Used