Skip to content
Snippets Groups Projects
Select Git revision
  • cd7398c7fe5ee067dc046d6eebb964bc7963780d
  • main default protected
  • feat/final
  • feature/Michelle
  • feat/010125
  • Luca
  • feat/knew
  • feat/try
  • feat/katha2
  • feature/katharina
10 results

2025-01-10_Client_V5.py

Blame
  • 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);
        }
    }