diff --git a/src/control.cpp b/src/control.cpp index 3433a9db8a8c8d31af0bf2aad9866c2fe2d5ca33..a307fda8b77b2754827ea73430ad468fa693d234 100644 --- a/src/control.cpp +++ b/src/control.cpp @@ -103,6 +103,8 @@ int Control::run(void (*userSetup)()) void Control::addPlot(int type) { + onTopSeries.emplace_back(); + plotHistory.emplace_back(); if (type == cute::PlotType::chart) { addChart(); @@ -110,11 +112,9 @@ void Control::addPlot(int type) } void Control::addChart() - { - onTopSeries.emplace_back(); - plotHistory.emplace_back(); - w->addChart(); - } +{ + w->addChart(); +} /** * @brief Appends a QString to MainWindows logging Area / textEdit. @@ -152,27 +152,34 @@ void Control::setStatusMessage(const QString message, int duration_s) * * @param plot The number of the plot to be manipulated. Starting from 0 for the left mose plot. * @param visible Whether to show or hide. - * @todo ensure every graphing widget can hide */ void Control::setPlotVisible(int plot, bool visible) { - w->graphingWidgets[plot]->setHidden(!visible); + if (plot < (int)w->graphingWidgets.size() && plot >= 0) + { + w->graphingWidgets[plot]->setHidden(!visible); + } } /** * @brief Change the given plots title in ChartMenu and in the Plot itself. * The ChartMenu has a separate memory of the plot titles, so it it can show them without having to look them up, * which would create complicated dependencies. - * @todo This will break, if other plotting widgets than QChart are added. Most easy thing would likely be, - * to offer setTile() as an interface in the new Widget and adjust the this->charts vector accordingly. * * @param plot The number of the plot to be manipulated. Starting from 0 for the left mose plot. * @param title The new title. */ void Control::setPlotTitle(int plot, const QString title) { - w->charts[plot]->setTitle(title); - w->cm->titleChanged(plot, title); + if (plot < (int)w->graphingWidgets.size() && plot >= 0) + { + auto chartView = dynamic_cast<QtCharts::QChartView*>(w->graphingWidgets[plot]); + if (chartView) + { + chartView->chart()->setTitle(title); + w->cm->titleChanged(plot, title); + } + } } /** @@ -191,11 +198,17 @@ void Control::setPlotTheme(int themeNumber) * * @param plot The number of the plot to be manipulated. Starting from 0 for the left mose plot. * @param visible Whether to show or hide. - * @todo dynamic graphs... */ void Control::setLegendVisible(int plot, bool visible) { - w->charts[plot]->legend()->setVisible(visible); + if (plot < (int)w->graphingWidgets.size() && plot >= 0) + { + auto chartView = dynamic_cast<QtCharts::QChartView*>(w->graphingWidgets[plot]); + if (chartView) + { + chartView->chart()->legend()->setVisible(visible); + } + } } @@ -207,24 +220,28 @@ void Control::setLegendVisible(int plot, bool visible) * @param plot The plot to be manipulated. * @param x Title for the x-axis. * @param y Title for the y-axis. - * @todo dynamic graphs... */ 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 (plot < (int)w->graphingWidgets.size() && plot >= 0) { - if (axis->orientation() == Qt::Horizontal) - { - axis->setTitleText(x); - axis->setTitleVisible(!x.isEmpty()); - } - if (axis->orientation() == Qt::Vertical) + auto chartView = dynamic_cast<QtCharts::QChartView*>(w->graphingWidgets[plot]); + if (chartView) { - axis->setTitleText(y); - axis->setTitleVisible(!y.isEmpty()); + auto axes = chartView->chart()->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()); + } + } } } } @@ -235,22 +252,26 @@ void Control::setAxisTitles(int plot, QString x, QString y) * @param plot The plot to be manipulated. * @param x Wether the x-axis should be visible. * @param y Wether the y-axis should be visible. - * @todo dynamic graphs... */ 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 (plot < (int)w->graphingWidgets.size() && plot >= 0) { - if (axis->orientation() == Qt::Horizontal) + auto chartView = dynamic_cast<QtCharts::QChartView*>(w->graphingWidgets[plot]); + if (chartView) { - axis->setVisible(x); - } - if (axis->orientation() == Qt::Vertical) - { - axis->setVisible(y); + auto axes = chartView->chart()->axes(); + for (auto axis : axes) + { + if (axis->orientation() == Qt::Horizontal) + { + axis->setVisible(x); + } + if (axis->orientation() == Qt::Vertical) + { + axis->setVisible(y); + } + } } } } @@ -290,18 +311,19 @@ void Control::threadEnded() { threadRunning = false; w->configPanel->setRunning(false); - thread = nullptr; + thread = nullptr; + problemPreviewed = false; } /** * @brief Sets up and starts an AlgoThread. - * A new thread is created and connected such, that when finished, it notifies Control and flags itself for deletion. - * CuteControl is then handed it's problem and the currently set verbosity, so that it can start execution right away. - * Most importantly CuteControl is then moved to the EventQueue of the newly created thread. - * That has to be done, since CuteControl was created in the main thread and thus is hooked into the main EventLoop. - * This can only be done, pushing from the objects current thread, so it has to be moved back by the AlgoThread. - * When the objects are in different threads, the signals are automatically stored and processed in the receivers - * EventQueue. This way, it is thread safe. + * A new thread is created and connected such, that when finished, it notifies Control and flags itself for + * deletion. CuteControl is then handed it's problem and the currently set verbosity, so that it can start + * execution right away. Most importantly CuteControl is then moved to the EventQueue of the newly created + * thread. That has to be done, since CuteControl was created in the main thread and thus is hooked into the + * main EventLoop. This can only be done, pushing from the objects current thread, so it has to be moved back by + * the AlgoThread. When the objects are in different threads, the signals are automatically stored and processed + * in the receivers EventQueue. This way, it is thread safe. * * @param func The function/algorithm to be executed by the AlgoThread. */ @@ -367,13 +389,14 @@ void Control::setProblemFile(QString path) } /** - * @brief Depending of if the problem was locked/retained or not, a new problem is generated or the old problem is used. + * @brief Depending of if the problem was locked/retained, previewed or not, a new problem is generated or the old + * problem is used. * * @return std::vector<cute::Point> The given Problem. Newly generated if problemRetained was not set. */ std::vector<cute::Point> Control::getProblem() { - if (!problemRetained) + if (!problemRetained && !problemPreviewed) { generateProblem(); } @@ -457,16 +480,24 @@ void Control::generateProblem() } /** - * @brief Resets the left most plot (plot0) and plots the problems Points to it. - * @todo getProblem is called here, but is that problem then used by the thread, if it is not locked? UX wise, that - * would make sense. + * @brief Resets the first plot that is a chart and plots the problems Points there. */ void Control::onPreviewButton() { if (!threadRunning) { - resetPlot(0); - CuteControl::get().plotPoints(getProblem(), 0, {}); + for (size_t i = 0; i < w->graphingWidgets.size(); ++i) + { + auto ptr = dynamic_cast<QtCharts::QChartView*>(w->graphingWidgets[i]); + if (ptr) + { + resetPlot(i); + problemPreviewed = false; + CuteControl::get().plotPoints(getProblem(), i, {}); + problemPreviewed = true; + return; + } + } } } @@ -494,15 +525,23 @@ void Control::onLockButton() void Control::plotXYSeries(QtCharts::QXYSeries* s, int plot, bool onTop, bool legend, QString tooltip) { - w->charts[plot]->addSeries(s); + using namespace QtCharts; + if (plot >= (int)w->graphingWidgets.size() || plot < 0) + return; + QChartView* chartView = dynamic_cast<QChartView*>(w->graphingWidgets[plot]); + if (!chartView) + return; + QChart* chart = chartView->chart(); + + chart->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]; + QtCharts::QLegendMarker* m = chart->legend()->markers(s)[0]; m->setVisible(legend); - QList<QtCharts::QAbstractAxis*> axes = w->charts[plot]->axes(); + QList<QtCharts::QAbstractAxis*> axes = chart->axes(); for (auto&& axis : axes) { s->attachAxis(axis); @@ -512,17 +551,11 @@ void Control::plotXYSeries(QtCharts::QXYSeries* s, int plot, bool onTop, bool le 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)); + QPoint pos = chartView->mapFromScene(chart->mapToPosition(point)); QString text = tooltip.arg(point.x()).arg(point.y()); QToolTip::showText(chartView->mapToGlobal(pos), text); @@ -537,31 +570,39 @@ void Control::plotXYSeries(QtCharts::QXYSeries* s, int plot, bool onTop, bool le void Control::plotBarSeries(QtCharts::QBarSeries* series, int plot, bool onTop, bool legend) { - w->charts[plot]->addSeries(series); + using namespace QtCharts; + if (plot >= (int)w->graphingWidgets.size() || plot < 0) + return; + QChartView* chartView = dynamic_cast<QChartView*>(w->graphingWidgets[plot]); + if (!chartView) + return; + QChart* chart = chartView->chart(); + + chart->addSeries(series); plotHistory[plot].push_back(series); if (onTop) onTopSeries[plot].push_back(series); - QtCharts::QLegendMarker* m = w->charts[plot]->legend()->markers(series)[0]; + QLegendMarker* m = chart->legend()->markers(series)[0]; m->setVisible(legend); - QtCharts::QCategoryAxis* axisX = new QtCharts::QCategoryAxis(); - int count = series->barSets()[0]->count(); + QCategoryAxis* axisX = new 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()) + for (auto&& axis : chart->axes()) { if (axis->orientation() == Qt::Horizontal) { axisX->setTitleVisible(axis->isTitleVisible()); axisX->setTitleText(axis->titleText()); axisX->setVisible(axis->isVisible()); - w->charts[plot]->removeAxis(axis); + chart->removeAxis(axis); axis->deleteLater(); } } - w->charts[plot]->addAxis(axisX, Qt::AlignBottom); + chart->addAxis(axisX, Qt::AlignBottom); series->attachAxis(axisX); redrawOnTopSeries(plot); @@ -570,6 +611,13 @@ void Control::plotBarSeries(QtCharts::QBarSeries* series, int plot, bool onTop, void Control::highlightValue(float value, int plot, QColor c) { using namespace QtCharts; + if (plot >= (int)w->graphingWidgets.size() || plot < 0) + return; + QChartView* chartView = dynamic_cast<QChartView*>(w->graphingWidgets[plot]); + if (!chartView) + return; + QChart* chart = chartView->chart(); + QBarSeries* barSeries = nullptr; for (auto series : plotHistory[plot]) { @@ -598,18 +646,16 @@ void Control::highlightValue(float value, int plot, QColor c) 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); + QPointF leftPoint = chart->mapToPosition(QPointF((qreal)index - 0.5, 0), barSeries); + QPointF rightPoint = chart->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); + QPointF topPoint = chart->mapToPosition(QPointF((qreal)index, value), barSeries); + QPointF bottomPoint = chart->mapToPosition(QPointF((qreal)index, 0), barSeries); qreal barPixelHeight = bottomPoint.y() - topPoint.y(); // Create a rectangle that exactly covers the bar area. @@ -621,7 +667,7 @@ void Control::highlightValue(float value, int plot, QColor c) //?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); + chart->scene()->addItem(rectItem); plotHistory[plot].push_back((QGraphicsItem*)rectItem); } @@ -629,11 +675,18 @@ void Control::highlightValue(float value, int plot, QColor c) void Control::resizeAxes(int plot) { using namespace QtCharts; + if (plot >= (int)w->graphingWidgets.size() || plot < 0) + return; + QChartView* chartView = dynamic_cast<QChartView*>(w->graphingWidgets[plot]); + if (!chartView) + return; + QChart* chart = chartView->chart(); + 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(); + auto series = chart->series(); if (series.empty()) { minX = 0.1; @@ -672,7 +725,7 @@ void Control::resizeAxes(int plot) } } - QList<QAbstractAxis*> axes = w->charts[plot]->axes(); + QList<QAbstractAxis*> axes = chart->axes(); for (auto axis : axes) { bool logAxis = dynamic_cast<QLogValueAxis*>(axis); @@ -722,21 +775,36 @@ void Control::resizeAxes(int plot) */ void Control::resetPlot(int plot) { - w->charts[plot]->removeAllSeries(); - plotHistory[plot].clear(); - onTopSeries[plot].clear(); + using namespace QtCharts; + if (plot < (int)w->graphingWidgets.size() && plot >= 0) + { + auto chartView = dynamic_cast<QtCharts::QChartView*>(w->graphingWidgets[plot]); + if (chartView) + { + chartView->chart()->removeAllSeries(); + } + plotHistory[plot].clear(); + onTopSeries[plot].clear(); + } } void Control::undo(int plot) { - using QtCharts::QAbstractSeries; + using namespace QtCharts; + if (plot >= (int)w->graphingWidgets.size() || plot < 0) + return; + QChartView* chartView = dynamic_cast<QChartView*>(w->graphingWidgets[plot]); + if (!chartView) + return; + QChart* chart = chartView->chart(); + 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); + chart->removeSeries(series); plotHistory[plot].pop_back(); onTopSeries[plot].erase(std::remove(onTopSeries[plot].begin(), onTopSeries[plot].end(), series), onTopSeries[plot].end()); @@ -745,27 +813,35 @@ void Control::undo(int plot) 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 + chart->scene()->removeItem(highlightRectItem); // Remove from the scene + delete highlightRectItem; // Free memory + plotHistory[plot].pop_back(); // Reset the pointer } } } void Control::redrawOnTopSeries(int plot) { + using namespace QtCharts; + if (plot >= (int)w->graphingWidgets.size() || plot < 0) + return; + QChartView* chartView = dynamic_cast<QChartView*>(w->graphingWidgets[plot]); + if (!chartView) + return; + QChart* chart = chartView->chart(); + for (auto s : onTopSeries[plot]) { - QtCharts::QLegendMarker* legendMarker = w->charts[plot]->legend()->markers(s)[0]; - bool legendVisible = legendMarker->isVisible(); - w->charts[plot]->removeSeries(s); - w->charts[plot]->addSeries(s); - auto axes = w->charts[plot]->axes(); + QLegendMarker* legendMarker = chart->legend()->markers(s)[0]; + bool legendVisible = legendMarker->isVisible(); + chart->removeSeries(s); + chart->addSeries(s); + auto axes = chart->axes(); for (auto&& axis : axes) { s->attachAxis(axis); } - legendMarker = w->charts[plot]->legend()->markers(s)[0]; + legendMarker = chart->legend()->markers(s)[0]; legendMarker->setVisible(legendVisible); }; } @@ -773,7 +849,14 @@ void Control::redrawOnTopSeries(int plot) void Control::setPlotScale(int plot, int x, int y) { using namespace QtCharts; - QList<QAbstractAxis*> axes = w->charts[plot]->axes(); + if (plot >= (int)w->graphingWidgets.size() || plot < 0) + return; + QChartView* chartView = dynamic_cast<QChartView*>(w->graphingWidgets[plot]); + if (!chartView) + return; + QChart* chart = chartView->chart(); + + QList<QAbstractAxis*> axes = chart->axes(); for (auto axis : axes) { QLogValueAxis* logAxis = dynamic_cast<QLogValueAxis*>(axis); @@ -798,10 +881,10 @@ void Control::setPlotScale(int plot, int x, int y) newLinAxis->setVisible(logAxis->isVisible()); newLinAxis->setTitleFont(logAxis->titleFont()); newLinAxis->setTitleText(logAxis->titleText()); - w->charts[plot]->removeAxis(axis); - w->charts[plot]->addAxis(newLinAxis, axis->alignment()); + chart->removeAxis(axis); + chart->addAxis(newLinAxis, axis->alignment()); axis->deleteLater(); - auto series = w->charts[plot]->series(); + auto series = chart->series(); for (auto s : series) { s->attachAxis(newLinAxis); @@ -818,10 +901,10 @@ void Control::setPlotScale(int plot, int x, int y) newLogAxis->setTitleText(linAxis->titleText()); newLogAxis->setMinorGridLineVisible(true); newLogAxis->setMinorTickCount(8); - w->charts[plot]->removeAxis(axis); - w->charts[plot]->addAxis(newLogAxis, axis->alignment()); + chart->removeAxis(axis); + chart->addAxis(newLogAxis, axis->alignment()); axis->deleteLater(); - auto series = w->charts[plot]->series(); + auto series = chart->series(); for (auto s : series) { s->attachAxis(newLogAxis); diff --git a/src/control.h b/src/control.h index 135ff35816c0e23cacb51149a583e81b211eae79..3e9027bbe20b24b3345f6ec0425e337de714aa51 100644 --- a/src/control.h +++ b/src/control.h @@ -23,6 +23,7 @@ private: void startThread(void (*func)()); void makeConnections(); bool problemRetained; + bool problemPreviewed; std::vector<cute::Point> problem; void generateProblem(); std::vector<cute::Point> getProblem(); diff --git a/src/mainWindow.cpp b/src/mainWindow.cpp index 91637fde8d38fc843156c18dd92ae448f21381be..177146d5f965fc1f2072d6a2956efc2606dac8a1 100644 --- a/src/mainWindow.cpp +++ b/src/mainWindow.cpp @@ -125,24 +125,25 @@ void MainWindow::switchToPaste() void MainWindow::addChart() { - QtCharts::QChartView* v = new QtCharts::QChartView(); + using namespace QtCharts; + QChartView* v = new QChartView(); graphingWidgets.push_back(v); centralWidget()->layout()->addWidget(v); - QtCharts::QChart* c = new QtCharts::QChart(); - charts.push_back(c); + QChart* c = new QChart(); v->setChart(c); v->setMouseTracking(true); v->setRenderHint(QPainter::Antialiasing); QString title = "Plot "; - title += std::to_string(charts.size() - 1).c_str(); + title += std::to_string(graphingWidgets.size() - 1).c_str(); c->setTitle(title); c->legend()->setVisible(true); c->legend()->setMarkerShape(QtCharts::QLegend::MarkerShapeFromSeries); c->setMinimumSize(300, 300); - QtCharts::QValueAxis* axisX = new QtCharts::QValueAxis(); + c->setTheme((QChart::ChartTheme)currentChartTheme); + QValueAxis* axisX = new QValueAxis(); c->addAxis(axisX, Qt::AlignBottom); - QtCharts::QValueAxis* axisY = new QtCharts::QValueAxis(); + QValueAxis* axisY = new QValueAxis(); c->addAxis(axisY, Qt::AlignLeft); cm->plotAdded(v); } @@ -167,26 +168,28 @@ void MainWindow::setupStatusBar() void MainWindow::changeChartTheme(int themeNumber) { + using namespace QtCharts; if (themeNumber > 7 || themeNumber < 0) { return; } + currentChartTheme = themeNumber; cm->chartThemeChanged(themeNumber); - for (QtCharts::QChart* chart : charts) + for (QWidget* plot : graphingWidgets) { - chart->setTheme((QtCharts::QChart::ChartTheme)themeNumber); + QChartView* chartView = dynamic_cast<QChartView*>(plot); + if (chartView) + chartView->chart()->setTheme((QtCharts::QChart::ChartTheme)themeNumber); } } void MainWindow::incrementChartTheme() { - int n = (int)charts.front()->theme(); - ++n; - if (n > 7) - { - n = 0; - } - changeChartTheme(n); + int newTheme = currentChartTheme + 1; + if (newTheme > 7) + newTheme = 0; + changeChartTheme(newTheme); + return; } void MainWindow::chartContextMenu(const QPoint& pos) diff --git a/src/mainWindow.h b/src/mainWindow.h index dc56fb8de66804fc13a67b340ee48e2a06ca7571..e4dbe2ffa8a4b24430407200c8a9dfa8be9855ba 100644 --- a/src/mainWindow.h +++ b/src/mainWindow.h @@ -20,6 +20,7 @@ private: QAction* probMenuFile; QAction* probMenuRandom; QAction* probMenuPaste; + int currentChartTheme; void incrementChartTheme(); public: @@ -31,8 +32,7 @@ public: QTextEdit* textEdit; ConfigPanel* configPanel; - std::vector<QWidget*> graphingWidgets; - std::vector<QtCharts::QChart*> charts; + std::vector<QWidget*> graphingWidgets; void addAlgorithm(const QString& name); void addChart();