// SPDX-FileCopyrightText: 2021 Ernie Pasveer // // SPDX-License-Identifier: GPL-3.0-or-later #include "SeerArrayVisualizerWidget.h" #include "SeerHelpPageDialog.h" #include "SeerUtl.h" #include #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 3) #include #include #endif #include #include #include #include #include #include // // Qt Charts 6.x workaround: scatter marker items can be (re)created without // inheriting the series' pen/brush, leaving near-invisible ~1px markers. // Small arrays usually render fine; large ones (e.g. 4000 points) reliably // degrade. The series-level state is correct in both cases, so the style is // lost at the marker-item level inside Qt Charts. Re-applying the pen/brush // after the series is added pushes the style down to the marker items. // // Two passes suffice, not four. This runs after addSeries(), so the series // already carries the chart theme's pen/brush; setPen(NoPen) and setBrush() // are therefore real value transitions (no built-in Qt theme uses NoPen for // a scatter pen nor this exact colour for the brush) and are not swallowed // by the guarded setters. Even if one were a no-op, the other setter's // update re-pushes both pen and brush to every marker item. Two passes over // the (up to thousands of) marker items instead of four roughly halves this // step's cost, with no visible change to the markers. // static void redecorateScatterSeries (QXYSeries* series) { QScatterSeries* scatter = qobject_cast(series); if (scatter == nullptr) { return; } scatter->setPen(QPen(Qt::NoPen)); scatter->setBrush(QBrush(QColor(31, 119, 180))); } SeerArrayVisualizerWidget::SeerArrayVisualizerWidget (QWidget* parent) : QWidget(parent) { // Init variables. _aVariableId = Seer::createID(); // Create id's for A queries. _aMemoryId = Seer::createID(); _aLengthId = Seer::createID(); _aOffsetId = Seer::createID(); _aStrideId = Seer::createID(); _bVariableId = Seer::createID(); // Create id's for B queries. _bMemoryId = Seer::createID(); _bLengthId = Seer::createID(); _bOffsetId = Seer::createID(); _bStrideId = Seer::createID(); _aSeries = 0; _bSeries = 0; // Set up UI. setupUi(this); // Setup the widgets setWindowTitle("Seer Array Visualizer"); setAttribute(Qt::WA_DeleteOnClose); arrayTableWidget->setAAxis(aAxisComboBox->currentText()); arrayTableWidget->setBAxis(bAxisComboBox->currentText()); lineRadioButton->setChecked(true); aArrayDisplayFormatComboBox->setCurrentIndex(0); bArrayDisplayFormatComboBox->setCurrentIndex(0); handleaArrayDisplayFormatComboBox(0); handlebArrayDisplayFormatComboBox(0); aVariableNameLineEdit->enableReturnPressedOnClear(); bVariableNameLineEdit->enableReturnPressedOnClear(); // A single series chart. QChart* chart = new QChart; chart->legend()->hide(); chart->createDefaultAxes(); chart->legend()->setVisible(true); chart->legend()->setAlignment(Qt::AlignBottom); arrayChartView->setRenderHint(QPainter::Antialiasing); arrayChartView->setChart(chart); arrayChartView->setFocusPolicy(Qt::StrongFocus); // Connect things. QObject::connect(aRefreshToolButton, &QToolButton::clicked, this, &SeerArrayVisualizerWidget::handleaRefreshButton); QObject::connect(bRefreshToolButton, &QToolButton::clicked, this, &SeerArrayVisualizerWidget::handlebRefreshButton); QObject::connect(helpToolButton, &QToolButton::clicked, this, &SeerArrayVisualizerWidget::handleHelpButton); QObject::connect(aVariableNameLineEdit, &SeerHistoryLineEdit::returnPressed, this, &SeerArrayVisualizerWidget::handleaVariableNameLineEdit); QObject::connect(aVariableNameLineEdit, &SeerHistoryLineEdit::editingFinished, this, &SeerArrayVisualizerWidget::handleaVariableNameLineEdit); QObject::connect(aArrayLengthLineEdit, &SeerHistoryLineEdit::returnPressed, this, &SeerArrayVisualizerWidget::handleaRefreshButton); QObject::connect(aArrayLengthLineEdit, &SeerHistoryLineEdit::editingFinished, this, &SeerArrayVisualizerWidget::handleaElementLengthLineEdit); QObject::connect(aArrayOffsetLineEdit, &SeerHistoryLineEdit::returnPressed, this, &SeerArrayVisualizerWidget::handleaRefreshButton); QObject::connect(aArrayOffsetLineEdit, &SeerHistoryLineEdit::editingFinished, this, &SeerArrayVisualizerWidget::handleaElementOffsetLineEdit); QObject::connect(aArrayStrideLineEdit, &SeerHistoryLineEdit::returnPressed, this, &SeerArrayVisualizerWidget::handleaRefreshButton); QObject::connect(aArrayStrideLineEdit, &SeerHistoryLineEdit::editingFinished, this, &SeerArrayVisualizerWidget::handleaElementStrideLineEdit); QObject::connect(aArrayDisplayFormatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &SeerArrayVisualizerWidget::handleaArrayDisplayFormatComboBox); QObject::connect(aAxisComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &SeerArrayVisualizerWidget::handleaAxisComboBox); QObject::connect(bVariableNameLineEdit, &SeerHistoryLineEdit::returnPressed, this, &SeerArrayVisualizerWidget::handlebVariableNameLineEdit); QObject::connect(bVariableNameLineEdit, &SeerHistoryLineEdit::editingFinished, this, &SeerArrayVisualizerWidget::handlebVariableNameLineEdit); QObject::connect(bArrayLengthLineEdit, &SeerHistoryLineEdit::returnPressed, this, &SeerArrayVisualizerWidget::handlebRefreshButton); QObject::connect(bArrayLengthLineEdit, &SeerHistoryLineEdit::editingFinished, this, &SeerArrayVisualizerWidget::handlebElementLengthLineEdit); QObject::connect(bArrayOffsetLineEdit, &SeerHistoryLineEdit::returnPressed, this, &SeerArrayVisualizerWidget::handlebRefreshButton); QObject::connect(bArrayOffsetLineEdit, &SeerHistoryLineEdit::editingFinished, this, &SeerArrayVisualizerWidget::handlebElementOffsetLineEdit); QObject::connect(bArrayStrideLineEdit, &SeerHistoryLineEdit::returnPressed, this, &SeerArrayVisualizerWidget::handlebRefreshButton); QObject::connect(bArrayStrideLineEdit, &SeerHistoryLineEdit::editingFinished, this, &SeerArrayVisualizerWidget::handlebElementStrideLineEdit); QObject::connect(bArrayDisplayFormatComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &SeerArrayVisualizerWidget::handlebArrayDisplayFormatComboBox); QObject::connect(bAxisComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &SeerArrayVisualizerWidget::handlebAxisComboBox); QObject::connect(arrayTableWidget, &SeerArrayWidget::dataChanged, this, &SeerArrayVisualizerWidget::handleDataChanged); QObject::connect(splitter, &QSplitter::splitterMoved, this, &SeerArrayVisualizerWidget::handleSplitterMoved); QObject::connect(titleLineEdit, &SeerHistoryLineEdit::returnPressed, this, &SeerArrayVisualizerWidget::handleTitleLineEdit); QObject::connect(titleLineEdit, &SeerHistoryLineEdit::editingFinished, this, &SeerArrayVisualizerWidget::handleTitleLineEdit); QObject::connect(pointsCheckBox, &QCheckBox::clicked, this, &SeerArrayVisualizerWidget::handlePointsCheckBox); QObject::connect(labelsCheckBox, &QCheckBox::clicked, this, &SeerArrayVisualizerWidget::handleLabelsCheckBox); QObject::connect(lineTypeButtonGroup, QOverload::of(&QButtonGroup::idClicked), this, &SeerArrayVisualizerWidget::handleLineTypeButtonGroup); QObject::connect(printPushButton, &QPushButton::clicked, arrayChartView, &QZoomChartView::printView); #if QT_VERSION >= QT_VERSION_CHECK(6, 6, 3) QObject::connect(QGuiApplication::styleHints(), &QStyleHints::colorSchemeChanged, this, &SeerArrayVisualizerWidget::handleThemeChanged); #endif // Colorize icons for theme. Seer::colorizeAllIcons(this, Seer::iconColorTheme()); Seer::colorizeChartViewItem(arrayChartView, Seer::iconColorTheme()); // Restore window settings. readSettings(); } SeerArrayVisualizerWidget::~SeerArrayVisualizerWidget () { } void SeerArrayVisualizerWidget::setAVariableName (const QString& name) { setWindowTitle("Seer Array Visualizer - '" + name + "'"); aVariableNameLineEdit->setText(name); if (aVariableNameLineEdit->text() == "") { aVariableAddressLineEdit->setText(""); aArrayLengthLineEdit->setText(""); aArrayOffsetLineEdit->setText(""); aArrayStrideLineEdit->setText(""); arrayTableWidget->setAData("deleted", 0); return; } // Coalesce this variable's setup (address reset + data/offset/stride) into // a single table rebuild; the guard flushes it when it leaves scope. SeerArrayWidget::BulkUpdate bulk(arrayTableWidget); setAVariableAddress(""); // Clear old contents. QByteArray array; bool ok; arrayTableWidget->setAData(name, new SeerArrayWidget::DataStorageArray(array)); if (aArrayOffsetLineEdit->text() != "") { arrayTableWidget->setAAddressOffset(aArrayOffsetLineEdit->text().toULong(&ok)); if (ok == false) { qWarning() << "Invalid string for address offset." << aArrayOffsetLineEdit->text(); } }else{ arrayTableWidget->setAAddressOffset(0); } if (aArrayStrideLineEdit->text() != "") { arrayTableWidget->setAAddressStride(aArrayStrideLineEdit->text().toULong(&ok)); if (ok == false) { qWarning() << "Invalid string for address stride." << aArrayStrideLineEdit->text(); } }else{ arrayTableWidget->setAAddressStride(1); } // Send signal to get variable address. emit evaluateVariableExpression(_aVariableId, aVariableNameLineEdit->text()); } QString SeerArrayVisualizerWidget::aVariableName () const { return aVariableNameLineEdit->text(); } void SeerArrayVisualizerWidget::setAVariableAddress (const QString& address) { unsigned long offset = 0; bool ok = false; if (address == "") { aVariableAddressLineEdit->setText(""); offset = 0; }else{ // Test for base10 if (ok == false) { offset = address.toULong(&ok, 10); if (ok) { aVariableAddressLineEdit->setText(QString("0x%1").arg(offset, 0, 16, QLatin1Char( '0' ))); } } // Test for base16 if (ok == false) { offset = address.toULong(&ok, 16); if (ok) { aVariableAddressLineEdit->setText(QString("0x%1").arg(offset, 0, 16, QLatin1Char( '0' ))); } } if (ok == false) { aVariableAddressLineEdit->setText("not an address"); offset = 0; } } arrayTableWidget->setAAddressOffset(0); } QString SeerArrayVisualizerWidget::aVariableAddress () const { return aVariableAddressLineEdit->text(); } void SeerArrayVisualizerWidget::setAVariableLength (const QString& length) { aArrayLengthLineEdit->setText(length); } QString SeerArrayVisualizerWidget::aVariableLength () const { return aArrayLengthLineEdit->text(); } void SeerArrayVisualizerWidget::setAVariableOffset (const QString& offset) { aArrayOffsetLineEdit->setText(offset); } QString SeerArrayVisualizerWidget::aVariableOffset () const { return aArrayOffsetLineEdit->text(); } void SeerArrayVisualizerWidget::setAVariableStride (const QString& stride) { aArrayStrideLineEdit->setText(stride); } QString SeerArrayVisualizerWidget::aVariableStride () const { return aArrayStrideLineEdit->text(); } void SeerArrayVisualizerWidget::setBVariableName (const QString& name) { setWindowTitle("Seer Array Visualizer - '" + name + "'"); bVariableNameLineEdit->setText(name); if (bVariableNameLineEdit->text() == "") { bVariableAddressLineEdit->setText(""); bArrayLengthLineEdit->setText(""); bArrayOffsetLineEdit->setText(""); bArrayStrideLineEdit->setText(""); arrayTableWidget->setBData("deleted", 0); return; } // Coalesce this variable's setup (address reset + data/offset/stride) into // a single table rebuild; the guard flushes it when it leaves scope. SeerArrayWidget::BulkUpdate bulk(arrayTableWidget); setBVariableAddress(""); // Clear old contents. QByteArray array; bool ok; arrayTableWidget->setBData(name, new SeerArrayWidget::DataStorageArray(array)); if (bArrayOffsetLineEdit->text() != "") { arrayTableWidget->setBAddressOffset(bArrayOffsetLineEdit->text().toULong(&ok)); if (ok == false) { qWarning() << "Invalid string for address offset." << bArrayOffsetLineEdit->text(); } }else{ arrayTableWidget->setBAddressOffset(0); } if (bArrayStrideLineEdit->text() != "") { arrayTableWidget->setBAddressStride(bArrayStrideLineEdit->text().toULong(&ok)); if (ok == false) { qWarning() << "Invalid string for address stride." << bArrayStrideLineEdit->text(); } }else{ arrayTableWidget->setBAddressStride(1); } // Send signal to get variable address. emit evaluateVariableExpression(_bVariableId, bVariableNameLineEdit->text()); } QString SeerArrayVisualizerWidget::bVariableName () const { return bVariableNameLineEdit->text(); } void SeerArrayVisualizerWidget::setBVariableAddress (const QString& address) { unsigned long offset = 0; bool ok = false; if (address == "") { bVariableAddressLineEdit->setText(""); offset = 0; }else{ // Test for base10 if (ok == false) { offset = address.toULong(&ok, 10); if (ok) { bVariableAddressLineEdit->setText(QString("0x%1").arg(offset, 0, 16, QLatin1Char( '0' ))); } } // Test for base16 if (ok == false) { offset = address.toULong(&ok, 16); if (ok) { bVariableAddressLineEdit->setText(QString("0x%1").arg(offset, 0, 16, QLatin1Char( '0' ))); } } if (ok == false) { bVariableAddressLineEdit->setText("not an address"); offset = 0; } } arrayTableWidget->setBAddressOffset(0); } QString SeerArrayVisualizerWidget::bVariableAddress () const { return bVariableAddressLineEdit->text(); } void SeerArrayVisualizerWidget::setBVariableLength (const QString& length) { bArrayLengthLineEdit->setText(length); } QString SeerArrayVisualizerWidget::bVariableLength () const { return bArrayLengthLineEdit->text(); } void SeerArrayVisualizerWidget::setBVariableOffset (const QString& offset) { bArrayOffsetLineEdit->setText(offset); } QString SeerArrayVisualizerWidget::bVariableOffset () const { return bArrayOffsetLineEdit->text(); } void SeerArrayVisualizerWidget::setBVariableStride (const QString& stride) { bArrayStrideLineEdit->setText(stride); } QString SeerArrayVisualizerWidget::bVariableStride () const { return bArrayStrideLineEdit->text(); } void SeerArrayVisualizerWidget::handleText (const QString& text) { //qDebug() << text; if (text.contains(QRegularExpression("^([0-9]+)\\^done,value="))) { // 11^done,value="1" // 11^done,value="0x7fffffffd538" QString id_text = text.section('^', 0,0); if (id_text.toInt() == _aVariableId) { QString value_text = Seer::parseFirst(text, "value=", '"', '"', false); QString address = ""; // Look for an address in the value. if (address == "") { QRegularExpression re("0[xX][0-9a-fA-F]+"); QRegularExpressionMatch match = re.match(value_text); if (match.hasMatch()) { address = match.captured(); } } // Look for a number in the value. if (address == "") { QRegularExpression re("[0-9]+"); QRegularExpressionMatch match = re.match(value_text); if (match.hasMatch()) { address = match.captured(); } } // Set the variable address. setAVariableAddress(address); } if (id_text.toInt() == _aLengthId) { // Set the memory length. QString value_text = Seer::parseFirst(text, "value=", '"', '"', false); setAVariableLength(value_text); handleaRefreshButton(); } if (id_text.toInt() == _aOffsetId) { // Set the memory offset. QString value_text = Seer::parseFirst(text, "value=", '"', '"', false); setAVariableOffset(value_text); handleaRefreshButton(); } if (id_text.toInt() == _aStrideId) { // Set the memory stride. QString value_text = Seer::parseFirst(text, "value=", '"', '"', false); setAVariableStride(value_text); handleaRefreshButton(); } if (id_text.toInt() == _bVariableId) { QString value_text = Seer::parseFirst(text, "value=", '"', '"', false); QString address = ""; // Look for an address in the value. if (address == "") { QRegularExpression re("0[xX][0-9a-fA-F]+"); QRegularExpressionMatch match = re.match(value_text); if (match.hasMatch()) { address = match.captured(); } } // Look for a number in the value. if (address == "") { QRegularExpression re("[0-9]+"); QRegularExpressionMatch match = re.match(value_text); if (match.hasMatch()) { address = match.captured(); } } // Set the variable address. setBVariableAddress(address); } if (id_text.toInt() == _bLengthId) { // Set the memory length. QString value_text = Seer::parseFirst(text, "value=", '"', '"', false); setBVariableLength(value_text); handlebRefreshButton(); } if (id_text.toInt() == _bOffsetId) { // Set the memory offset. QString value_text = Seer::parseFirst(text, "value=", '"', '"', false); setBVariableOffset(value_text); handlebRefreshButton(); } if (id_text.toInt() == _bStrideId) { // Set the memory stride. QString value_text = Seer::parseFirst(text, "value=", '"', '"', false); setBVariableStride(value_text); handlebRefreshButton(); } }else if (text.contains(QRegularExpression("^([0-9]+)\\^done,memory="))) { // 3^done,memory=[{begin="0x0000000000613e70",offset="0x0000000000000000",end="0x0000000000613e71",contents="00"}] // 4^done,memory=[{begin="0x0000000000613e70",offset="0x0000000000000000",end="0x0000000000613ed4",contents="000000000000000000000000"}] QString id_text = text.section('^', 0,0); if (id_text.toInt() == _aMemoryId) { QString memory_text = Seer::parseFirst(text, "memory=", '[', ']', false); QStringList range_list = Seer::parse(memory_text, "", '{', '}', false); // Loop through the memory ranges. for ( const auto& range_text : range_list ) { QString contents_text = Seer::parseFirst(range_text, "contents=", '"', '"', false); // Convert hex string to byte array. QByteArray array; for (int i = 0; isetAData(arrayTableWidget->aLabel(), new SeerArrayWidget::DataStorageArray(array)); if (aArrayOffsetLineEdit->text() != "") { arrayTableWidget->setAAddressOffset(aArrayOffsetLineEdit->text().toULong(&ok)); if (ok == false) { qWarning() << "Invalid string for address offset." << aArrayOffsetLineEdit->text(); } }else{ arrayTableWidget->setAAddressOffset(0); } if (aArrayStrideLineEdit->text() != "") { arrayTableWidget->setAAddressStride(aArrayStrideLineEdit->text().toULong(&ok)); if (ok == false) { qWarning() << "Invalid string for address stride." << aArrayStrideLineEdit->text(); } }else{ arrayTableWidget->setAAddressStride(1); } break; // Take just the first range for now. } } if (id_text.toInt() == _bMemoryId) { QString memory_text = Seer::parseFirst(text, "memory=", '[', ']', false); QStringList range_list = Seer::parse(memory_text, "", '{', '}', false); // Loop through the memory ranges. for ( const auto& range_text : range_list ) { QString contents_text = Seer::parseFirst(range_text, "contents=", '"', '"', false); // Convert hex string to byte array. QByteArray array; for (int i = 0; isetBData(arrayTableWidget->bLabel(), new SeerArrayWidget::DataStorageArray(array)); if (bArrayOffsetLineEdit->text() != "") { arrayTableWidget->setBAddressOffset(bArrayOffsetLineEdit->text().toULong(&ok)); if (ok == false) { qWarning() << "Invalid string for address offset." << bArrayOffsetLineEdit->text(); } }else{ arrayTableWidget->setBAddressOffset(0); } if (bArrayStrideLineEdit->text() != "") { arrayTableWidget->setBAddressStride(bArrayStrideLineEdit->text().toULong(&ok)); if (ok == false) { qWarning() << "Invalid string for address stride." << bArrayStrideLineEdit->text(); } }else{ arrayTableWidget->setBAddressStride(1); } break; // Take just the first range for now. } } }else if (text.contains(QRegularExpression("^([0-9]+)\\^error,msg="))) { // 12^error,msg="No symbol \"return\" in current context." // 13^error,msg="No symbol \"cout\" in current context." // 3^error,msg="Unable to read memory." QString id_text = text.section('^', 0,0); if (id_text.toInt() == _aVariableId) { aVariableAddressLineEdit->setText( Seer::filterEscapes(Seer::parseFirst(text, "msg=", '"', '"', false)) ); } if (id_text.toInt() == _aMemoryId) { // Display the error message. QString msg_text = Seer::parseFirst(text, "msg=", false); if (msg_text != "") { QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text)); } } if (id_text.toInt() == _aLengthId) { // Display the error message. QString msg_text = Seer::parseFirst(text, "msg=", false); QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text)); aArrayLengthLineEdit->setText(""); aArrayLengthLineEdit->setFocus(); } if (id_text.toInt() == _aOffsetId) { // Display the error message. QString msg_text = Seer::parseFirst(text, "msg=", false); QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text)); aArrayOffsetLineEdit->setText(""); aArrayOffsetLineEdit->setFocus(); } if (id_text.toInt() == _aStrideId) { // Display the error message. QString msg_text = Seer::parseFirst(text, "msg=", false); QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text)); aArrayStrideLineEdit->setText(""); aArrayStrideLineEdit->setFocus(); } if (id_text.toInt() == _bVariableId) { bVariableAddressLineEdit->setText( Seer::filterEscapes(Seer::parseFirst(text, "msg=", '"', '"', false)) ); } if (id_text.toInt() == _bMemoryId) { // Display the error message. QString msg_text = Seer::parseFirst(text, "msg=", false); if (msg_text != "") { QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text)); } } if (id_text.toInt() == _bLengthId) { // Display the error message. QString msg_text = Seer::parseFirst(text, "msg=", false); QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text)); bArrayLengthLineEdit->setText(""); bArrayLengthLineEdit->setFocus(); } if (id_text.toInt() == _bOffsetId) { // Display the error message. QString msg_text = Seer::parseFirst(text, "msg=", false); QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text)); bArrayOffsetLineEdit->setText(""); bArrayOffsetLineEdit->setFocus(); } if (id_text.toInt() == _bStrideId) { // Display the error message. QString msg_text = Seer::parseFirst(text, "msg=", false); QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text)); bArrayStrideLineEdit->setText(""); bArrayStrideLineEdit->setFocus(); } // At a stopping point, refresh. }else if (text.startsWith("*stopped,reason=\"")) { if (autoRefreshCheckBox->isChecked()) { handleaRefreshButton(); handlebRefreshButton(); } }else{ // Ignore anything else. } } void SeerArrayVisualizerWidget::handleaRefreshButton () { if (aVariableNameLineEdit->text() == "") { return; } if (aVariableAddressLineEdit->text() == "") { return; } if (aVariableAddressLineEdit->text() == "not an address") { return; } int bytes = aArrayLengthLineEdit->text().toInt() * Seer::typeBytes(aArrayDisplayFormatComboBox->currentText()); //qDebug() << _aMemoryId << aVariableAddressLineEdit->text() << aArrayLengthLineEdit->text() << aArrayDisplayFormatComboBox->currentText() << bytes; emit evaluateMemoryExpression(_aMemoryId, aVariableAddressLineEdit->text(), bytes); } void SeerArrayVisualizerWidget::handlebRefreshButton () { if (bVariableNameLineEdit->text() == "") { return; } if (bVariableAddressLineEdit->text() == "") { return; } if (bVariableAddressLineEdit->text() == "not an address") { return; } int bytes = bArrayLengthLineEdit->text().toInt() * Seer::typeBytes(bArrayDisplayFormatComboBox->currentText()); //qDebug() << _bMemoryId << bVariableAddressLineEdit->text() << bArrayLengthLineEdit->text() << bArrayDisplayFormatComboBox->currentText() << bytes; emit evaluateMemoryExpression(_bMemoryId, bVariableAddressLineEdit->text(), bytes); } void SeerArrayVisualizerWidget::handleHelpButton () { SeerHelpPageDialog* help = new SeerHelpPageDialog; help->loadFile(":/seer/resources/help/ArrayVisualizer.md"); help->show(); help->raise(); } void SeerArrayVisualizerWidget::handleaVariableNameLineEdit () { setAVariableName (aVariableNameLineEdit->text()); } void SeerArrayVisualizerWidget::handlebVariableNameLineEdit () { setBVariableName (bVariableNameLineEdit->text()); } void SeerArrayVisualizerWidget::handleaElementLengthLineEdit () { if (aVariableLength() == "") { return; } emit evaluateVariableExpression(_aLengthId, aVariableLength()); } void SeerArrayVisualizerWidget::handleaElementOffsetLineEdit () { if (aVariableOffset() == "") { return; } emit evaluateVariableExpression(_aOffsetId, aVariableOffset()); } void SeerArrayVisualizerWidget::handleaElementStrideLineEdit () { if (aVariableStride() == "") { return; } emit evaluateVariableExpression(_aStrideId, aVariableStride()); } void SeerArrayVisualizerWidget::handlebElementLengthLineEdit () { if (bVariableLength() == "") { return; } emit evaluateVariableExpression(_bLengthId, bVariableLength()); } void SeerArrayVisualizerWidget::handlebElementOffsetLineEdit () { if (bVariableOffset() == "") { return; } emit evaluateVariableExpression(_bOffsetId, bVariableOffset()); } void SeerArrayVisualizerWidget::handlebElementStrideLineEdit () { if (bVariableStride() == "") { return; } emit evaluateVariableExpression(_bStrideId, bVariableStride()); } void SeerArrayVisualizerWidget::handleaArrayDisplayFormatComboBox (int index) { //qDebug() << index; if (index == 0) { arrayTableWidget->setAArrayMode(SeerArrayWidget::Int16ArrayMode); }else if (index == 1) { arrayTableWidget->setAArrayMode(SeerArrayWidget::Int32ArrayMode); }else if (index == 2) { arrayTableWidget->setAArrayMode(SeerArrayWidget::Int64ArrayMode); }else if (index == 3) { arrayTableWidget->setAArrayMode(SeerArrayWidget::UInt16ArrayMode); }else if (index == 4) { arrayTableWidget->setAArrayMode(SeerArrayWidget::UInt32ArrayMode); }else if (index == 5) { arrayTableWidget->setAArrayMode(SeerArrayWidget::UInt64ArrayMode); }else if (index == 6) { arrayTableWidget->setAArrayMode(SeerArrayWidget::Float32ArrayMode); }else if (index == 7) { arrayTableWidget->setAArrayMode(SeerArrayWidget::Float64ArrayMode); }else{ // Do nothing. } handleaRefreshButton(); } void SeerArrayVisualizerWidget::handlebArrayDisplayFormatComboBox (int index) { //qDebug() << index; if (index == 0) { arrayTableWidget->setBArrayMode(SeerArrayWidget::Int16ArrayMode); }else if (index == 1) { arrayTableWidget->setBArrayMode(SeerArrayWidget::Int32ArrayMode); }else if (index == 2) { arrayTableWidget->setBArrayMode(SeerArrayWidget::Int64ArrayMode); }else if (index == 3) { arrayTableWidget->setBArrayMode(SeerArrayWidget::UInt16ArrayMode); }else if (index == 4) { arrayTableWidget->setBArrayMode(SeerArrayWidget::UInt32ArrayMode); }else if (index == 5) { arrayTableWidget->setBArrayMode(SeerArrayWidget::UInt64ArrayMode); }else if (index == 6) { arrayTableWidget->setBArrayMode(SeerArrayWidget::Float32ArrayMode); }else if (index == 7) { arrayTableWidget->setBArrayMode(SeerArrayWidget::Float64ArrayMode); }else{ // Do nothing. } handlebRefreshButton(); } void SeerArrayVisualizerWidget::handleaAxisComboBox (int index) { arrayTableWidget->setAAxis(aAxisComboBox->itemText(index)); handleDataChanged(); } void SeerArrayVisualizerWidget::handlebAxisComboBox (int index) { arrayTableWidget->setBAxis(bAxisComboBox->itemText(index)); handleDataChanged(); } void SeerArrayVisualizerWidget::handleDataChanged () { // Delete old series. if (_aSeries) { arrayChartView->chart()->removeSeries(_aSeries); arrayChartView->chart()->update(); delete _aSeries; _aSeries = 0; } if (_bSeries) { arrayChartView->chart()->removeSeries(_bSeries); arrayChartView->chart()->update(); delete _bSeries; _bSeries = 0; } // If only the first array is defined, create a series for it. // Handle the X and Y axis. if (arrayTableWidget->aSize() > 0 && arrayTableWidget->bSize() == 0) { createASeries(); // If only the second array is defined, create a series for it. // Handle the X and Y axis. }else if (arrayTableWidget->aSize() == 0 && arrayTableWidget->bSize() > 0) { createBSeries(); } else if (arrayTableWidget->aSize() > 0 && arrayTableWidget->bSize() > 0) { // If the axis is the same for the two arrays, plot them as // two unique series. if (arrayTableWidget->aAxis() == arrayTableWidget->bAxis()) { createASeries(); createBSeries(); // Otherwise, we have an X and Y axis. // Plot one series using the two arrays as an X and as a Y. }else{ if (scatterRadioButton->isChecked()) { QScatterSeries* scatter = new QScatterSeries; scatter->setMarkerShape(QScatterSeries::MarkerShapeRectangle); scatter->setMarkerSize(7); _aSeries = scatter; }else if (lineRadioButton->isChecked()) { QLineSeries* line = new QLineSeries; _aSeries = line; }else if (splineRadioButton->isChecked()) { QSplineSeries* line = new QSplineSeries; _aSeries = line; }else{ qWarning() << "Invalid line type."; return; } _aSeries->setPointsVisible(false); _aSeries->setPointLabelsVisible(false); _aSeries->setPointLabelsClipping(true); const QVector& avalues = arrayTableWidget->aArrayValues(); const QVector& bvalues = arrayTableWidget->bArrayValues(); if (arrayTableWidget->aAxis() == "X" && arrayTableWidget->bAxis() == "Y") { for (int i = 0; i < std::min(avalues.size(),bvalues.size()); ++i) { _aSeries->append(avalues[i], bvalues[i]); } }else if (arrayTableWidget->aAxis() == "Y" && arrayTableWidget->bAxis() == "X") { for (int i = 0; i < std::min(avalues.size(),bvalues.size()); ++i) { _aSeries->append(bvalues[i], avalues[i]); } }else{ qWarning() << "Invalid axis type of '" << arrayTableWidget->aAxis() << "'. Not 'X' or 'Y'."; } _aSeries->setName(aVariableName() + "/" + bVariableName()); _aSeries->setName(QString("%1:%2:%3 | %4:%5:%6") .arg(arrayTableWidget->aLabel()).arg(arrayTableWidget->aAddressOffset()).arg(arrayTableWidget->aAddressStride()) .arg(arrayTableWidget->bLabel()).arg(arrayTableWidget->bAddressOffset()).arg(arrayTableWidget->bAddressStride())); } } if (_aSeries) { QObject::connect(_aSeries, &QLineSeries::hovered, this, &SeerArrayVisualizerWidget::handleSeriesHovered); arrayChartView->chart()->addSeries(_aSeries); arrayChartView->chart()->createDefaultAxes(); redecorateScatterSeries(_aSeries); } if (_bSeries) { QObject::connect(_bSeries, &QLineSeries::hovered, this, &SeerArrayVisualizerWidget::handleSeriesHovered); arrayChartView->chart()->addSeries(_bSeries); arrayChartView->chart()->createDefaultAxes(); redecorateScatterSeries(_bSeries); } // Zoom out slightly to allow for text label at edges. arrayChartView->chart()->zoomReset(); arrayChartView->chart()->zoom(.9); arrayChartView->chart()->update(); // Check for points or labels to be shown. handlePointsCheckBox(); handleLabelsCheckBox(); } void SeerArrayVisualizerWidget::writeSettings() { QSettings settings; settings.beginGroup("arrayvisualizerwindow"); { settings.setValue("size", size()); settings.setValue("splitter", splitter->saveState()); } settings.endGroup(); } void SeerArrayVisualizerWidget::readSettings() { QSettings settings; settings.beginGroup("arrayvisualizerwindow"); { resize(settings.value("size", QSize(800, 400)).toSize()); splitter->restoreState(settings.value("splitter").toByteArray()); } settings.endGroup(); } void SeerArrayVisualizerWidget::resizeEvent (QResizeEvent* event) { writeSettings(); QWidget::resizeEvent(event); } void SeerArrayVisualizerWidget::handleSplitterMoved (int pos, int index) { Q_UNUSED(pos); Q_UNUSED(index); writeSettings(); } void SeerArrayVisualizerWidget::handleSeriesHovered (const QPointF& point, bool state) { //qDebug() << "QPointF=" << point << "State=" << state << "MapToPosition=" << arrayChartView->chart()->mapToPosition(point) << "MapFromScene=" << arrayChartView->mapFromScene(arrayChartView->chart()->mapToPosition(point)); if (state) { QToolTip::showText(arrayChartView->mapToGlobal(arrayChartView->mapFromScene(arrayChartView->chart()->mapToPosition(point))), QString("%1 / %2").arg(point.x()).arg(point.y()), this, QRect(), 10000); }else{ QToolTip::hideText(); } } void SeerArrayVisualizerWidget::handleTitleLineEdit () { arrayChartView->chart()->setTitle(titleLineEdit->text()); titleLineEdit->setText(""); } void SeerArrayVisualizerWidget::handlePointsCheckBox () { if (_aSeries) { _aSeries->setPointsVisible(pointsCheckBox->isChecked()); arrayChartView->chart()->update(); } if (_bSeries) { _bSeries->setPointsVisible(pointsCheckBox->isChecked()); arrayChartView->chart()->update(); } } void SeerArrayVisualizerWidget::handleLabelsCheckBox () { if (_aSeries) { _aSeries->setPointLabelsVisible(labelsCheckBox->isChecked()); arrayChartView->chart()->update(); } if (_bSeries) { _bSeries->setPointLabelsVisible(labelsCheckBox->isChecked()); arrayChartView->chart()->update(); } } void SeerArrayVisualizerWidget::handleLineTypeButtonGroup () { if (scatterRadioButton->isChecked()) { pointsCheckBox->setChecked(true); pointsCheckBox->setEnabled(false); }else{ pointsCheckBox->setChecked(false); pointsCheckBox->setEnabled(true); } handleDataChanged(); } void SeerArrayVisualizerWidget::handleThemeChanged () { // Colorize icons for theme. Seer::colorizeAllIcons(this, Seer::iconColorTheme()); // And the ChartView. Seer::colorizeChartViewItem(arrayChartView, Seer::iconColorTheme()); } void SeerArrayVisualizerWidget::createASeries() { if (scatterRadioButton->isChecked()) { QScatterSeries* scatter = new QScatterSeries; scatter->setMarkerShape(QScatterSeries::MarkerShapeRectangle); scatter->setMarkerSize(7); _aSeries = scatter; }else if (lineRadioButton->isChecked()) { QLineSeries* line = new QLineSeries; _aSeries = line; }else if (splineRadioButton->isChecked()) { QSplineSeries* line = new QSplineSeries; _aSeries = line; }else{ qWarning() << "Invalid line type."; return; } _aSeries->setPointsVisible(false); _aSeries->setPointLabelsVisible(false); _aSeries->setPointLabelsClipping(true); const QVector& values = arrayTableWidget->aArrayValues(); if (arrayTableWidget->aAxis() == "Y") { for (int i = 0; i < values.size(); ++i) { _aSeries->append(i, values[i]); } }else if (arrayTableWidget->aAxis() == "X") { for (int i = 0; i < values.size(); ++i) { _aSeries->append(values[i], i); } }else{ qWarning() << "Invalid axis type of '" << arrayTableWidget->aAxis() << "'. Not 'X' or 'Y'."; } _aSeries->setName(QString("%1:%2:%3").arg(arrayTableWidget->aLabel()).arg(arrayTableWidget->aAddressOffset()).arg(arrayTableWidget->aAddressStride())); } void SeerArrayVisualizerWidget::createBSeries() { if (scatterRadioButton->isChecked()) { QScatterSeries* scatter = new QScatterSeries; scatter->setMarkerShape(QScatterSeries::MarkerShapeRectangle); scatter->setMarkerSize(7); _bSeries = scatter; }else if (lineRadioButton->isChecked()) { QLineSeries* line = new QLineSeries; _bSeries = line; }else if (splineRadioButton->isChecked()) { QSplineSeries* line = new QSplineSeries; _bSeries = line; }else{ qWarning() << "Invalid line type."; return; } _bSeries->setPointsVisible(false); _bSeries->setPointLabelsVisible(false); _bSeries->setPointLabelsClipping(true); const QVector& values = arrayTableWidget->bArrayValues(); if (arrayTableWidget->bAxis() == "Y") { for (int i = 0; i < values.size(); ++i) { _bSeries->append(i, values[i]); } }else if (arrayTableWidget->bAxis() == "X") { for (int i = 0; i < values.size(); ++i) { _bSeries->append(values[i], i); } }else{ qWarning() << "Invalid axis type of '" << arrayTableWidget->bAxis() << "'. Not 'X' or 'Y'."; } _bSeries->setName(QString("%1:%2:%3").arg(arrayTableWidget->bLabel()).arg(arrayTableWidget->bAddressOffset()).arg(arrayTableWidget->bAddressStride())); }