Select Git revision
control.cpp
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
control.cpp 20.60 KiB
#include "control.h"
#include "algoThread.h"
#include "csvParser.h"
#include "csvWriter.h"
#include "cuteControl.h"
#include "mainWindow.h"
#include <QCategoryAxis>
#include <QChartView>
#include <QComboBox>
#include <QFileDialog>
#include <QLegendMarker>
#include <QLineEdit>
#include <QLogValueAxis>
#include <QString>
#include <QTooltip>
#include <QValueAxis>
#include <algorithm>
#include <cmath>
#include <random>
#include <variant>
void Control::makeConnections()
{
CuteControl* cc = &CuteControl::get();
connect(cc, &CuteControl::s_setProgress, this, &Control::setProgress);
connect(cc, &CuteControl::s_logMessage, this, &Control::logMessage);
connect(cc, &CuteControl::s_setStatusMessage, this, &Control::setStatusMessage);
connect(cc, &CuteControl::s_setPlotVisible, this, &Control::setPlotVisible);
connect(cc, &CuteControl::s_setPlotTitle, this, &Control::setPlotTitle);
connect(cc, &CuteControl::s_setPlotScale, this, &Control::setPlotScale);
connect(cc, &CuteControl::s_setPlotTheme, this, &Control::setPlotTheme);
connect(cc, &CuteControl::s_setLegendVisible, this, &Control::setLegendVisible);
connect(cc, &CuteControl::s_setAxisVisible, this, &Control::setAxisVisible);
connect(cc, &CuteControl::s_setAxisTitles, this, &Control::setAxisTitles);
connect(cc, &CuteControl::s_enableAutorun, this, &Control::enableAutorun);
connect(cc, &CuteControl::s_setProblemSize, this, &Control::setProblemSize);
connect(cc, &CuteControl::s_setProblemFile, this, &Control::setProblemFile);
connect(cc, &CuteControl::s_plotXYSeries, this, &Control::plotXYSeries);
connect(cc, &CuteControl::s_plotBarSeries, this, &Control::plotBarSeries);
connect(cc, &CuteControl::s_highlightValue, this, &Control::highlightValue);
connect(cc, &CuteControl::s_resetPlot, this, &Control::resetPlot);
connect(cc, &CuteControl::s_undo, this, &Control::undo);
connect(this, &Control::endThread, cc, &CuteControl::end);
connect(w->configPanel->runButton, &QPushButton::clicked, this, &Control::onRunButton);
connect(w->configPanel->fileGenerateButton, &QPushButton::clicked, this, &Control::onGenerateButton);
connect(w->configPanel->randomGenerateButton, &QPushButton::clicked, this, &Control::onGenerateButton);
connect(w->configPanel->pasteGenerateButton, &QPushButton::clicked, this, &Control::onGenerateButton);
connect(w->configPanel->fileLine, &QLineEdit::textChanged, this, &Control::onProblemSettingChanged);
connect(w->configPanel->problemSizeBox, &QSpinBox::textChanged, this, &Control::onProblemSettingChanged);
connect(w, &MainWindow::saveProblem, this, &Control::saveProblem);
connect(w->cm, &ChartMenu::s_setPlotScale, this, &Control::setPlotScale);
connect(cc, &CuteControl::s_verbosityChanged, w, &MainWindow::changeVerbosity);
connect(w->configPanel->verbosityBox,
QOverload<int>::of(&QComboBox::currentIndexChanged),
cc,
&CuteControl::changeVerbosity);
}
int Control::run(void (*userSetup)())
{
static bool running = false;
if (!running)
{
running = true;
int argc = 1;
char* argv[] = {(char*)"AppName"};
QApplication a(argc, argv);
auto Mw = MainWindow();
w = &Mw;
makeConnections();
userSetup();
w->show();
if (autorun)
startThread(algorithms.front().first);
int result = a.exec();
running = false;
return result;
}
else
return -1;
}
Control::Control()
{
onTopSeries.emplace_back();
onTopSeries.emplace_back();
onTopSeries.emplace_back();
plotHistory.emplace_back();
plotHistory.emplace_back();
plotHistory.emplace_back();
}
void Control::logMessage(const QString message)
{
w->textEdit->append(message);
}
void Control::setProgress(int percentage)
{
w->progressBar->setValue(percentage);
}
void Control::setStatusMessage(const QString message, int duration_s)
{
w->statusBar->showMessage(message, duration_s * 1000);
}
void Control::setPlotVisible(int plot, bool visible)
{
w->graphingWidgets[plot]->setHidden(!visible);
}
void Control::setPlotTitle(int plot, const QString title)
{
w->charts[plot]->setTitle(title);
w->cm->titleChanged(plot, title);
}
void Control::setPlotTheme(int themeNumber)
{
w->changeChartTheme(themeNumber);
}
void Control::setLegendVisible(int plot, bool visible)
{
w->charts[plot]->legend()->setVisible(visible);
}
void Control::setAxisTitles(int plot, QString x, QString y)
{
if ((size_t)plot >= w->charts.size())
return;
auto axes = w->charts[plot]->axes();
for (auto axis : axes)
{
if (axis->orientation() == Qt::Horizontal)
{
axis->setTitleText(x);
axis->setTitleVisible(!x.isEmpty());
}
if (axis->orientation() == Qt::Vertical)
{
axis->setTitleText(y);
axis->setTitleVisible(!y.isEmpty());
}
}
}
void Control::setAxisVisible(int plot, bool x, bool y)
{
if ((size_t)plot >= w->charts.size())
return;
auto axes = w->charts[plot]->axes();
for (auto axis : axes)
{
if (axis->orientation() == Qt::Horizontal)
{
axis->setVisible(x);
}
if (axis->orientation() == Qt::Vertical)
{
axis->setVisible(y);
}
}
}
void Control::registerAlgorithm(void (*func)(), const QString name)
{
algorithms.emplace_back(func, name);
w->addAlgorithm(name);
}
void Control::stopThread()
{
if (thread)
{
emit endThread();
}
}
void Control::threadEnded()
{
threadRunning = false;
w->configPanel->setRunning(false);
thread = nullptr;
}
void Control::startThread(void (*func)())
{
if (!thread)
{
thread = new AlgoThread();
thread->setAlgorithm(func);
connect(thread, &AlgoThread::finished, thread, &QObject::deleteLater);
connect(thread, &AlgoThread::finished, this, &Control::threadEnded);
CuteControl::get().setProblem(getProblem());
CuteControl::get().setVerbosity(w->configPanel->verbosityBox->currentIndex());
CuteControl::get().moveToWorkerThread(thread);
w->configPanel->setRunning(true);
threadRunning = true;
thread->start();
logMessage("Running...");
}
}
void Control::onRunButton()
{
if (!thread)
{
int index = w->configPanel->algoBox->currentIndex();
auto func = algorithms[index].first;
startThread(func);
}
else
{
stopThread();
}
}
void Control::setProblemSize(int n)
{
w->configPanel->stackedWidget->setCurrentWidget(w->configPanel->randomWidget);
w->configPanel->problemSizeBox->setValue(n);
}
void Control::setProblemFile(QString path)
{
w->configPanel->stackedWidget->setCurrentWidget(w->configPanel->fileWidget);
w->configPanel->fileLine->setText(path);
}
std::vector<cute::Point> Control::getProblem()
{
if (!problemRetained)
{
generateProblem();
}
return problem;
}
void Control::generateProblem()
{
problem.clear();
if (w->configPanel->stackedWidget->currentWidget() == w->configPanel->randomWidget)
{
int problemSize = w->configPanel->problemSizeBox->value();
problem.reserve(problemSize);
constexpr int highestValue = 100;
constexpr int decimalPlaces = 2;
float denominator = pow(10, decimalPlaces);
int enumerator = highestValue * denominator;
for (int i = 0; i < problemSize; ++i)
{
std::random_device rd = std::random_device();
problem.emplace_back(
cute::Point{(float)(rd() % enumerator) / denominator, (float)(rd() % enumerator) / denominator});
}
}
else if (w->configPanel->stackedWidget->currentWidget() == w->configPanel->fileWidget)
{
std::string filename = w->configPanel->fileLine->text().toStdString();
CSVParser csv;
bool success = csv.parseFile(filename);
if (!success)
{
std::string errorMessage = "Error parsing file: " + filename;
logMessage(errorMessage.c_str());
}
auto data = csv.getFloatRows();
for (auto&& row : data)
{
if (row.size() < 2)
continue;
else if (std::isnan(row[0]) || std::isnan(row[1]))
continue;
else if (std::isinf(row[0]) || std::isinf(row[1]))
continue;
else
problem.emplace_back(cute::Point{row[0], row[1]});
}
}
else if (w->configPanel->stackedWidget->currentWidget() == w->configPanel->pasteWidget)
{
std::string input = w->configPanel->pasteBox->toPlainText().toStdString();
CSVParser csv;
csv.parseString(input);
auto data = csv.getFloatRows();
for (auto&& row : data)
{
if (row.size() < 2)
continue;
else if (std::isnan(row[0]) || std::isnan(row[1]))
continue;
else if (std::isinf(row[0]) || std::isinf(row[1]))
continue;
else
problem.emplace_back(cute::Point{row[0], row[1]});
}
}
}
void Control::onGenerateButton()
{
generateProblem();
problemRetained = true;
w->configPanel->setGenLocked();
}
void Control::onProblemSettingChanged([[maybe_unused]] const QString& text)
{
problemRetained = false;
w->configPanel->setGenUnlocked();
}
void Control::plotXYSeries(QtCharts::QXYSeries* s, int plot, bool onTop, bool legend, QString tooltip)
{
w->charts[plot]->addSeries(s);
plotHistory[plot].push_back(s);
if (onTop)
onTopSeries[plot].push_back((QtCharts::QAbstractSeries*)s);
QtCharts::QLegendMarker* m = w->charts[plot]->legend()->markers(s)[0];
m->setVisible(legend);
QList<QtCharts::QAbstractAxis*> axes = w->charts[plot]->axes();
for (auto&& axis : axes)
{
s->attachAxis(axis);
}
resizeAxes(plot);
redrawOnTopSeries(plot);
if (!tooltip.isEmpty())
{
QtCharts::QChartView* chartView;
for (auto&& widget : w->graphingWidgets)
if ((chartView = dynamic_cast<QtCharts::QChartView*>(widget)))
if (chartView->chart() == w->charts[plot])
break;
QObject::connect(s, &QtCharts::QXYSeries::hovered, [=](const QPointF& point, bool state) {
if (state)
{
// Map the scene position back to the widget coordinates
QPoint pos = chartView->mapFromScene(w->charts[plot]->mapToPosition(point));
QString text = tooltip.arg(point.x()).arg(point.y());
QToolTip::showText(chartView->mapToGlobal(pos), text);
}
else
{
QToolTip::hideText();
}
});
}
}
void Control::plotBarSeries(QtCharts::QBarSeries* series, int plot, bool onTop, bool legend)
{
w->charts[plot]->addSeries(series);
plotHistory[plot].push_back(series);
if (onTop)
onTopSeries[plot].push_back(series);
QtCharts::QLegendMarker* m = w->charts[plot]->legend()->markers(series)[0];
m->setVisible(legend);
QtCharts::QCategoryAxis* axisX = new QtCharts::QCategoryAxis();
int count = series->barSets()[0]->count();
for (int i = 1; i <= count; ++i)
{
axisX->append(std::to_string(i).c_str(), i);
}
for (auto&& axis : w->charts[plot]->axes())
{
if (axis->orientation() == Qt::Horizontal)
{
axisX->setTitleVisible(axis->isTitleVisible());
axisX->setTitleText(axis->titleText());
axisX->setVisible(axis->isVisible());
w->charts[plot]->removeAxis(axis);
axis->deleteLater();
}
}
w->charts[plot]->addAxis(axisX, Qt::AlignBottom);
series->attachAxis(axisX);
redrawOnTopSeries(plot);
}
void Control::highlightValue(float value, int plot, QColor c)
{
using namespace QtCharts;
QBarSeries* barSeries = nullptr;
for (auto series : plotHistory[plot])
{
if (std::holds_alternative<QAbstractSeries*>(series))
{
QAbstractSeries* s = std::get<QAbstractSeries*>(series);
if (s->type() == QtCharts::QAbstractSeries::SeriesTypeBar)
{
barSeries = dynamic_cast<QBarSeries*>(s);
break;
}
}
}
if (!barSeries)
return;
auto barSets = barSeries->barSets();
if (barSets.size() == 0)
return;
QBarSet* barSet = barSets[0];
int index = 0;
for (; index < barSet->count(); ++index)
{
if (barSet->at(index) == (qreal)value)
break;
}
// Suppose we want to highlight the third bar (category value = 3).
// For a bar width of 1 (set earlier), the left and right x-values
// are (3 - 0.5) and (3 + 0.5) respectively.
QPointF leftPoint = w->charts[plot]->mapToPosition(QPointF((qreal)index - 0.5, 0), barSeries);
QPointF rightPoint = w->charts[plot]->mapToPosition(QPointF((qreal)index + 0.5, 0), barSeries);
qreal barPixelWidth = rightPoint.x() - leftPoint.x();
// Map the top and bottom of the bar (its value is 3).
QPointF topPoint = w->charts[plot]->mapToPosition(QPointF((qreal)index, value), barSeries);
QPointF bottomPoint = w->charts[plot]->mapToPosition(QPointF((qreal)index, 0), barSeries);
qreal barPixelHeight = bottomPoint.y() - topPoint.y();
// Create a rectangle that exactly covers the bar area.
QRectF highlightRect(leftPoint.x(), topPoint.y(), barPixelWidth, barPixelHeight);
// Create a QGraphicsRectItem with the desired highlight color.
QGraphicsRectItem* rectItem = new QGraphicsRectItem(highlightRect);
rectItem->setBrush(QBrush(c)); // Change to your desired highlight color.
//?rectItem->setPen(Qt::NoPen); // Remove the border for a clean look.
// Set a high z-value to ensure the overlay appears above the bar.
rectItem->setZValue(10);
w->charts[plot]->scene()->addItem(rectItem);
plotHistory[plot].push_back((QGraphicsItem*)rectItem);
}
void Control::resizeAxes(int plot)
{
using namespace QtCharts;
double maxX = std::numeric_limits<double>::min();
double maxY = std::numeric_limits<double>::min();
double minX = std::numeric_limits<double>::max();
double minY = std::numeric_limits<double>::max();
auto series = w->charts[plot]->series();
if (series.empty())
{
minX = 0.1;
minY = 0.1;
maxX = 1;
maxY = 1;
}
else
{
for (auto abstractSeries : series)
{
QXYSeries* xySeries = dynamic_cast<QXYSeries*>(abstractSeries);
if (xySeries)
{
auto points = xySeries->points();
for (auto&& point : points)
{
if (point.x() > maxX)
{
maxX = point.x();
}
if (point.x() < minX)
{
minX = point.x();
}
if (point.y() > maxY)
{
maxY = point.y();
}
if (point.y() < minY)
{
minY = point.y();
}
}
}
}
}
QList<QAbstractAxis*> axes = w->charts[plot]->axes();
for (auto axis : axes)
{
bool logAxis = dynamic_cast<QLogValueAxis*>(axis);
if (axis->orientation() == Qt::Orientation::Horizontal)
{
if (logAxis)
{
axis->setRange(std::max(0.0000001, 0.8 * minX), 1.2 * maxX);
}
else
{
float margin = abs((maxX - minX) * 0.05);
axis->setRange(floor(minX - margin), ceil(maxX + margin));
}
}
if (axis->orientation() == Qt::Orientation::Vertical)
{
if (logAxis)
{
axis->setRange(std::max(0.0000001, 0.8 * minY), 1.2 * maxY);
}
else
{
float margin = abs((maxY - minY) * 0.05);
axis->setRange(floor(minY - margin), ceil(maxY + margin));
}
}
}
}
void Control::resetPlot(int plot)
{
w->charts[plot]->removeAllSeries();
plotHistory[plot].clear();
onTopSeries[plot].clear();
}
void Control::undo(int plot)
{
using QtCharts::QAbstractSeries;
if (plotHistory[plot].size() > 0)
{
auto lastEvent = plotHistory[plot].back();
if (std::holds_alternative<QAbstractSeries*>(lastEvent))
{
QAbstractSeries* series = std::get<QAbstractSeries*>(lastEvent);
w->charts[plot]->removeSeries(series);
plotHistory[plot].pop_back();
onTopSeries[plot].erase(std::remove(onTopSeries[plot].begin(), onTopSeries[plot].end(), series),
onTopSeries[plot].end());
series->deleteLater();
}
else if (std::holds_alternative<QGraphicsItem*>(lastEvent))
{
QGraphicsItem* highlightRectItem = std::get<QGraphicsItem*>(lastEvent);
w->charts[plot]->scene()->removeItem(highlightRectItem); // Remove from the scene
delete highlightRectItem; // Free memory
plotHistory[plot].pop_back(); // Reset the pointer
}
}
}
void Control::redrawOnTopSeries(int plot)
{
for (auto s : onTopSeries[plot])
{
w->charts[plot]->removeSeries(s);
w->charts[plot]->addSeries(s);
auto axes = w->charts[plot]->axes();
for (auto&& axis : axes)
{
s->attachAxis(axis);
}
};
}
void Control::setPlotScale(int plot, int x, int y)
{
using namespace QtCharts;
QList<QAbstractAxis*> axes = w->charts[plot]->axes();
for (auto axis : axes)
{
QLogValueAxis* logAxis = dynamic_cast<QLogValueAxis*>(axis);
QValueAxis* linAxis = dynamic_cast<QValueAxis*>(axis);
bool switchToLin = false;
bool switchToLog = false;
if (axis->orientation() == Qt::Orientation::Horizontal)
{
switchToLin = (logAxis && x == cute::linear);
switchToLog = (linAxis && x == cute::logarithmic);
}
else if (axis->orientation() == Qt::Orientation::Vertical)
{
switchToLin = (logAxis && y == cute::linear);
switchToLog = (linAxis && y == cute::logarithmic);
}
if (switchToLin)
{
QValueAxis* newLinAxis = new QValueAxis();
newLinAxis->setMax(logAxis->max());
newLinAxis->setMin(logAxis->min());
newLinAxis->setVisible(logAxis->isVisible());
newLinAxis->setTitleFont(logAxis->titleFont());
newLinAxis->setTitleText(logAxis->titleText());
w->charts[plot]->removeAxis(axis);
w->charts[plot]->addAxis(newLinAxis, axis->alignment());
axis->deleteLater();
auto series = w->charts[plot]->series();
for (auto s : series)
{
s->attachAxis(newLinAxis);
}
}
else if (switchToLog)
{
QLogValueAxis* newLogAxis = new QLogValueAxis();
newLogAxis->setBase(10.0);
newLogAxis->setMax(std::max(linAxis->max(), 0.1));
newLogAxis->setMin(std::max(linAxis->min(), 0.000001));
newLogAxis->setVisible(linAxis->isVisible());
newLogAxis->setTitleFont(linAxis->titleFont());
newLogAxis->setTitleText(linAxis->titleText());
newLogAxis->setMinorGridLineVisible(true);
newLogAxis->setMinorTickCount(8);
w->charts[plot]->removeAxis(axis);
w->charts[plot]->addAxis(newLogAxis, axis->alignment());
axis->deleteLater();
auto series = w->charts[plot]->series();
for (auto s : series)
{
s->attachAxis(newLogAxis);
}
}
}
resizeAxes(plot);
}
void Control::saveProblem()
{
QString filter = "CSV File (*.csv)";
QString path = QFileDialog::getSaveFileName(w, "Write a CSV-File", QDir::homePath(), filter);
if (!path.isEmpty() && !problem.empty())
{
csvWriter writer(path.toStdString());
bool success = writer.write(problem);
if (success)
setStatusMessage("Saved Problem to " + path, 5000);
else
setStatusMessage("Error writing File " + path, 5000);
}
}