Files
seer/src/SeerMatrixVisualizerWidget.cpp
T
2025-08-26 17:26:05 -05:00

664 lines
21 KiB
C++

// SPDX-FileCopyrightText: 2021 Ernie Pasveer <epasveer@att.net>
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "SeerMatrixVisualizerWidget.h"
#include "SeerHelpPageDialog.h"
#include "SeerUtl.h"
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QToolTip>
#include <QtGui/QIntValidator>
#include <QtGui/QIcon>
#include <QtPrintSupport/QPrinter>
#include <QtPrintSupport/QPrintDialog>
#include <QtCore/QRegularExpression>
#include <QtCore/QSettings>
#include <QtCore/QDebug>
#include <QtGlobal>
#include <algorithm>
#include <cmath>
SeerMatrixVisualizerWidget::SeerMatrixVisualizerWidget (QWidget* parent) : QWidget(parent) {
// Init variables.
_variableId = Seer::createID(); // Create id's for queries.
_memoryId = Seer::createID();
_rowsId = Seer::createID();
_columnsId = Seer::createID();
_offsetId = Seer::createID();
_strideId = Seer::createID();
// Set up UI.
setupUi(this);
// Setup the widgets
setWindowIcon(QIcon(":/seer/resources/seergdb_64x64.png"));
setWindowTitle("Seer Matrix Visualizer");
setAttribute(Qt::WA_DeleteOnClose);
matrixDisplayFormatComboBox->setCurrentIndex(0);
handleMatrixDisplayFormatComboBox(0);
variableNameLineEdit->enableReturnPressedOnClear();
// Connect things.
QObject::connect(refreshToolButton, &QToolButton::clicked, this, &SeerMatrixVisualizerWidget::handleRefreshButton);
QObject::connect(helpToolButton, &QToolButton::clicked, this, &SeerMatrixVisualizerWidget::handleHelpButton);
QObject::connect(variableNameLineEdit, &SeerHistoryLineEdit::returnPressed, this, &SeerMatrixVisualizerWidget::handleVariableNameLineEdit);
QObject::connect(variableNameLineEdit, &SeerHistoryLineEdit::editingFinished, this, &SeerMatrixVisualizerWidget::handleVariableNameLineEdit);
QObject::connect(matrixRowsLineEdit, &SeerHistoryLineEdit::returnPressed, this, &SeerMatrixVisualizerWidget::handleRefreshButton);
QObject::connect(matrixRowsLineEdit, &SeerHistoryLineEdit::editingFinished, this, &SeerMatrixVisualizerWidget::handleElementRowsLineEdit);
QObject::connect(matrixColumnsLineEdit, &SeerHistoryLineEdit::returnPressed, this, &SeerMatrixVisualizerWidget::handleRefreshButton);
QObject::connect(matrixColumnsLineEdit, &SeerHistoryLineEdit::editingFinished, this, &SeerMatrixVisualizerWidget::handleElementColumnsLineEdit);
QObject::connect(matrixOffsetLineEdit, &SeerHistoryLineEdit::returnPressed, this, &SeerMatrixVisualizerWidget::handleRefreshButton);
QObject::connect(matrixOffsetLineEdit, &SeerHistoryLineEdit::editingFinished, this, &SeerMatrixVisualizerWidget::handleElementOffsetLineEdit);
QObject::connect(matrixStrideLineEdit, &SeerHistoryLineEdit::returnPressed, this, &SeerMatrixVisualizerWidget::handleRefreshButton);
QObject::connect(matrixStrideLineEdit, &SeerHistoryLineEdit::editingFinished, this, &SeerMatrixVisualizerWidget::handleElementStrideLineEdit);
QObject::connect(matrixDisplayFormatComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &SeerMatrixVisualizerWidget::handleMatrixDisplayFormatComboBox);
QObject::connect(matrixTableWidget, &SeerMatrixWidget::dataChanged, this, &SeerMatrixVisualizerWidget::handleDataChanged);
// Restore window settings.
readSettings();
}
SeerMatrixVisualizerWidget::~SeerMatrixVisualizerWidget () {
}
void SeerMatrixVisualizerWidget::setVariableName (const QString& name) {
setWindowTitle("Seer Matrix Visualizer - '" + name + "'");
variableNameLineEdit->setText(name);
if (variableNameLineEdit->text() == "") {
variableAddressLineEdit->setText("");
matrixRowsLineEdit->setText("");
matrixColumnsLineEdit->setText("");
matrixOffsetLineEdit->setText("");
matrixStrideLineEdit->setText("");
matrixTableWidget->setData(0);
return;
}
setVariableAddress("");
// Clear old contents.
QByteArray array;
bool ok;
matrixTableWidget->setData(new SeerMatrixWidget::DataStorageArray(array));
if (matrixOffsetLineEdit->text() != "") {
matrixTableWidget->setAddressOffset(matrixOffsetLineEdit->text().toULong(&ok));
if (ok == false) {
qWarning() << "Invalid string for address offset." << matrixOffsetLineEdit->text();
}
}else{
matrixTableWidget->setAddressOffset(0);
}
if (matrixStrideLineEdit->text() != "") {
matrixTableWidget->setAddressStride(matrixStrideLineEdit->text().toULong(&ok));
if (ok == false) {
qWarning() << "Invalid string for address stride." << matrixStrideLineEdit->text();
}
}else{
matrixTableWidget->setAddressStride(1);
}
// Send signal to get variable address.
emit evaluateVariableExpression(_variableId, variableNameLineEdit->text());
}
QString SeerMatrixVisualizerWidget::variableName () const {
return variableNameLineEdit->text();
}
void SeerMatrixVisualizerWidget::setVariableAddress (const QString& address) {
unsigned long offset = 0;
bool ok = false;
if (address == "") {
variableAddressLineEdit->setText("");
offset = 0;
}else{
// Test for base10
if (ok == false) {
offset = address.toULong(&ok, 10);
if (ok) {
variableAddressLineEdit->setText(QString("0x%1").arg(offset, 0, 16, QLatin1Char( '0' )));
}
}
// Test for base16
if (ok == false) {
offset = address.toULong(&ok, 16);
if (ok) {
variableAddressLineEdit->setText(QString("0x%1").arg(offset, 0, 16, QLatin1Char( '0' )));
}
}
if (ok == false) {
variableAddressLineEdit->setText("not an address");
offset = 0;
}
}
matrixTableWidget->setAddressOffset(0);
}
QString SeerMatrixVisualizerWidget::variableAddress () const {
return variableAddressLineEdit->text();
}
void SeerMatrixVisualizerWidget::setVariableRows (const QString& rows) {
matrixRowsLineEdit->setText(rows);
}
QString SeerMatrixVisualizerWidget::variableRows () const {
return matrixRowsLineEdit->text();
}
void SeerMatrixVisualizerWidget::setVariableColumns (const QString& columns) {
matrixColumnsLineEdit->setText(columns);
}
QString SeerMatrixVisualizerWidget::variableColumns () const {
return matrixColumnsLineEdit->text();
}
void SeerMatrixVisualizerWidget::setVariableOffset (const QString& offset) {
matrixOffsetLineEdit->setText(offset);
}
QString SeerMatrixVisualizerWidget::variableOffset () const {
return matrixOffsetLineEdit->text();
}
void SeerMatrixVisualizerWidget::setVariableStride (const QString& stride) {
matrixStrideLineEdit->setText(stride);
}
QString SeerMatrixVisualizerWidget::variableStride () const {
return matrixStrideLineEdit->text();
}
void SeerMatrixVisualizerWidget::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() == _variableId) {
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.
setVariableAddress(address);
}
if (id_text.toInt() == _rowsId) {
// Set the memory length.
QString value_text = Seer::parseFirst(text, "value=", '"', '"', false);
setVariableRows(value_text);
handleRefreshButton();
}
if (id_text.toInt() == _columnsId) {
// Set the memory length.
QString value_text = Seer::parseFirst(text, "value=", '"', '"', false);
setVariableColumns(value_text);
handleRefreshButton();
}
if (id_text.toInt() == _offsetId) {
// Set the memory offset.
QString value_text = Seer::parseFirst(text, "value=", '"', '"', false);
setVariableOffset(value_text);
handleRefreshButton();
}
if (id_text.toInt() == _strideId) {
// Set the memory stride.
QString value_text = Seer::parseFirst(text, "value=", '"', '"', false);
setVariableStride(value_text);
handleRefreshButton();
}
}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() == _memoryId) {
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; i<contents_text.size(); i += 2) {
QString num = contents_text.mid(i, 2);
bool ok = false;
array.push_back(num.toInt(&ok, 16));
Q_ASSERT(ok);
}
// Give the byte array to the hex widget.
bool ok;
matrixTableWidget->setData(new SeerMatrixWidget::DataStorageArray(array));
if (matrixOffsetLineEdit->text() != "") {
matrixTableWidget->setAddressOffset(matrixOffsetLineEdit->text().toULong(&ok));
if (ok == false) {
qWarning() << "Invalid string for address offset." << matrixOffsetLineEdit->text();
}
}else{
matrixTableWidget->setAddressOffset(0);
}
if (matrixStrideLineEdit->text() != "") {
matrixTableWidget->setAddressStride(matrixStrideLineEdit->text().toULong(&ok));
if (ok == false) {
qWarning() << "Invalid string for address stride." << matrixStrideLineEdit->text();
}
}else{
matrixTableWidget->setAddressStride(1);
}
// Set the dimension of the matrix table.
if (matrixRowsLineEdit->text() != "" && matrixColumnsLineEdit->text() != "") {
int rows = matrixRowsLineEdit->text().toInt(&ok);
if (ok == false) {
qWarning() << "Invalid string for number of rows." << matrixRowsLineEdit->text();
rows = 0;
}
int columns = matrixColumnsLineEdit->text().toInt(&ok);
if (ok == false) {
qWarning() << "Invalid string for number of columns." << matrixColumnsLineEdit->text();
columns = 0;
}
matrixTableWidget->setDimensions(rows,columns);
}else{
matrixTableWidget->setDimensions(0,0);
}
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() == _variableId) {
variableAddressLineEdit->setText( Seer::filterEscapes(Seer::parseFirst(text, "msg=", '"', '"', false)) );
}
if (id_text.toInt() == _memoryId) {
// 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() == _rowsId) {
// Display the error message.
QString msg_text = Seer::parseFirst(text, "msg=", false);
QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text));
matrixRowsLineEdit->setText("");
matrixRowsLineEdit->setFocus();
}
if (id_text.toInt() == _columnsId) {
// Display the error message.
QString msg_text = Seer::parseFirst(text, "msg=", false);
QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text));
matrixColumnsLineEdit->setText("");
matrixColumnsLineEdit->setFocus();
}
if (id_text.toInt() == _offsetId) {
// Display the error message.
QString msg_text = Seer::parseFirst(text, "msg=", false);
QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text));
matrixOffsetLineEdit->setText("");
matrixOffsetLineEdit->setFocus();
}
if (id_text.toInt() == _strideId) {
// Display the error message.
QString msg_text = Seer::parseFirst(text, "msg=", false);
QMessageBox::warning(this, "Error.", Seer::filterEscapes(msg_text));
matrixStrideLineEdit->setText("");
matrixStrideLineEdit->setFocus();
}
}else{
// Ignore anything else.
}
}
void SeerMatrixVisualizerWidget::handleRefreshButton () {
if (variableNameLineEdit->text() == "") {
return;
}
if (variableAddressLineEdit->text() == "") {
return;
}
if (variableAddressLineEdit->text() == "not an address") {
return;
}
int bytes = matrixRowsLineEdit->text().toInt() * matrixColumnsLineEdit->text().toInt() * Seer::typeBytes(matrixDisplayFormatComboBox->currentText());
// qDebug() << "Asking for" << bytes << "bytes of matrix data.";
emit evaluateMemoryExpression(_memoryId, variableAddressLineEdit->text(), bytes);
}
void SeerMatrixVisualizerWidget::handleHelpButton () {
SeerHelpPageDialog* help = new SeerHelpPageDialog;
help->loadFile(":/seer/resources/help/MatrixVisualizer.md");
help->show();
help->raise();
}
void SeerMatrixVisualizerWidget::handleVariableNameLineEdit () {
setVariableName (variableNameLineEdit->text());
}
void SeerMatrixVisualizerWidget::handleElementRowsLineEdit () {
// Nothing set? Return.
if (variableRows() == "") {
return;
}
// A regular integer? Return.
QRegularExpression re("[0-9]+");
QRegularExpressionMatch match = re.match(variableRows());
if (match.hasMatch()) {
return;
}
// Send for the value of the variable.
emit evaluateVariableExpression(_rowsId, variableRows());
}
void SeerMatrixVisualizerWidget::handleElementColumnsLineEdit () {
// Nothing set? Return.
if (variableColumns() == "") {
return;
}
// A regular integer? Return.
QRegularExpression re("[0-9]+");
QRegularExpressionMatch match = re.match(variableColumns());
if (match.hasMatch()) {
return;
}
// Send for the value of the variable.
emit evaluateVariableExpression(_columnsId, variableColumns());
}
void SeerMatrixVisualizerWidget::handleElementOffsetLineEdit () {
// Nothing set? Return.
if (variableOffset() == "") {
return;
}
// A regular integer? Return.
QRegularExpression re("[0-9]+");
QRegularExpressionMatch match = re.match(variableOffset());
if (match.hasMatch()) {
return;
}
emit evaluateVariableExpression(_offsetId, variableOffset());
}
void SeerMatrixVisualizerWidget::handleElementStrideLineEdit () {
// Nothing set? Return.
if (variableStride() == "") {
return;
}
// A regular integer? Return.
QRegularExpression re("[0-9]+");
QRegularExpressionMatch match = re.match(variableRows());
if (match.hasMatch()) {
return;
}
// Send for the value of the variable.
emit evaluateVariableExpression(_strideId, variableStride());
}
void SeerMatrixVisualizerWidget::handleMatrixDisplayFormatComboBox (int index) {
//qDebug() << index;
if (index == 0) {
matrixTableWidget->setDataType(SeerMatrixWidget::Int16MatrixType);
}else if (index == 1) {
matrixTableWidget->setDataType(SeerMatrixWidget::Int32MatrixType);
}else if (index == 2) {
matrixTableWidget->setDataType(SeerMatrixWidget::Int64MatrixType);
}else if (index == 3) {
matrixTableWidget->setDataType(SeerMatrixWidget::UInt16MatrixType);
}else if (index == 4) {
matrixTableWidget->setDataType(SeerMatrixWidget::UInt32MatrixType);
}else if (index == 5) {
matrixTableWidget->setDataType(SeerMatrixWidget::UInt64MatrixType);
}else if (index == 6) {
matrixTableWidget->setDataType(SeerMatrixWidget::Float32MatrixType);
}else if (index == 7) {
matrixTableWidget->setDataType(SeerMatrixWidget::Float64MatrixType);
}else{
// Do nothing.
}
handleRefreshButton();
}
void SeerMatrixVisualizerWidget::handleDataChanged () {
// Update the meta information.
// Clear everything.
countLineEdit->setText("");
rowsLineEdit->setText("");
columnsLineEdit->setText("");
minimumLineEdit->setText("");
maximumLineEdit->setText("");
sumLineEdit->setText("");
averageLineEdit->setText("");
medianLineEdit->setText("");
rmsLineEdit->setText("");
// If there's nothing to show, just return.
if (matrixTableWidget->dataCount() <= 0) {
return;
}
if (matrixTableWidget->dataValues().count() <= 0) {
return;
}
// Make a copy of the values for us to play with.
QVector<double> values = matrixTableWidget->dataValues();
// Fill in counts.
countLineEdit->setText(QString::number(matrixTableWidget->dataCount()));
rowsLineEdit->setText(QString::number(matrixTableWidget->dataRows()));
columnsLineEdit->setText(QString::number(matrixTableWidget->dataColumns()));
// Calculate statistics.
double val = 0.0;
double min = 0.0;
double max = 0.0;
double sum = 0.0;
double sum2 = 0.0;
double avg = 0.0;
double med = 0.0;
double rms = 0.0;
for (int i=0; i<values.size(); i++) {
val = values[i];
min = std::min(min, val);
max = std::max(max, val);
sum += val;
sum2 += std::pow(val, 2);
}
avg = sum / values.size();
rms = std::sqrt(sum2 / values.size());
// Post them.
minimumLineEdit->setText(QString::number(min));
maximumLineEdit->setText(QString::number(max));
sumLineEdit->setText(QString::number(sum));
averageLineEdit->setText(QString::number(avg));
rmsLineEdit->setText(QString::number(rms));
// For median, we need to sort the values. So do this last.
std::sort(values.begin(), values.end());
// If the number of elements is odd, the median is the middle element
if (values.size() % 2 != 0) {
med = values[values.size() / 2];
}else{
double mid1 = values[values.size() / 2 - 1];
double mid2 = values[values.size() / 2];
med = (mid1 + mid2) / 2.0;
}
medianLineEdit->setText(QString::number(med));
return;
}
void SeerMatrixVisualizerWidget::writeSettings() {
QSettings settings;
settings.beginGroup("matrixvisualizerwindow"); {
settings.setValue("size", size());
} settings.endGroup();
}
void SeerMatrixVisualizerWidget::readSettings() {
QSettings settings;
settings.beginGroup("matrixvisualizerwindow"); {
resize(settings.value("size", QSize(800, 400)).toSize());
} settings.endGroup();
}
void SeerMatrixVisualizerWidget::resizeEvent (QResizeEvent* event) {
writeSettings();
QWidget::resizeEvent(event);
}