File indexing completed on 2024-04-21 03:42:12

0001 /*
0002     KmPlot - a math. function plotter for the KDE-Desktop
0003 
0004     SPDX-FileCopyrightText: 1998, 1999, 2000, 2002 Klaus-Dieter Möller <kd.moeller@t-online.de>
0005     SPDX-FileCopyrightText: 2006 David Saxton <david@bluehaze.org>
0006 
0007     This file is part of the KDE Project.
0008     KmPlot is part of the KDE-EDU Project.
0009 
0010     SPDX-License-Identifier: GPL-2.0-or-later
0011 
0012 */
0013 
0014 #include "view.h"
0015 
0016 #include <kmplot/config-kmplot.h>
0017 
0018 // Qt includes
0019 #include <QAbstractTextDocumentLayout>
0020 #include <QBitmap>
0021 #include <QCursor>
0022 #include <QDataStream>
0023 #include <QElapsedTimer>
0024 #include <QList>
0025 #include <QMenu>
0026 #include <QPaintEvent>
0027 #include <QPainter>
0028 #include <QSlider>
0029 #include <QTextEdit>
0030 #include <QTimer>
0031 
0032 // KDE includes
0033 #include <KActionCollection>
0034 #include <KMessageBox>
0035 
0036 // local includes
0037 #include "functioneditor.h"
0038 #include "functiontools.h"
0039 #include "ksliderwindow.h"
0040 #include "maindlg.h"
0041 #include "parameteranimator.h"
0042 #include "settings.h"
0043 #include "viewadaptor.h"
0044 #include "xparser.h"
0045 
0046 // other includes
0047 #include <assert.h>
0048 #include <cmath>
0049 #ifdef HAVE_IEEEFP_H
0050 #include <ieeefp.h>
0051 #endif
0052 
0053 #if defined(Q_CC_MINGW)
0054 using namespace std;
0055 #endif
0056 
0057 // does for real numbers what "%" does for integers
0058 double realModulo(double x, double mod)
0059 {
0060     return x - floor(x / mod) * mod;
0061 }
0062 
0063 // BEGIN class View
0064 View *View::m_self = nullptr;
0065 
0066 View::View(bool readOnly, QMenu *functionPopup, QWidget *parent)
0067     : QWidget(parent)
0068     , buffer(width(), height())
0069     , m_popupMenu(functionPopup)
0070     , m_readonly(readOnly)
0071     , m_AccumulatedDelta(0)
0072     , m_viewportAnimation(new QPropertyAnimation(this, "viewport"))
0073 {
0074     assert(!m_self); // this class should only be constructed once
0075     m_self = this;
0076     setAttribute(Qt::WA_StaticContents);
0077 
0078     m_haveRoot = false;
0079     emit updateRootValue(false, 0);
0080     m_xmin = m_xmax = m_ymin = m_ymax = 0.0;
0081     m_printHeaderTable = false;
0082     m_printBackground = false;
0083     m_printWidth = 0.0;
0084     m_printHeight = 0.0;
0085     m_stopCalculating = false;
0086     m_isDrawing = false;
0087     m_popupMenuStatus = NoPopup;
0088     m_zoomMode = Normal;
0089     m_prevCursor = CursorArrow;
0090     m_backgroundColor = Settings::backgroundcolor();
0091 
0092     m_textEdit = new KTextEdit;
0093     m_textEdit->setWordWrapMode(QTextOption::NoWrap);
0094     m_textEdit->setLineWrapMode(QTextEdit::NoWrap);
0095     m_textDocument = m_textEdit->document();
0096 
0097     m_mousePressTimer = new QElapsedTimer();
0098 
0099     new ViewAdaptor(this);
0100     QDBusConnection::sessionBus().registerObject("/view", this);
0101 
0102     setMouseTracking(true);
0103     m_sliderWindow = nullptr;
0104 
0105     m_popupMenuTitle = m_popupMenu->insertSection(MainDlg::self()->m_firstFunctionAction, "");
0106     connect(XParser::self(), &XParser::functionRemoved, this, &View::functionRemoved);
0107 }
0108 
0109 View::~View()
0110 {
0111     m_textEdit->deleteLater();
0112     delete XParser::self();
0113     delete m_mousePressTimer;
0114 }
0115 
0116 void View::initDrawLabels()
0117 {
0118     m_labelFont = Settings::labelFont();
0119 
0120     for (int i = 0; i < LabelGridSize; ++i)
0121         for (int j = 0; j < LabelGridSize; ++j)
0122             m_usedDiagramArea[i][j] = false;
0123 
0124     // Add the axis
0125     double x = xToPixel(0);
0126     double y = yToPixel(0);
0127 
0128     double x0 = xToPixel(m_xmin);
0129     double x1 = xToPixel(m_xmax);
0130     double y0 = yToPixel(m_ymin);
0131     double y1 = yToPixel(m_ymax);
0132 
0133     // x-axis
0134     markDiagramAreaUsed(QRectF(x - 20, y0, 40, y1 - y0));
0135 
0136     // y-axis
0137     markDiagramAreaUsed(QRectF(x0, y - 20, x1 - x0, 40));
0138 }
0139 
0140 double View::niceTicSpacing(double length_mm, double range)
0141 {
0142     Q_ASSERT_X(range > 0, "View::niceTicSpacing", "Range must be positive");
0143 
0144     if (length_mm <= 0) {
0145         // Don't assert, as we can at least handle this situation - and it can
0146         // happen with extreme zooms
0147 
0148         qWarning() << "Non-positive length: length_mm=" << length_mm;
0149         length_mm = 120;
0150     }
0151 
0152     // Custom case for trigonometric scaled
0153     if (qFuzzyCompare(range, 4 * M_PI))
0154         return M_PI / 2;
0155 
0156     // Aim to space the tics by around 16 mm
0157     double target = range * 16.0 / length_mm;
0158 
0159     // The scaling required to bring target to a number between 1 and 10
0160     double scale = pow(10, -std::floor(log(target) / log(10.0)));
0161 
0162     // Calculate the first digit of target, e.g. if target is 0.0352, then leading will be set to 3
0163     int leading = int(target * scale);
0164 
0165     if (leading == 1)
0166         return 1 / scale;
0167     else if (leading >= 2 && leading <= 4)
0168         return 2 / scale;
0169     else
0170         return 5 / scale;
0171 }
0172 
0173 double View::validatedTicSpacing(double spacing, double range, double pixels, double minPixels)
0174 {
0175     Q_ASSERT_X(range > 0, "View::validatedTicSpacing", "Range must be positive");
0176     Q_ASSERT_X(minPixels > 0, "View::validatedTicSpacing", "MinPixels must be positive");
0177 
0178     spacing = qAbs(spacing);
0179     if (qFuzzyCompare(spacing, 0))
0180         return 2.0 * range;
0181 
0182     double factor;
0183 
0184     // Make sure spacing between tics is at least minPixels
0185     pixels /= range / spacing;
0186     factor = pixels / minPixels;
0187     if (factor < 1.0) {
0188         int exponent;
0189         frexp(factor, &exponent);
0190         spacing = ldexp(spacing, -exponent + 1);
0191     }
0192 
0193     // Make sure there are at least two tics
0194     factor = spacing / range;
0195     if (factor > 0.5) {
0196         int exponent;
0197         frexp(factor, &exponent);
0198         spacing = ldexp(spacing, -exponent - 1);
0199     }
0200 
0201     return spacing;
0202 }
0203 
0204 void View::initDrawing(QPaintDevice *device, PlotMedium medium)
0205 {
0206     switch (medium) {
0207     case SVG:
0208     case Screen: {
0209         m_clipRect = QRect(0, 0, width(), height());
0210         break;
0211     }
0212 
0213     case Printer: {
0214         double inchesPerMeter = 100.0 / 2.54;
0215 
0216         int pixels_x = int(m_printWidth * device->logicalDpiX() * inchesPerMeter);
0217         int pixels_y = int(m_printHeight * device->logicalDpiY() * inchesPerMeter);
0218 
0219         m_clipRect = QRect(0, 0, pixels_x, pixels_y);
0220         break;
0221     }
0222 
0223     case Pixmap: {
0224         QPixmap *pic = static_cast<QPixmap *>(device);
0225         m_clipRect = pic->rect();
0226         break;
0227     }
0228     }
0229 
0230     if (m_clipRect.width() <= 0 || m_clipRect.height() <= 0) {
0231         qWarning() << "Invalid clip rect: m_clipRect=" << m_clipRect;
0232         return;
0233     }
0234 
0235     // BEGIN get X/Y range
0236     m_xmin = XParser::self()->eval(Settings::xMin());
0237     m_xmax = XParser::self()->eval(Settings::xMax());
0238 
0239     if (m_xmax <= m_xmin || !std::isfinite(m_xmin) || !std::isfinite(m_xmax)) {
0240         m_xmin = -8;
0241         m_xmax = +8;
0242     }
0243 
0244     m_ymin = XParser::self()->eval(Settings::yMin());
0245     m_ymax = XParser::self()->eval(Settings::yMax());
0246     if (m_ymax <= m_ymin || !std::isfinite(m_ymin) || !std::isfinite(m_ymax)) {
0247         m_ymin = -8;
0248         m_ymax = +8;
0249     }
0250     // END get X/Y range
0251 
0252     // BEGIN calculate scaling matrices
0253     m_realToPixel.reset();
0254     m_realToPixel.scale(m_clipRect.width() / (m_xmax - m_xmin), m_clipRect.height() / (m_ymin - m_ymax));
0255     m_realToPixel.translate(-m_xmin, -m_ymax);
0256 
0257     m_pixelToReal = m_realToPixel.inverted();
0258     // END calculate scaling matrices
0259 
0260     // BEGIN get Tic Separation
0261     QFontMetricsF fm(Settings::axesFont(), device);
0262     if (Settings::xScalingMode() == 0) {
0263         double length = pixelsToMillimeters(xToPixel(m_xmax), device);
0264         double spacing = niceTicSpacing(length, m_xmax - m_xmin);
0265         ticSepX.updateExpression(spacing);
0266     } else {
0267         ticSepX.updateExpression(Settings::xScaling());
0268         double spacing = ticSepX.value();
0269         spacing = validatedTicSpacing(spacing, m_xmax - m_xmin, xToPixel(m_xmax), fm.lineSpacing());
0270         ticSepX.updateExpression(spacing);
0271     }
0272 
0273     if (Settings::yScalingMode() == 0) {
0274         double length = pixelsToMillimeters(yToPixel(m_ymin), device);
0275         double spacing = niceTicSpacing(length, m_ymax - m_ymin);
0276         ticSepY.updateExpression(spacing);
0277     } else {
0278         ticSepY.updateExpression(Settings::yScaling());
0279         double spacing = ticSepY.value();
0280         spacing = validatedTicSpacing(spacing, m_ymax - m_ymin, yToPixel(m_ymin), fm.lineSpacing());
0281         ticSepY.updateExpression(spacing);
0282     }
0283 
0284     ticStartX = ceil(m_xmin / ticSepX.value()) * ticSepX.value();
0285     ticStartY = ceil(m_ymin / ticSepY.value()) * ticSepY.value();
0286     // END get Tic Separation
0287 
0288     // BEGIN get colours
0289     m_backgroundColor = Settings::backgroundcolor();
0290     if (!m_backgroundColor.isValid())
0291         m_backgroundColor = Qt::white;
0292     // END get colours
0293 
0294     XParser::self()->setAngleMode((Parser::AngleMode)Settings::anglemode());
0295 
0296     initDrawLabels();
0297 }
0298 
0299 void View::draw(QPaintDevice *dev, PlotMedium medium)
0300 {
0301     if (m_isDrawing)
0302         return;
0303 
0304     m_isDrawing = true;
0305     updateCursor();
0306     initDrawing(dev, medium);
0307 
0308     QPainter painter(dev);
0309 
0310     switch (medium) {
0311     case SVG:
0312     case Screen:
0313         break;
0314 
0315     case Printer: {
0316         if (m_printHeaderTable)
0317             drawHeaderTable(&painter);
0318 
0319         painter.translate((dev->width() - m_clipRect.width()) / 2, 0);
0320 
0321         if (m_printBackground)
0322             painter.fillRect(m_clipRect, m_backgroundColor); // draw a colored background
0323 
0324         break;
0325     }
0326 
0327     case Pixmap: {
0328         QPixmap *pic = static_cast<QPixmap *>(dev);
0329         pic->fill(m_backgroundColor);
0330         break;
0331     }
0332     }
0333 
0334     painter.setClipRect(m_clipRect);
0335 
0336     // BEGIN draw diagram background stuff
0337     painter.setRenderHint(QPainter::Antialiasing, true);
0338 
0339     drawGrid(&painter);
0340     if (Settings::showAxes())
0341         drawAxes(&painter);
0342     if (Settings::showLabel())
0343         drawLabels(&painter);
0344     // END draw diagram background stuff
0345 
0346     // BEGIN draw the functions
0347     m_stopCalculating = false;
0348 
0349     // Antialiasing slows down rendering a lot, so turn it off if we are
0350     // sliding the view about
0351     painter.setRenderHint(QPainter::Antialiasing, m_zoomMode != Translating);
0352 
0353     double at = -1;
0354     for (Function *function : qAsConst(XParser::self()->m_ufkt)) {
0355         at += 1;
0356 
0357         if (m_stopCalculating)
0358             break;
0359 
0360         //      QDBusInterface( QDBus::sessionBus().baseService(), "/kmplot", "org.kde.kmplot.KmPlot" ).call( QDBus::Block, "setDrawProgress", at/numPlots );
0361 
0362         if (function->type() == Function::Implicit)
0363             drawImplicit(function, &painter);
0364         else
0365             drawFunction(function, &painter);
0366     }
0367     //  QDBusInterface( QDBus::sessionBus().baseService(), "/kmplot", "org.kde.kmplot.KmPlot" ).call( QDBus::Block, "setDrawProgress", 1.0 );
0368 
0369     drawFunctionInfo(&painter);
0370 
0371     m_isDrawing = false;
0372     // END draw the functions
0373 
0374     // Reset are stuff back to the screen stuff
0375     initDrawing(&buffer, Screen);
0376 
0377     updateCursor();
0378 }
0379 
0380 // BEGIN coordinate mapping functions
0381 QPointF View::toPixel(const QPointF &real, ClipBehaviour clipBehaviour, const QPointF &pixelIfNaN)
0382 {
0383     xclipflg = false;
0384     yclipflg = false;
0385 
0386     QPointF pixel = m_realToPixel.map(real);
0387     double x = pixel.x();
0388     double y = pixel.y();
0389 
0390     if (std::isnan(x)) {
0391         xclipflg = true;
0392         x = pixelIfNaN.x();
0393     } else if (clipBehaviour == ClipAll) {
0394         if (x < 0) {
0395             xclipflg = true;
0396             x = 0;
0397         } else if (x > m_clipRect.right()) {
0398             xclipflg = true;
0399             x = m_clipRect.right();
0400         }
0401     } else {
0402         if (std::isinf(x) && x < 0)
0403             x = 0;
0404 
0405         else if (std::isinf(x) && x > 0)
0406             x = m_clipRect.right();
0407     }
0408 
0409     if (std::isnan(y)) {
0410         yclipflg = true;
0411         y = pixelIfNaN.y();
0412     } else if (clipBehaviour == ClipAll) {
0413         if (y < 0) {
0414             yclipflg = true;
0415             y = 0;
0416         } else if (y > m_clipRect.bottom()) {
0417             yclipflg = true;
0418             y = m_clipRect.bottom();
0419         }
0420     } else {
0421         if (std::isinf(y) && y < 0)
0422             y = 0;
0423 
0424         else if (std::isinf(y) && y > 0)
0425             y = m_clipRect.bottom();
0426     }
0427 
0428     // Make sure that x and y are *reasonably* bounded at least, even if they're not infinite
0429     double min_x = -1e3 * m_clipRect.width();
0430     double max_x = +1e3 * m_clipRect.width();
0431     double min_y = -1e3 * m_clipRect.height();
0432     double max_y = +1e3 * m_clipRect.height();
0433 
0434     if (x < min_x)
0435         x = min_x;
0436     else if (x > max_x)
0437         x = max_x;
0438 
0439     if (y < min_y)
0440         y = min_y;
0441     else if (y > max_y)
0442         y = max_y;
0443 
0444     return QPointF(x, y);
0445 }
0446 
0447 double View::xToPixel(double x, ClipBehaviour clipBehaviour, double xIfNaN)
0448 {
0449     return toPixel(QPointF(x, 0), clipBehaviour, QPointF(xIfNaN, 0)).x();
0450 }
0451 
0452 double View::yToPixel(double y, ClipBehaviour clipBehaviour, double yIfNaN)
0453 {
0454     return toPixel(QPointF(0, y), clipBehaviour, QPointF(0, yIfNaN)).y();
0455 }
0456 
0457 QPointF View::toReal(const QPointF &pixel)
0458 {
0459     return m_pixelToReal.map(pixel);
0460 }
0461 
0462 double View::xToReal(double x)
0463 {
0464     return toReal(QPointF(x, 0)).x();
0465 }
0466 
0467 double View::yToReal(double y)
0468 {
0469     return toReal(QPointF(0, y)).y();
0470 }
0471 // END coordinate mapping functions
0472 
0473 void View::drawAxes(QPainter *painter)
0474 {
0475     double axesLineWidth = millimetersToPixels(Settings::axesLineWidth(), painter->device());
0476     double ticWidth = millimetersToPixels(Settings::ticWidth(), painter->device());
0477     double ticLength = millimetersToPixels(Settings::ticLength(), painter->device());
0478     QColor axesColor = Settings::axesColor();
0479 
0480     painter->save();
0481 
0482     double arrowWidth = ticLength * 1.4;
0483     double arrowLength = arrowWidth * 2.8;
0484 
0485     painter->setPen(QPen(axesColor, axesLineWidth));
0486     painter->setBrush(axesColor);
0487 
0488     // BEGIN draw x axis
0489     double a = m_clipRect.right() - ticLength;
0490     double b = yToPixel(0.);
0491 
0492     double b_max = m_clipRect.bottom() - ticLength;
0493     if (b < ticLength)
0494         b = ticLength;
0495     else if (b > b_max)
0496         b = b_max;
0497 
0498     // horizontal line
0499     painter->Lineh(ticLength, b, a);
0500 
0501     // arrow head
0502     if (Settings::showArrows()) {
0503         a = m_clipRect.right();
0504 
0505         QPolygonF p(3);
0506         p[0] = QPointF(a, b);
0507         p[1] = QPointF(a - arrowLength, b + arrowWidth);
0508         p[2] = QPointF(a - arrowLength, b - arrowWidth);
0509         painter->drawPolygon(p);
0510     }
0511     // END draw x axis
0512 
0513     // BEGIN draw y axis
0514     a = xToPixel(0.);
0515     b = ticLength;
0516 
0517     double a_max = m_clipRect.right() - ticLength;
0518     if (a < ticLength)
0519         a = ticLength;
0520     else if (a > a_max)
0521         a = a_max;
0522 
0523     // vertical line
0524     painter->Linev(a, m_clipRect.bottom() - ticLength, b);
0525 
0526     // arrow head
0527     if (Settings::showArrows()) {
0528         b = 0;
0529 
0530         QPolygonF p(3);
0531         p[0] = QPointF(a, b);
0532         p[1] = QPointF(a - arrowWidth, b + arrowLength);
0533         p[2] = QPointF(a + arrowWidth, b + arrowLength);
0534         painter->drawPolygon(p);
0535     }
0536     // END draw y axis
0537 
0538     painter->restore();
0539 
0540     painter->setPen(QPen(axesColor, ticWidth));
0541 
0542     double da = yToPixel(0) - ticLength;
0543     double db = yToPixel(0) + ticLength;
0544     double d = ticStartX;
0545     if (da < 0) {
0546         a = 0;
0547         b = 2 * ticLength;
0548     } else if (db > (double)m_clipRect.bottom()) {
0549         b = m_clipRect.bottom();
0550         a = m_clipRect.bottom() - 2 * ticLength;
0551     } else {
0552         a = da;
0553         b = db;
0554     }
0555 
0556     while (d < m_xmax - ticSepX.value() / 2.) {
0557         double d_pixel = xToPixel(d);
0558         if (d_pixel > ticLength)
0559             painter->Linev(xToPixel(d), a, b);
0560         d += ticSepX.value();
0561     }
0562 
0563     da = xToPixel(0) - ticLength;
0564     db = xToPixel(0) + ticLength;
0565     d = ticStartY;
0566     if (da < 0) {
0567         a = 0;
0568         b = 2 * ticLength;
0569     } else if (db > (double)m_clipRect.right()) {
0570         b = m_clipRect.right();
0571         a = m_clipRect.right() - 2 * ticLength;
0572     } else {
0573         a = da;
0574         b = db;
0575     }
0576 
0577     while (d < m_ymax - ticSepY.value() / 2.) {
0578         double d_pixel = yToPixel(d);
0579         if (d_pixel < m_clipRect.bottom() - ticLength)
0580             painter->Lineh(a, d_pixel, b);
0581         d += ticSepY.value();
0582     }
0583 }
0584 
0585 void View::drawGrid(QPainter *painter)
0586 {
0587     QColor gridColor = Settings::gridColor();
0588 
0589     double gridLineWidth = millimetersToPixels(Settings::gridLineWidth(), painter->device());
0590     QPen pen(gridColor, gridLineWidth);
0591 
0592     painter->setPen(pen);
0593 
0594     enum GridStyle { GridNone, GridLines, GridCrosses, GridPolar };
0595     GridStyle gridMode = (GridStyle)Settings::gridStyle();
0596 
0597     switch (gridMode) {
0598     case GridNone:
0599         break;
0600 
0601     case GridLines: {
0602         for (double d = ticStartX; d <= m_xmax; d += ticSepX.value())
0603             painter->Linev(xToPixel(d), m_clipRect.bottom(), 0);
0604 
0605         for (double d = ticStartY; d <= m_ymax; d += ticSepY.value())
0606             painter->Lineh(0, yToPixel(d), m_clipRect.right());
0607 
0608         break;
0609     }
0610 
0611     case GridCrosses: {
0612         int const dx = 5;
0613         int const dy = 5;
0614 
0615         for (double x = ticStartX; x < m_xmax; x += ticSepX.value()) {
0616             double a = xToPixel(x);
0617             for (double y = ticStartY; y < m_ymax; y += ticSepY.value()) {
0618                 double b = yToPixel(y);
0619                 painter->Lineh(a - dx, b, a + dx);
0620                 painter->Linev(a, b - dy, b + dy);
0621             }
0622         }
0623 
0624         break;
0625     }
0626 
0627     case GridPolar: {
0628         // Note: 1.42 \approx sqrt(2)
0629 
0630         double xMax = qMax(qAbs(m_xmin), qAbs(m_xmax)) * 1.42;
0631         double yMax = qMax(qAbs(m_ymin), qAbs(m_ymax)) * 1.42;
0632         double rMax = qMax(xMax, yMax);
0633 
0634         // The furthest pixel away from the origin
0635         double pixelMax = qMax(xMax * m_realToPixel.m11(), yMax * m_realToPixel.m22());
0636 
0637         double ticSep = qMin(ticSepX.value(), ticSepY.value());
0638         double r = ticSep;
0639 
0640         while (r < rMax) {
0641             QRectF rect;
0642             rect.setTopLeft(toPixel(QPointF(-r, r), ClipInfinite));
0643             rect.setBottomRight(toPixel(QPointF(r, -r), ClipInfinite));
0644             painter->drawEllipse(rect);
0645             r += ticSep;
0646         }
0647 
0648         for (double theta = 0; theta < 2.0 * M_PI; theta += M_PI / 12.0) {
0649             QPointF start = toPixel(QPointF(0, 0), ClipInfinite);
0650             QPointF end = start + QPointF(pixelMax * cos(theta), pixelMax * sin(theta));
0651 
0652             painter->drawLine(start, end);
0653         }
0654 
0655         break;
0656     }
0657     }
0658 }
0659 
0660 void View::drawLabels(QPainter *painter)
0661 {
0662     const QString xLabel = Settings::labelHorizontalAxis();
0663     const QString yLabel = Settings::labelVerticalAxis();
0664 
0665     int const dx = 10;
0666     int const dy = 15;
0667     QFont const font = Settings::axesFont();
0668     painter->setFont(font);
0669     m_textDocument->setDefaultFont(font);
0670 
0671     double const x = xToPixel(0.);
0672     double const y = yToPixel(0.);
0673 
0674     QRectF drawRect;
0675 
0676     // Whether the x-axis is along the top of the view
0677     // and the y-axis is along the right edge of the view
0678     bool axesInTopRight = (m_ymax < ticSepY.value() && m_xmax < ticSepX.value());
0679 
0680     // for "x" label
0681     double endLabelWidth = 0;
0682 
0683     int flags = Qt::AlignVCenter | Qt::TextDontClip | Qt::AlignRight;
0684 
0685     // Draw x label
0686     if (axesInTopRight)
0687         drawRect = QRectF(xToPixel(m_xmax) - (3 * dx), y + dy, 0, 0);
0688     else if (m_ymin > -ticSepY.value())
0689         drawRect = QRectF(xToPixel(m_xmax) - dx, y - dy, 0, 0);
0690     else
0691         drawRect = QRectF(xToPixel(m_xmax) - dx, y + dy, 0, 0);
0692     painter->drawText(drawRect, flags, xLabel);
0693     endLabelWidth = m_clipRect.right() - painter->boundingRect(drawRect, flags, xLabel).right();
0694 
0695     // Draw y label
0696     if (axesInTopRight)
0697         drawRect = QRectF(x - dx, yToPixel(m_ymax) + (2 * dy), 0, 0);
0698     else if (m_xmin > -ticSepX.value())
0699         drawRect = QRectF(x + (2 * dx), yToPixel(m_ymax) + dy, 0, 0);
0700     else
0701         drawRect = QRectF(x - dx, yToPixel(m_ymax) + dy, 0, 0);
0702     painter->drawText(drawRect, flags, yLabel);
0703 
0704     // Draw the numbers on the axes
0705     drawXAxisLabels(painter, pixelsToMillimeters(endLabelWidth, painter->device()));
0706     drawYAxisLabels(painter);
0707 }
0708 
0709 /**
0710  * If \p d is a rational multiple of pi, then return a string appropriate for
0711  * displaying it in fraction form.
0712  */
0713 QString tryPiFraction(double d, double sep)
0714 {
0715     // Avoid strange bug where get pi at large separation
0716     if (sep > 10)
0717         return QString();
0718 
0719     bool positive = d > 0;
0720 
0721     d /= M_PI;
0722     if (!positive)
0723         d = -d;
0724 
0725     if (d < 1e-2)
0726         return QString();
0727 
0728     // Try denominators from 1 to 6
0729     for (int denom = 1; denom <= 6; ++denom) {
0730         if (realModulo(d * denom, 1) > 1e-3 * sep)
0731             continue;
0732 
0733         int num = qRound(d * denom);
0734 
0735         QString s = positive ? "+" : QString(MinusSymbol);
0736         if (num != 1)
0737             s += QString::number(num);
0738 
0739         s += PiSymbol;
0740 
0741         if (denom != 1)
0742             s += '/' + QString::number(denom);
0743 
0744         return s;
0745     }
0746 
0747     return QString();
0748 }
0749 
0750 void View::drawXAxisLabels(QPainter *painter, double endLabelWidth_mm)
0751 {
0752     QColor axesColor = Settings::axesColor();
0753     int const dy = 8;
0754 
0755     double const y = yToPixel(0.);
0756 
0757     // Used to ensure that labels aren't drawn too closely together
0758     // These numbers contain the pixel position of the left and right endpoints of the last label
0759     double last_x_start = -1e3; // (just a large negative number)
0760     double last_x_end = -1e3; // (just a large negative number)
0761 
0762     // The strange label drawing order here is so that the labels eitherside
0763     // of zero are always drawn, and then the other labels are drawn if there
0764     // is enough space
0765 
0766     bool first = true;
0767     bool forwards = true;
0768     double d = 0;
0769 
0770     while (true) {
0771         if (first) {
0772             // Draw x>0 first
0773             d = qMax(ticSepX.value(), ticStartX);
0774             last_x_end = xToPixel(0);
0775             first = false;
0776         } else {
0777             if (forwards) {
0778                 d += ticSepX.value();
0779                 if (d > m_xmax) {
0780                     // Continue on other side
0781                     d = qMin(-ticSepX.value(), ticStartX + floor((m_xmax - m_xmin) / ticSepX.value()) * ticSepX.value());
0782                     last_x_start = xToPixel(0);
0783                     forwards = false;
0784                 }
0785             } else {
0786                 d -= ticSepX.value();
0787                 if (d < m_xmin)
0788                     return;
0789             }
0790         }
0791 
0792         // Don't draw too close to the left edge if the y axis is there
0793         if (m_xmin >= -ticSepX.value() && (d - m_xmin) <= ticSepX.value())
0794             continue;
0795 
0796         QString s = tryPiFraction(d, ticSepX.value());
0797 
0798         if (s.isEmpty())
0799             s = posToString(d, ticSepX.value() * 5, View::ScientificFormat, axesColor).replace('.', QLocale().decimalPoint());
0800 
0801         m_textDocument->setHtml(s);
0802         double idealWidth = m_textDocument->idealWidth();
0803         double idealHeight = m_textDocument->size().height();
0804 
0805         double x_pos = xToPixel(d) - (idealWidth / 2) - 4;
0806         if (x_pos < 0)
0807             continue;
0808 
0809         double y_pos = y + dy;
0810         if ((y_pos + idealHeight) > m_clipRect.bottom())
0811             y_pos = y - dy - idealHeight;
0812 
0813         double x_start = x_pos;
0814         double x_end = x_start + idealWidth;
0815 
0816         // Use a minimum spacing between labels
0817         if ((last_x_start < x_start) && pixelsToMillimeters(x_start - last_x_end, painter->device()) < 7)
0818             continue;
0819         if ((last_x_start > x_start) && pixelsToMillimeters(last_x_start - x_end, painter->device()) < 7)
0820             continue;
0821 
0822         // Don't draw too close to the right edge (has x axis label)
0823         if (pixelsToMillimeters(m_clipRect.right() - x_end, painter->device()) < endLabelWidth_mm + 3)
0824             continue;
0825 
0826         last_x_start = x_start;
0827         last_x_end = x_end;
0828 
0829         QPointF drawPoint(x_pos, y_pos);
0830         painter->translate(drawPoint);
0831         m_textDocument->documentLayout()->draw(painter, QAbstractTextDocumentLayout::PaintContext());
0832         painter->translate(-drawPoint);
0833     }
0834 }
0835 
0836 void View::drawYAxisLabels(QPainter *painter)
0837 {
0838     QColor axesColor = Settings::axesColor();
0839     int const dx = 12;
0840 
0841     double const x = xToPixel(0.);
0842 
0843     double d = ticStartY;
0844     long long n = (long long)ceil(m_ymin / ticSepY.value());
0845     for (; d < m_ymax; d += ticSepY.value(), ++n) {
0846         // Don't draw zero
0847         if (n == 0)
0848             continue;
0849 
0850         // Don't draw too close to top
0851         if ((m_ymax - d) <= ticSepY.value() * 0.6)
0852             continue;
0853 
0854         // Don't draw too close to bottom if the x axis is there
0855         if (m_ymin > -ticSepY.value() && (d - m_ymin) <= ticSepY.value())
0856             continue;
0857 
0858         QString s = tryPiFraction(d, ticSepY.value());
0859 
0860         if (s.isEmpty())
0861             s = posToString(d, ticSepY.value() * 5, View::ScientificFormat, axesColor).replace('.', QLocale().decimalPoint());
0862 
0863         m_textDocument->setHtml(s);
0864 
0865         double idealWidth = m_textDocument->idealWidth();
0866         double idealHeight = m_textDocument->size().height();
0867 
0868         QPointF drawPoint(0, yToPixel(d) - (idealHeight / 2));
0869 
0870         if (m_xmin > -ticSepX.value()) {
0871             drawPoint.setX(x + dx);
0872         } else {
0873             drawPoint.setX(x - dx - idealWidth);
0874 
0875             if (drawPoint.x() < 0) {
0876                 // Don't draw off the left edge of the screen
0877                 drawPoint.setX(0);
0878             }
0879         }
0880 
0881         // Shouldn't have the label cut off by the bottom of the view
0882         if (drawPoint.y() + idealHeight > m_clipRect.height())
0883             continue;
0884 
0885         painter->translate(drawPoint);
0886         m_textDocument->documentLayout()->draw(painter, QAbstractTextDocumentLayout::PaintContext());
0887         painter->translate(-drawPoint);
0888     }
0889 }
0890 
0891 double View::h(const Plot &plot) const
0892 {
0893     if ((plot.plotMode == Function::Integral) || (plot.function()->type() == Function::Differential))
0894         return plot.function()->eq[0]->differentialStates.step().value();
0895 
0896     double dx = (m_xmax - m_xmin) / m_clipRect.width();
0897     double dy = (m_ymax - m_ymin) / m_clipRect.height();
0898 
0899     switch (plot.function()->type()) {
0900     case Function::Cartesian:
0901     case Function::Differential:
0902         return dx;
0903 
0904     case Function::Polar:
0905     case Function::Parametric:
0906     case Function::Implicit:
0907         return qMin(dx, dy);
0908     }
0909 
0910     qWarning() << "Unknown coord\n";
0911     return qMin(dx, dy);
0912 }
0913 
0914 double View::value(const Plot &plot, int eq, double x, bool updateFunction)
0915 {
0916     Function *function = plot.function();
0917     assert(function);
0918 
0919     if (updateFunction)
0920         plot.updateFunction();
0921 
0922     Equation *equation = function->eq[eq];
0923 
0924     double dx = h(plot);
0925     DifferentialState *state = plot.state();
0926 
0927     return XParser::self()->derivative(plot.derivativeNumber(), equation, state, x, dx);
0928 }
0929 
0930 QPointF View::realValue(const Plot &plot, double x, bool updateFunction)
0931 {
0932     Function *function = plot.function();
0933     assert(function);
0934 
0935     switch (function->type()) {
0936     case Function::Differential:
0937     case Function::Cartesian: {
0938         double y = value(plot, 0, x, updateFunction);
0939         return QPointF(x, y);
0940     }
0941 
0942     case Function::Polar: {
0943         double y = value(plot, 0, x, updateFunction);
0944         return QPointF(y * lcos(x), y * lsin(x));
0945     }
0946 
0947     case Function::Parametric: {
0948         double X = value(plot, 0, x, updateFunction);
0949         double Y = value(plot, 1, x, updateFunction);
0950         return QPointF(X, Y);
0951     }
0952 
0953     case Function::Implicit: {
0954         // Can only calculate the value when either x or y is fixed.
0955         assert(function->m_implicitMode != Function::UnfixedXY);
0956 
0957         double val = value(plot, 0, x, updateFunction);
0958 
0959         if (function->m_implicitMode == Function::FixedX)
0960             return QPointF(function->x, val);
0961         else
0962             return QPointF(val, function->y);
0963     }
0964     }
0965 
0966     qWarning() << "Unknown function type!\n";
0967     return QPointF();
0968 }
0969 
0970 double View::getXmin(Function *function, bool overlapEdge)
0971 {
0972     switch (function->type()) {
0973     case Function::Parametric:
0974     case Function::Polar:
0975         return function->dmin.value();
0976 
0977     case Function::Implicit:
0978         qWarning() << "You probably don't want to do this!\n";
0979         // fall through
0980 
0981     case Function::Differential:
0982     case Function::Cartesian: {
0983         double min = m_xmin;
0984         if (overlapEdge)
0985             min -= (m_xmax - m_xmin) * 0.02;
0986 
0987         if (function->usecustomxmin)
0988             return qMax(min, function->dmin.value());
0989         else
0990             return min;
0991     }
0992     }
0993 
0994     return 0;
0995 }
0996 
0997 double View::getXmax(Function *function, bool overlapEdge)
0998 {
0999     switch (function->type()) {
1000     case Function::Parametric:
1001     case Function::Polar:
1002         return function->dmax.value();
1003 
1004     case Function::Implicit:
1005         qWarning() << "You probably don't want to do this!\n";
1006         // fall through
1007 
1008     case Function::Differential:
1009     case Function::Cartesian: {
1010         double max = m_xmax;
1011         if (overlapEdge)
1012             max += (m_xmax - m_xmin) * 0.02;
1013 
1014         if (function->usecustomxmax)
1015             return qMin(max, function->dmax.value());
1016         else
1017             return max;
1018     }
1019     }
1020 
1021     return 0;
1022 }
1023 
1024 // #define DEBUG_IMPLICIT
1025 
1026 #ifdef DEBUG_IMPLICIT
1027 // Used in profiling root finding
1028 int root_find_iterations;
1029 int root_find_requests;
1030 #endif
1031 
1032 /**
1033  * For comparing points where two points close together are considered equal.
1034  */
1035 class FuzzyPoint
1036 {
1037 public:
1038     FuzzyPoint(const QPointF &point)
1039     {
1040         x = point.x();
1041         y = point.y();
1042     }
1043 
1044     FuzzyPoint(double x, double y)
1045     {
1046         FuzzyPoint::x = x;
1047         FuzzyPoint::y = y;
1048     }
1049 
1050     bool operator<(const FuzzyPoint &other) const
1051     {
1052         double du = qAbs(other.x - x) / dx;
1053         double dv = qAbs(other.y - y) / dy;
1054 
1055         bool x_eq = (du < 1); // Whether the x coordinates are considered equal
1056         bool y_eq = (dv < 1); // Whether the y coordinates are considered equal
1057 
1058         if (x_eq && y_eq) {
1059             // Points are close together.
1060             return false;
1061         }
1062 
1063         bool x_lt = !x_eq && (x < other.x);
1064         bool y_lt = !y_eq && (y < other.y);
1065 
1066         return (x_lt || (x_eq && y_lt));
1067     }
1068 
1069     double x;
1070     double y;
1071 
1072     static double dx;
1073     static double dy;
1074 };
1075 typedef QMap<FuzzyPoint, QPointF> FuzzyPointMap;
1076 
1077 double FuzzyPoint::dx = 0;
1078 double FuzzyPoint::dy = 0;
1079 
1080 double SegmentMin = 0.1;
1081 double SegmentMax = 6.0;
1082 
1083 // The viewable area is divided up into square*squares squares, and the curve
1084 // is traced around in each square.
1085 // NOTE: it is generally a good idea to make this number prime
1086 int squares = 19;
1087 
1088 void View::drawImplicit(Function *function, QPainter *painter)
1089 {
1090     assert(function->type() == Function::Implicit);
1091 
1092 #ifdef DEBUG_IMPLICIT
1093     QTime t;
1094     t.start();
1095 
1096     painter->setPen(Qt::black);
1097 
1098     for (double i = 0; i <= squares; ++i) {
1099         double x = m_xmin + i * (m_xmax - m_xmin) / squares;
1100         double y = m_ymin + i * (m_ymax - m_ymin) / squares;
1101 
1102         painter->drawLine(toPixel(QPointF(m_xmin, y), ClipInfinite), toPixel(QPointF(m_xmax, y), ClipInfinite));
1103         painter->drawLine(toPixel(QPointF(x, m_ymin), ClipInfinite), toPixel(QPointF(x, m_ymax), ClipInfinite));
1104     }
1105 
1106     root_find_iterations = 0;
1107     root_find_requests = 0;
1108 #endif
1109 
1110     // Need another function for investigating singular points
1111     Plot circular;
1112     QString fname("f(x)=0");
1113     XParser::self()->fixFunctionName(fname, Equation::Cartesian, -1);
1114     circular.setFunctionID(XParser::self()->Parser::addFunction(fname, nullptr, Function::Cartesian));
1115     assert(circular.function());
1116 
1117     const QList<Plot> plots = function->plots();
1118     for (const Plot &plot : plots) {
1119         bool setAliased = false;
1120         if (plot.parameter.type() == Parameter::Animated) {
1121             // Don't use antialiasing, so that rendering is sped up
1122             if (painter->renderHints() & QPainter::Antialiasing) {
1123                 setAliased = true;
1124                 painter->setRenderHint(QPainter::Antialiasing, false);
1125             }
1126         }
1127 
1128         painter->setPen(penForPlot(plot, painter));
1129 
1130         QList<QPointF> singular;
1131 
1132         for (int i = 0; i <= squares; ++i) {
1133             double y = m_ymin + i * (m_ymax - m_ymin) / double(squares);
1134 
1135             function->y = y;
1136             function->m_implicitMode = Function::FixedY;
1137             QList<double> roots = findRoots(plot, m_xmin, m_xmax, RoughRoot);
1138 
1139             for (double x : qAsConst(roots)) {
1140 #ifdef DEBUG_IMPLICIT
1141                 painter->setPen(QPen(Qt::red, painter->pen().width()));
1142 #endif
1143                 drawImplicitInSquare(plot, painter, x, y, Qt::Horizontal, &singular);
1144             }
1145 
1146             double x = m_xmin + i * (m_xmax - m_xmin) / double(squares);
1147 
1148             function->x = x;
1149             function->m_implicitMode = Function::FixedX;
1150             roots = findRoots(plot, m_ymin, m_ymax, RoughRoot);
1151 
1152             for (double y : qAsConst(roots)) {
1153 #ifdef DEBUG_IMPLICIT
1154                 painter->setPen(QPen(Qt::blue, painter->pen().width()));
1155 #endif
1156                 drawImplicitInSquare(plot, painter, x, y, Qt::Vertical, &singular);
1157             }
1158         }
1159 
1160         // Sort out the implicit points
1161         FuzzyPointMap singularSorted;
1162         FuzzyPoint::dx = (m_xmax - m_xmin) * SegmentMin * 0.1 / m_clipRect.width();
1163         FuzzyPoint::dy = (m_ymax - m_ymin) * SegmentMin * 0.1 / m_clipRect.height();
1164         for (const QPointF &point : qAsConst(singular))
1165             singularSorted.insert(point, point);
1166         singular = singularSorted.values();
1167 
1168         for (const QPointF &point : qAsConst(singular)) {
1169             // radius of circle around singular point
1170             double epsilon = qMin(FuzzyPoint::dx, FuzzyPoint::dy);
1171 
1172             QString fstr;
1173             fstr = QString("%1(x)=%2(%3+%6*cos(x),%4+%6*sin(x)%5)")
1174                        .arg(circular.function()->eq[0]->name())
1175                        .arg(function->eq[0]->name())
1176                        .arg(XParser::self()->number(point.x()))
1177                        .arg(XParser::self()->number(point.y()))
1178                        .arg(function->eq[0]->usesParameter() ? ',' + XParser::self()->number(function->k) : QString())
1179                        .arg(XParser::self()->number(epsilon));
1180 
1181             bool setFstrOk = circular.function()->eq[0]->setFstr(fstr);
1182             qDebug() << "------------ " << setFstrOk;
1183             assert(setFstrOk);
1184 
1185             QList<double> roots = findRoots(circular, 0, 2 * M_PI / XParser::self()->radiansPerAngleUnit(), PreciseRoot);
1186 
1187 #ifdef DEBUG_IMPLICIT
1188             qDebug() << "Singular point at (x,y)=(" << point.x() << ',' << point.y() << ")\n";
1189             qDebug() << "fstr is    " << fstr;
1190             qDebug() << "Found " << roots.size() << " roots.\n";
1191 #endif
1192 
1193             for (double t : qAsConst(roots)) {
1194 #ifdef DEBUG_IMPLICIT
1195                 painter->setPen(QPen(Qt::green, painter->pen().width()));
1196 #endif
1197                 double x = point.x() + epsilon * lcos(t);
1198                 double y = point.y() + epsilon * lsin(t);
1199                 drawImplicitInSquare(plot, painter, x, y, {}, &singular);
1200             }
1201         }
1202 
1203         if (setAliased)
1204             painter->setRenderHint(QPainter::Antialiasing, true);
1205     }
1206 
1207 #ifdef DEBUG_IMPLICIT
1208     if (root_find_requests != 0)
1209         qDebug() << "Average iterations in root finding was " << root_find_iterations / root_find_requests;
1210     qDebug() << "Time taken was " << t.elapsed();
1211 #endif
1212 
1213     XParser::self()->removeFunction(circular.functionID());
1214 }
1215 
1216 // static
1217 double View::maxSegmentLength(double curvature)
1218 {
1219     // Use a circle angle of 4 degrees to determine the maximum segment length
1220     // Also, limit the length to be between 0.1 and 6 pixels.
1221 
1222     double arc = 4 * (M_PI / 180);
1223 
1224     if (curvature < 0)
1225         curvature = -curvature;
1226 
1227     if (curvature < 1e-20)
1228         return SegmentMax; // very large circle
1229 
1230     double radius = 1.0 / curvature;
1231 
1232     double segment = arc * radius;
1233     if (segment < SegmentMin)
1234         segment = SegmentMin;
1235     else if (segment > SegmentMax)
1236         segment = SegmentMax;
1237 
1238     return segment;
1239 }
1240 
1241 void View::drawImplicitInSquare(const Plot &plot, QPainter *painter, double x, double y, Qt::Orientations orientation, QList<QPointF> *singular)
1242 {
1243     plot.updateFunction();
1244     Plot diff1 = plot;
1245     diff1.differentiate();
1246     Plot diff2 = diff1;
1247     diff2.differentiate();
1248 
1249 #ifdef DEBUG_IMPLICIT
1250     painter->save();
1251     painter->setPen(QPen(Qt::black, painter->pen().width()));
1252     QPointF tl = toPixel(QPointF(x, y), ClipInfinite) - QPoint(2, 2);
1253     painter->drawRect(QRectF(tl, QSizeF(4, 4)));
1254     painter->restore();
1255 #endif
1256 
1257     double x_side = (m_xmax - m_xmin) / squares;
1258     double y_side = (m_ymax - m_ymin) / squares;
1259 
1260     // Use a square around the root to bound the tracing
1261     // To start with, assume that tracing will go up,right. But this
1262     // might not be so, so the upper/lower boundaries may be adjusted depending
1263     // on where the tracing ends up
1264     double x_lower, x_upper, y_lower, y_upper;
1265     if (orientation & Qt::Vertical) {
1266         x_lower = x;
1267         x_upper = x + x_side;
1268     } else {
1269         double x_prop = (x - m_xmin) / (m_xmax - m_xmin);
1270         x_lower = std::floor(x_prop * squares) * x_side + m_xmin;
1271         x_upper = x_lower + x_side;
1272     }
1273     if (orientation & Qt::Horizontal) {
1274         y_lower = y;
1275         y_upper = y + y_side;
1276     } else {
1277         double y_prop = (y - m_ymin) / (m_ymax - m_ymin);
1278         y_lower = std::floor(y_prop * squares) * y_side + m_ymin;
1279         y_upper = y_lower + y_side;
1280     }
1281 
1282     // If during tracing, the root could not be found, then this will be set to true,
1283     // the route will be retraced using a smaller step size and it will attempt to find
1284     // a root again. If it fails for a second time, then tracing is finished.
1285     bool foundRootPreviously = true;
1286 
1287     // Used for focal points.
1288     double prevAngle = 0;
1289     int switchCount = 0;
1290 
1291     // This is so that the algorithm can "look ahead" to see what is coming up,
1292     // before drawing or committing itself to anything potentially bad
1293     QPointF prev2 = toPixel(QPointF(x, y), ClipInfinite);
1294     QPointF prev1 = prev2;
1295 
1296     // Allow us to doubly retrace
1297     double prev_diff_x = 0;
1298     double prev_diff_y = 0;
1299 
1300     for (int i = 0; i < 500; ++i) // allow a maximum of 500 traces (to prevent possibly infinite loop)
1301     {
1302         if (i == 500 - 1) {
1303             qDebug() << "Implicit: got to last iteration!\n";
1304         }
1305 
1306         // (dx, dy) is perpendicular to curve
1307 
1308         plot.function()->x = x;
1309         plot.function()->y = y;
1310 
1311         plot.function()->m_implicitMode = Function::FixedY;
1312         double dx = value(diff1, 0, x, false);
1313 
1314         plot.function()->m_implicitMode = Function::FixedX;
1315         double dy = value(diff1, 0, y, false);
1316 
1317         double k = pixelCurvature(plot, x, y);
1318         double segment_step = maxSegmentLength(k) * pow(0.5, switchCount);
1319 
1320         // If we couldn't find a root in the previous iteration, it was possibly
1321         // because we were using too large a step size. So reduce the step size
1322         // and try again.
1323         if (!foundRootPreviously)
1324             segment_step = qMin(segment_step / 4, SegmentMin);
1325 
1326         //      qDebug() << "k="<<k<<" segment_step="<<segment_step;
1327 
1328         QPointF p1 = toPixel(QPointF(x, y), ClipInfinite) * painter->transform();
1329         QPointF p2 = toPixel(QPointF(x + dx, y + dy), ClipInfinite) * painter->transform();
1330         double l = QLineF(p1, p2).length() / segment_step;
1331 
1332         if (l == 0) {
1333             qDebug() << "length is zero!\n";
1334             break;
1335         }
1336 
1337         // (tx, ty) is tangent to the curve in the direction that we are tracing
1338         double tx = -dy / l;
1339         double ty = dx / l;
1340 
1341         double angle = atan(ty / tx) + ((tx < 0) ? M_PI : 0);
1342         double diff = realModulo(angle - prevAngle, 2 * M_PI);
1343 
1344         bool switchedDirection = (i > 0) && (diff > (3. / 4.) * M_PI) && (diff < (5. / 4.) * M_PI);
1345         if (switchedDirection) {
1346             // Why do I care about suddenly changing the direction?
1347             // Because the chances are, a attracting or repelling point has been reached.
1348             // Even if not, it suggests that a smaller step size is needed. If we have
1349             // switched direction and are already at the smallest step size, then note
1350             // the dodgy point for further investigation and give up for now
1351 
1352             //          qDebug() << "Switched direction: x="<<x<<" switchCount="<<switchCount<<" segment_step="<<segment_step<<" i="<<i;
1353 
1354             // Use a step size much smaller than segment min to obtain good accuracy,
1355             // needed for investigating the point further
1356             if (segment_step <= SegmentMin * 0.01) {
1357                 // Give up. Tell our parent function to investigate the point further
1358                 *singular << QPointF(x, y);
1359                 break;
1360             }
1361 
1362             // Rewind the last tangent addition as well
1363             x -= prev_diff_x;
1364             y -= prev_diff_y;
1365 
1366             prev_diff_x = 0;
1367             prev_diff_y = 0;
1368 
1369             switchCount += 2;
1370             continue;
1371         } else {
1372             // Reset the stepping adjustment
1373             switchCount = qMax(0, switchCount - 1);
1374             prevAngle = angle;
1375             //          qDebug() << "Didn't switch - x="<<x<<" segment_step="<<segment_step;
1376         }
1377 
1378         if (i == 0) {
1379             // First trace; does the bounding square need adjusting?
1380 
1381             if ((tx < 0) && (orientation & Qt::Vertical)) {
1382                 x_lower -= x_side;
1383                 x_upper -= x_side;
1384             }
1385 
1386             if ((ty < 0) && (orientation & Qt::Horizontal)) {
1387                 y_lower -= y_side;
1388                 y_upper -= y_side;
1389             }
1390         }
1391 
1392         // The maximum tangent length before we end up outside our bounding square
1393         double max_tx, max_ty;
1394         if (tx > 0)
1395             max_tx = x_upper - x;
1396         else
1397             max_tx = x - x_lower;
1398         if (ty > 0)
1399             max_ty = y_upper - y;
1400         else
1401             max_ty = y - y_lower;
1402 
1403         // Does (tx,ty) need to be scaled to make sure the tangent stays inside the square?
1404         double scale = qMax((tx == 0) ? 0 : qAbs(tx) / max_tx, (ty == 0) ? 0 : qAbs(ty) / max_ty);
1405         bool outOfBounds = scale > 1;
1406         if (outOfBounds) {
1407             tx /= scale;
1408             ty /= scale;
1409         }
1410 
1411         double x0 = x;
1412         double y0 = y;
1413 
1414         x += tx;
1415         y += ty;
1416 
1417         plot.function()->x = x;
1418         plot.function()->y = y;
1419 
1420         double *coord = nullptr;
1421         if (qAbs(tx) > qAbs(ty)) {
1422             plot.function()->m_implicitMode = Function::FixedX;
1423             coord = &y;
1424         } else {
1425             plot.function()->m_implicitMode = Function::FixedY;
1426             coord = &x;
1427         }
1428 
1429         bool found = findRoot(coord, plot, RoughRoot);
1430         if (!found) {
1431             if (foundRootPreviously) {
1432 #ifdef DEBUG_IMPLICIT
1433                 qDebug() << "Could not find root!\n";
1434 #endif
1435 
1436                 // Retrace our steps
1437                 x = x0;
1438                 y = y0;
1439                 prev_diff_x = 0;
1440                 prev_diff_y = 0;
1441                 foundRootPreviously = false;
1442                 continue;
1443             } else {
1444                 qDebug() << "Couldn't find root - giving up.\n";
1445                 break;
1446             }
1447         } else
1448             foundRootPreviously = true;
1449 
1450         prev_diff_x = x - x0;
1451         prev_diff_y = y - y0;
1452 
1453         painter->drawLine(prev2, prev1);
1454         prev2 = prev1;
1455         prev1 = toPixel(QPointF(x, y), ClipInfinite);
1456         markDiagramPointUsed(prev1);
1457 
1458         if (outOfBounds)
1459             break;
1460     }
1461 
1462     // and the final line
1463     painter->drawLine(prev2, prev1);
1464 }
1465 
1466 void View::drawFunction(Function *function, QPainter *painter)
1467 {
1468     if ((function->type() == Function::Differential) && (function->eq[0]->order() == 1) && function->plotAppearance(Function::Derivative0).showTangentField) {
1469         const QList<Plot> plots = function->plots(Function::PlotCombinations(Function::AllCombinations) & ~Function::DifferentInitialStates);
1470         for (const Plot &plot : plots)
1471             drawTangentField(plot, painter);
1472     }
1473 
1474     const QList<Plot> plots = function->plots();
1475     for (const Plot &plot : plots)
1476         drawPlot(plot, painter);
1477 }
1478 
1479 void View::drawTangentField(const Plot &plot, QPainter *painter)
1480 {
1481     plot.updateFunction();
1482     Function *function = plot.function();
1483 
1484     assert(function->type() == Function::Differential);
1485     // Can only draw tangent fields for first order differential equations
1486     assert(function->eq[0]->order() == 1);
1487 
1488     painter->setPen(penForPlot(plot, painter));
1489 
1490     bool useParameter = function->eq[0]->usesParameter();
1491     Vector v(useParameter ? 3 : 2);
1492 
1493     if (useParameter)
1494         v[1] = function->k;
1495 
1496     // For converting from real to pixels
1497     double sx = m_clipRect.width() / (m_xmax - m_xmin);
1498     double sy = m_clipRect.height() / (m_ymax - m_ymin);
1499 
1500     for (double x = ticStartX; x <= m_xmax; x += ticSepX.value()) {
1501         v[0] = x;
1502         for (double y = ticStartY; y <= m_ymax; y += ticSepY.value()) {
1503             v[useParameter ? 2 : 1] = y;
1504 
1505             double df = XParser::self()->fkt(function->eq[0], v) * (sy / sx);
1506             double theta = std::atan(df);
1507             double dx = std::cos(theta) * (ticSepX.value() / 8.0);
1508             double dy = std::sin(theta) * (ticSepY.value() / 8.0);
1509 
1510             QPointF mid(x, y);
1511             QPointF diff(dx, dy);
1512 
1513             painter->drawLine(toPixel(mid - diff), toPixel(mid + diff));
1514         }
1515     }
1516 }
1517 
1518 /**
1519  * Convenience function for drawing lines. Unfortunately, QPainter::drawPolyline
1520  * takes a long time to draw the line joins, which is only necessary when we are
1521  * using a fat pen. Therefore, draw each line individually if we are using a
1522  * thin pen to save time.
1523  */
1524 void drawPolyline(QPainter *painter, const QPolygonF &points)
1525 {
1526     if (painter->pen().width() > 5)
1527         painter->drawPolyline(points);
1528     else if (points.size() >= 2) {
1529         QPointF prev = points.first();
1530         for (int i = 1; i < points.size(); ++i) {
1531             //          QPen pen( painter->pen() );
1532             //          pen.setColor( (i%2==0) ? Qt::red : Qt::blue );
1533             //          painter->setPen( pen );
1534 
1535             QPointF next = points[i];
1536             painter->drawLine(prev, next);
1537             prev = next;
1538         }
1539     }
1540 }
1541 
1542 /**
1543  * Speed up drawing by only drawing one line between each straightish section of the curve
1544  * These variable are used to determine when the curve can no longer be approximate by a
1545  * straight line as the new angle has changed too much
1546  */
1547 class CurveApproximator
1548 {
1549 public:
1550     CurveApproximator(const QPolygonF &points)
1551     {
1552         assert(points.size() >= 2);
1553         reset();
1554 
1555         QPointF diff = points[points.size() - 2] - points.last();
1556         currentAngle = atan2(diff.y(), diff.x());
1557         approximatingCurve = true;
1558     }
1559 
1560     CurveApproximator()
1561     {
1562         reset();
1563     }
1564 
1565     void reset()
1566     {
1567         currentAngle = 0;
1568         maxClockwise = 0;
1569         maxAnticlockwise = 0;
1570         maxDistance = 0;
1571         approximatingCurve = false;
1572     }
1573 
1574     bool shouldDraw() const
1575     {
1576         return ((maxAnticlockwise + maxClockwise) * maxDistance) >= 0.5;
1577     }
1578 
1579     void update(const QPolygonF &points)
1580     {
1581         // Should have at least two points in the list
1582         assert(points.size() >= 2);
1583 
1584         QPointF p1 = points[points.size() - 2];
1585         QPointF p2 = points.last();
1586 
1587         QPointF diff = p1 - p2;
1588         double angle = atan2(diff.y(), diff.x());
1589 
1590         double lineLength = QLineF(p1, p2).length();
1591         if (lineLength > maxDistance)
1592             maxDistance = lineLength;
1593 
1594         double clockwise = realModulo(currentAngle - angle, 2 * M_PI);
1595         double anticlockwise = realModulo(angle - currentAngle, 2 * M_PI);
1596 
1597         bool goingClockwise = (clockwise < anticlockwise);
1598 
1599         if (goingClockwise) {
1600             // anti-clockwise
1601             if (clockwise > maxClockwise)
1602                 maxClockwise = clockwise;
1603         } else {
1604             // clockwise
1605             if (anticlockwise > maxAnticlockwise)
1606                 maxAnticlockwise = anticlockwise;
1607         }
1608     }
1609 
1610     double currentAngle;
1611     double maxClockwise;
1612     double maxAnticlockwise;
1613     double maxDistance;
1614     bool approximatingCurve;
1615 };
1616 
1617 void View::drawPlot(const Plot &plot, QPainter *painter)
1618 {
1619     plot.updateFunction();
1620     Function *function = plot.function();
1621 
1622     // should use drawImplicit for implicit functions
1623     assert(function->type() != Function::Implicit);
1624 
1625     double dmin = getXmin(function, true);
1626     double dmax = getXmax(function, true);
1627 
1628     if (dmin >= dmax)
1629         return;
1630 
1631     painter->save();
1632 
1633     // Bug in Qt 4.2 TP - QPainter::drawPolyline draws the background as well while printing
1634     // So for testing printing, use a brush where one can see the function being drawn
1635     painter->setBrush(Qt::white);
1636 
1637     if ((plot.parameter.type() == Parameter::Animated) && (painter->renderHints() & QPainter::Antialiasing)) {
1638         // Don't use antialiasing, so that rendering is speeded up
1639         painter->setRenderHint(QPainter::Antialiasing, false);
1640     }
1641 
1642     painter->setPen(penForPlot(plot, painter));
1643 
1644     // the 'middle' dx, which may be increased or decreased
1645     double max_dx = (dmax - dmin) / m_clipRect.width();
1646     if ((function->type() == Function::Parametric) || (function->type() == Function::Polar))
1647         max_dx *= 0.01;
1648 
1649     // Increase speed while translating the view
1650     bool quickDraw = (m_zoomMode == Translating);
1651     if (quickDraw)
1652         max_dx *= 4.0;
1653 
1654     double dx = max_dx;
1655 
1656     double maxLength = quickDraw ? 8.0 : (function->plotAppearance(plot.plotMode).style == Qt::SolidLine) ? 4.0 : 1.5;
1657     double minLength = maxLength * 0.5;
1658 
1659     bool drawIntegral = m_integralDrawSettings.draw && (m_integralDrawSettings.plot == plot);
1660     double totalLength = 0.0; // total pixel length; used for drawing dotted lines
1661 
1662     bool p1Set = false;
1663     QPointF p1, p2;
1664 
1665     CurveApproximator approximator;
1666     QPolygonF drawPoints;
1667 
1668     double x = dmin;
1669     double prevX = x; // the value of x before last adding dx to it
1670     do {
1671         QPointF rv = realValue(plot, x, false);
1672 
1673         // If we are currently plotting a differential equation, and it became infinite,
1674         // then skip x forward to a point where it is finite
1675         if (function->type() == Function::Differential && !XParser::self()->differentialFinite) {
1676             double new_x = XParser::self()->differentialDiverge;
1677             if (new_x > x) {
1678                 x = new_x;
1679                 prevX = x;
1680 
1681                 continue;
1682             }
1683         }
1684 
1685         p2 = toPixel(rv, ClipInfinite);
1686 
1687         if (xclipflg || yclipflg) {
1688             prevX = x;
1689             x += dx;
1690 
1691             p1Set = false; // p1 wouldn't be finite (if we had set it)
1692             continue;
1693         }
1694 
1695         if (!p1Set) {
1696             prevX = x;
1697             x += dx;
1698 
1699             p1 = p2;
1700             p1Set = true;
1701             continue;
1702         }
1703 
1704         // BEGIN adjust dx
1705         QRectF bound = QRectF(p1, QSizeF((p2 - p1).x(), (p2 - p1).y())).normalized();
1706         double length = QLineF(p1, p2).length();
1707         totalLength += length;
1708 
1709         double min_mod = (function->type() == Function::Cartesian || function->type() == Function::Differential) ? 1e-2 : 5e-4;
1710         bool dxAtMinimum = (dx <= max_dx * min_mod);
1711         bool dxAtMaximum = (dx >= max_dx);
1712         bool dxTooBig = false;
1713         bool dxTooSmall = false;
1714 
1715         if (QRectF(m_clipRect).intersects(bound)) {
1716             dxTooBig = !dxAtMinimum && (length > maxLength);
1717             dxTooSmall = !dxAtMaximum && (length < minLength);
1718         } else
1719             dxTooSmall = !dxAtMaximum;
1720 
1721         if (dxTooBig) {
1722             dx *= 0.5;
1723             x = prevX + dx;
1724             totalLength -= length;
1725             continue;
1726         }
1727 
1728         if (dxTooSmall)
1729             dx *= 2.0;
1730         // END adjust dx
1731 
1732         if (drawIntegral && (x >= m_integralDrawSettings.dmin) && (x <= m_integralDrawSettings.dmax)) {
1733             double y0 = yToPixel(0);
1734 
1735             /// \todo should draw the shape in one go
1736 
1737             QPointF points[4];
1738             points[0] = QPointF(p1.x(), y0);
1739             points[1] = QPointF(p2.x(), y0);
1740             points[2] = QPointF(p2.x(), p2.y());
1741             points[3] = QPointF(p1.x(), p1.y());
1742 
1743             painter->drawPolygon(points, 4);
1744         } else if (penShouldDraw(totalLength, plot)) {
1745             if (drawPoints.isEmpty()) {
1746                 drawPoints << p1;
1747             } else if (drawPoints.last() != p1) {
1748                 drawPolyline(painter, drawPoints);
1749                 drawPoints.clear();
1750                 drawPoints << p1;
1751                 approximator.reset();
1752             }
1753 
1754             // The above code should guarantee that drawPoints isn't empty
1755             // But check it now in case I do something stupid
1756             assert(!drawPoints.isEmpty());
1757 
1758             if (!approximator.approximatingCurve) {
1759                 // Cool, about to add another point. This defines the working angle of the line
1760                 // approximation
1761                 drawPoints << p2;
1762                 approximator = CurveApproximator(drawPoints);
1763             } else {
1764                 QPointF prev = drawPoints.last();
1765                 drawPoints.last() = p2;
1766                 approximator.update(drawPoints);
1767 
1768                 // Allow a maximum deviation (in pixels)
1769                 if (approximator.shouldDraw()) {
1770                     // The approximation is too bad; will have to start again now
1771                     drawPoints.last() = prev;
1772                     drawPoints << p2;
1773                     approximator = CurveApproximator(drawPoints);
1774                 }
1775             }
1776         }
1777 
1778         markDiagramPointUsed(p2);
1779 
1780         p1 = p2;
1781 
1782         Q_ASSERT(dx > 0);
1783         prevX = x;
1784         x += dx;
1785     } while (x <= dmax);
1786 
1787     //  qDebug() << "drawPoints.size()="<<drawPoints.size();
1788     drawPolyline(painter, drawPoints);
1789 
1790     painter->restore();
1791 }
1792 
1793 void View::drawFunctionInfo(QPainter *painter)
1794 {
1795     // Don't draw info if translating the view
1796     if (m_zoomMode == Translating)
1797         return;
1798 
1799     // The names of the plots are drawn around the edge of the view, in a clockwise
1800     // direction, starting from the top-right. Picture the positions like this:
1801     //
1802     //   7  8  9  0
1803     //   6        1
1804     //   5  4  3  2
1805 
1806     // Used for determining where to draw the next label indicating the plot name
1807     int plotNameAt = 0;
1808 
1809     for (Function *function : qAsConst(XParser::self()->m_ufkt)) {
1810         if (m_stopCalculating)
1811             break;
1812 
1813         for (const Plot &plot : function->plots()) {
1814             plot.updateFunction();
1815 
1816             // Draw extrema points?
1817             if ((function->type() == Function::Cartesian) && function->plotAppearance(plot.plotMode).showExtrema) {
1818                 const QList<QPointF> stationaryPoints = findStationaryPoints(plot);
1819                 for (const QPointF &realValue : stationaryPoints) {
1820                     painter->setPen(QPen(Qt::black, millimetersToPixels(1.5, painter->device())));
1821                     painter->drawPoint(toPixel(realValue));
1822 
1823                     QString x = posToString(realValue.x(), (m_xmax - m_xmin) / m_clipRect.width(), View::DecimalFormat);
1824                     QString y = posToString(realValue.y(), (m_ymax - m_ymin) / m_clipRect.width(), View::DecimalFormat);
1825 
1826                     drawLabel(painter,
1827                               plot.color(),
1828                               realValue,
1829                               i18nc("Extrema point", "x = %1   y = %2", x.replace('.', QLocale().decimalPoint()), y.replace('.', QLocale().decimalPoint())));
1830                 }
1831             }
1832 
1833             // Show the name of the plot?
1834             if (function->plotAppearance(plot.plotMode).showPlotName) {
1835                 double x, y;
1836 
1837                 double xmin = m_xmin + 0.1 * (m_xmax - m_xmin);
1838                 double xmax = m_xmax - 0.1 * (m_xmax - m_xmin);
1839                 double ymin = m_ymin + 0.1 * (m_ymax - m_ymin);
1840                 double ymax = m_ymax - 0.1 * (m_ymax - m_ymin);
1841 
1842                 // Find out where on the outer edge of the view to draw it
1843                 if (0 <= plotNameAt && plotNameAt <= 2) {
1844                     x = xmax;
1845                     y = ymax - (ymax - ymin) * plotNameAt / 2;
1846                 } else if (3 <= plotNameAt && plotNameAt <= 5) {
1847                     x = xmax - (xmax - xmin) * (plotNameAt - 2) / 3;
1848                     y = ymin;
1849                 } else if (6 <= plotNameAt && plotNameAt <= 7) {
1850                     x = xmin;
1851                     y = ymin + (ymax - ymin) * (plotNameAt - 5) / 2;
1852                 } else {
1853                     x = xmin + (xmax - xmin) * (plotNameAt - 7) / 3;
1854                     y = ymax;
1855                 }
1856 
1857                 plotNameAt = (plotNameAt + 1) % 10;
1858 
1859                 QPointF realPos;
1860 
1861                 if (function->type() == Function::Implicit) {
1862                     findRoot(&x, &y, plot, RoughRoot);
1863                     realPos = QPointF(x, y);
1864                 } else {
1865                     double t = getClosestPoint(QPointF(x, y), plot);
1866                     realPos = realValue(plot, t, false);
1867                 }
1868 
1869                 // If the closest point isn't in the view, then don't draw the label
1870                 if (realPos.x() < m_xmin || realPos.x() > m_xmax || realPos.y() < m_ymin || realPos.y() > m_ymax)
1871                     continue;
1872 
1873                 drawLabel(painter, plot.color(), realPos, plot.name());
1874             }
1875         }
1876     }
1877 }
1878 
1879 void View::drawLabel(QPainter *painter, const QColor &color, const QPointF &realPos, const QString &text)
1880 {
1881     QPalette palette;
1882     QColor outline = color;
1883     QColor background = outline.lighter(500);
1884     background.setAlpha(127);
1885 
1886     QPointF pixelCenter = toPixel(realPos);
1887     QRectF rect(pixelCenter, QSizeF(1, 1));
1888 
1889     painter->setFont(m_labelFont);
1890     int flags = Qt::TextSingleLine | Qt::AlignLeft | Qt::AlignTop;
1891     rect = painter->boundingRect(rect, flags, text).adjusted(-7, -3, 4, 2);
1892 
1893     // Try and find a nice place for inserting the rectangle
1894     int bestCost = int(1e7);
1895     QPointF bestCenter = realPos;
1896     for (double x = pixelCenter.x() - 300; x <= pixelCenter.x() + 300; x += 20) {
1897         for (double y = pixelCenter.y() - 300; y <= pixelCenter.y() + 300; y += 20) {
1898             QPointF center(x, y);
1899             rect.moveCenter(center);
1900             double length = (x - pixelCenter.x()) * (x - pixelCenter.x()) + (y - pixelCenter.y()) * (y - pixelCenter.y());
1901             int cost = rectCost(rect) + int(length) / 100;
1902 
1903             if (cost < bestCost) {
1904                 bestCenter = center;
1905                 bestCost = cost;
1906             }
1907         }
1908     }
1909 
1910     rect.moveCenter(bestCenter);
1911 
1912     markDiagramAreaUsed(rect);
1913 
1914     painter->setBrush(background);
1915     painter->setPen(outline);
1916     painter->drawRoundedRect(rect, int(1000 / rect.width()), int(1000 / rect.height()));
1917 
1918     // If the rectangle does not lie over realPos, then draw a line to realPos from the rectangle
1919     if (!rect.contains(pixelCenter)) {
1920         QPointF lineStart = bestCenter;
1921         QLineF line(pixelCenter, bestCenter);
1922 
1923         QPointF intersect = bestCenter;
1924 
1925         // Where does line intersect the rectangle?
1926         if (QLineF(rect.topLeft(), rect.topRight()).intersects(line, &intersect) == QLineF::BoundedIntersection)
1927             lineStart = intersect;
1928         else if (QLineF(rect.topRight(), rect.bottomRight()).intersects(line, &intersect) == QLineF::BoundedIntersection)
1929             lineStart = intersect;
1930         else if (QLineF(rect.bottomRight(), rect.bottomLeft()).intersects(line, &intersect) == QLineF::BoundedIntersection)
1931             lineStart = intersect;
1932         else if (QLineF(rect.bottomLeft(), rect.topLeft()).intersects(line, &intersect) == QLineF::BoundedIntersection)
1933             lineStart = intersect;
1934 
1935         painter->drawLine(lineStart, pixelCenter);
1936     }
1937 
1938     painter->setPen(Qt::black);
1939     painter->drawText(rect.adjusted(7, 3, -4, -2), flags, text);
1940 }
1941 
1942 QRect View::usedDiagramRect(const QRectF &rect) const
1943 {
1944     double x0 = rect.left() / m_clipRect.width();
1945     double x1 = rect.right() / m_clipRect.width();
1946 
1947     double y0 = rect.top() / m_clipRect.height();
1948     double y1 = rect.bottom() / m_clipRect.height();
1949 
1950     int i0 = qMax(int(x0 * LabelGridSize), 0);
1951     int i1 = qMin(int(x1 * LabelGridSize), LabelGridSize - 1);
1952     int j0 = qMax(int(y0 * LabelGridSize), 0);
1953     int j1 = qMin(int(y1 * LabelGridSize), LabelGridSize - 1);
1954 
1955     return QRect(i0, j0, i1 - i0 + 1, j1 - j0 + 1) & QRect(0, 0, LabelGridSize, LabelGridSize);
1956 }
1957 
1958 void View::markDiagramAreaUsed(const QRectF &rect)
1959 {
1960     if (m_zoomMode == Translating)
1961         return;
1962 
1963     QRect r = usedDiagramRect(rect);
1964 
1965     for (int i = r.left(); i <= r.right(); ++i)
1966         for (int j = r.top(); j <= r.bottom(); ++j)
1967             m_usedDiagramArea[i][j] = true;
1968 }
1969 
1970 void View::markDiagramPointUsed(const QPointF &point)
1971 {
1972     if (m_zoomMode == Translating)
1973         return;
1974 
1975     double x = point.x() / m_clipRect.width();
1976     double y = point.y() / m_clipRect.height();
1977 
1978     int i = int(x * LabelGridSize);
1979     int j = int(y * LabelGridSize);
1980 
1981     if (i < 0 || i >= LabelGridSize || j < 0 || j >= LabelGridSize)
1982         return;
1983 
1984     m_usedDiagramArea[i][j] = true;
1985 }
1986 
1987 int View::rectCost(QRectF rect) const
1988 {
1989     rect = rect.normalized();
1990 
1991     int cost = 0;
1992 
1993     // If the rectangle goes off the edge, mark it as very high cost)
1994     if (rect.intersects(m_clipRect)) {
1995         QRectF intersect = (rect & m_clipRect);
1996         cost += int(rect.width() * rect.height() - intersect.width() * intersect.height());
1997     } else {
1998         // The rectangle is completely outside!
1999         cost += int(rect.width() * rect.height());
2000     }
2001 
2002     QRect r = usedDiagramRect(rect);
2003 
2004     for (int i = r.left(); i <= r.right(); ++i)
2005         for (int j = r.top(); j <= r.bottom(); ++j)
2006             if (m_usedDiagramArea[i][j])
2007                 cost += 200;
2008 
2009     return cost;
2010 }
2011 
2012 bool View::penShouldDraw(double length, const Plot &plot)
2013 {
2014     // Always use a solid line when translating the view
2015     if (m_zoomMode == Translating)
2016         return true;
2017 
2018     Function *function = plot.function();
2019 
2020     Qt::PenStyle style = function->plotAppearance(plot.plotMode).style;
2021 
2022     double sepBig = 8.0; // separation distance between dashes
2023     double sepMid = 7.0; // separation between a dash and a dot
2024     double sepSmall = 6.5; // separation distance between dots
2025     double dash = 9.0; // length of a dash
2026     double dot = 3.5; // length of a dot
2027 
2028     switch (style) {
2029     case Qt::NoPen:
2030         // *whatever*...
2031         return false;
2032 
2033     case Qt::SolidLine:
2034         return true;
2035 
2036     case Qt::DashLine:
2037         return realModulo(length, dash + sepBig) < dash;
2038 
2039     case Qt::DotLine:
2040         return realModulo(length, dot + sepSmall) < dot;
2041 
2042     case Qt::DashDotLine: {
2043         double at = realModulo(length, dash + sepMid + dot + sepMid);
2044 
2045         if (at < dash)
2046             return true;
2047         if (at < (dash + sepMid))
2048             return false;
2049         if (at < (dash + sepMid + dot))
2050             return true;
2051         return false;
2052     }
2053 
2054     case Qt::DashDotDotLine: {
2055         double at = realModulo(length, dash + sepMid + dot + sepSmall + dot + sepMid);
2056 
2057         if (at < dash)
2058             return true;
2059         if (at < (dash + sepMid))
2060             return false;
2061         if (at < (dash + sepMid + dot))
2062             return true;
2063         if (at < (dash + sepMid + dot + sepSmall))
2064             return false;
2065         if (at < (dash + sepMid + dot + sepSmall + dot))
2066             return true;
2067         return false;
2068     }
2069 
2070     case Qt::MPenStyle:
2071     case Qt::CustomDashLine: {
2072         assert(!"Do not know how to handle this style!");
2073         return true;
2074     }
2075     }
2076 
2077     assert(!"Unknown pen style!");
2078     return true;
2079 }
2080 
2081 QPen View::penForPlot(const Plot &plot, QPainter *painter) const
2082 {
2083     QPen pen;
2084     if (m_zoomMode == Translating) {
2085         // plot style is always a solid line when translating the view
2086         pen.setCapStyle(Qt::FlatCap);
2087     } else {
2088         pen.setCapStyle(Qt::RoundCap);
2089         // (the style will be set back to FlatCap if the plot style is a solid line)
2090     }
2091 
2092     pen.setColor(plot.color());
2093 
2094     Function *ufkt = plot.function();
2095     PlotAppearance appearance = ufkt->plotAppearance(plot.plotMode);
2096 
2097     double lineWidth_mm = appearance.lineWidth;
2098 
2099     if (appearance.style == Qt::SolidLine)
2100         pen.setCapStyle(Qt::FlatCap);
2101 
2102     double width = millimetersToPixels(lineWidth_mm, painter->device());
2103     pen.setWidthF(width);
2104 
2105     return pen;
2106 }
2107 
2108 double View::millimetersToPixels(double width_mm, QPaintDevice *device) const
2109 {
2110     //  assert( device->logicalDpiX() == device->logicalDpiY() );
2111     return device->logicalDpiX() * (width_mm / 25.4);
2112 }
2113 
2114 double View::pixelsToMillimeters(double width_pixels, QPaintDevice *device) const
2115 {
2116     //  assert( device->logicalDpiX() == device->logicalDpiY() );
2117     return (width_pixels * 25.4) / device->logicalDpiX();
2118 }
2119 
2120 void View::drawHeaderTable(QPainter *painter)
2121 {
2122     painter->setFont(Settings::headerTableFont());
2123 
2124     QString alx = i18nc("%1=minimum value, %2=maximum value", "%1 to %2", Settings::xMin(), Settings::xMax());
2125     QString aly = i18nc("%1=minimum value, %2=maximum value", "%1 to %2", Settings::yMin(), Settings::yMax());
2126 
2127     QString atx = i18nc("'E' is the distance between ticks on the axis", "1E = ") + ticSepX.expression();
2128     QString aty = i18nc("'E' is the distance between ticks on the axis", "1E = ") + ticSepY.expression();
2129 
2130     QString text = "<div style=\"margin: 0 auto;\"><table border=\"1\" cellpadding=\"4\" cellspacing=\"0\">"
2131             "<tr><td><b>" + i18n("Parameters") + "</b></td><td><b>" + i18n("Plotting Range") + "</b></td><td><b>" + i18n("Axes Division") + "</b></td></tr>"
2132             "<tr><td><b>" + i18n("x-Axis:") + "</b></td><td>" + alx + "</td><td>" + atx + "</td></tr>"
2133             "<tr><td><b>" + i18n("y-Axis:") + "</b></td><td>" + aly + "</td><td>" + aty + "</td></tr>"
2134             "</table></div>";
2135 
2136     text += "<br><br><b>" + i18n("Functions:") + "</b><ul>";
2137 
2138     for (Function *function : qAsConst(XParser::self()->m_ufkt))
2139         text += "<li>" + function->name().replace('\n', "<br>") + "</li>";
2140 
2141     text += "</ul>";
2142 
2143     m_textDocument->setHtml(text);
2144     m_textDocument->documentLayout()->draw(painter, QAbstractTextDocumentLayout::PaintContext());
2145 
2146     QRectF br = m_textDocument->documentLayout()->frameBoundingRect(m_textDocument->rootFrame());
2147     painter->translate(0, br.height());
2148 }
2149 
2150 QList<QPointF> View::findStationaryPoints(const Plot &plot)
2151 {
2152     Plot plot2 = plot;
2153     plot2.differentiate();
2154 
2155     const QList<double> roots = findRoots(plot2, getXmin(plot.function()), getXmax(plot.function()), RoughRoot);
2156 
2157     plot.updateFunction();
2158     QList<QPointF> stationaryPoints;
2159     for (double x : roots) {
2160         QPointF real = realValue(plot, x, false);
2161         if (real.y() >= m_ymin && real.y() <= m_ymax)
2162             stationaryPoints << real;
2163     }
2164 
2165     return stationaryPoints;
2166 }
2167 
2168 QList<double> View::findRoots(const Plot &plot, double min, double max, RootAccuracy accuracy)
2169 {
2170     typedef QMap<double, double> DoubleMap;
2171     DoubleMap roots;
2172 
2173     int count = 10; // number of points to (initially) check for roots
2174 
2175     int prevNumRoots = 0;
2176     while (count < 1000) {
2177         // Use this to detect finding the same root.
2178         double prevX = 0.0;
2179 
2180         double dx = (max - min) / double(count);
2181         for (int i = 0; i <= count; ++i) {
2182             double x = min + dx * i;
2183 
2184             bool found = findRoot(&x, plot, accuracy);
2185             if (!found || x < min || x > max)
2186                 continue;
2187 
2188             if (!roots.isEmpty()) {
2189                 // Check if already have a close root
2190                 if (qAbs(x - prevX) <= (dx / 4))
2191                     continue;
2192 
2193                 DoubleMap::iterator nextIt = roots.lowerBound(x);
2194                 if (nextIt == roots.end())
2195                     --nextIt;
2196 
2197                 double lower, upper;
2198                 lower = upper = *nextIt;
2199                 if (nextIt != roots.begin())
2200                     lower = *(--nextIt);
2201 
2202                 if ((qAbs(x - lower) <= (dx / 4)) || (qAbs(x - upper) <= (dx / 4)))
2203                     continue;
2204             }
2205 
2206             roots.insert(x, x);
2207             prevX = x;
2208         }
2209 
2210         int newNumRoots = roots.size();
2211         if (newNumRoots == prevNumRoots)
2212             break;
2213 
2214         prevNumRoots = newNumRoots;
2215         count *= 4;
2216     }
2217 
2218     return roots.keys();
2219 }
2220 
2221 void View::setupFindRoot(const Plot &plot, RootAccuracy accuracy, double *max_k, double *max_f, int *n)
2222 {
2223     plot.updateFunction();
2224 
2225     if (accuracy == PreciseRoot) {
2226         *max_k = 200;
2227         *max_f = 1e-14;
2228     } else {
2229         // Rough root
2230         *max_k = 10;
2231         *max_f = 1e-10;
2232     }
2233 
2234     *n = 1 + plot.derivativeNumber();
2235 }
2236 
2237 bool View::findRoot(double *x, const Plot &plot, RootAccuracy accuracy)
2238 {
2239 #ifdef DEBUG_IMPLICIT
2240     root_find_requests++;
2241 #endif
2242 
2243     double max_k, max_f;
2244     int n;
2245     setupFindRoot(plot, accuracy, &max_k, &max_f, &n);
2246 
2247     Equation *eq = plot.function()->eq[0];
2248     DifferentialState *state = plot.state();
2249 
2250     double h = qMin(m_xmax - m_xmin, m_ymax - m_ymin) * 1e-4;
2251 
2252     double f = value(plot, 0, *x, false);
2253     int k;
2254     for (k = 0; k < max_k; ++k) {
2255         double df = XParser::self()->derivative(n, eq, state, *x, h);
2256         if (qAbs(df) < 1e-20)
2257             df = 1e-20 * ((df < 0) ? -1 : 1);
2258 
2259         double dx = f / df;
2260         *x -= dx;
2261         f = value(plot, 0, *x, false);
2262 
2263         if ((qAbs(f) <= max_f) && (qAbs(dx) <= (h * 1e-5)))
2264             break;
2265     }
2266 
2267 #ifdef DEBUG_IMPLICIT
2268     root_find_iterations += k;
2269 #endif
2270 
2271     // We continue calculating until |f| < max_f; this may result in k reaching
2272     // max_k. However, if |f| is reasonably small (even if reaching max_k),
2273     // we consider it a root.
2274     return (qAbs(f) < 1e-6);
2275 }
2276 
2277 bool View::findRoot(double *x, double *y, const Plot &plot, RootAccuracy accuracy)
2278 {
2279     double max_k, max_f;
2280     int n;
2281     setupFindRoot(plot, accuracy, &max_k, &max_f, &n);
2282 
2283     Function *function = plot.function();
2284     Equation *eq = function->eq[0];
2285     DifferentialState *state = plot.state();
2286 
2287     double hx = (m_xmax - m_xmin) * 1e-5;
2288     double hy = (m_ymax - m_ymin) * 1e-5;
2289 
2290     function->y = *y;
2291     function->m_implicitMode = Function::FixedY;
2292     double f = value(plot, 0, *x, false);
2293 
2294     for (int k = 0; k < max_k; ++k) {
2295         function->x = *x;
2296         function->y = *y;
2297 
2298         function->m_implicitMode = Function::FixedY;
2299         double dfx = XParser::self()->derivative(n, eq, state, *x, hx);
2300 
2301         function->m_implicitMode = Function::FixedX;
2302         double dfy = XParser::self()->derivative(n, eq, state, *y, hy);
2303 
2304         double dff = dfx * dfx + dfy * dfy;
2305         if (dff < 1e-20)
2306             dff = 1e-20;
2307 
2308         double dx = f * dfx / dff;
2309         *x -= dx;
2310         double dy = f * dfy / dff;
2311         *y -= dy;
2312 
2313         function->y = *y;
2314         function->m_implicitMode = Function::FixedY;
2315         f = value(plot, 0, *x, false);
2316 
2317         if ((qAbs(f) <= max_f) && (qAbs(dx) <= (hx * 1e-5)) && (qAbs(dy) <= (hy * 1e-5)))
2318             break;
2319     }
2320 
2321     // We continue calculating until |f| < max_f; this may result in k reaching
2322     // max_k. However, if |f| is reasonably small (even if reaching max_k),
2323     // we consider it a root.
2324     return (qAbs(f) < 1e-6);
2325 }
2326 
2327 void View::paintEvent(QPaintEvent *)
2328 {
2329     // Note: it is important to have this function call before we begin painting
2330     // as updateCrosshairPosition may set the statusbar text
2331     bool inBounds = updateCrosshairPosition();
2332 
2333     QPainter p;
2334     p.begin(this);
2335 
2336     p.drawPixmap(QPoint(0, 0), buffer);
2337 
2338     // the current cursor position in widget coordinates
2339     QPoint mousePos = mapFromGlobal(QCursor::pos());
2340 
2341     if ((m_zoomMode == ZoomInDrawing) || (m_zoomMode == ZoomOutDrawing)) {
2342         QPalette palette;
2343         QColor highlightColor = palette.color(QPalette::Highlight);
2344         QColor backgroundColor = highlightColor;
2345         backgroundColor.setAlpha(63);
2346 
2347         p.setPen(highlightColor);
2348         p.setBrush(backgroundColor);
2349 
2350         p.setBackgroundMode(Qt::OpaqueMode);
2351         p.setBackground(Qt::blue);
2352 
2353         QRect rect(m_zoomRectangleStart, mousePos);
2354         p.drawRect(rect);
2355     } else if (m_zoomMode == AnimatingZoom) {
2356         QPointF tl(toPixel(m_animateZoomRect.topLeft()));
2357         QPointF br(toPixel(m_animateZoomRect.bottomRight()));
2358         p.drawRect(QRectF(tl, QSizeF(br.x() - tl.x(), br.y() - tl.y())));
2359     } else if (shouldShowCrosshairs()) {
2360         Function *function = m_currentPlot.function();
2361 
2362         QPen pen;
2363 
2364         if (function) {
2365             QColor functionColor = m_currentPlot.color();
2366             pen.setColor(functionColor);
2367             p.setPen(pen);
2368             p.setRenderHint(QPainter::Antialiasing, true);
2369 
2370             double x = m_crosshairPosition.x();
2371             double y = m_crosshairPosition.y();
2372 
2373             // BEGIN calculate curvature, normal
2374             double k = 0;
2375             double normalAngle = 0;
2376 
2377             switch (function->type()) {
2378             case Function::Parametric:
2379             case Function::Polar:
2380                 normalAngle = pixelNormal(m_currentPlot, m_trace_x);
2381                 k = pixelCurvature(m_currentPlot, m_trace_x);
2382                 break;
2383 
2384             case Function::Differential:
2385             case Function::Cartesian:
2386             case Function::Implicit:
2387                 normalAngle = pixelNormal(m_currentPlot, x, y);
2388                 k = pixelCurvature(m_currentPlot, x, y);
2389                 break;
2390             }
2391 
2392             if (k < 0) {
2393                 k = -k;
2394                 normalAngle += M_PI;
2395             }
2396             // END calculate curvature, normal
2397 
2398             if (k > 1e-5 && Settings::detailedTracing() && inBounds) {
2399                 p.save();
2400 
2401                 // Transform the painter so that the center of the osculating circle is the origin,
2402                 // with the normal line coming in from the left.
2403                 QPointF center = m_crosshairPixelCoords + (1 / k) * QPointF(cos(normalAngle), sin(normalAngle));
2404                 p.translate(center);
2405                 p.rotate(normalAngle * 180 / M_PI);
2406 
2407                 // draw osculating circle
2408                 pen.setColor(functionColor);
2409                 p.setPen(pen);
2410                 p.drawEllipse(QRectF(-QPointF(1 / k, 1 / k), QSizeF(2 / k, 2 / k)));
2411 
2412                 // draw normal
2413                 pen.setColor(functionColor);
2414                 p.setPen(pen);
2415                 p.setBrush(pen.color());
2416                 p.drawLine(QLineF(-1 / k, 0, 0, 0));
2417 
2418                 // draw normal arrow
2419                 QPolygonF arrowHead(3);
2420                 arrowHead[0] = QPointF(0, 0);
2421                 arrowHead[1] = QPointF(-3, -2);
2422                 arrowHead[2] = QPointF(-3, +2);
2423                 p.drawPolygon(arrowHead);
2424 
2425                 // draw tangent
2426                 double tangent_scale = 1.2; // make the tangent look better
2427                 p.drawLine(QLineF(-1 / k, -qMax(1 / k, qreal(15.)) * tangent_scale, -1 / k, qMax(1 / k, qreal(15.)) * tangent_scale));
2428 
2429                 // draw perpendicular symbol
2430                 QPolygonF perp(3);
2431                 perp[0] = QPointF(-1 / k, 10);
2432                 perp[1] = QPointF(-1 / k + 10, 10);
2433                 perp[2] = QPointF(-1 / k + 10, 0);
2434                 p.drawPolyline(perp);
2435 
2436                 // draw intersection blob
2437                 p.drawRect(QRectF(-1 / k - 1, -1, 2, 2));
2438 
2439                 p.restore();
2440 
2441                 // Already show osculating circle, etc, so don't draw crosshairs quite so prominently
2442                 functionColor.setAlpha(63);
2443                 pen.setColor(functionColor);
2444             }
2445         } else {
2446             // Use an inverted background color for contrast
2447             QColor inverted = QColor(255 - m_backgroundColor.red(), 255 - m_backgroundColor.green(), 255 - m_backgroundColor.blue());
2448             pen.setColor(inverted);
2449         }
2450 
2451         p.setPen(pen);
2452         double x = m_crosshairPixelCoords.x();
2453         double y = m_crosshairPixelCoords.y();
2454         p.drawLine(QPointF(0, y), QPointF(m_clipRect.right(), y));
2455         p.drawLine(QPointF(x, 0), QPointF(x, m_clipRect.height()));
2456     }
2457 
2458     p.end();
2459 }
2460 
2461 double View::pixelNormal(const Plot &plot, double x, double y)
2462 {
2463     Function *f = plot.function();
2464     assert(f);
2465 
2466     plot.updateFunction();
2467 
2468     // For converting from real to pixels
2469     double sx = m_clipRect.width() / (m_xmax - m_xmin);
2470     double sy = m_clipRect.height() / (m_ymax - m_ymin);
2471 
2472     double dx = 0;
2473     double dy = 0;
2474 
2475     double h = this->h(plot);
2476 
2477     int d0 = plot.derivativeNumber();
2478     int d1 = d0 + 1;
2479 
2480     switch (f->type()) {
2481     case Function::Differential:
2482     case Function::Cartesian: {
2483         double df = XParser::self()->derivative(d1, f->eq[0], plot.state(), x, h);
2484         return -atan(df * (sy / sx)) - (M_PI / 2);
2485     }
2486 
2487     case Function::Implicit: {
2488         dx = XParser::self()->partialDerivative(d1, d0, f->eq[0], nullptr, x, y, h, h) / sx;
2489         dy = XParser::self()->partialDerivative(d0, d1, f->eq[0], nullptr, x, y, h, h) / sy;
2490 
2491         double theta = -atan(dy / dx);
2492 
2493         if (dx < 0)
2494             theta += M_PI;
2495 
2496         theta += M_PI;
2497 
2498         return theta;
2499     }
2500 
2501     case Function::Polar: {
2502         double r = XParser::self()->derivative(d0, f->eq[0], nullptr, x, h);
2503         double dr = XParser::self()->derivative(d1, f->eq[0], nullptr, x, h);
2504 
2505         dx = (dr * lcos(x) - r * lsin(x) * XParser::self()->radiansPerAngleUnit()) * sx;
2506         dy = (dr * lsin(x) + r * lcos(x) * XParser::self()->radiansPerAngleUnit()) * sy;
2507         break;
2508     }
2509 
2510     case Function::Parametric: {
2511         dx = XParser::self()->derivative(d1, f->eq[0], nullptr, x, h) * sx;
2512         dy = XParser::self()->derivative(d1, f->eq[1], nullptr, x, h) * sy;
2513         break;
2514     }
2515     }
2516 
2517     double theta = -atan(dy / dx) - (M_PI / 2);
2518 
2519     if (dx < 0)
2520         theta += M_PI;
2521 
2522     return theta;
2523 }
2524 
2525 double View::pixelCurvature(const Plot &plot, double x, double y)
2526 {
2527     Function *f = plot.function();
2528 
2529     // For converting from real to pixels
2530     double sx = m_clipRect.width() / (m_xmax - m_xmin);
2531     double sy = m_clipRect.height() / (m_ymax - m_ymin);
2532 
2533     double fdx = 0;
2534     double fdy = 0;
2535     double fddx = 0;
2536     double fddy = 0;
2537     double fdxy = 0;
2538 
2539     double h = this->h(plot);
2540 
2541     int d0 = plot.derivativeNumber();
2542     int d1 = d0 + 1;
2543     int d2 = d0 + 2;
2544 
2545     switch (f->type()) {
2546     case Function::Differential:
2547     case Function::Cartesian: {
2548         DifferentialState *state = plot.state();
2549 
2550         fdx = sx;
2551         fddx = 0;
2552 
2553         fdy = XParser::self()->derivative(d1, f->eq[0], state, x, h) * sy;
2554         fddy = XParser::self()->derivative(d2, f->eq[0], state, x, h) * sy;
2555 
2556         //          qDebug() << "fdy="<<fdy<<" fddy="<<fddy;
2557 
2558         break;
2559     }
2560 
2561     case Function::Polar: {
2562         double r = XParser::self()->derivative(d0, f->eq[0], nullptr, x, h);
2563         double dr = XParser::self()->derivative(d1, f->eq[0], nullptr, x, h);
2564         double ddr = XParser::self()->derivative(d2, f->eq[0], nullptr, x, h);
2565 
2566         fdx = (dr * lcos(x) - r * lsin(x) * XParser::self()->radiansPerAngleUnit()) * sx;
2567         fdy = (dr * lsin(x) + r * lcos(x) * XParser::self()->radiansPerAngleUnit()) * sy;
2568 
2569         double rpau = XParser::self()->radiansPerAngleUnit();
2570 
2571         fddx = (ddr * lcos(x) - 2 * dr * lsin(x) * rpau - r * lcos(x) * rpau * rpau) * sx;
2572         fddy = (ddr * lsin(x) + 2 * dr * lcos(x) * rpau - r * lsin(x) * rpau * rpau) * sy;
2573 
2574         break;
2575     }
2576 
2577     case Function::Parametric: {
2578         fdx = XParser::self()->derivative(d1, f->eq[0], nullptr, x, h) * sx;
2579         fdy = XParser::self()->derivative(d1, f->eq[1], nullptr, x, h) * sy;
2580 
2581         fddx = XParser::self()->derivative(d2, f->eq[0], nullptr, x, h) * sx;
2582         fddy = XParser::self()->derivative(d2, f->eq[1], nullptr, x, h) * sy;
2583 
2584         break;
2585     }
2586 
2587     case Function::Implicit: {
2588         fdx = XParser::self()->partialDerivative(d1, d0, f->eq[0], nullptr, x, y, h, h) / sx;
2589         fdy = XParser::self()->partialDerivative(d0, d1, f->eq[0], nullptr, x, y, h, h) / sy;
2590 
2591         fddx = XParser::self()->partialDerivative(d2, d0, f->eq[0], nullptr, x, y, h, h) / (sx * sx);
2592         fddy = XParser::self()->partialDerivative(d0, d2, f->eq[0], nullptr, x, y, h, h) / (sy * sy);
2593 
2594         fdxy = XParser::self()->partialDerivative(d1, d1, f->eq[0], nullptr, x, y, h, h) / (sx * sy);
2595 
2596         break;
2597     }
2598     }
2599 
2600     double mod = pow(fdx * fdx + fdy * fdy, 1.5);
2601 
2602     switch (f->type()) {
2603     case Function::Differential:
2604     case Function::Cartesian:
2605     case Function::Parametric:
2606     case Function::Polar:
2607         return (fdx * fddy - fdy * fddx) / mod;
2608 
2609     case Function::Implicit:
2610         return (fdx * fdx * fddy + fdy * fdy * fddx - 2 * fdx * fdy * fdxy) / mod;
2611     }
2612 
2613     qCritical() << "Unknown function type!\n";
2614     return 0;
2615 }
2616 
2617 void View::resizeEvent(QResizeEvent *)
2618 {
2619     if (m_isDrawing) // stop drawing integrals
2620     {
2621         m_stopCalculating = true; // stop drawing
2622         return;
2623     }
2624     qreal dpr = devicePixelRatioF();
2625     buffer = QPixmap(size() * dpr);
2626     buffer.setDevicePixelRatio(dpr);
2627     drawPlot();
2628 }
2629 
2630 void View::drawPlot()
2631 {
2632     if (buffer.width() == 0 || buffer.height() == 0)
2633         return;
2634 
2635     buffer.fill(m_backgroundColor);
2636     draw(&buffer, Screen);
2637     update();
2638 }
2639 
2640 void View::focusOutEvent(QFocusEvent *)
2641 {
2642     // Redraw ourselves to get rid of the crosshair (if we had it)...
2643     QTimer::singleShot(0, this, SLOT(update()));
2644     QTimer::singleShot(0, this, &View::updateCursor);
2645 }
2646 
2647 void View::focusInEvent(QFocusEvent *)
2648 {
2649     // Redraw ourselves to get the crosshair (if we should have it)...
2650     QTimer::singleShot(0, this, SLOT(update()));
2651     QTimer::singleShot(0, this, &View::updateCursor);
2652 }
2653 
2654 bool View::crosshairPositionValid(Function *plot) const
2655 {
2656     if (!plot)
2657         return false;
2658 
2659     // only relevant for cartesian plots - assume true for none
2660     if (plot->type() != Function::Cartesian)
2661         return true;
2662 
2663     bool lowerOk = ((!plot->usecustomxmin) || (plot->usecustomxmin && m_crosshairPosition.x() > plot->dmin.value()));
2664     bool upperOk = ((!plot->usecustomxmax) || (plot->usecustomxmax && m_crosshairPosition.x() < plot->dmax.value()));
2665 
2666     return lowerOk && upperOk;
2667 }
2668 
2669 void View::mousePressEvent(QMouseEvent *e)
2670 {
2671     m_AccumulatedDelta = 0;
2672     m_mousePressTimer->start();
2673 
2674     // In general, we want to update the view
2675     update();
2676 
2677     if (m_popupMenuStatus != NoPopup)
2678         return;
2679 
2680     if (m_isDrawing) {
2681         m_stopCalculating = true; // stop drawing
2682         return;
2683     }
2684 
2685     if (m_zoomMode != Normal) {
2686         // If the user clicked with the right mouse button will zooming in or out, then cancel it
2687         if ((m_zoomMode == ZoomInDrawing) || (m_zoomMode == ZoomOutDrawing)) {
2688             m_zoomMode = Normal;
2689         }
2690         updateCursor();
2691         return;
2692     }
2693 
2694     m_haveRoot = false;
2695 
2696     bool hadFunction = (m_currentPlot.functionID() != -1);
2697 
2698     updateCrosshairPosition();
2699 
2700     if (!m_readonly && e->button() == Qt::RightButton) // clicking with the right mouse button
2701     {
2702         getPlotUnderMouse();
2703         if (m_currentPlot.function()) {
2704             if (hadFunction)
2705                 m_popupMenuStatus = PopupDuringTrace;
2706             else
2707                 m_popupMenuStatus = Popup;
2708 
2709             fillPopupMenu();
2710             m_popupMenu->exec(QCursor::pos());
2711         }
2712         return;
2713     }
2714 
2715     if (e->button() != Qt::LeftButton)
2716         return;
2717 
2718     if (m_currentPlot.functionID() >= 0) // disable trace mode if trace mode is enable
2719     {
2720         m_currentPlot.setFunctionID(-1);
2721         setStatusBar(QString(), RootSection);
2722         setStatusBar(QString(), FunctionSection);
2723         mouseMoveEvent(e);
2724         return;
2725     }
2726 
2727     QPointF closestPoint = getPlotUnderMouse();
2728     Function *function = m_currentPlot.function();
2729     if (function) {
2730         QPointF ptd(toPixel(closestPoint));
2731         QPoint globalPos = mapToGlobal(ptd.toPoint());
2732         QCursor::setPos(globalPos);
2733         setStatusBar(m_currentPlot.name().replace('\n', " ; "), FunctionSection);
2734         return;
2735     }
2736 
2737     // user didn't click on a plot; so we prepare to enter translation mode
2738     m_currentPlot.setFunctionID(-1);
2739     m_zoomMode = AboutToTranslate;
2740     m_prevDragMousePos = e->pos();
2741     updateCursor();
2742 }
2743 
2744 void View::fillPopupMenu()
2745 {
2746     Function *function = m_currentPlot.function();
2747     if (!function)
2748         return;
2749 
2750     m_popupMenuTitle->setText(m_currentPlot.name().replace('\n', "; "));
2751 
2752     QAction *calcArea = MainDlg::self()->actionCollection()->action("grapharea");
2753     QAction *maxValue = MainDlg::self()->actionCollection()->action("maximumvalue");
2754     QAction *minValue = MainDlg::self()->actionCollection()->action("minimumvalue");
2755 
2756     m_popupMenu->removeAction(calcArea);
2757     m_popupMenu->removeAction(maxValue);
2758     m_popupMenu->removeAction(minValue);
2759 
2760     if (function->type() == Function::Cartesian || function->type() == Function::Differential) {
2761         m_popupMenu->addAction(calcArea);
2762         m_popupMenu->addAction(maxValue);
2763         m_popupMenu->addAction(minValue);
2764     }
2765 }
2766 
2767 QPointF View::getPlotUnderMouse()
2768 {
2769     m_currentPlot.setFunctionID(-1);
2770     m_trace_x = 0.0;
2771 
2772     Plot bestPlot;
2773 
2774     double best_distance = 1e30; // a nice large number
2775     QPointF best_cspos;
2776 
2777     for (Function *function : qAsConst(XParser::self()->m_ufkt)) {
2778         const QList<Plot> plots = function->plots();
2779         for (const Plot &plot : plots) {
2780             plot.updateFunction();
2781 
2782             double best_x = 0.0, distance;
2783             QPointF cspos;
2784 
2785             if (function->type() == Function::Implicit) {
2786                 double x = m_crosshairPosition.x();
2787                 double y = m_crosshairPosition.y();
2788                 findRoot(&x, &y, plot, PreciseRoot);
2789 
2790                 QPointF d = toPixel(QPointF(x, y), ClipInfinite) - toPixel(QPointF(m_crosshairPosition.x(), m_crosshairPosition.y()), ClipInfinite);
2791 
2792                 distance = std::sqrt(d.x() * d.x() + d.y() * d.y());
2793                 cspos = QPointF(x, y);
2794             } else {
2795                 best_x = getClosestPoint(m_crosshairPosition, plot);
2796                 distance = pixelDistance(m_crosshairPosition, plot, best_x, false);
2797                 cspos = realValue(plot, best_x, false);
2798             }
2799 
2800             if (distance < best_distance) {
2801                 best_distance = distance;
2802                 bestPlot = plot;
2803                 m_trace_x = best_x;
2804                 best_cspos = cspos;
2805             }
2806         }
2807     }
2808 
2809     if (best_distance < 10.0) {
2810         m_currentPlot = bestPlot;
2811         m_crosshairPosition = best_cspos;
2812         return m_crosshairPosition;
2813     } else
2814         return QPointF();
2815 }
2816 
2817 double View::getClosestPoint(const QPointF &pos, const Plot &plot)
2818 {
2819     plot.updateFunction();
2820 
2821     double best_x = 0.0;
2822 
2823     Function *function = plot.function();
2824     assert(function->type() != Function::Implicit); // should use findRoot (3D version) for this
2825 
2826     switch (function->type()) {
2827     case Function::Implicit:
2828         break;
2829 
2830     case Function::Differential:
2831     case Function::Cartesian: {
2832         double best_pixel_x = m_clipRect.width() / 2;
2833 
2834         QPointF pixelPos = toPixel(pos, ClipInfinite);
2835 
2836         double dmin = getXmin(function);
2837         double dmax = getXmax(function);
2838 
2839         double stepSize = (m_xmax - m_xmin) / m_clipRect.width();
2840 
2841         // Algorithm in use here: Work out the shortest distance between the
2842         // line joining (x0,y0) to (x1,y1) and the given point (real_x,real_y)
2843 
2844         double x = dmin;
2845         double y0 = value(plot, 0, x, false);
2846 
2847         double best_distance = 1e20; // a large distance
2848 
2849         while (x <= dmax && (xToPixel(x) < best_pixel_x + best_distance)) {
2850             x += stepSize;
2851 
2852             double y1 = value(plot, 0, x, false);
2853 
2854             double _x0 = xToPixel(x - stepSize, ClipInfinite);
2855             double _x1 = xToPixel(x, ClipInfinite);
2856 
2857             double _y0 = yToPixel(y0, ClipInfinite);
2858             double _y1 = yToPixel(y1, ClipInfinite);
2859 
2860             double k = (_y1 - _y0) / (_x1 - _x0);
2861 
2862             double closest_x, closest_y;
2863             if (k == 0) {
2864                 closest_x = pixelPos.x();
2865                 closest_y = _y0;
2866             } else {
2867                 closest_x = (pixelPos.y() + pixelPos.x() / k + _x0 * k - _y0) / (k + 1.0 / k);
2868                 closest_y = (pixelPos.x() + pixelPos.y() * k + _y0 / k - _x0) / (k + 1.0 / k);
2869             }
2870 
2871             bool valid = (x - 1.5 * stepSize <= xToReal(closest_x)) && (xToReal(closest_x) <= x + 0.5 * stepSize);
2872 
2873             double dfx = closest_x - pixelPos.x();
2874             double dfy = closest_y - pixelPos.y();
2875 
2876             double distance = sqrt(dfx * dfx + dfy * dfy);
2877             bool insideView = 0 <= closest_y && closest_y <= m_clipRect.height();
2878 
2879             if (distance < best_distance && insideView && valid) {
2880                 best_distance = distance;
2881                 best_pixel_x = closest_x;
2882             }
2883 
2884             y0 = y1;
2885         }
2886 
2887         best_x = xToReal(best_pixel_x);
2888         break;
2889     }
2890 
2891     case Function::Polar:
2892     case Function::Parametric: {
2893         double minX = getXmin(function);
2894         double maxX = getXmax(function);
2895         double stepSize = 0.001;
2896 
2897         while (stepSize > 0.0000009) {
2898             double best_distance = 1e20; // a large distance
2899 
2900             double x = minX;
2901             while (x <= maxX) {
2902                 double distance = pixelDistance(pos, plot, x, false);
2903                 bool insideView = QRectF(m_clipRect).contains(toPixel(realValue(plot, x, false), ClipInfinite));
2904 
2905                 if (distance < best_distance && insideView) {
2906                     best_distance = distance;
2907                     best_x = x;
2908                 }
2909 
2910                 x += stepSize;
2911             }
2912 
2913             minX = best_x - stepSize;
2914             maxX = best_x + stepSize;
2915 
2916             stepSize *= 0.1;
2917         }
2918         break;
2919     }
2920     }
2921 
2922     return best_x;
2923 }
2924 
2925 double View::pixelDistance(const QPointF &pos, const Plot &plot, double x, bool updateFunction)
2926 {
2927     QPointF f = realValue(plot, x, updateFunction);
2928     QPointF df = toPixel(pos, ClipInfinite) - toPixel(f, ClipInfinite);
2929 
2930     return std::sqrt(df.x() * df.x() + df.y() * df.y());
2931 }
2932 
2933 QString View::posToString(double x, double delta, PositionFormatting format, const QColor &color) const
2934 {
2935     delta = qAbs(delta);
2936     if (delta == 0)
2937         delta = 1;
2938 
2939     QString numberText;
2940 
2941     int decimalPlaces = 1 - int(log(delta) / log(10.0));
2942 
2943     // Avoid exponential format for smallish numbers
2944     if (0.01 < qAbs(x) && qAbs(x) < 10000)
2945         format = DecimalFormat;
2946 
2947     switch (format) {
2948     case ScientificFormat: {
2949         int accuracy = 1 + decimalPlaces + int(log(qAbs(x)) / log(10.0));
2950         if (accuracy < 2) {
2951             // Ensure a minimum of two significant digits
2952             accuracy = 2;
2953         }
2954 
2955         QString number = QString::number(x, 'g', accuracy);
2956         if (number.contains('e')) {
2957             number.remove("+0");
2958             number.remove('+');
2959             number.replace("-0", MinusSymbol);
2960 
2961             number.replace('e', QChar(215) + QString("10<sup>"));
2962             number.append("</sup>");
2963         }
2964         if (x > 0.0)
2965             number.prepend('+');
2966 
2967         numberText = QString("<html><body><span style=\"color:%1;\">").arg(color.name()) + number + "</span></body></html>";
2968 
2969         break;
2970     }
2971 
2972     case DecimalFormat: {
2973         if (decimalPlaces >= 0)
2974             numberText = QString::number(x, 'f', decimalPlaces);
2975         else
2976             numberText = QString::number(x * (pow(10.0, decimalPlaces)), 'f', 0) + QString(-decimalPlaces, '0');
2977 
2978         break;
2979     }
2980     }
2981 
2982     numberText.replace('-', MinusSymbol);
2983 
2984     return numberText;
2985 }
2986 
2987 void View::mouseMoveEvent(QMouseEvent *e)
2988 {
2989     if (m_previousMouseMovePos != e->globalPos()) {
2990         m_AccumulatedDelta = 0;
2991     }
2992     m_previousMouseMovePos = e->globalPos();
2993     m_AccumulatedDelta = 0;
2994     if (m_isDrawing || !e)
2995         return;
2996 
2997     bool inBounds = updateCrosshairPosition();
2998     if (!m_haveRoot)
2999         setStatusBar(QString(), RootSection);
3000 
3001     QString sx, sy;
3002 
3003     if (inBounds) {
3004         sx = i18n("x = %1",
3005                   posToString(m_crosshairPosition.x(), (m_xmax - m_xmin) / m_clipRect.width(), View::DecimalFormat).replace('.', QLocale().decimalPoint()));
3006         sy = i18n("y = %1",
3007                   posToString(m_crosshairPosition.y(), (m_ymax - m_ymin) / m_clipRect.width(), View::DecimalFormat).replace('.', QLocale().decimalPoint()));
3008     } else
3009         sx = sy = "";
3010 
3011     setStatusBar(sx, XSection);
3012     setStatusBar(sy, YSection);
3013 
3014     if (e->buttons() & Qt::LeftButton) {
3015         if (m_zoomMode == ZoomIn) {
3016             m_zoomMode = ZoomInDrawing;
3017             m_zoomRectangleStart = e->pos();
3018         } else if (m_zoomMode == ZoomOut) {
3019             m_zoomMode = ZoomOutDrawing;
3020             m_zoomRectangleStart = e->pos();
3021         } else if (((m_zoomMode == AboutToTranslate) || (m_zoomMode == Translating)) && (e->pos() != m_prevDragMousePos)) {
3022             m_zoomMode = Translating;
3023             QPoint d = m_prevDragMousePos - e->pos();
3024             m_prevDragMousePos = e->pos();
3025             translateView(d.x(), d.y());
3026         }
3027     }
3028 
3029     if ((m_zoomMode == Normal) && (m_popupMenuStatus != NoPopup) && !m_popupMenu->isVisible()) {
3030         if (m_popupMenuStatus == Popup)
3031             m_currentPlot.setFunctionID(-1);
3032         m_popupMenuStatus = NoPopup;
3033     }
3034 
3035     update();
3036     updateCursor();
3037 }
3038 
3039 void View::leaveEvent(QEvent *)
3040 {
3041     setStatusBar("", XSection);
3042     setStatusBar("", YSection);
3043 
3044     updateCrosshairPosition();
3045     update();
3046 }
3047 
3048 void View::wheelEvent(QWheelEvent *e)
3049 {
3050     m_AccumulatedDelta += e->angleDelta().y();
3051 
3052     if (e->modifiers() & Qt::ControlModifier) {
3053         if (m_AccumulatedDelta >= QWheelEvent::DefaultDeltasPerStep) {
3054             zoomIn(e->position(), double(Settings::zoomInStep()) / 100.0);
3055             m_AccumulatedDelta = 0;
3056         } else if (m_AccumulatedDelta <= -QWheelEvent::DefaultDeltasPerStep) {
3057             zoomIn(e->position(), (double(Settings::zoomOutStep()) / 100.0) + 1.0);
3058             m_AccumulatedDelta = 0;
3059         }
3060         e->accept();
3061         return;
3062     } else {
3063         m_AccumulatedDelta = 0;
3064     }
3065     QWidget::wheelEvent(e);
3066 }
3067 
3068 bool View::updateCrosshairPosition()
3069 {
3070     QPointF mousePos = mapFromGlobal(QCursor::pos());
3071 
3072     bool out_of_bounds = false; // for the ypos
3073 
3074     m_crosshairPosition = toReal(mousePos);
3075 
3076     m_currentPlot.updateFunction();
3077     Function *it = m_currentPlot.function();
3078 
3079     if (it && crosshairPositionValid(it) && (m_popupMenuStatus != Popup)) {
3080         // The user currently has a plot selected, with the mouse in a valid position
3081 
3082         if ((it->type() == Function::Parametric) || (it->type() == Function::Polar)) {
3083             // Should we increase or decrease t to get closer to the mouse?
3084             double dx[2] = {-0.00001, +0.00001};
3085             double d[] = {0.0, 0.0};
3086             for (int i = 0; i < 2; ++i)
3087                 d[i] = pixelDistance(m_crosshairPosition, m_currentPlot, m_trace_x + dx[i], false);
3088 
3089             double prev_best = pixelDistance(m_crosshairPosition, m_currentPlot, m_trace_x, false);
3090             double current_dx = dx[(d[0] < d[1]) ? 0 : 1] * 1e3;
3091 
3092             while (true) {
3093                 double new_distance = pixelDistance(m_crosshairPosition, m_currentPlot, m_trace_x + current_dx, false);
3094                 if (new_distance < prev_best) {
3095                     prev_best = new_distance;
3096                     m_trace_x += current_dx;
3097                 } else {
3098                     if (qAbs(current_dx) > 9e-10)
3099                         current_dx *= 0.1;
3100                     else
3101                         break;
3102                 }
3103             }
3104 
3105             double min = getXmin(it);
3106             double max = getXmax(it);
3107 
3108             if (m_trace_x > max)
3109                 m_trace_x = max;
3110 
3111             else if (m_trace_x < min)
3112                 m_trace_x = min;
3113 
3114             m_crosshairPosition = realValue(m_currentPlot, m_trace_x, false);
3115         } else if (it->type() == Function::Implicit) {
3116             double x = m_crosshairPosition.x();
3117             double y = m_crosshairPosition.y();
3118             findRoot(&x, &y, m_currentPlot, PreciseRoot);
3119             m_crosshairPosition = QPointF(x, y);
3120         } else {
3121             // cartesian or differential plot
3122 
3123             m_crosshairPosition.setY(value(m_currentPlot, 0, m_crosshairPosition.x(), false));
3124             mousePos.setY(yToPixel(m_crosshairPosition.y()));
3125 
3126             if (m_crosshairPosition.y() < m_ymin || m_crosshairPosition.y() > m_ymax) // the ypoint is not visible
3127             {
3128                 out_of_bounds = true;
3129             } else if ((fabs(yToReal(mousePos.y())) < (m_ymax - m_ymin) / 80) && (it->type() == Function::Cartesian || it->type() == Function::Differential)) {
3130                 double x0 = m_crosshairPosition.x();
3131                 if (!m_haveRoot && findRoot(&x0, m_currentPlot, PreciseRoot)) {
3132                     QString str = "  ";
3133                     str += i18nc("%1 is a subscript zero symbol", "root: x%1 = ", SubscriptZeroSymbol);
3134                     setStatusBar(str + QLocale().toString(x0, 'f', 5), RootSection);
3135                     m_haveRoot = true;
3136                     emit updateRootValue(true, x0);
3137                 }
3138             } else {
3139                 m_haveRoot = false;
3140                 emit updateRootValue(false, 0);
3141             }
3142         }
3143 
3144         // For Cartesian plots, only adjust the cursor position if it is not at the ends of the view
3145         if (((it->type() != Function::Cartesian) && (it->type() != Function::Differential)) || m_clipRect.contains(mousePos.toPoint())) {
3146             mousePos = toPixel(m_crosshairPosition, ClipAll, mousePos);
3147             QPoint globalPos = mapToGlobal(mousePos.toPoint());
3148             QCursor::setPos(globalPos);
3149         }
3150     }
3151 
3152     m_crosshairPixelCoords = mousePos;
3153 
3154     return !out_of_bounds && m_clipRect.contains(mousePos.toPoint());
3155 }
3156 
3157 void View::mouseReleaseEvent(QMouseEvent *e)
3158 {
3159     bool doDrawPlot = false;
3160 
3161     // avoid zooming in if the zoom rectangle is very small and the mouse was
3162     // just pressed, which suggests that the user dragged the mouse accidentally
3163     QRect zoomRect = QRect(m_zoomRectangleStart, e->pos()).normalized();
3164     int area = zoomRect.width() * zoomRect.height();
3165 
3166     if ((area <= 500) && (m_mousePressTimer->elapsed() < QApplication::startDragTime())) {
3167         if (m_zoomMode == ZoomInDrawing)
3168             m_zoomMode = ZoomIn;
3169         else if (m_zoomMode == ZoomOutDrawing)
3170             m_zoomMode = ZoomOut;
3171     }
3172 
3173     switch (m_zoomMode) {
3174     case Normal:
3175     case AnimatingZoom:
3176     case AboutToTranslate:
3177         break;
3178 
3179     case Translating:
3180         doDrawPlot = true;
3181         Settings::self()->save();
3182         MainDlg::self()->requestSaveCurrentState();
3183         break;
3184 
3185     case ZoomIn:
3186         zoomIn(e->pos(), double(Settings::zoomInStep()) / 100.0);
3187         break;
3188 
3189     case ZoomOut:
3190         zoomIn(e->pos(), (double(Settings::zoomOutStep()) / 100.0) + 1.0);
3191         break;
3192 
3193     case ZoomInDrawing:
3194         zoomIn(zoomRect);
3195         break;
3196 
3197     case ZoomOutDrawing:
3198         zoomOut(zoomRect);
3199         break;
3200     }
3201 
3202     m_zoomMode = Normal;
3203 
3204     if (doDrawPlot)
3205         drawPlot();
3206     else
3207         update();
3208 
3209     updateCursor();
3210 }
3211 
3212 void View::zoomIn(const QPointF &mousePos, double zoomFactor)
3213 {
3214     QPointF real = toReal(mousePos);
3215 
3216     double diffx = (m_xmax - m_xmin) * zoomFactor;
3217     double diffy = (m_ymax - m_ymin) * zoomFactor;
3218 
3219     animateZoom(QRectF(real.x() - diffx, real.y() - diffy, 2.0 * diffx, 2.0 * diffy));
3220 }
3221 
3222 void View::zoomIn(const QRectF &zoomRect)
3223 {
3224     QPointF p = zoomRect.topLeft();
3225     double real1x = xToReal(p.x());
3226     double real1y = yToReal(p.y());
3227     p = zoomRect.bottomRight();
3228     double real2x = xToReal(p.x());
3229     double real2y = yToReal(p.y());
3230 
3231     if (real1x > real2x)
3232         qSwap(real1x, real2x);
3233     if (real1y > real2y)
3234         qSwap(real1y, real2y);
3235 
3236     animateZoom(QRectF(QPointF(real1x, real1y), QSizeF(real2x - real1x, real2y - real1y)));
3237 }
3238 
3239 void View::zoomOut(const QRectF &zoomRect)
3240 {
3241     QPointF p = zoomRect.topLeft();
3242     double _real1x = xToReal(p.x());
3243     double _real1y = yToReal(p.y());
3244     p = zoomRect.bottomRight();
3245     double _real2x = xToReal(p.x());
3246     double _real2y = yToReal(p.y());
3247 
3248     double kx = (_real1x - _real2x) / (m_xmin - m_xmax);
3249     double lx = _real1x - (kx * m_xmin);
3250 
3251     double ky = (_real1y - _real2y) / (m_ymax - m_ymin);
3252     double ly = _real1y - (ky * m_ymax);
3253 
3254     double real1x = (m_xmin - lx) / kx;
3255     double real2x = (m_xmax - lx) / kx;
3256 
3257     double real1y = (m_ymax - ly) / ky;
3258     double real2y = (m_ymin - ly) / ky;
3259 
3260     animateZoom(QRectF(QPointF(real1x, real1y), QSizeF(real2x - real1x, real2y - real1y)));
3261 }
3262 
3263 void View::animateZoom(const QRectF &_newCoords)
3264 {
3265     QRectF oldCoords(m_xmin, m_ymin, m_xmax - m_xmin, m_ymax - m_ymin);
3266     QRectF newCoords(_newCoords.normalized());
3267 
3268     if (newCoords.left() == m_xmin && newCoords.right() == m_xmax && newCoords.top() == m_ymin && newCoords.bottom() == m_ymax)
3269         return;
3270 
3271     m_zoomMode = AnimatingZoom;
3272 
3273     if (style()->styleHint(QStyle::SH_Widget_Animate) && m_viewportAnimation->state() == QAbstractAnimation::Stopped) {
3274         m_viewportAnimation->setDuration(150);
3275         m_viewportAnimation->setEasingCurve(QEasingCurve::OutCubic);
3276         m_viewportAnimation->setStartValue(oldCoords);
3277         m_viewportAnimation->setEndValue(newCoords);
3278         m_viewportAnimation->start();
3279         connect(m_viewportAnimation, &QPropertyAnimation::finished, [this, newCoords] {
3280             finishAnimation(newCoords);
3281         });
3282     } else {
3283         finishAnimation(newCoords);
3284     }
3285     Settings::self()->save();
3286 }
3287 
3288 void View::finishAnimation(const QRectF &rect)
3289 {
3290     m_xmin = rect.left();
3291     m_xmax = rect.right();
3292     m_ymin = rect.top();
3293     m_ymax = rect.bottom();
3294 
3295     Settings::setXMin(Parser::number(m_xmin));
3296     Settings::setXMax(Parser::number(m_xmax));
3297     Settings::setYMin(Parser::number(m_ymin));
3298     Settings::setYMax(Parser::number(m_ymax));
3299     MainDlg::self()->coordsDialog()->updateXYRange();
3300     MainDlg::self()->requestSaveCurrentState();
3301 
3302     drawPlot(); // update all graphs
3303 
3304     m_zoomMode = Normal;
3305 }
3306 
3307 const QRectF View::getViewport()
3308 {
3309     return m_animateZoomRect;
3310 }
3311 
3312 void View::setViewport(const QRectF &rect)
3313 {
3314     m_animateZoomRect = rect;
3315     repaint();
3316 }
3317 
3318 void View::translateView(int dx, int dy)
3319 {
3320     double rdx = xToReal(dx) - xToReal(0.0);
3321     double rdy = yToReal(dy) - yToReal(0.0);
3322 
3323     m_xmin += rdx;
3324     m_xmax += rdx;
3325     m_ymin += rdy;
3326     m_ymax += rdy;
3327 
3328     Settings::setXMin(Parser::number(m_xmin));
3329     Settings::setXMax(Parser::number(m_xmax));
3330     Settings::setYMin(Parser::number(m_ymin));
3331     Settings::setYMax(Parser::number(m_ymax));
3332     MainDlg::self()->coordsDialog()->updateXYRange();
3333 
3334     drawPlot(); // update all graphs
3335 }
3336 
3337 void View::stopDrawing()
3338 {
3339     if (m_isDrawing)
3340         m_stopCalculating = true;
3341 }
3342 
3343 QPointF View::findMinMaxValue(const Plot &plot, ExtremaType type, double dmin, double dmax)
3344 {
3345     Function *ufkt = plot.function();
3346     assert((ufkt->type() == Function::Cartesian) || (ufkt->type() == Function::Differential));
3347     Q_UNUSED(ufkt);
3348 
3349     plot.updateFunction();
3350 
3351     Plot differentiated = plot;
3352     differentiated.differentiate();
3353     QList<double> roots = findRoots(differentiated, dmin, dmax, RoughRoot);
3354 
3355     // The minimum / maximum might occur at the end points
3356     roots << dmin << dmax;
3357 
3358     double best = (type == Maximum) ? -HUGE_VAL : +HUGE_VAL;
3359     QPointF bestPoint;
3360 
3361     for (double root : qAsConst(roots)) {
3362         QPointF rv = realValue(plot, root, false);
3363         if ((type == Maximum && rv.y() > best) || (type == Minimum && rv.y() < best)) {
3364             best = rv.y();
3365             bestPoint = QPointF(rv.x(), rv.y());
3366         }
3367     }
3368 
3369     return bestPoint;
3370 }
3371 
3372 void View::keyPressEvent(QKeyEvent *e)
3373 {
3374     // if a zoom operation is in progress, assume that the key press is to cancel it
3375     if (m_zoomMode != Normal) {
3376         m_zoomMode = Normal;
3377         update();
3378         updateCursor();
3379         return;
3380     }
3381 
3382     if (m_isDrawing) {
3383         m_stopCalculating = true;
3384         return;
3385     }
3386 
3387     if (m_currentPlot.functionID() == -1)
3388         return;
3389 
3390     QMouseEvent *event = nullptr;
3391     if (e->key() == Qt::Key_Left)
3392         event = new QMouseEvent(QEvent::MouseMove, m_crosshairPixelCoords.toPoint() - QPoint(1, 1), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
3393     else if (e->key() == Qt::Key_Right)
3394         event = new QMouseEvent(QEvent::MouseMove, m_crosshairPixelCoords.toPoint() + QPoint(1, 1), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
3395     else if (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down) // switch graph in trace mode
3396     {
3397         /// \todo reimplement moving between plots
3398 #if 0
3399         QMap<int, Function*>::iterator it = XParser::self()->m_ufkt.find( m_currentPlot.functionID );
3400         int const ke=(*it)->parameters.count();
3401         if (ke>0)
3402         {
3403             m_currentFunctionParameter++;
3404             if (m_currentFunctionParameter >= ke)
3405                 m_currentFunctionParameter=0;
3406         }
3407         if (m_currentFunctionParameter==0)
3408         {
3409             int const old_m_currentPlot.functionID=m_currentPlot.functionID;
3410             Function::PMode const old_m_currentPlot.plotMode = m_currentPlot.plotMode;
3411             bool start = true;
3412             bool found = false;
3413             while ( 1 )
3414             {
3415                 if ( old_m_currentPlot.functionID==m_currentPlot.functionID && !start)
3416                 {
3417                     m_currentPlot.plotMode=old_m_currentPlot.plotMode;
3418                     break;
3419                 }
3420                 qDebug() << "m_currentPlot.functionID: " << m_currentPlot.functionID;
3421                 switch ( (*it)->type() )
3422                 {
3423                     case Function::Parametric:
3424                 case Function::Polar:
3425                     break;
3426                 default:
3427                 {
3428                     //going through the function, the first and the second derivative
3429                     for ( m_currentPlot.plotMode = (Function::PMode)0; m_currentPlot.plotMode < 3; m_currentPlot.plotMode = (Function::PMode)(m_currentPlot.plotMode+1) )
3430 //                  for (m_currentPlot.plotMode=0;m_currentPlot.plotMode<3;m_currentPlot.plotMode++)
3431                     {
3432                             if (start)
3433                             {
3434                                 if ( m_currentPlot.plotMode==Function::Derivative2)
3435                                     m_currentPlot.plotMode=Function::Derivative0;
3436                                 else
3437                                     m_currentPlot.plotMode = (Function::PMode)(old_m_currentPlot.plotMode+1);
3438                                 start=false;
3439                             }
3440                         qDebug() << "   m_currentPlot.plotMode: " << (int)m_currentPlot.plotMode;
3441 
3442                         if ( (*it)->plotAppearance( m_currentPlot.plotMode ).visible )
3443                             found = true;
3444 
3445                             if (found)
3446                                 break;
3447                         }
3448                         break;
3449                     }
3450                 }
3451                 if (found)
3452                     break;
3453 
3454                 if ( ++it == XParser::self()->m_ufkt.end())
3455                     it = XParser::self()->m_ufkt.begin();
3456                 m_currentPlot.functionID = (*it)->id();
3457             }
3458         }
3459 
3460         qDebug() << "************************";
3461         qDebug() << "m_currentPlot.functionID: " << (int)m_currentPlot.functionID;
3462         qDebug() << "m_currentPlot.plotMode: " << (int)m_currentPlot.plotMode;
3463         qDebug() << "m_currentFunctionParameter: " << m_currentFunctionParameter;
3464 
3465         setStatusBar( (*it)->prettyName( m_currentPlot.plotMode ), FunctionSection );
3466 
3467         event = new QMouseEvent( QEvent::MouseMove, m_crosshairPixelCoords.toPoint(), Qt::LeftButton, Qt::LeftButton, 0 );
3468 #else
3469         return;
3470 #endif
3471     } else if (e->key() == Qt::Key_Space) {
3472         event = new QMouseEvent(QEvent::MouseButtonPress, QCursor::pos(), Qt::RightButton, Qt::RightButton, Qt::NoModifier);
3473         mousePressEvent(event);
3474         delete event;
3475         return;
3476     } else {
3477         event = new QMouseEvent(QEvent::MouseButtonPress, m_crosshairPixelCoords.toPoint(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
3478         mousePressEvent(event);
3479         delete event;
3480         return;
3481     }
3482     mouseMoveEvent(event);
3483     delete event;
3484 }
3485 
3486 double View::areaUnderGraph(IntegralDrawSettings s)
3487 {
3488     int sign = 1;
3489     if (s.dmax < s.dmin) {
3490         qSwap(s.dmin, s.dmax);
3491         sign = -1;
3492     }
3493 
3494     else if (s.dmax == s.dmin)
3495         return 0;
3496 
3497     Function *ufkt = s.plot.function();
3498     assert(ufkt);
3499 
3500     double dx = (s.dmax - s.dmin) / m_clipRect.width();
3501     if (s.plot.plotMode == Function::Integral) {
3502         double max_dx = ufkt->eq[0]->differentialStates.step().value();
3503         if (dx > max_dx)
3504             dx = max_dx;
3505     }
3506 
3507     // Make sure that we calculate the exact area (instead of missing out a
3508     // vertical slither at the end) by making sure dx tiles the x-range
3509     // a whole number of times
3510     int intervals = qRound((s.dmax - s.dmin) / dx);
3511     dx = (s.dmax - s.dmin) / intervals;
3512 
3513     double calculated_area = 0;
3514     double x = s.dmin;
3515 
3516     s.plot.updateFunction();
3517 
3518     for (int i = 0; i <= intervals; ++i) {
3519         double y = value(s.plot, 0, x, false);
3520 
3521         // Trapezoid rule for integrals: only add on half for the first and last value
3522         if ((i == 0) || (i == intervals))
3523             calculated_area += 0.5 * dx * y;
3524         else
3525             calculated_area += dx * y;
3526 
3527         x = x + dx;
3528     }
3529 
3530     m_integralDrawSettings = s;
3531     m_integralDrawSettings.draw = true;
3532     drawPlot();
3533     m_integralDrawSettings.draw = false;
3534     return calculated_area * sign;
3535 }
3536 
3537 bool View::isCalculationStopped()
3538 {
3539     if (m_stopCalculating) {
3540         m_stopCalculating = false;
3541         return true;
3542     } else
3543         return false;
3544 }
3545 
3546 void View::updateSliders()
3547 {
3548     bool needSliderWindow = false;
3549     for (Function *it : qAsConst(XParser::self()->m_ufkt)) {
3550         if (it->m_parameters.useSlider && !it->allPlotsAreHidden()) {
3551             needSliderWindow = true;
3552             break;
3553         }
3554     }
3555 
3556     if (!needSliderWindow) {
3557         if (m_sliderWindow)
3558             m_sliderWindow->hide();
3559         m_menuSliderAction->setChecked(false);
3560         return;
3561     }
3562 
3563     if (!m_sliderWindow) {
3564         m_sliderWindow = new KSliderWindow(this);
3565         connect(m_sliderWindow, &KSliderWindow::valueChanged, this, QOverload<>::of(&View::drawPlot));
3566         connect(m_sliderWindow, &KSliderWindow::windowClosed, this, &View::sliderWindowClosed);
3567         connect(m_sliderWindow, &KSliderWindow::finished, this, &View::sliderWindowClosed);
3568     }
3569     if (m_menuSliderAction->isChecked())
3570         m_sliderWindow->show();
3571 }
3572 
3573 void View::sliderWindowClosed()
3574 {
3575     m_menuSliderAction->setChecked(false); // set the slider-item in the menu
3576 }
3577 
3578 void View::functionRemoved(int id)
3579 {
3580     if (id == m_currentPlot.functionID()) {
3581         m_currentPlot.setFunctionID(-1);
3582         setStatusBar(QString(), RootSection);
3583         setStatusBar(QString(), FunctionSection);
3584     }
3585 }
3586 
3587 void View::hideCurrentFunction()
3588 {
3589     if (m_currentPlot.functionID() == -1)
3590         return;
3591 
3592     Function *ufkt = m_currentPlot.function();
3593     ufkt->plotAppearance(m_currentPlot.plotMode).visible = false;
3594 
3595     MainDlg::self()->functionEditor()->functionsChanged();
3596     drawPlot();
3597     MainDlg::self()->requestSaveCurrentState();
3598     updateSliders();
3599     if (m_currentPlot.functionID() == -1)
3600         return;
3601     if (ufkt->allPlotsAreHidden()) {
3602         m_currentPlot.setFunctionID(-1);
3603         QMouseEvent *event = new QMouseEvent(QMouseEvent::KeyPress, QCursor::pos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
3604         mousePressEvent(event); // leave trace mode
3605         delete event;
3606         return;
3607     } else {
3608         QKeyEvent *event = new QKeyEvent(QKeyEvent::KeyPress, Qt::Key_Up, Qt::NoModifier);
3609         keyPressEvent(event); // change selected graph
3610         delete event;
3611         return;
3612     }
3613 }
3614 void View::removeCurrentPlot()
3615 {
3616     if (m_currentPlot.functionID() == -1)
3617         return;
3618 
3619     Function *ufkt = m_currentPlot.function();
3620     Function::Type function_type = ufkt->type();
3621     if (!XParser::self()->removeFunction(ufkt))
3622         return;
3623 
3624     if (m_currentPlot.functionID() != -1) // if trace mode is enabled
3625     {
3626         m_currentPlot.setFunctionID(-1);
3627         QMouseEvent *event = new QMouseEvent(QMouseEvent::KeyPress, QCursor::pos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
3628         mousePressEvent(event); // leave trace mode
3629         delete event;
3630     }
3631 
3632     drawPlot();
3633     if (function_type == Function::Cartesian)
3634         updateSliders();
3635     MainDlg::self()->requestSaveCurrentState();
3636 }
3637 
3638 void View::animateFunction()
3639 {
3640     Function *f = m_currentPlot.function();
3641     if (!f)
3642         return;
3643 
3644     ParameterAnimator *anim = new ParameterAnimator(this, f);
3645     anim->show();
3646 }
3647 
3648 void View::editCurrentPlot()
3649 {
3650     MainDlg::self()->functionEditor()->setCurrentFunction(m_currentPlot.functionID());
3651 }
3652 
3653 void View::zoomIn()
3654 {
3655     m_zoomMode = ZoomIn;
3656     updateCursor();
3657 }
3658 
3659 void View::zoomOut()
3660 {
3661     m_zoomMode = ZoomOut;
3662     updateCursor();
3663 }
3664 
3665 void View::zoomToTrigonometric()
3666 {
3667     double rpau = XParser::self()->radiansPerAngleUnit();
3668     animateZoom(QRectF(-2 * M_PI / rpau, -4.0, 4 * M_PI / rpau, 8.0));
3669 }
3670 
3671 void View::updateCursor()
3672 {
3673     Cursor newCursor = m_prevCursor;
3674 
3675     if (m_isDrawing && (m_zoomMode != Translating))
3676         newCursor = CursorWait;
3677 
3678     else
3679         switch (m_zoomMode) {
3680         case AnimatingZoom:
3681             newCursor = CursorArrow;
3682             break;
3683 
3684         case Normal:
3685             if (shouldShowCrosshairs()) {
3686                 // Don't show any cursor if we're tracing a function or the crosshairs should be shown
3687                 newCursor = CursorBlank;
3688             } else
3689                 newCursor = CursorArrow;
3690             break;
3691 
3692         case ZoomIn:
3693         case ZoomInDrawing:
3694             newCursor = CursorMagnify;
3695             break;
3696 
3697         case ZoomOut:
3698         case ZoomOutDrawing:
3699             newCursor = CursorLessen;
3700             break;
3701 
3702         case AboutToTranslate:
3703         case Translating:
3704             newCursor = CursorMove;
3705             break;
3706         }
3707 
3708     if (newCursor == m_prevCursor)
3709         return;
3710     m_prevCursor = newCursor;
3711 
3712     switch (newCursor) {
3713     case CursorWait:
3714         setCursor(Qt::WaitCursor);
3715         break;
3716     case CursorBlank:
3717         setCursor(Qt::BlankCursor);
3718         break;
3719     case CursorArrow:
3720         setCursor(Qt::ArrowCursor);
3721         break;
3722     case CursorCross:
3723         setCursor(Qt::CrossCursor);
3724         break;
3725     case CursorMagnify:
3726         setCursor(QCursor(QIcon::fromTheme("zoom-in").pixmap(48), 22, 15));
3727         break;
3728     case CursorLessen:
3729         setCursor(QCursor(QIcon::fromTheme("zoom-out").pixmap(48), 22, 15));
3730         break;
3731     case CursorMove:
3732         setCursor(Qt::SizeAllCursor);
3733     }
3734 }
3735 
3736 bool View::shouldShowCrosshairs() const
3737 {
3738     switch (m_zoomMode) {
3739     case Normal:
3740     case ZoomIn:
3741     case ZoomOut:
3742         break;
3743 
3744     case AnimatingZoom:
3745     case ZoomInDrawing:
3746     case ZoomOutDrawing:
3747     case AboutToTranslate:
3748     case Translating:
3749         return false;
3750     }
3751 
3752     if (m_popupMenuStatus != NoPopup)
3753         return false;
3754 
3755     Function *it = m_currentPlot.function();
3756 
3757     return (underMouse() && (!it || crosshairPositionValid(it)));
3758 }
3759 
3760 bool View::event(QEvent *e)
3761 {
3762     if (e->type() == QEvent::WindowDeactivate && m_isDrawing) {
3763         m_stopCalculating = true;
3764         return true;
3765     }
3766     return QWidget::event(e); // send the information further
3767 }
3768 
3769 void View::setStatusBar(const QString &t, StatusBarSection section)
3770 {
3771     QString text;
3772     if (section == FunctionSection)
3773         text = ' ' + t + ' ';
3774     else
3775         text = t;
3776 
3777     if (m_readonly) // if KmPlot is shown as a KPart with e.g Konqueror, it is only possible to change the status bar in one way: to call setStatusBarText
3778     {
3779         m_statusBarText[section] = text;
3780 
3781         QString text;
3782         for (int i = 0; i < 4; ++i) {
3783             if (m_statusBarText[i].isEmpty())
3784                 continue;
3785 
3786             if (!text.isEmpty())
3787                 text.append("  |  ");
3788 
3789             text.append(m_statusBarText[i]);
3790         }
3791 
3792         emit setStatusBarText(text);
3793     } else {
3794         QDBusReply<void> reply = QDBusInterface(QDBusConnection::sessionBus().baseService(), "/kmplot", "org.kde.kmplot.KmPlot")
3795                                      .call(QDBus::NoBlock, "setStatusBarText", text, (int)section);
3796     }
3797 }
3798 
3799 void View::setPrintHeaderTable(bool status)
3800 {
3801     m_printHeaderTable = status;
3802 }
3803 
3804 void View::setPrintBackground(bool status)
3805 {
3806     m_printBackground = status;
3807 }
3808 
3809 void View::setPrintWidth(double width)
3810 {
3811     m_printWidth = width;
3812 }
3813 
3814 void View::setPrintHeight(double height)
3815 {
3816     m_printHeight = height;
3817 }
3818 
3819 QPointF View::getCrosshairPosition() const
3820 {
3821     return m_crosshairPosition;
3822 }
3823 
3824 // END class View
3825 
3826 // BEGIN class IntegralDrawSettings
3827 IntegralDrawSettings::IntegralDrawSettings()
3828 {
3829     dmin = dmax = 0.0;
3830     draw = false;
3831 }
3832 // END class IntegralDrawSettings
3833 
3834 #include "moc_view.cpp"