1 /*
2     KmPlot - a math. function plotter for the KDE-Desktop
3 
4     SPDX-FileCopyrightText: 1998, 1999, 2000, 2002 Klaus-Dieter Möller <kd.moeller@t-online.de>
5     SPDX-FileCopyrightText: 2006 David Saxton <david@bluehaze.org>
6 
7     This file is part of the KDE Project.
8     KmPlot is part of the KDE-EDU Project.
9 
10     SPDX-License-Identifier: GPL-2.0-or-later
11 
12 */
13 
14 #include "view.h"
15 
16 #include <kmplot/config-kmplot.h>
17 
18 // Qt includes
19 #include <QAbstractTextDocumentLayout>
20 #include <QBitmap>
21 #include <QCursor>
22 #include <QDataStream>
23 #include <QList>
24 #include <QMenu>
25 #include <QPaintEvent>
26 #include <QPainter>
27 #include <QSlider>
28 #include <QTextEdit>
29 #include <QElapsedTimer>
30 #include <QTimer>
31 
32 // KDE includes
33 #include <KActionCollection>
34 #include <KMessageBox>
35 
36 // local includes
37 #include "functioneditor.h"
38 #include "functiontools.h"
39 #include "settings.h"
40 #include "ksliderwindow.h"
41 #include "maindlg.h"
42 #include "parameteranimator.h"
43 #include "viewadaptor.h"
44 #include "xparser.h"
45 
46 
47 // other includes
48 #include <assert.h>
49 #include <cmath>
50 #ifdef HAVE_IEEEFP_H
51 #include <ieeefp.h>
52 #endif
53 
54 #if defined(Q_CC_MINGW)
55 using namespace std;
56 #endif
57 
58 // does for real numbers what "%" does for integers
realModulo(double x,double mod)59 double realModulo( double x, double mod )
60 {
61 	return x - floor(x/mod)*mod;
62 }
63 
64 
65 //BEGIN class View
66 View * View::m_self = 0;
67 
View(bool readOnly,QMenu * functionPopup,QWidget * parent)68 View::View( bool readOnly, QMenu * functionPopup, QWidget* parent )
69 	: QWidget( parent ),
70 	  buffer( width(), height() ),
71 	  m_popupMenu( functionPopup ),
72 	  m_readonly( readOnly ),
73 	  m_AccumulatedDelta(0),
74 	  m_viewportAnimation( new QPropertyAnimation( this, "viewport" ) )
75 {
76 	assert( !m_self ); // this class should only be constructed once
77 	m_self = this;
78     setAttribute( Qt::WA_StaticContents );
79 
80 	m_haveRoot = false;
81 	emit updateRootValue( false, 0 );
82 	m_xmin = m_xmax = m_ymin = m_ymax = 0.0;
83 	m_printHeaderTable = false;
84 	m_printBackground = false;
85 	m_printWidth = 0.0;
86 	m_printHeight = 0.0;
87 	m_stopCalculating = false;
88 	m_isDrawing = false;
89 	m_popupMenuStatus = NoPopup;
90 	m_zoomMode = Normal;
91 	m_prevCursor = CursorArrow;
92 	m_backgroundColor = Settings::backgroundcolor();
93 
94 	m_textEdit = new KTextEdit;
95 	m_textEdit->setWordWrapMode( QTextOption::NoWrap );
96 	m_textEdit->setLineWrapMode( QTextEdit::NoWrap );
97 	m_textDocument = m_textEdit->document();
98 
99     m_mousePressTimer = new QElapsedTimer();
100 
101     new ViewAdaptor(this);
102     QDBusConnection::sessionBus().registerObject("/view", this);
103 
104 	setMouseTracking( true );
105 	m_sliderWindow = 0;
106 
107 	m_popupMenuTitle = m_popupMenu->insertSection( MainDlg::self()->m_firstFunctionAction, "" );
108 	connect(XParser::self(), &XParser::functionRemoved, this, &View::functionRemoved);
109 }
110 
111 
~View()112 View::~View()
113 {
114 	m_textEdit->deleteLater();
115 	delete XParser::self();
116 	delete m_mousePressTimer;
117 }
118 
119 
initDrawLabels()120 void View::initDrawLabels()
121 {
122 	m_labelFont = Settings::labelFont();
123 
124 	for ( int i = 0; i < LabelGridSize; ++i )
125 		for ( int j = 0; j < LabelGridSize; ++j )
126 			m_usedDiagramArea[i][j] = false;
127 
128 	// Add the axis
129 	double x = xToPixel( 0 );
130 	double y = yToPixel( 0 );
131 
132 	double x0 = xToPixel( m_xmin );
133 	double x1 = xToPixel( m_xmax );
134 	double y0 = yToPixel( m_ymin );
135 	double y1 = yToPixel( m_ymax );
136 
137 	// x-axis
138 	markDiagramAreaUsed( QRectF( x-20, y0, 40, y1-y0 ) );
139 
140 	// y-axis
141 	markDiagramAreaUsed( QRectF( x0, y-20, x1-x0, 40 ) );
142 }
143 
144 
niceTicSpacing(double length_mm,double range)145 double View::niceTicSpacing( double length_mm, double range )
146 {
147 	Q_ASSERT_X( range > 0, "View::niceTicSpacing", "Range must be positive" );
148 
149 	if ( length_mm <= 0 )
150 	{
151 		// Don't assert, as we can at least handle this situation - and it can
152 		// happen with extreme zooms
153 
154 		qWarning() << "Non-positive length: length_mm="<<length_mm;
155 		length_mm = 120;
156 	}
157 
158 	// Custom case for trigonometric scaled
159 	if ( qFuzzyCompare( range, 4*M_PI ) )
160 		return M_PI/2;
161 
162 	// Aim to space the tics by around 16 mm
163 	double target = range * 16.0 / length_mm;
164 
165 	// The scaling required to bring target to a number between 1 and 10
166 	double scale = pow( 10, -std::floor(log(target)/log(10.0)) );
167 
168 	// Calculate the first digit of target, e.g. if target is 0.0352, then leading will be set to 3
169 	int leading = int(target * scale);
170 
171 	if ( leading == 1 )
172 		return 1/scale;
173 	else if ( leading >= 2 && leading <= 4 )
174 		return 2/scale;
175 	else
176 		return 5/scale;
177 }
178 
179 
validatedTicSpacing(double spacing,double range,double pixels,double minPixels)180 double View::validatedTicSpacing( double spacing, double range, double pixels, double minPixels )
181 {
182 	Q_ASSERT_X( range > 0, "View::validatedTicSpacing", "Range must be positive" );
183 	Q_ASSERT_X( minPixels > 0, "View::validatedTicSpacing", "MinPixels must be positive" );
184 
185 	spacing = qAbs( spacing );
186 	if ( qFuzzyCompare( spacing, 0 ) )
187 		return 2.0 * range;
188 
189 	double factor;
190 
191 	// Make sure spacing between tics is at least minPixels
192 	pixels /= range / spacing;
193 	factor = pixels / minPixels;
194 	if ( factor < 1.0 ) {
195 		int exponent;
196 		frexp( factor, &exponent );
197 		spacing = ldexp( spacing, -exponent + 1 );
198 	}
199 
200 	// Make sure there are at least two tics
201 	factor = spacing / range;
202 	if ( factor > 0.5 ) {
203 		int exponent;
204 		frexp( factor, &exponent );
205 		spacing = ldexp( spacing, -exponent - 1);
206 	}
207 
208 	return spacing;
209 }
210 
211 
initDrawing(QPaintDevice * device,PlotMedium medium)212 void View::initDrawing( QPaintDevice * device, PlotMedium medium )
213 {
214 	switch ( medium )
215 	{
216 		case SVG:
217 		case Screen:
218 		{
219 			m_clipRect = QRect( 0, 0, width(), height() );
220 			break;
221 		}
222 
223 		case Printer:
224 		{
225 			double inchesPerMeter = 100.0/2.54;
226 
227 			int pixels_x = int(m_printWidth * device->logicalDpiX() * inchesPerMeter);
228 			int pixels_y = int(m_printHeight * device->logicalDpiY() * inchesPerMeter);
229 
230 			m_clipRect = QRect( 0, 0, pixels_x, pixels_y );
231 			break;
232 		}
233 
234 		case Pixmap:
235 		{
236 			QPixmap * pic = static_cast<QPixmap*>(device);
237 			m_clipRect = pic->rect();
238 			break;
239 		}
240 	}
241 
242 
243 	if ( m_clipRect.width() <= 0 || m_clipRect.height() <= 0 )
244 	{
245 		qWarning() << "Invalid clip rect: m_clipRect="<<m_clipRect;
246 		return;
247 	}
248 
249 
250 	//BEGIN get X/Y range
251 	m_xmin = XParser::self()->eval( Settings::xMin() );
252 	m_xmax = XParser::self()->eval( Settings::xMax() );
253 
254 	if ( m_xmax <= m_xmin || !std::isfinite(m_xmin) || !std::isfinite(m_xmax) )
255 	{
256 		m_xmin = -8;
257 		m_xmax = +8;
258 	}
259 
260 	m_ymin = XParser::self()->eval( Settings::yMin() );
261 	m_ymax = XParser::self()->eval( Settings::yMax() );
262 	if ( m_ymax <= m_ymin || !std::isfinite(m_ymin) || !std::isfinite(m_ymax) )
263 	{
264 		m_ymin = -8;
265 		m_ymax = +8;
266 	}
267 	//END get X/Y range
268 
269 
270 	//BEGIN calculate scaling matrices
271 	m_realToPixel.reset();
272 	m_realToPixel.scale( m_clipRect.width()/(m_xmax-m_xmin), m_clipRect.height()/(m_ymin-m_ymax) );
273 	m_realToPixel.translate( -m_xmin, -m_ymax );
274 
275 	m_pixelToReal = m_realToPixel.inverted();
276 	//END calculate scaling matrices
277 
278 
279 
280 	//BEGIN get Tic Separation
281 	QFontMetricsF fm( Settings::axesFont(), device );
282 	if ( Settings::xScalingMode() == 0 )
283 	{
284 		double length = pixelsToMillimeters( xToPixel( m_xmax ), device );
285 		double spacing = niceTicSpacing( length, m_xmax-m_xmin );
286 		ticSepX.updateExpression( spacing );
287 	}
288 	else
289 	{
290 		ticSepX.updateExpression( Settings::xScaling() );
291 		double spacing = ticSepX.value();
292 		spacing = validatedTicSpacing( spacing, m_xmax-m_xmin, xToPixel( m_xmax ), fm.lineSpacing());
293 		ticSepX.updateExpression( spacing );
294 	}
295 
296 	if ( Settings::yScalingMode() == 0 )
297 	{
298 		double length = pixelsToMillimeters( yToPixel( m_ymin ), device );
299 		double spacing = niceTicSpacing( length, m_ymax-m_ymin );
300 		ticSepY.updateExpression( spacing );
301 	}
302 	else
303 	{
304 		ticSepY.updateExpression( Settings::yScaling() );
305 		double spacing = ticSepY.value();
306 		spacing = validatedTicSpacing( spacing, m_ymax-m_ymin, yToPixel( m_ymin ), fm.lineSpacing());
307 		ticSepY.updateExpression( spacing );
308 	}
309 
310 	ticStartX = ceil(m_xmin/ticSepX.value())*ticSepX.value();
311 	ticStartY = ceil(m_ymin/ticSepY.value())*ticSepY.value();
312 	//END get Tic Separation
313 
314 
315 
316 	//BEGIN get colours
317 	m_backgroundColor = Settings::backgroundcolor();
318 	if ( !m_backgroundColor.isValid() )
319 		m_backgroundColor = Qt::white;
320 	//END get colours
321 
322 
323 	XParser::self()->setAngleMode( (Parser::AngleMode)Settings::anglemode() );
324 
325 	initDrawLabels();
326 }
327 
328 
draw(QPaintDevice * dev,PlotMedium medium)329 void View::draw( QPaintDevice * dev, PlotMedium medium )
330 {
331 	if ( m_isDrawing )
332 		return;
333 
334 	m_isDrawing = true;
335 	updateCursor();
336 	initDrawing( dev, medium );
337 
338 	QPainter painter( dev );
339 
340 	switch ( medium )
341 	{
342 		case SVG:
343 		case Screen:
344 			break;
345 
346 		case Printer:
347 		{
348 			if ( m_printHeaderTable )
349 				drawHeaderTable( &painter );
350 
351 			painter.translate( (dev->width() - m_clipRect.width()) / 2, 0 );
352 
353 			if ( m_printBackground )
354 				painter.fillRect( m_clipRect,  m_backgroundColor); //draw a colored background
355 
356 			break;
357 		}
358 
359 		case Pixmap:
360 		{
361 			QPixmap * pic = static_cast<QPixmap*>(dev);
362 			pic->fill(m_backgroundColor);
363 			break;
364 		}
365 	}
366 
367 	painter.setClipRect( m_clipRect );
368 
369 
370 	//BEGIN draw diagram background stuff
371 	painter.setRenderHint( QPainter::Antialiasing, true );
372 
373 	drawGrid( &painter );
374 	if ( Settings::showAxes() )
375 		drawAxes( &painter );
376 	if( Settings::showLabel() )
377 		drawLabels( &painter );
378 	//END draw diagram background stuff
379 
380 
381 	//BEGIN draw the functions
382 	m_stopCalculating = false;
383 
384 	// Antialiasing slows down rendering a lot, so turn it off if we are
385 	// sliding the view about
386 	painter.setRenderHint( QPainter::Antialiasing, m_zoomMode != Translating );
387 
388 	double at = -1;
389 	for ( Function * function : qAsConst(XParser::self()->m_ufkt) )
390 	{
391 		at += 1;
392 
393 		if ( m_stopCalculating )
394 			break;
395 
396 // 		QDBusInterface( QDBus::sessionBus().baseService(), "/kmplot", "org.kde.kmplot.KmPlot" ).call( QDBus::Block, "setDrawProgress", at/numPlots );
397 
398 		if ( function->type() == Function::Implicit )
399 			drawImplicit( function, & painter );
400 		else
401 			drawFunction( function, & painter );
402 	}
403 // 	QDBusInterface( QDBus::sessionBus().baseService(), "/kmplot", "org.kde.kmplot.KmPlot" ).call( QDBus::Block, "setDrawProgress", 1.0 );
404 
405 	drawFunctionInfo( & painter );
406 
407 	m_isDrawing=false;
408 	//END draw the functions
409 
410 	// Reset are stuff back to the screen stuff
411 	initDrawing( & buffer, Screen );
412 
413 	updateCursor();
414 }
415 
416 
417 //BEGIN coordinate mapping functions
toPixel(const QPointF & real,ClipBehaviour clipBehaviour,const QPointF & pixelIfNaN)418 QPointF View::toPixel( const QPointF & real, ClipBehaviour clipBehaviour, const QPointF & pixelIfNaN )
419 {
420 	xclipflg = false;
421 	yclipflg = false;
422 
423 	QPointF pixel = m_realToPixel.map( real );
424 	double x = pixel.x();
425 	double y = pixel.y();
426 
427 	if ( std::isnan(x) )
428 	{
429 		xclipflg = true;
430 		x = pixelIfNaN.x();
431 	}
432 	else if ( clipBehaviour == ClipAll )
433 	{
434 		if ( x<0 )
435 		{
436 			xclipflg = true;
437 			x = 0;
438 		}
439 		else if ( x > m_clipRect.right() )
440 		{
441 			xclipflg = true;
442 			x = m_clipRect.right();
443 		}
444 	}
445 	else
446 	{
447 		if ( std::isinf(x) && x<0 )
448 			x = 0;
449 
450 		else if ( std::isinf(x) && x>0 )
451 			x = m_clipRect.right();
452 	}
453 
454 	if ( std::isnan(y) )
455 	{
456 		yclipflg = true;
457 		y = pixelIfNaN.y();
458 	}
459 	else if ( clipBehaviour == ClipAll )
460 	{
461 		if ( y<0 )
462 		{
463 			yclipflg = true;
464 			y = 0;
465 		}
466 		else if ( y > m_clipRect.bottom() )
467 		{
468 			yclipflg = true;
469 			y = m_clipRect.bottom();
470 		}
471 	}
472 	else
473 	{
474 		if ( std::isinf(y) && y<0 )
475 			y = 0;
476 
477 		else if ( std::isinf(y) && y>0 )
478 			y = m_clipRect.bottom();
479 	}
480 
481 	// Make sure that x and y are *reasonably* bounded at least, even if they're not infinite
482 	double min_x = -1e3 * m_clipRect.width();
483 	double max_x = +1e3 * m_clipRect.width();
484 	double min_y = -1e3 * m_clipRect.height();
485 	double max_y = +1e3 * m_clipRect.height();
486 
487 	if ( x < min_x )
488 		x = min_x;
489 	else if ( x > max_x )
490 		x = max_x;
491 
492 	if ( y < min_y )
493 		y = min_y;
494 	else if ( y > max_y )
495 		y = max_y;
496 
497 	return QPointF( x, y );
498 }
499 
xToPixel(double x,ClipBehaviour clipBehaviour,double xIfNaN)500 double View::xToPixel( double x, ClipBehaviour clipBehaviour, double xIfNaN )
501 {
502 	return toPixel( QPointF( x, 0 ), clipBehaviour, QPointF( xIfNaN, 0 ) ).x();
503 }
504 
yToPixel(double y,ClipBehaviour clipBehaviour,double yIfNaN)505 double View::yToPixel( double y, ClipBehaviour clipBehaviour, double yIfNaN )
506 {
507 	return toPixel( QPointF( 0, y ), clipBehaviour, QPointF( 0, yIfNaN ) ).y();
508 }
509 
510 
toReal(const QPointF & pixel)511 QPointF View::toReal( const QPointF & pixel )
512 {
513 	return m_pixelToReal.map( pixel );
514 }
515 
xToReal(double x)516 double View::xToReal( double x )
517 {
518 	return toReal( QPointF( x, 0 ) ).x();
519 }
520 
yToReal(double y)521 double View::yToReal( double y )
522 {
523 	return toReal( QPointF( 0, y ) ).y();
524 }
525 //END coordinate mapping functions
526 
527 
drawAxes(QPainter * painter)528 void View::drawAxes( QPainter* painter )
529 {
530 	double axesLineWidth = millimetersToPixels( Settings::axesLineWidth(), painter->device() );
531 	double ticWidth = millimetersToPixels( Settings::ticWidth(), painter->device() );
532 	double ticLength = millimetersToPixels( Settings::ticLength(), painter->device() );
533 	QColor axesColor = Settings::axesColor();
534 
535 	painter->save();
536 
537 	double arrowWidth = ticLength*1.4;
538 	double arrowLength = arrowWidth*2.8;
539 
540 	painter->setPen( QPen( axesColor, axesLineWidth ) );
541 	painter->setBrush( axesColor );
542 
543 	//BEGIN draw x axis
544 	double a = m_clipRect.right()-ticLength;
545 	double b = yToPixel(0.);
546 
547 	double b_max = m_clipRect.bottom() - ticLength;
548 	if ( b < ticLength )
549 		b = ticLength;
550 	else if ( b > b_max )
551 		b = b_max;
552 
553 	// horizontal line
554 	painter->Lineh(ticLength, b, a);
555 
556 	// arrow head
557 	if ( Settings::showArrows())
558 	{
559 		a = m_clipRect.right();
560 
561 		QPolygonF p(3);
562 		p[0] = QPointF( a, b );
563 		p[1] = QPointF( a-arrowLength, b+arrowWidth );
564 		p[2] = QPointF( a-arrowLength, b-arrowWidth );
565 		painter->drawPolygon( p );
566 	}
567 	//END draw x axis
568 
569 
570 	//BEGIN draw y axis
571 	a = xToPixel(0.);
572 	b = ticLength;
573 
574 	double a_max = m_clipRect.right() - ticLength;
575 	if ( a < ticLength )
576 		a = ticLength;
577 	else if ( a > a_max )
578 		a = a_max;
579 
580 	// vertical line
581 	painter->Linev(a, m_clipRect.bottom()-ticLength, b);
582 
583 	// arrow head
584 	if ( Settings::showArrows() )
585 	{
586 		b = 0;
587 
588 		QPolygonF p(3);
589 		p[0] = QPointF( a, b );
590 		p[1] = QPointF( a-arrowWidth, b+arrowLength );
591 		p[2] = QPointF( a+arrowWidth, b+arrowLength );
592 		painter->drawPolygon( p );
593 	}
594 	//END draw y axis
595 
596 	painter->restore();
597 
598 	painter->setPen( QPen( axesColor, ticWidth ) );
599 
600 	double da = yToPixel(0)-ticLength;
601 	double db = yToPixel(0)+ticLength;
602 	double d = ticStartX;
603 	if (da<0)
604 	{
605 		a = 0;
606 		b = 2*ticLength;
607 	}
608 	else if(db>(double)m_clipRect.bottom())
609 	{
610 		b = m_clipRect.bottom();
611 		a = m_clipRect.bottom()-2*ticLength;
612 	}
613 	else
614 	{
615 		a = da;
616 		b = db;
617 	}
618 
619 	while(d<m_xmax-ticSepX.value()/2.)
620 	{
621 		double d_pixel = xToPixel(d);
622 		if ( d_pixel > ticLength )
623 			painter->Linev(xToPixel(d), a, b);
624 		d+=ticSepX.value();
625 	}
626 
627 	da = xToPixel(0)-ticLength;
628 	db = xToPixel(0)+ticLength;
629 	d = ticStartY;
630 	if (da<0)
631 	{
632 		a = 0;
633 		b = 2*ticLength;
634 	}
635 	else if(db>(double)m_clipRect.right())
636 	{
637 		b = m_clipRect.right();
638 		a = m_clipRect.right()-2*ticLength;
639 	}
640 	else
641 	{
642 		a = da;
643 		b = db;
644 	}
645 
646 	while(d<m_ymax-ticSepY.value()/2.)
647 	{
648 		double d_pixel = yToPixel(d);
649 		if ( d_pixel < m_clipRect.bottom()-ticLength )
650 			painter->Lineh(a, d_pixel, b);
651 		d+=ticSepY.value();
652 	}
653 }
654 
655 
drawGrid(QPainter * painter)656 void View::drawGrid( QPainter* painter )
657 {
658 	QColor gridColor = Settings::gridColor();
659 
660 	double gridLineWidth = millimetersToPixels( Settings::gridLineWidth(), painter->device() );
661 	QPen pen( gridColor, gridLineWidth );
662 
663 	painter->setPen(pen);
664 
665 	enum GridStyle
666 	{
667 		GridNone,
668 		GridLines,
669 		GridCrosses,
670 		GridPolar
671 	};
672 	GridStyle gridMode = (GridStyle)Settings::gridStyle();
673 
674 	switch ( gridMode )
675 	{
676 		case GridNone:
677 			break;
678 
679 		case GridLines:
680 		{
681 			for ( double d = ticStartX; d <= m_xmax; d += ticSepX.value() )
682 				painter->Linev(xToPixel(d), m_clipRect.bottom(), 0);
683 
684 			for ( double d = ticStartY; d <= m_ymax; d += ticSepY.value() )
685 				painter->Lineh(0, yToPixel(d), m_clipRect.right());
686 
687 			break;
688 		}
689 
690 		case GridCrosses:
691 		{
692 			int const dx = 5;
693 			int const dy = 5;
694 
695 			for( double x = ticStartX; x<m_xmax; x+=ticSepX.value() )
696 			{
697 				double a = xToPixel(x);
698 				for( double y=ticStartY; y<m_ymax; y+=ticSepY.value())
699 				{
700 					double b = yToPixel(y);
701 					painter->Lineh(a-dx, b, a+dx);
702 					painter->Linev(a, b-dy, b+dy);
703 				}
704 			}
705 
706 			break;
707 		}
708 
709 		case GridPolar:
710 		{
711 			// Note: 1.42 \approx sqrt(2)
712 
713 			double xMax = qMax( qAbs(m_xmin), qAbs(m_xmax) ) * 1.42;
714 			double yMax = qMax( qAbs(m_ymin), qAbs(m_ymax) ) * 1.42;
715 			double rMax = qMax( xMax, yMax );
716 
717 			// The furthest pixel away from the origin
718 			double pixelMax = qMax( xMax*m_realToPixel.m11(), yMax*m_realToPixel.m22() );
719 
720 			double ticSep = qMin( ticSepX.value(), ticSepY.value() );
721 			double r = ticSep;
722 
723 			while ( r < rMax )
724 			{
725 				QRectF rect;
726 				rect.setTopLeft(		toPixel( QPointF( -r, r ), ClipInfinite ) );
727 				rect.setBottomRight(	toPixel( QPointF( r, -r ), ClipInfinite ) );
728 				painter->drawEllipse( rect );
729 				r += ticSep;
730 			}
731 
732 			for ( double theta = 0; theta < 2.0*M_PI; theta += M_PI/12.0 )
733 			{
734 				QPointF start = toPixel( QPointF( 0, 0 ), ClipInfinite );
735 				QPointF end = start + QPointF( pixelMax * cos(theta), pixelMax * sin(theta) );
736 
737 				painter->drawLine( start, end );
738 			}
739 
740 			break;
741 		}
742 	}
743 }
744 
745 
drawLabels(QPainter * painter)746 void View::drawLabels( QPainter *painter )
747 {
748 	const QString xLabel = Settings::labelHorizontalAxis();
749 	const QString yLabel = Settings::labelVerticalAxis();
750 
751 	int const dx=10;
752 	int const dy=15;
753 	QFont const font = Settings::axesFont();
754 	painter->setFont(font);
755 	m_textDocument->setDefaultFont( font );
756 
757 	double const x=xToPixel(0.);
758 	double const y=yToPixel(0.);
759 
760 
761 	QRectF drawRect;
762 
763 	// Whether the x-axis is along the top of the view
764 	// and the y-axis is along the right edge of the view
765 	bool axesInTopRight = ( m_ymax<ticSepY.value() && m_xmax<ticSepX.value() );
766 
767 	// for "x" label
768 	double endLabelWidth = 0;
769 
770 	int flags = Qt::AlignVCenter|Qt::TextDontClip|Qt::AlignRight;
771 
772 	// Draw x label
773 	if ( axesInTopRight )
774 		drawRect = QRectF( xToPixel(m_xmax)-(3*dx), y+dy, 0, 0 );
775 	else if (m_ymin>-ticSepY.value())
776 		drawRect = QRectF( xToPixel(m_xmax)-dx, y-dy, 0, 0 );
777 	else
778 		drawRect = QRectF( xToPixel(m_xmax)-dx, y+dy, 0, 0 );
779 	painter->drawText( drawRect, flags, xLabel );
780 	endLabelWidth = m_clipRect.right() - painter->boundingRect( drawRect, flags, xLabel ).right();
781 
782 
783 	// Draw y label
784 	if ( axesInTopRight )
785 		drawRect = QRectF( x-dx, yToPixel(m_ymax)+(2*dy), 0, 0 );
786 	else if (m_xmin>-ticSepX.value())
787 		drawRect = QRectF( x+(2*dx), yToPixel(m_ymax)+dy, 0, 0 );
788 	else
789 		drawRect = QRectF( x-dx, yToPixel(m_ymax)+dy, 0, 0 );
790 	painter->drawText( drawRect, flags, yLabel );
791 
792 
793 
794 	// Draw the numbers on the axes
795 	drawXAxisLabels( painter, pixelsToMillimeters( endLabelWidth, painter->device() ) );
796 	drawYAxisLabels( painter );
797 }
798 
799 
800 /**
801  * If \p d is a rational multiple of pi, then return a string appropriate for
802  * displaying it in fraction form.
803  */
tryPiFraction(double d,double sep)804 QString tryPiFraction( double d, double sep )
805 {
806 	// Avoid strange bug where get pi at large separation
807 	if ( sep > 10 )
808 		return QString();
809 
810 	bool positive = d > 0;
811 
812 	d /= M_PI;
813 	if ( !positive )
814 		d = -d;
815 
816 	if ( d < 1e-2 )
817 		return QString();
818 
819 	// Try denominators from 1 to 6
820 	for ( int denom = 1; denom <= 6; ++denom )
821 	{
822 		if ( realModulo( d * denom, 1 ) > 1e-3*sep )
823 			continue;
824 
825 		int num = qRound( d * denom );
826 
827 		QString s = positive ? "+" : QString(MinusSymbol);
828 		if ( num != 1 )
829 			s += QString::number( num );
830 
831 		s += PiSymbol;
832 
833 		if ( denom != 1 )
834 			s += '/' + QString::number( denom );
835 
836 		return s;
837 	}
838 
839 	return QString();
840 }
841 
842 
drawXAxisLabels(QPainter * painter,double endLabelWidth_mm)843 void View::drawXAxisLabels( QPainter *painter, double endLabelWidth_mm )
844 {
845 	QColor axesColor = Settings::axesColor();
846 	int const dy = 8;
847 
848 	double const y = yToPixel(0.);
849 
850 	// Used to ensure that labels aren't drawn too closely together
851 	// These numbers contain the pixel position of the left and right endpoints of the last label
852 	double last_x_start = -1e3; // (just a large negative number)
853 	double last_x_end = -1e3; // (just a large negative number)
854 
855 
856 	// The strange label drawing order here is so that the labels eitherside
857 	// of zero are always drawn, and then the other labels are drawn if there
858 	// is enough space
859 
860 	bool first = true;
861 	bool forwards = true;
862 	double d = 0;
863 
864 	while ( true )
865 	{
866 		if ( first )
867 		{
868 			// Draw x>0 first
869 			d = qMax( ticSepX.value(), ticStartX );
870 			last_x_end = xToPixel(0);
871 			first = false;
872 		}
873 		else
874 		{
875 			if ( forwards )
876 			{
877 				d += ticSepX.value();
878 				if ( d > m_xmax )
879 				{
880 					// Continue on other side
881 					d = qMin( -ticSepX.value(), ticStartX + floor((m_xmax-m_xmin)/ticSepX.value())*ticSepX.value() );
882 					last_x_start = xToPixel(0);
883 					forwards = false;
884 				}
885 			}
886 			else
887 			{
888 				d -= ticSepX.value();
889 				if ( d < m_xmin )
890 					return;
891 			}
892 		}
893 
894 		// Don't draw too close to the left edge if the y axis is there
895 		if ( m_xmin >= -ticSepX.value() && (d-m_xmin) <= ticSepX.value() )
896 			continue;
897 
898 		QString s = tryPiFraction( d, ticSepX.value() );
899 
900 		if ( s.isEmpty() )
901 			s = posToString( d, ticSepX.value()*5, View::ScientificFormat, axesColor ).replace('.', QLocale().decimalPoint());
902 
903 		m_textDocument->setHtml( s );
904 		double idealWidth = m_textDocument->idealWidth();
905 		double idealHeight = m_textDocument->size().height();
906 
907 		double x_pos = xToPixel(d)-(idealWidth/2)-4;
908 		if ( x_pos < 0 )
909 			continue;
910 
911 		double y_pos = y+dy;
912 		if ( (y_pos+idealHeight) > m_clipRect.bottom() )
913 			y_pos = y-dy-idealHeight;
914 
915 		double x_start = x_pos;
916 		double x_end = x_start + idealWidth;
917 
918 		// Use a minimum spacing between labels
919 		if ( (last_x_start < x_start) && pixelsToMillimeters( x_start - last_x_end, painter->device() ) < 7 )
920 			continue;
921 		if ( (last_x_start > x_start) && pixelsToMillimeters( last_x_start - x_end, painter->device() ) < 7 )
922 			continue;
923 
924 		// Don't draw too close to the right edge (has x axis label)
925 		if ( pixelsToMillimeters( m_clipRect.right()-x_end, painter->device() ) < endLabelWidth_mm+3 )
926 			continue;
927 
928 		last_x_start = x_start;
929 		last_x_end = x_end;
930 
931 		QPointF drawPoint( x_pos, y_pos );
932 		painter->translate( drawPoint );
933 		m_textDocument->documentLayout()->draw( painter, QAbstractTextDocumentLayout::PaintContext() );
934 		painter->translate( -drawPoint );
935 	}
936 }
937 
938 
drawYAxisLabels(QPainter * painter)939 void View::drawYAxisLabels( QPainter *painter )
940 {
941 	QColor axesColor = Settings::axesColor();
942 	int const dx = 12;
943 
944 	double const x=xToPixel(0.);
945 
946 	double d = ticStartY;
947 	long long n = (long long)ceil(m_ymin/ticSepY.value());
948 	for( ; d<m_ymax; d += ticSepY.value(), ++n )
949 	{
950 		// Don't draw zero
951 		if ( n == 0 )
952 			continue;
953 
954 		// Don't draw too close to top
955 		if ( (m_ymax-d) <= ticSepY.value()*0.6 )
956 			continue;
957 
958 		// Don't draw too close to bottom if the x axis is there
959 		if ( m_ymin > -ticSepY.value() && (d-m_ymin) <= ticSepY.value() )
960 			continue;
961 
962 		QString s = tryPiFraction( d, ticSepY.value() );
963 
964 		if ( s.isEmpty() )
965 			s = posToString( d, ticSepY.value()*5, View::ScientificFormat, axesColor ).replace('.', QLocale().decimalPoint());
966 
967 		m_textDocument->setHtml( s );
968 
969 		double idealWidth = m_textDocument->idealWidth();
970 		double idealHeight = m_textDocument->size().height();
971 
972 		QPointF drawPoint( 0, yToPixel(d)-(idealHeight/2) );
973 
974 		if ( m_xmin > -ticSepX.value() )
975 		{
976 			drawPoint.setX( x+dx );
977 		}
978 		else
979 		{
980 			drawPoint.setX( x-dx-idealWidth );
981 
982 			if ( drawPoint.x() < 0 )
983 			{
984 				// Don't draw off the left edge of the screen
985 				drawPoint.setX( 0 );
986 			}
987 		}
988 
989 		// Shouldn't have the label cut off by the bottom of the view
990 		if ( drawPoint.y() + idealHeight > m_clipRect.height() )
991 			continue;
992 
993 		painter->translate( drawPoint );
994 		m_textDocument->documentLayout()->draw( painter, QAbstractTextDocumentLayout::PaintContext() );
995 		painter->translate( -drawPoint );
996 	}
997 }
998 
999 
h(const Plot & plot) const1000 double View::h( const Plot & plot ) const
1001 {
1002 	if ( (plot.plotMode == Function::Integral) || (plot.function()->type() == Function::Differential) )
1003 		return plot.function()->eq[0]->differentialStates.step().value();
1004 
1005 	double dx = (m_xmax-m_xmin)/m_clipRect.width();
1006 	double dy = (m_ymax-m_ymin)/m_clipRect.height();
1007 
1008 	switch ( plot.function()->type() )
1009 	{
1010 		case Function::Cartesian:
1011 		case Function::Differential:
1012 			return dx;
1013 
1014 		case Function::Polar:
1015 		case Function::Parametric:
1016 		case Function::Implicit:
1017 			return qMin( dx, dy );
1018 	}
1019 
1020 	qWarning() << "Unknown coord\n";
1021 	return qMin( dx, dy );
1022 }
1023 
1024 
value(const Plot & plot,int eq,double x,bool updateFunction)1025 double View::value( const Plot & plot, int eq, double x, bool updateFunction )
1026 {
1027 	Function * function = plot.function();
1028 	assert( function );
1029 
1030 	if ( updateFunction )
1031 		plot.updateFunction();
1032 
1033 	Equation * equation = function->eq[eq];
1034 
1035 	double dx = h( plot );
1036 	DifferentialState * state = plot.state();
1037 
1038 	return XParser::self()->derivative( plot.derivativeNumber(), equation, state, x, dx );
1039 }
1040 
1041 
realValue(const Plot & plot,double x,bool updateFunction)1042 QPointF View::realValue( const Plot & plot, double x, bool updateFunction )
1043 {
1044 	Function * function = plot.function();
1045 	assert( function );
1046 
1047 	switch ( function->type() )
1048 	{
1049 		case Function::Differential:
1050 		case Function::Cartesian:
1051 		{
1052 			double y = value( plot, 0, x, updateFunction );
1053 			return QPointF( x, y );
1054 		}
1055 
1056 		case Function::Polar:
1057 		{
1058 			double y = value( plot, 0, x, updateFunction );
1059 			return QPointF( y * lcos(x), y * lsin(x) );
1060 		}
1061 
1062 		case Function::Parametric:
1063 		{
1064 			double X = value( plot, 0, x, updateFunction );
1065 			double Y = value( plot, 1, x, updateFunction );
1066 			return QPointF( X, Y );
1067 		}
1068 
1069 		case Function::Implicit:
1070 		{
1071 			// Can only calculate the value when either x or y is fixed.
1072 			assert( function->m_implicitMode != Function::UnfixedXY );
1073 
1074 			double val = value( plot, 0, x, updateFunction );
1075 
1076 			if ( function->m_implicitMode == Function::FixedX )
1077 				return QPointF( function->x, val );
1078 			else
1079 				return QPointF( val, function->y );
1080 		}
1081 	}
1082 
1083 	qWarning() << "Unknown function type!\n";
1084 	return QPointF();
1085 }
1086 
1087 
getXmin(Function * function,bool overlapEdge)1088 double View::getXmin( Function * function, bool overlapEdge  )
1089 {
1090 	switch ( function->type() )
1091 	{
1092 		case Function::Parametric:
1093 		case Function::Polar:
1094 			return function->dmin.value();
1095 
1096 		case Function::Implicit:
1097 			qWarning() << "You probably don't want to do this!\n";
1098 			// fall through
1099 
1100 		case Function::Differential:
1101 		case Function::Cartesian:
1102 		{
1103 			double min = m_xmin;
1104 			if ( overlapEdge )
1105 				min -= (m_xmax-m_xmin)*0.02;
1106 
1107 			if ( function->usecustomxmin )
1108 				return qMax( min, function->dmin.value() );
1109 			else
1110 				return min;
1111 		}
1112 	}
1113 
1114 	return 0;
1115 }
1116 
1117 
getXmax(Function * function,bool overlapEdge)1118 double View::getXmax( Function * function, bool overlapEdge )
1119 {
1120 	switch ( function->type() )
1121 	{
1122 		case Function::Parametric:
1123 		case Function::Polar:
1124 			return function->dmax.value();
1125 
1126 		case Function::Implicit:
1127 			qWarning() << "You probably don't want to do this!\n";
1128 			// fall through
1129 
1130 		case Function::Differential:
1131 		case Function::Cartesian:
1132 		{
1133 			double max = m_xmax;
1134 			if ( overlapEdge )
1135 				max += (m_xmax-m_xmin)*0.02;
1136 
1137 			if ( function->usecustomxmax )
1138 				return qMin( max, function->dmax.value() );
1139 			else
1140 				return max;
1141 		}
1142 	}
1143 
1144 	return 0;
1145 }
1146 
1147 // #define DEBUG_IMPLICIT
1148 
1149 #ifdef DEBUG_IMPLICIT
1150 // Used in profiling root finding
1151 int root_find_iterations;
1152 int root_find_requests;
1153 #endif
1154 
1155 
1156 /**
1157  * For comparing points where two points close together are considered equal.
1158  */
1159 class FuzzyPoint
1160 {
1161 	public:
FuzzyPoint(const QPointF & point)1162 		FuzzyPoint( const QPointF & point )
1163 		{
1164 			x = point.x();
1165 			y = point.y();
1166 		}
1167 
1168 
FuzzyPoint(double x,double y)1169 		FuzzyPoint( double x, double y )
1170 		{
1171 			FuzzyPoint::x = x;
1172 			FuzzyPoint::y = y;
1173 		}
1174 
1175 
operator <(const FuzzyPoint & other) const1176 		bool operator < ( const FuzzyPoint & other ) const
1177 		{
1178 			double du = qAbs(other.x - x) / dx;
1179 			double dv = qAbs(other.y - y) / dy;
1180 
1181 			bool x_eq = (du < 1); // Whether the x coordinates are considered equal
1182 			bool y_eq = (dv < 1); // Whether the y coordinates are considered equal
1183 
1184 			if ( x_eq && y_eq )
1185 			{
1186 				// Points are close together.
1187 				return false;
1188 			}
1189 
1190 			bool x_lt = !x_eq && (x < other.x);
1191 			bool y_lt = !y_eq && (y < other.y);
1192 
1193 			return ( x_lt || (x_eq && y_lt) );
1194 		}
1195 
1196 		double x;
1197 		double y;
1198 
1199 		static double dx;
1200 		static double dy;
1201 };
1202 typedef QMap< FuzzyPoint, QPointF > FuzzyPointMap;
1203 
1204 double FuzzyPoint::dx = 0;
1205 double FuzzyPoint::dy = 0;
1206 
1207 
1208 double SegmentMin = 0.1;
1209 double SegmentMax = 6.0;
1210 
1211 
1212 // The viewable area is divided up into square*squares squares, and the curve
1213 // is traced around in each square.
1214 // NOTE: it is generally a good idea to make this number prime
1215 int squares = 19;
1216 
drawImplicit(Function * function,QPainter * painter)1217 void View::drawImplicit( Function * function, QPainter * painter )
1218 {
1219 	assert( function->type() == Function::Implicit );
1220 
1221 #ifdef DEBUG_IMPLICIT
1222 	QTime t;
1223 	t.start();
1224 
1225 	painter->setPen( Qt::black );
1226 
1227 	for ( double i = 0; i <= squares; ++i )
1228 	{
1229 		double x = m_xmin + i * (m_xmax-m_xmin)/squares;
1230 		double y = m_ymin + i * (m_ymax-m_ymin)/squares;
1231 
1232 		painter->drawLine( toPixel( QPointF( m_xmin, y ), ClipInfinite ), toPixel( QPointF( m_xmax, y ), ClipInfinite ) );
1233 		painter->drawLine( toPixel( QPointF( x, m_ymin ), ClipInfinite ), toPixel( QPointF( x, m_ymax ), ClipInfinite ) );
1234 	}
1235 
1236 	root_find_iterations = 0;
1237 	root_find_requests = 0;
1238 #endif
1239 
1240 	// Need another function for investigating singular points
1241 	Plot circular;
1242 	QString fname( "f(x)=0" );
1243 	XParser::self()->fixFunctionName( fname, Equation::Cartesian, -1 );
1244 	circular.setFunctionID( XParser::self()->Parser::addFunction( fname, 0, Function::Cartesian ) );
1245 	assert( circular.function() );
1246 
1247 	const QList< Plot > plots = function->plots();
1248 	for ( const Plot &plot : plots )
1249 	{
1250 		bool setAliased = false;
1251 		if ( plot.parameter.type() == Parameter::Animated )
1252 		{
1253 			// Don't use antialiasing, so that rendering is sped up
1254 			if ( painter->renderHints() & QPainter::Antialiasing )
1255 			{
1256 				setAliased = true;
1257 				painter->setRenderHint( QPainter::Antialiasing, false );
1258 			}
1259 		}
1260 
1261 		painter->setPen( penForPlot( plot, painter ) );
1262 
1263 		QList<QPointF> singular;
1264 
1265 		for ( int i = 0; i <= squares; ++i )
1266 		{
1267 			double y = m_ymin + i*(m_ymax-m_ymin)/double(squares);
1268 
1269 			function->y = y;
1270 			function->m_implicitMode = Function::FixedY;
1271 			QList<double> roots = findRoots( plot, m_xmin, m_xmax, RoughRoot );
1272 
1273 			for ( double x : qAsConst(roots) )
1274 			{
1275 #ifdef DEBUG_IMPLICIT
1276 				painter->setPen( QPen( Qt::red, painter->pen().width() ) );
1277 #endif
1278 				drawImplicitInSquare( plot, painter, x, y, Qt::Horizontal, & singular );
1279 			}
1280 
1281 
1282 
1283 			double x = m_xmin + i*(m_xmax-m_xmin)/double(squares);
1284 
1285 			function->x = x;
1286 			function->m_implicitMode = Function::FixedX;
1287 			roots = findRoots( plot, m_ymin, m_ymax, RoughRoot );
1288 
1289 			for ( double y : qAsConst(roots) )
1290 			{
1291 #ifdef DEBUG_IMPLICIT
1292 				painter->setPen( QPen( Qt::blue, painter->pen().width() ) );
1293 #endif
1294 				drawImplicitInSquare( plot, painter, x, y, Qt::Vertical, & singular );
1295 			}
1296 		}
1297 
1298 		// Sort out the implicit points
1299 		FuzzyPointMap singularSorted;
1300 		FuzzyPoint::dx = (m_xmax-m_xmin) * SegmentMin * 0.1 / m_clipRect.width();
1301 		FuzzyPoint::dy = (m_ymax-m_ymin) * SegmentMin * 0.1 / m_clipRect.height();
1302 		for ( const QPointF &point : qAsConst(singular) )
1303 			singularSorted.insert( point, point );
1304 		singular = singularSorted.values();
1305 
1306 		for ( const QPointF &point : qAsConst(singular) )
1307 		{
1308 			// radius of circle around singular point
1309 			double epsilon = qMin( FuzzyPoint::dx, FuzzyPoint::dy );
1310 
1311 			QString fstr;
1312 			fstr = QString("%1(x)=%2(%3+%6*cos(x),%4+%6*sin(x)%5)")
1313 					.arg( circular.function()->eq[0]->name() )
1314 					.arg( function->eq[0]->name() )
1315 					.arg( XParser::self()->number( point.x() ) )
1316 					.arg( XParser::self()->number( point.y() ) )
1317 					.arg( function->eq[0]->usesParameter() ? ',' + XParser::self()->number( function->k ) : QString() )
1318 					.arg( XParser::self()->number( epsilon ) );
1319 
1320 			bool setFstrOk = circular.function()->eq[0]->setFstr( fstr );
1321                         qDebug() << "------------ " << setFstrOk;
1322 			assert( setFstrOk );
1323 
1324 			QList<double> roots = findRoots( circular, 0, 2*M_PI / XParser::self()->radiansPerAngleUnit(), PreciseRoot );
1325 
1326 #ifdef DEBUG_IMPLICIT
1327 			qDebug() << "Singular point at (x,y)=("<<point.x()<<','<<point.y()<<")\n";
1328 			qDebug() << "fstr is    " << fstr;
1329 			qDebug() << "Found " << roots.size() << " roots.\n";
1330 #endif
1331 
1332 			for ( double t : qAsConst(roots) )
1333 			{
1334 #ifdef DEBUG_IMPLICIT
1335 				painter->setPen( QPen( Qt::green, painter->pen().width() ) );
1336 #endif
1337 				double x = point.x() + epsilon * lcos(t);
1338 				double y = point.y() + epsilon * lsin(t);
1339 				drawImplicitInSquare( plot, painter, x, y, {}, & singular );
1340 			}
1341 		}
1342 
1343 
1344 		if ( setAliased )
1345 			painter->setRenderHint( QPainter::Antialiasing, true );
1346 	}
1347 
1348 #ifdef DEBUG_IMPLICIT
1349 	if ( root_find_requests != 0 )
1350 		qDebug() << "Average iterations in root finding was " << root_find_iterations/root_find_requests;
1351 	qDebug() << "Time taken was " << t.elapsed();
1352 #endif
1353 
1354 	XParser::self()->removeFunction( circular.functionID() );
1355 }
1356 
1357 
1358 // static
maxSegmentLength(double curvature)1359 double View::maxSegmentLength( double curvature )
1360 {
1361 	// Use a circle angle of 4 degrees to determine the maximum segment length
1362 	// Also, limit the length to be between 0.1 and 6 pixels.
1363 
1364 	double arc = 4 * (M_PI / 180);
1365 
1366 	if ( curvature < 0 )
1367 		curvature = -curvature;
1368 
1369 	if ( curvature < 1e-20 )
1370 		return SegmentMax; // very large circle
1371 
1372 	double radius = 1.0/curvature;
1373 
1374 	double segment = arc * radius;
1375 	if ( segment < SegmentMin )
1376 		segment = SegmentMin;
1377 	else if ( segment > SegmentMax )
1378 		segment = SegmentMax;
1379 
1380 	return segment;
1381 }
1382 
1383 
drawImplicitInSquare(const Plot & plot,QPainter * painter,double x,double y,Qt::Orientations orientation,QList<QPointF> * singular)1384 void View::drawImplicitInSquare( const Plot & plot, QPainter * painter, double x, double y, Qt::Orientations orientation, QList<QPointF> * singular )
1385 {
1386 	plot.updateFunction();
1387 	Plot diff1 = plot;
1388 	diff1.differentiate();
1389 	Plot diff2 = diff1;
1390 	diff2.differentiate();
1391 
1392 #ifdef DEBUG_IMPLICIT
1393 	painter->save();
1394 	painter->setPen( QPen( Qt::black, painter->pen().width() ) );
1395 	QPointF tl = toPixel( QPointF( x, y ), ClipInfinite ) - QPoint( 2, 2 );
1396 	painter->drawRect( QRectF( tl, QSizeF( 4, 4 ) ) );
1397 	painter->restore();
1398 #endif
1399 
1400 	double x_side = (m_xmax-m_xmin)/squares;
1401 	double y_side = (m_ymax-m_ymin)/squares;
1402 
1403 	// Use a square around the root to bound the tracing
1404 	// To start with, assume that tracing will go up,right. But this
1405 	// might not be so, so the upper/lower boundaries may be adjusted depending
1406 	// on where the tracing ends up
1407 	double x_lower, x_upper, y_lower, y_upper;
1408 	if ( orientation & Qt::Vertical )
1409 	{
1410 		x_lower = x;
1411 		x_upper = x + x_side;
1412 	}
1413 	else
1414 	{
1415 		double x_prop = (x-m_xmin)/(m_xmax-m_xmin);
1416 		x_lower = std::floor( x_prop * squares ) * x_side + m_xmin;
1417 		x_upper = x_lower + x_side;
1418 	}
1419 	if ( orientation & Qt::Horizontal )
1420 	{
1421 		y_lower = y;
1422 		y_upper = y + y_side;
1423 	}
1424 	else
1425 	{
1426 		double y_prop = (y-m_ymin)/(m_ymax-m_ymin);
1427 		y_lower = std::floor( y_prop * squares ) * y_side + m_ymin;
1428 		y_upper = y_lower + y_side;
1429 	}
1430 
1431 	// If during tracing, the root could not be found, then this will be set to true,
1432 	// the route will be retraced using a smaller step size and it will attempt to find
1433 	// a root again. If it fails for a second time, then tracing is finished.
1434 	bool foundRootPreviously = true;
1435 
1436 	// Used for focal points.
1437 	double prevAngle = 0;
1438 	int switchCount = 0;
1439 
1440 	// This is so that the algorithm can "look ahead" to see what is coming up,
1441 	// before drawing or committing itself to anything potentially bad
1442 	QPointF prev2 = toPixel( QPointF( x, y ), ClipInfinite );
1443 	QPointF prev1 = prev2;
1444 
1445 	// Allow us to doubly retrace
1446 	double prev_diff_x = 0;
1447 	double prev_diff_y = 0;
1448 
1449 	for ( int i = 0; i < 500; ++i ) // allow a maximum of 500 traces (to prevent possibly infinite loop)
1450 	{
1451 		if ( i == 500 - 1 )
1452 		{
1453 			qDebug() << "Implicit: got to last iteration!\n";
1454 		}
1455 
1456 		// (dx, dy) is perpendicular to curve
1457 
1458 		plot.function()->x = x;
1459 		plot.function()->y = y;
1460 
1461 		plot.function()->m_implicitMode = Function::FixedY;
1462 		double dx = value( diff1, 0, x, false );
1463 
1464 		plot.function()->m_implicitMode = Function::FixedX;
1465 		double dy = value( diff1, 0, y, false );
1466 
1467 		double k = pixelCurvature( plot, x, y );
1468 		double segment_step = maxSegmentLength( k ) * pow( 0.5, switchCount );
1469 
1470 		// If we couldn't find a root in the previous iteration, it was possibly
1471 		// because we were using too large a step size. So reduce the step size
1472 		// and try again.
1473 		if ( !foundRootPreviously )
1474 			segment_step = qMin( segment_step/4, SegmentMin );
1475 
1476 // 		qDebug() << "k="<<k<<" segment_step="<<segment_step;
1477 
1478 		QPointF p1 = toPixel( QPointF( x, y ),			ClipInfinite ) * painter->transform();
1479 		QPointF p2 = toPixel( QPointF( x+dx, y+dy ),	ClipInfinite ) * painter->transform();
1480 		double l = QLineF( p1, p2 ).length() / segment_step;
1481 
1482 		if ( l == 0 )
1483 		{
1484 			qDebug() << "length is zero!\n";
1485 			break;
1486 		}
1487 
1488 		// (tx, ty) is tangent to the curve in the direction that we are tracing
1489 		double tx = -dy/l;
1490 		double ty = dx/l;
1491 
1492 		double angle = atan(ty/tx) + ((tx<0) ? M_PI : 0);
1493 		double diff = realModulo( angle-prevAngle, 2*M_PI );
1494 
1495 		bool switchedDirection = (i > 0) && (diff > (3./4.)*M_PI) && (diff < (5./4.)*M_PI);
1496 		if ( switchedDirection )
1497 		{
1498 			// Why do I care about suddenly changing the direction?
1499 			// Because the chances are, a attracting or repelling point has been reached.
1500 			// Even if not, it suggests that a smaller step size is needed. If we have
1501 			// switched direction and are already at the smallest step size, then note
1502 			// the dodgy point for further investigation and give up for now
1503 
1504 // 			qDebug() << "Switched direction: x="<<x<<" switchCount="<<switchCount<<" segment_step="<<segment_step<<" i="<<i;
1505 
1506 			// Use a step size much smaller than segment min to obtain good accuracy,
1507 			// needed for investigating the point further
1508 			if ( segment_step <= SegmentMin * 0.01 )
1509 			{
1510 				// Give up. Tell our parent function to investigate the point further
1511 				*singular << QPointF( x, y );
1512 				break;
1513 			}
1514 
1515 			// Rewind the last tangent addition as well
1516 			x -= prev_diff_x;
1517 			y -= prev_diff_y;
1518 
1519 			prev_diff_x = 0;
1520 			prev_diff_y = 0;
1521 
1522 			switchCount += 2;
1523 			continue;
1524 		}
1525 		else
1526 		{
1527 			// Reset the stepping adjustment
1528 			switchCount = qMax( 0, switchCount-1 );
1529 			prevAngle = angle;
1530 // 			qDebug() << "Didn't switch - x="<<x<<" segment_step="<<segment_step;
1531 		}
1532 
1533 		if ( i == 0 )
1534 		{
1535 			// First trace; does the bounding square need adjusting?
1536 
1537 			if ( (tx < 0) && (orientation & Qt::Vertical) )
1538 			{
1539 				x_lower -= x_side;
1540 				x_upper -= x_side;
1541 			}
1542 
1543 			if ( (ty < 0) && (orientation & Qt::Horizontal) )
1544 			{
1545 				y_lower -= y_side;
1546 				y_upper -= y_side;
1547 			}
1548 		}
1549 
1550 
1551 		// The maximum tangent length before we end up outside our bounding square
1552 		double max_tx, max_ty;
1553 		if ( tx > 0 )
1554 			max_tx = x_upper - x;
1555 		else
1556 			max_tx = x - x_lower;
1557 		if ( ty > 0 )
1558 			max_ty = y_upper - y;
1559 		else
1560 			max_ty = y - y_lower;
1561 
1562 
1563 		// Does (tx,ty) need to be scaled to make sure the tangent stays inside the square?
1564 		double scale = qMax( (tx==0) ? 0 : qAbs(tx)/max_tx, (ty==0) ? 0 : qAbs(ty)/max_ty );
1565 		bool outOfBounds = scale > 1;
1566 		if ( outOfBounds )
1567 		{
1568 			tx /= scale;
1569 			ty /= scale;
1570 		}
1571 
1572 		double x0 = x;
1573 		double y0 = y;
1574 
1575 		x += tx;
1576 		y += ty;
1577 
1578 		plot.function()->x = x;
1579 		plot.function()->y = y;
1580 
1581 		double * coord = 0;
1582 		if ( qAbs(tx) > qAbs(ty) )
1583 		{
1584 			plot.function()->m_implicitMode = Function::FixedX;
1585 			coord = & y;
1586 		}
1587 		else
1588 		{
1589 			plot.function()->m_implicitMode = Function::FixedY;
1590 			coord = & x;
1591 		}
1592 
1593 		bool found = findRoot( coord, plot, RoughRoot );
1594 		if ( !found )
1595 		{
1596 			if ( foundRootPreviously )
1597 			{
1598 #ifdef DEBUG_IMPLICIT
1599 				qDebug() << "Could not find root!\n";
1600 #endif
1601 
1602 				// Retrace our steps
1603 				x = x0;
1604 				y = y0;
1605 				prev_diff_x = 0;
1606 				prev_diff_y = 0;
1607 				foundRootPreviously = false;
1608 				continue;
1609 			}
1610 			else
1611 			{
1612 				qDebug() << "Couldn't find root - giving up.\n";
1613 				break;
1614 			}
1615 		}
1616 		else
1617 			foundRootPreviously = true;
1618 
1619 		prev_diff_x = x - x0;
1620 		prev_diff_y = y - y0;
1621 
1622 		painter->drawLine( prev2, prev1 );
1623 		prev2 = prev1;
1624 		prev1 = toPixel( QPointF( x, y ), ClipInfinite );
1625 		markDiagramPointUsed( prev1 );
1626 
1627 		if ( outOfBounds )
1628 			break;
1629 	}
1630 
1631 	// and the final line
1632 	painter->drawLine( prev2, prev1 );
1633 }
1634 
1635 
drawFunction(Function * function,QPainter * painter)1636 void View::drawFunction( Function * function, QPainter * painter )
1637 {
1638 	if ( (function->type() == Function::Differential) &&
1639 			 (function->eq[0]->order() == 1) &&
1640 			function->plotAppearance( Function::Derivative0 ).showTangentField )
1641 	{
1642 		const QList<Plot> plots = function->plots( Function::PlotCombinations(Function::AllCombinations) & ~Function::DifferentInitialStates );
1643 		for ( const Plot &plot : plots )
1644 			drawTangentField( plot, painter );
1645 	}
1646 
1647 	const QList<Plot> plots = function->plots();
1648 	for ( const Plot &plot : plots )
1649 		drawPlot( plot, painter );
1650 
1651 }
1652 
1653 
drawTangentField(const Plot & plot,QPainter * painter)1654 void View::drawTangentField( const Plot & plot, QPainter * painter )
1655 {
1656 	plot.updateFunction();
1657 	Function * function = plot.function();
1658 
1659 	assert( function->type() == Function::Differential );
1660 	// Can only draw tangent fields for first order differential equations
1661 	assert( function->eq[0]->order() == 1 );
1662 
1663 	painter->setPen( penForPlot( plot, painter ) );
1664 
1665 	bool useParameter = function->eq[0]->usesParameter();
1666 	Vector v( useParameter ? 3 : 2 );
1667 
1668 	if ( useParameter )
1669 		v[1] = function->k;
1670 
1671 	// For converting from real to pixels
1672 	double sx = m_clipRect.width() / (m_xmax - m_xmin);
1673 	double sy = m_clipRect.height() / (m_ymax - m_ymin);
1674 
1675 	for ( double x = ticStartX; x <= m_xmax; x += ticSepX.value() )
1676 	{
1677 		v[0] = x;
1678 		for ( double y = ticStartY; y <= m_ymax; y += ticSepY.value() )
1679 		{
1680 			v[ useParameter ? 2 : 1 ] = y;
1681 
1682 			double df = XParser::self()->fkt( function->eq[0], v ) * (sy / sx);
1683 			double theta = std::atan( df );
1684 			double dx = std::cos( theta ) * (ticSepX.value() / 8.0);
1685 			double dy = std::sin( theta ) * (ticSepY.value() / 8.0);
1686 
1687 			QPointF mid( x, y );
1688 			QPointF diff( dx, dy );
1689 
1690 			painter->drawLine( toPixel( mid-diff ), toPixel( mid+diff ) );
1691 		}
1692 	}
1693 }
1694 
1695 
1696 /**
1697  * Convenience function for drawing lines. Unfortunately, QPainter::drawPolyline
1698  * takes a long time to draw the line joins, which is only necessary when we are
1699  * using a fat pen. Therefore, draw each line individually if we are using a
1700  * thin pen to save time.
1701  */
drawPolyline(QPainter * painter,const QPolygonF & points)1702 void drawPolyline( QPainter * painter, const QPolygonF & points )
1703 {
1704 	if ( painter->pen().width() > 5 )
1705 		painter->drawPolyline( points );
1706 	else if ( points.size() >= 2 )
1707 	{
1708 		QPointF prev = points.first();
1709 		for ( int i = 1; i < points.size(); ++i )
1710 		{
1711 // 			QPen pen( painter->pen() );
1712 // 			pen.setColor( (i%2==0) ? Qt::red : Qt::blue );
1713 // 			painter->setPen( pen );
1714 
1715 			QPointF next = points[i];
1716 			painter->drawLine( prev, next );
1717 			prev = next;
1718 		}
1719 	}
1720 }
1721 
1722 /**
1723  * Speed up drawing by only drawing one line between each straightish section of the curve
1724  * These variable are used to determine when the curve can no longer be approximate by a
1725  * straight line as the new angle has changed too much
1726  */
1727 class CurveApproximator
1728 {
1729 	public:
CurveApproximator(const QPolygonF & points)1730 		CurveApproximator( const QPolygonF & points )
1731 		{
1732 			assert( points.size() >= 2 );
1733 			reset();
1734 
1735 			QPointF diff = points[ points.size() - 2 ] - points.last();
1736 			currentAngle = atan2( diff.y(), diff.x() );
1737 			approximatingCurve = true;
1738 		}
1739 
CurveApproximator()1740 		CurveApproximator() { reset(); }
1741 
reset()1742 		void reset()
1743 		{
1744 			currentAngle = 0;
1745 			maxClockwise = 0;
1746 			maxAnticlockwise = 0;
1747 			maxDistance = 0;
1748 			approximatingCurve = false;
1749 		}
1750 
1751 
shouldDraw() const1752 		bool shouldDraw() const
1753 		{
1754 			return ((maxAnticlockwise + maxClockwise) * maxDistance) >= 0.5;
1755 		}
1756 
1757 
update(const QPolygonF & points)1758 		void update( const QPolygonF & points )
1759 		{
1760 			// Should have at least two points in the list
1761 			assert( points.size() >= 2 );
1762 
1763 			QPointF p1 = points[ points.size() - 2 ];
1764 			QPointF p2 = points.last();
1765 
1766 			QPointF diff = p1 - p2;
1767 			double angle = atan2( diff.y(), diff.x() );
1768 
1769 			double lineLength = QLineF( p1, p2 ).length();
1770 			if ( lineLength > maxDistance )
1771 				maxDistance = lineLength;
1772 
1773 			double clockwise =		realModulo( currentAngle-angle, 2*M_PI );
1774 			double anticlockwise =	realModulo( angle-currentAngle, 2*M_PI );
1775 
1776 			bool goingClockwise = (clockwise < anticlockwise);
1777 
1778 			if ( goingClockwise )
1779 			{
1780 				// anti-clockwise
1781 				if ( clockwise > maxClockwise )
1782 					maxClockwise = clockwise;
1783 			}
1784 			else
1785 			{
1786 				// clockwise
1787 				if ( anticlockwise > maxAnticlockwise )
1788 					maxAnticlockwise = anticlockwise;
1789 			}
1790 		}
1791 
1792 		double currentAngle;
1793 		double maxClockwise;
1794 		double maxAnticlockwise;
1795 		double maxDistance;
1796 		bool approximatingCurve;
1797 };
1798 
1799 
drawPlot(const Plot & plot,QPainter * painter)1800 void View::drawPlot( const Plot & plot, QPainter *painter )
1801 {
1802 	plot.updateFunction();
1803 	Function * function = plot.function();
1804 
1805 	// should use drawImplicit for implicit functions
1806 	assert( function->type() != Function::Implicit );
1807 
1808 	double dmin = getXmin( function, true );
1809 	double dmax = getXmax( function, true );
1810 
1811 	if ( dmin >= dmax )
1812 		return;
1813 
1814 	painter->save();
1815 
1816 	// Bug in Qt 4.2 TP - QPainter::drawPolyline draws the background as well while printing
1817 	// So for testing printing, use a brush where one can see the function being drawn
1818 	painter->setBrush( Qt::white );
1819 
1820 	if ( (plot.parameter.type() == Parameter::Animated) && (painter->renderHints() & QPainter::Antialiasing) )
1821 	{
1822 		// Don't use antialiasing, so that rendering is speeded up
1823 		painter->setRenderHint( QPainter::Antialiasing, false );
1824 	}
1825 
1826 	painter->setPen( penForPlot( plot, painter ) );
1827 
1828 	// the 'middle' dx, which may be increased or decreased
1829 	double max_dx = (dmax-dmin)/m_clipRect.width();
1830 	if ( (function->type() == Function::Parametric) || (function->type() == Function::Polar) )
1831 		max_dx *= 0.01;
1832 
1833 	// Increase speed while translating the view
1834 	bool quickDraw = ( m_zoomMode == Translating );
1835 	if ( quickDraw )
1836 		max_dx *= 4.0;
1837 
1838 	double dx = max_dx;
1839 
1840 	double maxLength = quickDraw ? 8.0 : (function->plotAppearance( plot.plotMode ).style == Qt::SolidLine) ? 4.0 : 1.5;
1841 	double minLength = maxLength * 0.5;
1842 
1843 	bool drawIntegral = m_integralDrawSettings.draw && (m_integralDrawSettings.plot == plot);
1844 	double totalLength = 0.0; // total pixel length; used for drawing dotted lines
1845 
1846 	bool p1Set = false;
1847 	QPointF p1, p2;
1848 
1849 	CurveApproximator approximator;
1850 	QPolygonF drawPoints;
1851 
1852 	double x = dmin;
1853 	double prevX = x; // the value of x before last adding dx to it
1854 	do
1855 	{
1856 		QPointF rv = realValue( plot, x, false );
1857 
1858 		// If we are currently plotting a differential equation, and it became infinite,
1859 		// then skip x forward to a point where it is finite
1860 		if ( function->type() == Function::Differential && !XParser::self()->differentialFinite )
1861 		{
1862 			double new_x = XParser::self()->differentialDiverge;
1863 			if ( new_x > x )
1864 			{
1865 				x = new_x;
1866 				prevX = x;
1867 
1868 				continue;
1869 			}
1870 		}
1871 
1872 		p2 = toPixel( rv, ClipInfinite );
1873 
1874 		if ( xclipflg || yclipflg )
1875 		{
1876 			prevX = x;
1877 			x += dx;
1878 
1879 			p1Set = false; // p1 wouldn't be finite (if we had set it)
1880 			continue;
1881 		}
1882 
1883 		if ( !p1Set )
1884 		{
1885 			prevX = x;
1886 			x += dx;
1887 
1888 			p1 = p2;
1889 			p1Set = true;
1890 			continue;
1891 		}
1892 
1893 
1894 		//BEGIN adjust dx
1895 		QRectF bound = QRectF( p1, QSizeF( (p2-p1).x(), (p2-p1).y() ) ).normalized();
1896 		double length = QLineF( p1, p2 ).length();
1897 		totalLength += length;
1898 
1899 		double min_mod = (function->type() == Function::Cartesian || function->type() == Function::Differential) ? 1e-2 : 5e-4;
1900 		bool dxAtMinimum = (dx <= max_dx*min_mod);
1901 		bool dxAtMaximum = (dx >= max_dx);
1902 		bool dxTooBig = false;
1903 		bool dxTooSmall = false;
1904 
1905 		if ( QRectF(m_clipRect).intersects( bound ) )
1906 		{
1907 			dxTooBig = !dxAtMinimum && (length > maxLength);
1908 			dxTooSmall = !dxAtMaximum && (length < minLength);
1909 		}
1910 		else
1911 			dxTooSmall = !dxAtMaximum;
1912 
1913 		if ( dxTooBig )
1914 		{
1915 			dx *= 0.5;
1916 			x = prevX + dx;
1917 			totalLength -= length;
1918 			continue;
1919 		}
1920 
1921 		if ( dxTooSmall )
1922 			dx *= 2.0;
1923 		//END adjust dx
1924 
1925 
1926 		if ( drawIntegral && (x >= m_integralDrawSettings.dmin) && (x <= m_integralDrawSettings.dmax) )
1927 		{
1928 			double y0 = yToPixel( 0 );
1929 
1930 			/// \todo should draw the shape in one go
1931 
1932 			QPointF points[4];
1933 			points[0] = QPointF( p1.x(), y0 );
1934 			points[1] = QPointF( p2.x(), y0 );
1935 			points[2] = QPointF( p2.x(), p2.y() );
1936 			points[3] = QPointF( p1.x(), p1.y() );
1937 
1938 			painter->drawPolygon( points, 4 );
1939 		}
1940 		else if ( penShouldDraw( totalLength, plot ) )
1941 		{
1942 			if ( drawPoints.isEmpty() )
1943 			{
1944 				drawPoints << p1;
1945 			}
1946 			else if ( drawPoints.last() != p1 )
1947 			{
1948 				drawPolyline( painter, drawPoints );
1949 				drawPoints.clear();
1950 				drawPoints << p1;
1951 				approximator.reset();
1952 			}
1953 
1954 			// The above code should guarantee that drawPoints isn't empty
1955 			// But check it now in case I do something stupid
1956 			assert( !drawPoints.isEmpty() );
1957 
1958 			if ( !approximator.approximatingCurve )
1959 			{
1960 				// Cool, about to add another point. This defines the working angle of the line
1961 				// approximation
1962 				drawPoints << p2;
1963 				approximator = CurveApproximator( drawPoints );
1964 			}
1965 			else
1966 			{
1967 				QPointF prev = drawPoints.last();
1968 				drawPoints.last() = p2;
1969 				approximator.update( drawPoints );
1970 
1971 				// Allow a maximum deviation (in pixels)
1972 				if ( approximator.shouldDraw() )
1973 				{
1974 					// The approximation is too bad; will have to start again now
1975 					drawPoints.last() = prev;
1976 					drawPoints << p2;
1977 					approximator = CurveApproximator( drawPoints );
1978 				}
1979 			}
1980 		}
1981 
1982 		markDiagramPointUsed( p2 );
1983 
1984 		p1 = p2;
1985 
1986 		Q_ASSERT( dx > 0 );
1987 		prevX = x;
1988 		x += dx;
1989 	}
1990 	while ( x <= dmax );
1991 
1992 // 	qDebug() << "drawPoints.size()="<<drawPoints.size();
1993 	drawPolyline( painter, drawPoints );
1994 
1995 	painter->restore();
1996 }
1997 
1998 
drawFunctionInfo(QPainter * painter)1999 void View::drawFunctionInfo( QPainter * painter )
2000 {
2001 	// Don't draw info if translating the view
2002 	if ( m_zoomMode == Translating )
2003 		return;
2004 
2005 	// The names of the plots are drawn around the edge of the view, in a clockwise
2006 	// direction, starting from the top-right. Picture the positions like this:
2007 	//
2008 	//   7  8  9  0
2009 	//   6        1
2010 	//   5  4  3  2
2011 
2012 	// Used for determining where to draw the next label indicating the plot name
2013 	int plotNameAt = 0;
2014 
2015 	for ( Function * function : qAsConst(XParser::self()->m_ufkt) )
2016 	{
2017 		if ( m_stopCalculating )
2018 			break;
2019 
2020 		for ( const Plot &plot : function->plots() )
2021 		{
2022 			plot.updateFunction();
2023 
2024 			// Draw extrema points?
2025 			if ( (function->type() == Function::Cartesian) && function->plotAppearance( plot.plotMode ).showExtrema )
2026 			{
2027 				const QList<QPointF> stationaryPoints = findStationaryPoints( plot );
2028 				for ( const QPointF &realValue : stationaryPoints )
2029 				{
2030 					painter->setPen( QPen( Qt::black, millimetersToPixels( 1.5, painter->device() ) ) );
2031 					painter->drawPoint( toPixel( realValue ) );
2032 
2033 					QString x = posToString( realValue.x(), (m_xmax-m_xmin)/m_clipRect.width(), View::DecimalFormat );
2034 					QString y = posToString( realValue.y(), (m_ymax-m_ymin)/m_clipRect.width(), View::DecimalFormat );
2035 
2036 					drawLabel( painter, plot.color(), realValue, i18nc( "Extrema point", "x = %1   y = %2", x.replace('.', QLocale().decimalPoint()), y.replace('.', QLocale().decimalPoint()) ) );
2037 				}
2038 			}
2039 
2040 			// Show the name of the plot?
2041 			if ( function->plotAppearance( plot.plotMode ).showPlotName )
2042 			{
2043 				double x, y;
2044 
2045 				double xmin = m_xmin + 0.1 * (m_xmax-m_xmin);
2046 				double xmax = m_xmax - 0.1 * (m_xmax-m_xmin);
2047 				double ymin = m_ymin + 0.1 * (m_ymax-m_ymin);
2048 				double ymax = m_ymax - 0.1 * (m_ymax-m_ymin);
2049 
2050 				// Find out where on the outer edge of the view to draw it
2051 				if ( 0 <= plotNameAt && plotNameAt <= 2 )
2052 				{
2053 					x = xmax;
2054 					y = ymax - (ymax-ymin)*plotNameAt/2;
2055 				}
2056 				else if ( 3 <= plotNameAt && plotNameAt <= 5 )
2057 				{
2058 					x = xmax - (xmax-xmin)*(plotNameAt-2)/3;
2059 					y = ymin;
2060 				}
2061 				else if ( 6 <= plotNameAt && plotNameAt <= 7 )
2062 				{
2063 					x = xmin;
2064 					y = ymin + (ymax-ymin)*(plotNameAt-5)/2;
2065 				}
2066 				else
2067 				{
2068 					x = xmin + (xmax-xmin)*(plotNameAt-7)/3;
2069 					y = ymax;
2070 				}
2071 
2072 				plotNameAt = (plotNameAt+1) % 10;
2073 
2074 				QPointF realPos;
2075 
2076 				if ( function->type() == Function::Implicit )
2077 				{
2078 					findRoot( & x, & y, plot, RoughRoot );
2079 					realPos = QPointF( x, y );
2080 				}
2081 				else
2082 				{
2083 					double t = getClosestPoint( QPointF( x, y ), plot );
2084 					realPos = realValue( plot, t, false );
2085 				}
2086 
2087 				// If the closest point isn't in the view, then don't draw the label
2088 				if ( realPos.x() < m_xmin || realPos.x() > m_xmax || realPos.y() < m_ymin || realPos.y() > m_ymax )
2089 					continue;
2090 
2091 				drawLabel( painter, plot.color(), realPos, plot.name() );
2092 			}
2093 		}
2094 	}
2095 }
2096 
2097 
drawLabel(QPainter * painter,const QColor & color,const QPointF & realPos,const QString & text)2098 void View::drawLabel( QPainter * painter, const QColor & color, const QPointF & realPos, const QString & text )
2099 {
2100 	QPalette palette;
2101 	QColor outline = color;
2102 	QColor background = outline.lighter( 500 );
2103 	background.setAlpha( 127 );
2104 
2105 
2106 	QPointF pixelCenter = toPixel( realPos );
2107 	QRectF rect( pixelCenter, QSizeF( 1, 1 ) );
2108 
2109 	painter->setFont( m_labelFont );
2110 	int flags = Qt::TextSingleLine | Qt::AlignLeft | Qt::AlignTop;
2111 	rect = painter->boundingRect( rect, flags, text ).adjusted( -7, -3, 4, 2 );
2112 
2113 	// Try and find a nice place for inserting the rectangle
2114 	int bestCost = int(1e7);
2115 	QPointF bestCenter = realPos;
2116 	for ( double x = pixelCenter.x() - 300; x <= pixelCenter.x() + 300; x += 20 )
2117 	{
2118 		for ( double y = pixelCenter.y() - 300; y <= pixelCenter.y() + 300; y += 20 )
2119 		{
2120 			QPointF center( x, y ) ;
2121 			rect.moveCenter( center );
2122 			double length = (x-pixelCenter.x())*(x-pixelCenter.x()) + (y-pixelCenter.y())*(y-pixelCenter.y());
2123 			int cost = rectCost( rect ) + int(length)/100;
2124 
2125 			if ( cost < bestCost )
2126 			{
2127 				bestCenter = center;
2128 				bestCost = cost;
2129 			}
2130 		}
2131 	}
2132 
2133 	rect.moveCenter( bestCenter );
2134 
2135 	markDiagramAreaUsed( rect );
2136 
2137 	painter->setBrush( background );
2138 	painter->setPen( outline );
2139 	painter->drawRoundedRect( rect, int(1000/rect.width()), int(1000/rect.height()) );
2140 
2141 
2142 	// If the rectangle does not lie over realPos, then draw a line to realPos from the rectangle
2143 	if ( ! rect.contains( pixelCenter ) )
2144 	{
2145 		QPointF lineStart = bestCenter;
2146 		QLineF line( pixelCenter, bestCenter );
2147 
2148 		QPointF intersect = bestCenter;
2149 
2150 		// Where does line intersect the rectangle?
2151 		if ( QLineF( rect.topLeft(), rect.topRight() ).intersect( line, & intersect ) == QLineF::BoundedIntersection )
2152 			lineStart = intersect;
2153 		else if ( QLineF( rect.topRight(), rect.bottomRight() ).intersect( line, & intersect ) == QLineF::BoundedIntersection )
2154 			lineStart = intersect;
2155 		else if ( QLineF( rect.bottomRight(), rect.bottomLeft() ).intersect( line, & intersect ) == QLineF::BoundedIntersection )
2156 			lineStart = intersect;
2157 		else if ( QLineF( rect.bottomLeft(), rect.topLeft() ).intersect( line, & intersect ) == QLineF::BoundedIntersection )
2158 			lineStart = intersect;
2159 
2160 		painter->drawLine( lineStart, pixelCenter );
2161 	}
2162 
2163 
2164 	painter->setPen( Qt::black );
2165 	painter->drawText( rect.adjusted( 7, 3, -4, -2 ), flags, text );
2166 }
2167 
2168 
usedDiagramRect(const QRectF & rect) const2169 QRect View::usedDiagramRect( const QRectF & rect ) const
2170 {
2171 	double x0 = rect.left() / m_clipRect.width();
2172 	double x1 = rect.right() / m_clipRect.width();
2173 
2174 	double y0 = rect.top() / m_clipRect.height();
2175 	double y1 = rect.bottom() / m_clipRect.height();
2176 
2177 	int i0 = qMax( int( x0 * LabelGridSize ), 0 );
2178 	int i1 = qMin( int( x1 * LabelGridSize ), LabelGridSize-1 );
2179 	int j0 = qMax( int( y0 * LabelGridSize ), 0 );
2180 	int j1 = qMin( int( y1 * LabelGridSize ), LabelGridSize-1 );
2181 
2182 	return QRect( i0, j0, i1-i0+1, j1-j0+1 ) & QRect( 0, 0, LabelGridSize, LabelGridSize );
2183 }
2184 
2185 
markDiagramAreaUsed(const QRectF & rect)2186 void View::markDiagramAreaUsed( const QRectF & rect )
2187 {
2188 	if ( m_zoomMode == Translating )
2189 		return;
2190 
2191 	QRect r = usedDiagramRect( rect );
2192 
2193 	for ( int i = r.left(); i <= r.right(); ++i )
2194 		for ( int j = r.top(); j <= r.bottom(); ++j )
2195 			m_usedDiagramArea[i][j] = true;
2196 }
2197 
2198 
markDiagramPointUsed(const QPointF & point)2199 void View::markDiagramPointUsed( const QPointF & point )
2200 {
2201 	if ( m_zoomMode == Translating )
2202 		return;
2203 
2204 	double x = point.x() / m_clipRect.width();
2205 	double y = point.y() / m_clipRect.height();
2206 
2207 	int i = int( x * LabelGridSize );
2208 	int j = int( y * LabelGridSize );
2209 
2210 	if ( i<0 || i>=LabelGridSize || j<0 || j>=LabelGridSize )
2211 		return;
2212 
2213 	m_usedDiagramArea[i][j] = true;
2214 }
2215 
2216 
rectCost(QRectF rect) const2217 int View::rectCost( QRectF rect ) const
2218 {
2219 	rect = rect.normalized();
2220 
2221 	int cost = 0;
2222 
2223 	// If the rectangle goes off the edge, mark it as very high cost)
2224 	if ( rect.intersects( m_clipRect ) )
2225 	{
2226 		QRectF intersect = (rect & m_clipRect);
2227 		cost += int(rect.width() * rect.height() - intersect.width() * intersect.height());
2228 	}
2229 	else
2230 	{
2231 		// The rectangle is completely outside!
2232 		cost += int(rect.width() * rect.height());
2233 	}
2234 
2235 
2236 	QRect r = usedDiagramRect( rect );
2237 
2238 	for ( int i = r.left(); i <= r.right(); ++i )
2239 		for ( int j = r.top(); j <= r.bottom(); ++j )
2240 			if ( m_usedDiagramArea[i][j] )
2241 				cost += 200;
2242 
2243 	return cost;
2244 }
2245 
2246 
penShouldDraw(double length,const Plot & plot)2247 bool View::penShouldDraw( double length, const Plot & plot )
2248 {
2249 	// Always use a solid line when translating the view
2250 	if ( m_zoomMode == Translating )
2251 		return true;
2252 
2253 	Function * function = plot.function();
2254 
2255 	Qt::PenStyle style = function->plotAppearance( plot.plotMode ).style;
2256 
2257 	double sepBig = 8.0;	// separation distance between dashes
2258 	double sepMid = 7.0;	// separation between a dash and a dot
2259 	double sepSmall = 6.5;	// separation distance between dots
2260 	double dash = 9.0;		// length of a dash
2261 	double dot = 3.5;		// length of a dot
2262 
2263 	switch ( style )
2264 	{
2265 		case Qt::NoPen:
2266 			// *whatever*...
2267 			return false;
2268 
2269 		case Qt::SolidLine:
2270 			return true;
2271 
2272 		case Qt::DashLine:
2273 			return realModulo( length, dash + sepBig ) < dash;
2274 
2275 		case Qt::DotLine:
2276 			return realModulo( length, dot + sepSmall ) < dot;
2277 
2278 		case Qt::DashDotLine:
2279 		{
2280 			double at = realModulo( length, dash + sepMid + dot + sepMid );
2281 
2282 			if ( at < dash )
2283 				return true;
2284 			if ( at < (dash + sepMid) )
2285 				return false;
2286 			if ( at < (dash + sepMid + dot) )
2287 				return true;
2288 			return false;
2289 		}
2290 
2291 		case Qt::DashDotDotLine:
2292 		{
2293 			double at = realModulo( length, dash + sepMid + dot + sepSmall + dot + sepMid );
2294 
2295 			if ( at < dash )
2296 				return true;
2297 			if ( at < (dash + sepMid) )
2298 				return false;
2299 			if ( at < (dash + sepMid + dot) )
2300 				return true;
2301 			if ( at < (dash + sepMid + dot + sepSmall) )
2302 				return false;
2303 			if ( at < (dash + sepMid + dot + sepSmall + dot) )
2304 				return true;
2305 			return false;
2306 		}
2307 
2308 		case Qt::MPenStyle:
2309 		case Qt::CustomDashLine:
2310 		{
2311 			assert( ! "Do not know how to handle this style!" );
2312 			return true;
2313 		}
2314 	}
2315 
2316 	assert( ! "Unknown pen style!" );
2317 	return true;
2318 }
2319 
2320 
penForPlot(const Plot & plot,QPainter * painter) const2321 QPen View::penForPlot( const Plot & plot, QPainter * painter ) const
2322 {
2323 	QPen pen;
2324 	if ( m_zoomMode == Translating )
2325 	{
2326 		// plot style is always a solid line when translating the view
2327 		pen.setCapStyle( Qt::FlatCap );
2328 	}
2329 	else
2330 	{
2331 		pen.setCapStyle( Qt::RoundCap );
2332 		// (the style will be set back to FlatCap if the plot style is a solid line)
2333 	}
2334 
2335 	pen.setColor( plot.color() );
2336 
2337 	Function * ufkt = plot.function();
2338 	PlotAppearance appearance = ufkt->plotAppearance( plot.plotMode );
2339 
2340 	double lineWidth_mm = appearance.lineWidth;
2341 
2342 	if ( appearance.style == Qt::SolidLine )
2343 		pen.setCapStyle( Qt::FlatCap );
2344 
2345 	double width = millimetersToPixels( lineWidth_mm, painter->device() );
2346 	pen.setWidthF( width );
2347 
2348 	return pen;
2349 }
2350 
2351 
millimetersToPixels(double width_mm,QPaintDevice * device) const2352 double View::millimetersToPixels( double width_mm, QPaintDevice * device ) const
2353 {
2354 // 	assert( device->logicalDpiX() == device->logicalDpiY() );
2355 	return device->logicalDpiX() * (width_mm/25.4);
2356 }
2357 
2358 
pixelsToMillimeters(double width_pixels,QPaintDevice * device) const2359 double View::pixelsToMillimeters( double width_pixels, QPaintDevice * device ) const
2360 {
2361 // 	assert( device->logicalDpiX() == device->logicalDpiY() );
2362 	return (width_pixels * 25.4) / device->logicalDpiX();
2363 }
2364 
2365 
drawHeaderTable(QPainter * painter)2366 void View::drawHeaderTable( QPainter *painter )
2367 {
2368 	painter->setFont( Settings::headerTableFont() );
2369 
2370 	QString alx = i18nc("%1=minimum value, %2=maximum value", "%1 to %2", Settings::xMin(), Settings::xMax());
2371 	QString aly = i18nc("%1=minimum value, %2=maximum value", "%1 to %2", Settings::yMin(), Settings::yMax());
2372 
2373 	QString atx = i18nc("'E' is the distance between ticks on the axis", "1E = ") + ticSepX.expression();
2374 	QString aty = i18nc("'E' is the distance between ticks on the axis", "1E = ") + ticSepY.expression();
2375 
2376 	QString text = "<div style=\"margin: 0 auto;\"><table border=\"1\" cellpadding=\"4\" cellspacing=\"0\">"
2377 			"<tr><td><b>" + i18n("Parameters") + "</b></td><td><b>" + i18n("Plotting Range") + "</b></td><td><b>" + i18n("Axes Division") + "</b></td></tr>"
2378 			"<tr><td><b>" + i18n("x-Axis:") + "</b></td><td>" + alx + "</td><td>" + atx + "</td></tr>"
2379 			"<tr><td><b>" + i18n("y-Axis:") + "</b></td><td>" + aly + "</td><td>" + aty + "</td></tr>"
2380 			"</table></div>";
2381 
2382 	text += "<br><br><b>" + i18n("Functions:") + "</b><ul>";
2383 
2384 	for ( Function * function : qAsConst(XParser::self()->m_ufkt) )
2385 		text += "<li>" + function->name().replace( '\n', "<br>" ) + "</li>";
2386 
2387 	text += "</ul>";
2388 
2389 	m_textDocument->setHtml( text );
2390 	m_textDocument->documentLayout()->draw( painter, QAbstractTextDocumentLayout::PaintContext() );
2391 
2392 	QRectF br = m_textDocument->documentLayout()->frameBoundingRect( m_textDocument->rootFrame() );
2393 	painter->translate( 0, br.height() );
2394 }
2395 
2396 
findStationaryPoints(const Plot & plot)2397 QList< QPointF > View::findStationaryPoints( const Plot & plot )
2398 {
2399 	Plot plot2 = plot;
2400 	plot2.differentiate();
2401 
2402 	const QList< double > roots = findRoots( plot2, getXmin( plot.function() ), getXmax( plot.function() ), RoughRoot );
2403 
2404 	plot.updateFunction();
2405 	QList< QPointF > stationaryPoints;
2406 	for ( double x : roots )
2407 	{
2408 		QPointF real = realValue( plot, x, false );
2409 		if ( real.y() >= m_ymin && real.y() <= m_ymax )
2410 			stationaryPoints << real;
2411 	}
2412 
2413 	return stationaryPoints;
2414 }
2415 
2416 
findRoots(const Plot & plot,double min,double max,RootAccuracy accuracy)2417 QList< double > View::findRoots( const Plot & plot, double min, double max, RootAccuracy accuracy )
2418 {
2419 	typedef QMap< double, double > DoubleMap;
2420 	DoubleMap roots;
2421 
2422 	int count = 10; // number of points to (initially) check for roots
2423 
2424 	int prevNumRoots = 0;
2425 	while ( count < 1000 )
2426 	{
2427 		// Use this to detect finding the same root.
2428 		double prevX = 0.0;
2429 
2430 		double dx = (max-min) / double(count);
2431 		for ( int i = 0; i <= count; ++i )
2432 		{
2433 			double x = min + dx*i;
2434 
2435 			bool found = findRoot( & x, plot, accuracy );
2436 			if ( !found || x < min || x > max )
2437 				continue;
2438 
2439 			if ( !roots.isEmpty() )
2440 			{
2441 				// Check if already have a close root
2442 				if ( qAbs(x-prevX) <= (dx/4) )
2443 					continue;
2444 
2445 				DoubleMap::iterator nextIt = roots.lowerBound(x);
2446 				if ( nextIt == roots.end() )
2447 					--nextIt;
2448 
2449 				double lower, upper;
2450 				lower = upper = *nextIt;
2451 				if ( nextIt != roots.begin() )
2452 					lower = *(--nextIt);
2453 
2454 				if ( (qAbs(x-lower) <= (dx/4)) || (qAbs(x-upper) <= (dx/4)) )
2455 					continue;
2456 			}
2457 
2458 			roots.insert( x, x );
2459 			prevX = x;
2460 		}
2461 
2462 		int newNumRoots = roots.size();
2463 		if ( newNumRoots == prevNumRoots )
2464 			break;
2465 
2466 		prevNumRoots = newNumRoots;
2467 		count *= 4;
2468 	}
2469 
2470 	return roots.keys();
2471 }
2472 
2473 
setupFindRoot(const Plot & plot,RootAccuracy accuracy,double * max_k,double * max_f,int * n)2474 void View::setupFindRoot( const Plot & plot, RootAccuracy accuracy, double * max_k, double * max_f, int * n )
2475 {
2476 	plot.updateFunction();
2477 
2478 	if ( accuracy == PreciseRoot )
2479 	{
2480 		*max_k = 200;
2481 		*max_f = 1e-14;
2482 	}
2483 	else
2484 	{
2485 		// Rough root
2486 		*max_k = 10;
2487 		*max_f = 1e-10;
2488 	}
2489 
2490 	*n = 1 + plot.derivativeNumber();
2491 }
2492 
2493 
findRoot(double * x,const Plot & plot,RootAccuracy accuracy)2494 bool View::findRoot( double * x, const Plot & plot, RootAccuracy accuracy )
2495 {
2496 #ifdef DEBUG_IMPLICIT
2497 	root_find_requests++;
2498 #endif
2499 
2500 	double max_k, max_f;
2501 	int n;
2502 	setupFindRoot( plot, accuracy, & max_k, & max_f, & n );
2503 
2504 	Equation * eq = plot.function()->eq[0];
2505 	DifferentialState * state = plot.state();
2506 
2507 	double h = qMin( m_xmax-m_xmin, m_ymax-m_ymin ) * 1e-4;
2508 
2509 	double f = value( plot, 0, *x, false );
2510 	int k;
2511 	for ( k=0; k < max_k; ++k )
2512 	{
2513 		double df = XParser::self()->derivative( n, eq, state, *x, h );
2514 		if ( qAbs(df) < 1e-20 )
2515 			df = 1e-20 * ((df < 0) ? -1 : 1);
2516 
2517 		double dx = f / df;
2518 		*x -= dx;
2519 		f = value( plot, 0, *x, false );
2520 
2521 		if ( (qAbs(f) <= max_f) && (qAbs(dx) <= (h*1e-5)) )
2522 			break;
2523 	}
2524 
2525 #ifdef DEBUG_IMPLICIT
2526 	root_find_iterations += k;
2527 #endif
2528 
2529 	// We continue calculating until |f| < max_f; this may result in k reaching
2530 	// max_k. However, if |f| is reasonably small (even if reaching max_k),
2531 	// we consider it a root.
2532 	return ( qAbs(f) < 1e-6 );
2533 }
2534 
2535 
findRoot(double * x,double * y,const Plot & plot,RootAccuracy accuracy)2536 bool View::findRoot( double * x, double * y, const Plot & plot, RootAccuracy accuracy )
2537 {
2538 	double max_k, max_f;
2539 	int n;
2540 	setupFindRoot( plot, accuracy, & max_k, & max_f, & n );
2541 
2542 	Function * function = plot.function();
2543 	Equation * eq = function->eq[0];
2544 	DifferentialState * state = plot.state();
2545 
2546 	double hx = (m_xmax-m_xmin) * 1e-5;
2547 	double hy = (m_ymax-m_ymin) * 1e-5;
2548 
2549 	function->y = *y;
2550 	function->m_implicitMode = Function::FixedY;
2551 	double f = value( plot, 0, *x, false );
2552 
2553 	for ( int k=0; k < max_k; ++k )
2554 	{
2555 		function->x = *x;
2556 		function->y = *y;
2557 
2558 		function->m_implicitMode = Function::FixedY;
2559 		double dfx = XParser::self()->derivative( n, eq, state, *x, hx );
2560 
2561 		function->m_implicitMode = Function::FixedX;
2562 		double dfy = XParser::self()->derivative( n, eq, state, *y, hy );
2563 
2564 		double dff = dfx*dfx + dfy*dfy;
2565 		if ( dff < 1e-20 )
2566 			dff = 1e-20;
2567 
2568 		double dx = f * dfx / dff;
2569 		*x -= dx;
2570 		double dy = f * dfy / dff;
2571 		*y -= dy;
2572 
2573 		function->y = *y;
2574 		function->m_implicitMode = Function::FixedY;
2575 		f = value( plot, 0, *x, false );
2576 
2577 		if ( (qAbs(f) <= max_f) && (qAbs(dx) <= (hx*1e-5)) && (qAbs(dy) <= (hy*1e-5)) )
2578 			break;
2579 	}
2580 
2581 	// We continue calculating until |f| < max_f; this may result in k reaching
2582 	// max_k. However, if |f| is reasonably small (even if reaching max_k),
2583 	// we consider it a root.
2584 	return ( qAbs(f) < 1e-6 );
2585 }
2586 
2587 
2588 
paintEvent(QPaintEvent *)2589 void View::paintEvent(QPaintEvent *)
2590 {
2591 	// Note: it is important to have this function call before we begin painting
2592 	// as updateCrosshairPosition may set the statusbar text
2593 	bool inBounds = updateCrosshairPosition();
2594 
2595 	QPainter p;
2596 	p.begin(this);
2597 
2598 	p.drawPixmap( QPoint( 0, 0 ), buffer );
2599 
2600 	// the current cursor position in widget coordinates
2601 	QPoint mousePos = mapFromGlobal( QCursor::pos() );
2602 
2603 	if ( (m_zoomMode == ZoomInDrawing) || (m_zoomMode == ZoomOutDrawing) )
2604 	{
2605 		QPalette palette;
2606 		QColor highlightColor = palette.color( QPalette::Highlight );
2607 		QColor backgroundColor = highlightColor;
2608 		backgroundColor.setAlpha( 63 );
2609 
2610 		p.setPen( highlightColor );
2611 		p.setBrush( backgroundColor );
2612 
2613 		p.setBackgroundMode (Qt::OpaqueMode);
2614 		p.setBackground (Qt::blue);
2615 
2616 		QRect rect( m_zoomRectangleStart, mousePos );
2617 		p.drawRect( rect );
2618 	}
2619 	else if ( m_zoomMode == AnimatingZoom )
2620 	{
2621 		QPointF tl( toPixel( m_animateZoomRect.topLeft() ) );
2622 		QPointF br( toPixel( m_animateZoomRect.bottomRight() ) );
2623 		p.drawRect( QRectF( tl, QSizeF( br.x()-tl.x(), br.y()-tl.y() ) ) );
2624 	}
2625 	else if ( shouldShowCrosshairs() )
2626 	{
2627 		Function * function = m_currentPlot.function();
2628 
2629 		QPen pen;
2630 
2631 		if ( function )
2632 		{
2633 			QColor functionColor = m_currentPlot.color();
2634 			pen.setColor( functionColor );
2635 			p.setPen( pen );
2636 			p.setRenderHint( QPainter::Antialiasing, true );
2637 
2638 			double x = m_crosshairPosition.x();
2639 			double y = m_crosshairPosition.y();
2640 
2641 			//BEGIN calculate curvature, normal
2642 			double k = 0;
2643 			double normalAngle = 0;
2644 
2645 			switch ( function->type() )
2646 			{
2647 				case Function::Parametric:
2648 				case Function::Polar:
2649 					normalAngle = pixelNormal( m_currentPlot, m_trace_x );
2650 					k = pixelCurvature( m_currentPlot, m_trace_x );
2651 					break;
2652 
2653 				case Function::Differential:
2654 				case Function::Cartesian:
2655 				case Function::Implicit:
2656 					normalAngle = pixelNormal( m_currentPlot, x, y );
2657 					k = pixelCurvature( m_currentPlot, x, y );
2658 					break;
2659 			}
2660 
2661 			if ( k < 0 )
2662 			{
2663 				k = -k;
2664 				normalAngle += M_PI;
2665 			}
2666 			//END calculate curvature, normal
2667 
2668 			if ( k > 1e-5 && Settings::detailedTracing() && inBounds )
2669 			{
2670 				p.save();
2671 
2672 				// Transform the painter so that the center of the osculating circle is the origin,
2673 				// with the normal line coming in from the left.
2674 				QPointF center = m_crosshairPixelCoords + (1/k) * QPointF( cos( normalAngle ), sin( normalAngle ) );
2675 				p.translate( center );
2676 				p.rotate( normalAngle * 180 / M_PI );
2677 
2678 				// draw osculating circle
2679 				pen.setColor( functionColor );
2680 				p.setPen( pen );
2681 				p.drawEllipse( QRectF( -QPointF( 1/k, 1/k ), QSizeF( 2/k, 2/k ) ) );
2682 
2683 				// draw normal
2684 				pen.setColor( functionColor );
2685 				p.setPen( pen );
2686 				p.setBrush( pen.color() );
2687 				p.drawLine( QLineF( -1/k, 0, 0, 0 ) );
2688 
2689 				// draw normal arrow
2690 				QPolygonF arrowHead(3);
2691 				arrowHead[0] = QPointF( 0, 0 );
2692 				arrowHead[1] = QPointF( -3, -2 );
2693 				arrowHead[2] = QPointF( -3, +2 );
2694 				p.drawPolygon( arrowHead );
2695 
2696 				// draw tangent
2697 				double tangent_scale = 1.2; // make the tangent look better
2698 				p.drawLine( QLineF( -1/k, -qMax( 1/k, qreal(15.) ) * tangent_scale, -1/k, qMax( 1/k, qreal(15.) ) * tangent_scale ) );
2699 
2700 				// draw perpendicular symbol
2701 				QPolygonF perp(3);
2702 				perp[0] = QPointF( -1/k, 10 );
2703 				perp[1] = QPointF( -1/k + 10, 10 );
2704 				perp[2] = QPointF( -1/k + 10, 0 );
2705 				p.drawPolyline( perp );
2706 
2707 				// draw intersection blob
2708 				p.drawRect( QRectF( -1/k-1, -1, 2, 2 ) );
2709 
2710 				p.restore();
2711 
2712 				// Already show osculating circle, etc, so don't draw crosshairs quite so prominently
2713 				functionColor.setAlpha( 63 );
2714 				pen.setColor( functionColor );
2715 			}
2716 		}
2717 		else
2718 		{
2719 			// Use an inverted background color for contrast
2720 			QColor inverted = QColor( 255-m_backgroundColor.red(), 255-m_backgroundColor.green(), 255-m_backgroundColor.blue() );
2721 			pen.setColor( inverted );
2722 		}
2723 
2724 		p.setPen( pen );
2725 		double x = m_crosshairPixelCoords.x();
2726 		double y = m_crosshairPixelCoords.y();
2727 		p.drawLine( QPointF( 0, y ), QPointF( m_clipRect.right(), y ) );
2728 		p.drawLine( QPointF( x, 0 ), QPointF( x, m_clipRect.height() ) );
2729 	}
2730 
2731 	p.end();
2732 }
2733 
2734 
pixelNormal(const Plot & plot,double x,double y)2735 double View::pixelNormal( const Plot & plot, double x, double y )
2736 {
2737 	Function * f = plot.function();
2738 	assert( f );
2739 
2740 	plot.updateFunction();
2741 
2742 	// For converting from real to pixels
2743 	double sx = m_clipRect.width() / (m_xmax - m_xmin);
2744 	double sy = m_clipRect.height() / (m_ymax - m_ymin);
2745 
2746 	double dx = 0;
2747 	double dy = 0;
2748 
2749 	double h = this->h( plot );
2750 
2751 	int d0 = plot.derivativeNumber();
2752 	int d1 = d0+1;
2753 
2754 	switch ( f->type() )
2755 	{
2756 		case Function::Differential:
2757 		case Function::Cartesian:
2758 		{
2759 			double df = XParser::self()->derivative( d1, f->eq[0], plot.state(), x, h );
2760 			return -atan( df * (sy/sx) ) - (M_PI/2);
2761 		}
2762 
2763 		case Function::Implicit:
2764 		{
2765 			dx = XParser::self()->partialDerivative( d1, d0, f->eq[0], 0, x, y, h, h ) / sx;
2766 			dy = XParser::self()->partialDerivative( d0, d1, f->eq[0], 0, x, y, h, h ) / sy;
2767 
2768 			double theta = -atan( dy / dx );
2769 
2770 			if ( dx < 0 )
2771 				theta += M_PI;
2772 
2773 			theta += M_PI;
2774 
2775 			return theta;
2776 		}
2777 
2778 		case Function::Polar:
2779 		{
2780 			double r =  XParser::self()->derivative( d0, f->eq[0], 0, x, h );
2781 			double dr = XParser::self()->derivative( d1, f->eq[0], 0, x, h );
2782 
2783 			dx = (dr * lcos(x) - r * lsin(x) * XParser::self()->radiansPerAngleUnit()) * sx;
2784 			dy = (dr * lsin(x) + r * lcos(x) * XParser::self()->radiansPerAngleUnit()) * sy;
2785 			break;
2786 		}
2787 
2788 		case Function::Parametric:
2789 		{
2790 			dx = XParser::self()->derivative( d1, f->eq[0], 0, x, h ) * sx;
2791 			dy = XParser::self()->derivative( d1, f->eq[1], 0, x, h ) * sy;
2792 			break;
2793 		}
2794 	}
2795 
2796 	double theta = - atan( dy / dx ) - (M_PI/2);
2797 
2798 	if ( dx < 0 )
2799 		theta += M_PI;
2800 
2801 	return theta;
2802 }
2803 
2804 
pixelCurvature(const Plot & plot,double x,double y)2805 double View::pixelCurvature( const Plot & plot, double x, double y )
2806 {
2807 	Function * f = plot.function();
2808 
2809 	// For converting from real to pixels
2810 	double sx = m_clipRect.width() / (m_xmax - m_xmin);
2811 	double sy = m_clipRect.height() / (m_ymax - m_ymin);
2812 
2813 	double fdx = 0;
2814 	double fdy = 0;
2815 	double fddx = 0;
2816 	double fddy = 0;
2817 	double fdxy = 0;
2818 
2819 	double h = this->h( plot );
2820 
2821 	int d0 = plot.derivativeNumber();
2822 	int d1 = d0+1;
2823 	int d2 = d0+2;
2824 
2825 	switch ( f->type() )
2826 	{
2827 		case Function::Differential:
2828 		case Function::Cartesian:
2829 		{
2830 			DifferentialState * state = plot.state();
2831 
2832 			fdx = sx;
2833 			fddx = 0;
2834 
2835 			fdy = XParser::self()->derivative( d1, f->eq[0], state, x, h ) * sy;
2836 			fddy = XParser::self()->derivative( d2, f->eq[0], state, x, h) * sy;
2837 
2838 // 			qDebug() << "fdy="<<fdy<<" fddy="<<fddy;
2839 
2840 			break;
2841 		}
2842 
2843 		case Function::Polar:
2844 		{
2845 			double r = XParser::self()->derivative( d0, f->eq[0], 0, x, h );
2846 			double dr = XParser::self()->derivative( d1, f->eq[0], 0, x, h );
2847 			double ddr = XParser::self()->derivative( d2, f->eq[0], 0, x, h );
2848 
2849 			fdx = (dr * lcos(x) - r * lsin(x) * XParser::self()->radiansPerAngleUnit()) * sx;
2850 			fdy = (dr * lsin(x) + r * lcos(x) * XParser::self()->radiansPerAngleUnit()) * sy;
2851 
2852 			double rpau = XParser::self()->radiansPerAngleUnit();
2853 
2854 			fddx = (ddr * lcos(x) - 2 * dr * lsin(x) * rpau - r * lcos(x) * rpau*rpau) * sx;
2855 			fddy = (ddr * lsin(x) + 2 * dr * lcos(x) * rpau - r * lsin(x) * rpau*rpau) * sy;
2856 
2857 			break;
2858 		}
2859 
2860 		case Function::Parametric:
2861 		{
2862 			fdx = XParser::self()->derivative( d1, f->eq[0], 0, x, h ) * sx;
2863 			fdy = XParser::self()->derivative( d1, f->eq[1], 0, x, h ) * sy;
2864 
2865 			fddx = XParser::self()->derivative( d2, f->eq[0], 0, x, h ) * sx;
2866 			fddy = XParser::self()->derivative( d2, f->eq[1], 0, x, h ) * sy;
2867 
2868 			break;
2869 		}
2870 
2871 		case Function::Implicit:
2872 		{
2873 			fdx =  XParser::self()->partialDerivative( d1, d0, f->eq[0], 0, x, y, h, h ) / sx;
2874 			fdy =  XParser::self()->partialDerivative( d0, d1, f->eq[0], 0, x, y, h, h ) / sy;
2875 
2876 			fddx = XParser::self()->partialDerivative( d2, d0, f->eq[0], 0, x, y, h, h ) / (sx*sx);
2877 			fddy = XParser::self()->partialDerivative( d0, d2, f->eq[0], 0, x, y, h, h ) / (sy*sy);
2878 
2879 			fdxy = XParser::self()->partialDerivative( d1, d1, f->eq[0], 0, x, y, h, h ) / (sx*sy);
2880 
2881 
2882 			break;
2883 		}
2884 	}
2885 
2886 	double mod = pow( fdx*fdx + fdy*fdy, 1.5 );
2887 
2888 	switch ( f->type() )
2889 	{
2890 		case Function::Differential:
2891 		case Function::Cartesian:
2892 		case Function::Parametric:
2893 		case Function::Polar:
2894 			return (fdx * fddy - fdy * fddx) / mod;
2895 
2896 		case Function::Implicit:
2897 			return ( fdx*fdx*fddy + fdy*fdy*fddx - 2*fdx*fdy*fdxy ) / mod;
2898 	}
2899 
2900 	qCritical() << "Unknown function type!\n";
2901 	return 0;
2902 }
2903 
2904 
resizeEvent(QResizeEvent *)2905 void View::resizeEvent(QResizeEvent *)
2906 {
2907 	if (m_isDrawing) //stop drawing integrals
2908 	{
2909 		m_stopCalculating = true; //stop drawing
2910 		return;
2911 	}
2912 	qreal dpr = devicePixelRatioF();
2913 	buffer = QPixmap( size() * dpr );
2914 	buffer.setDevicePixelRatio(dpr);
2915 	drawPlot();
2916 }
2917 
2918 
drawPlot()2919 void View::drawPlot()
2920 {
2921 	if ( buffer.width() == 0 || buffer.height() == 0 )
2922 		return;
2923 
2924 	buffer.fill(m_backgroundColor);
2925 	draw(&buffer, Screen );
2926 	update();
2927 }
2928 
2929 
focusOutEvent(QFocusEvent *)2930 void View::focusOutEvent( QFocusEvent * )
2931 {
2932 	// Redraw ourselves to get rid of the crosshair (if we had it)...
2933 	QTimer::singleShot( 0, this, SLOT(update()) );
2934 	QTimer::singleShot( 0, this, &View::updateCursor );
2935 }
2936 
2937 
focusInEvent(QFocusEvent *)2938 void View::focusInEvent( QFocusEvent * )
2939 {
2940 	// Redraw ourselves to get the crosshair (if we should have it)...
2941 	QTimer::singleShot( 0, this, SLOT(update()) );
2942 	QTimer::singleShot( 0, this, &View::updateCursor );
2943 }
2944 
2945 
crosshairPositionValid(Function * plot) const2946 bool View::crosshairPositionValid( Function * plot ) const
2947 {
2948 	if ( !plot )
2949 		return false;
2950 
2951 	// only relevant for cartesian plots - assume true for none
2952 	if ( plot->type() != Function::Cartesian )
2953 		return true;
2954 
2955 	bool lowerOk = ((!plot->usecustomxmin) || (plot->usecustomxmin && m_crosshairPosition.x()>plot->dmin.value()));
2956 	bool upperOk = ((!plot->usecustomxmax) || (plot->usecustomxmax && m_crosshairPosition.x()<plot->dmax.value()));
2957 
2958 	return lowerOk && upperOk;
2959 }
2960 
2961 
mousePressEvent(QMouseEvent * e)2962 void View::mousePressEvent(QMouseEvent *e)
2963 {
2964 	m_AccumulatedDelta = 0;
2965 	m_mousePressTimer->start();
2966 
2967 	// In general, we want to update the view
2968 	update();
2969 
2970 	if ( m_popupMenuStatus != NoPopup )
2971 		return;
2972 
2973 	if (m_isDrawing)
2974 	{
2975 		m_stopCalculating = true; //stop drawing
2976 		return;
2977 	}
2978 
2979 	if ( m_zoomMode != Normal )
2980 	{
2981 		// If the user clicked with the right mouse button will zooming in or out, then cancel it
2982 		if ( (m_zoomMode == ZoomInDrawing) ||
2983 					(m_zoomMode == ZoomOutDrawing) )
2984 		{
2985 			m_zoomMode = Normal;
2986 		}
2987 		updateCursor();
2988 		return;
2989 	}
2990 
2991 	m_haveRoot = false;
2992 
2993 	bool hadFunction = (m_currentPlot.functionID() != -1 );
2994 
2995 	updateCrosshairPosition();
2996 
2997 	if( !m_readonly && e->button()==Qt::RightButton) //clicking with the right mouse button
2998 	{
2999 		getPlotUnderMouse();
3000 		if ( m_currentPlot.function() )
3001 		{
3002 			if ( hadFunction )
3003 				m_popupMenuStatus = PopupDuringTrace;
3004 			else
3005 				m_popupMenuStatus = Popup;
3006 
3007 			fillPopupMenu();
3008 			m_popupMenu->exec( QCursor::pos() );
3009 		}
3010 		return;
3011 	}
3012 
3013 	if(e->button()!=Qt::LeftButton)
3014 		return;
3015 
3016 	if ( m_currentPlot.functionID() >= 0 ) //disable trace mode if trace mode is enable
3017 	{
3018 		m_currentPlot.setFunctionID( -1 );
3019 		setStatusBar( QString(), RootSection );
3020 		setStatusBar( QString(), FunctionSection );
3021 		mouseMoveEvent(e);
3022 		return;
3023 	}
3024 
3025 	QPointF closestPoint = getPlotUnderMouse();
3026 	Function * function = m_currentPlot.function();
3027 	if ( function )
3028 	{
3029 		QPointF ptd( toPixel( closestPoint ) );
3030 		QPoint globalPos = mapToGlobal( ptd.toPoint() );
3031 		QCursor::setPos( globalPos );
3032 		setStatusBar( m_currentPlot.name().replace( '\n', " ; " ), FunctionSection );
3033 		return;
3034 	}
3035 
3036 	// user didn't click on a plot; so we prepare to enter translation mode
3037 	m_currentPlot.setFunctionID( -1 );
3038 	m_zoomMode = AboutToTranslate;
3039 	m_prevDragMousePos = e->pos();
3040 	updateCursor();
3041 }
3042 
3043 
fillPopupMenu()3044 void View::fillPopupMenu( )
3045 {
3046 	Function * function = m_currentPlot.function();
3047 	if ( !function )
3048 		return;
3049 
3050 	m_popupMenuTitle->setText( m_currentPlot.name().replace( '\n', "; " ) );
3051 
3052 	QAction *calcArea = MainDlg::self()->actionCollection()->action("grapharea");
3053 	QAction *maxValue = MainDlg::self()->actionCollection()->action("maximumvalue");
3054 	QAction *minValue = MainDlg::self()->actionCollection()->action("minimumvalue");
3055 
3056 	m_popupMenu->removeAction(calcArea);
3057 	m_popupMenu->removeAction(maxValue);
3058 	m_popupMenu->removeAction(minValue);
3059 
3060 	if ( function->type() == Function::Cartesian || function->type() == Function::Differential )
3061 	{
3062 		m_popupMenu->addAction(calcArea);
3063 		m_popupMenu->addAction(maxValue);
3064 		m_popupMenu->addAction(minValue);
3065 	}
3066 }
3067 
3068 
getPlotUnderMouse()3069 QPointF View::getPlotUnderMouse()
3070 {
3071 	m_currentPlot.setFunctionID( -1 );
3072 	m_trace_x = 0.0;
3073 
3074 	Plot bestPlot;
3075 
3076 	double best_distance = 1e30; // a nice large number
3077 	QPointF best_cspos;
3078 
3079 	for ( Function * function : qAsConst(XParser::self()->m_ufkt) )
3080 	{
3081 		const QList< Plot > plots = function->plots();
3082 		for ( const Plot &plot : plots )
3083 		{
3084 			plot.updateFunction();
3085 
3086 			double best_x = 0.0, distance;
3087 			QPointF cspos;
3088 
3089 			if ( function->type() == Function::Implicit )
3090 			{
3091 				double x = m_crosshairPosition.x();
3092 				double y = m_crosshairPosition.y();
3093 				findRoot( & x, & y, plot, PreciseRoot );
3094 
3095 				QPointF d = toPixel( QPointF( x, y ), ClipInfinite ) - toPixel( QPointF( m_crosshairPosition.x(), m_crosshairPosition.y() ), ClipInfinite );
3096 
3097 				distance = std::sqrt( d.x()*d.x() + d.y()*d.y() );
3098 				cspos = QPointF( x, y );
3099 			}
3100 			else
3101 			{
3102 				best_x = getClosestPoint( m_crosshairPosition, plot );
3103 				distance = pixelDistance( m_crosshairPosition, plot, best_x, false );
3104 				cspos = realValue( plot, best_x, false );
3105 			}
3106 
3107 			if ( distance < best_distance )
3108 			{
3109 				best_distance = distance;
3110 				bestPlot = plot;
3111 				m_trace_x = best_x;
3112 				best_cspos = cspos;
3113 			}
3114 		}
3115 	}
3116 
3117 	if ( best_distance < 10.0 )
3118 	{
3119 		m_currentPlot = bestPlot;
3120 		m_crosshairPosition = best_cspos;
3121 		return m_crosshairPosition;
3122 	}
3123 	else
3124 		return QPointF();
3125 }
3126 
3127 
getClosestPoint(const QPointF & pos,const Plot & plot)3128 double View::getClosestPoint( const QPointF & pos, const Plot & plot )
3129 {
3130 	plot.updateFunction();
3131 
3132 	double best_x = 0.0;
3133 
3134 	Function * function = plot.function();
3135 	assert( function->type() != Function::Implicit ); // should use findRoot (3D version) for this
3136 
3137 	switch ( function->type() )
3138 	{
3139 		case Function::Implicit:
3140 			break;
3141 
3142 		case Function::Differential:
3143 		case Function::Cartesian:
3144 		{
3145 			double best_pixel_x = m_clipRect.width() / 2;
3146 
3147 			QPointF pixelPos = toPixel( pos, ClipInfinite );
3148 
3149 			double dmin = getXmin( function );
3150 			double dmax = getXmax( function );
3151 
3152 			double stepSize = (m_xmax-m_xmin)/m_clipRect.width();
3153 
3154 			// Algorithm in use here: Work out the shortest distance between the
3155 			// line joining (x0,y0) to (x1,y1) and the given point (real_x,real_y)
3156 
3157 			double x = dmin;
3158 			double y0 = value( plot, 0, x, false );
3159 
3160 			double best_distance = 1e20; // a large distance
3161 
3162 			while ( x <= dmax && (xToPixel(x) < best_pixel_x+best_distance) )
3163 			{
3164 				x += stepSize;
3165 
3166 				double y1 = value( plot, 0, x, false );
3167 
3168 				double _x0 = xToPixel( x-stepSize, ClipInfinite );
3169 				double _x1 = xToPixel( x, ClipInfinite );
3170 
3171 				double _y0 = yToPixel( y0, ClipInfinite );
3172 				double _y1 = yToPixel( y1, ClipInfinite );
3173 
3174 				double k = (_y1-_y0)/(_x1-_x0);
3175 
3176 				double closest_x, closest_y;
3177 				if ( k == 0 )
3178 				{
3179 					closest_x = pixelPos.x();
3180 					closest_y = _y0;
3181 				}
3182 				else
3183 				{
3184 					closest_x = (pixelPos.y() + pixelPos.x()/k + _x0*k - _y0) / (k + 1.0/k);
3185 					closest_y = (pixelPos.x() + pixelPos.y()*k + _y0/k - _x0) / (k + 1.0/k);
3186 				}
3187 
3188 				bool valid = (x-1.5*stepSize <= xToReal(closest_x)) && (xToReal(closest_x) <= x+0.5*stepSize);
3189 
3190 				double dfx = closest_x - pixelPos.x();
3191 				double dfy = closest_y - pixelPos.y();
3192 
3193 				double distance = sqrt( dfx*dfx + dfy*dfy );
3194 				bool insideView = 0 <= closest_y && closest_y <= m_clipRect.height();
3195 
3196 				if ( distance < best_distance && insideView && valid )
3197 				{
3198 					best_distance = distance;
3199 					best_pixel_x = closest_x;
3200 				}
3201 
3202 				y0 = y1;
3203 			}
3204 
3205 			best_x = xToReal( best_pixel_x );
3206 			break;
3207 		}
3208 
3209 		case Function::Polar:
3210 		case Function::Parametric:
3211 		{
3212 			double minX = getXmin( function );
3213 			double maxX = getXmax( function );
3214 			double stepSize = 0.001;
3215 
3216 			while ( stepSize > 0.0000009 )
3217 			{
3218 				double best_distance = 1e20; // a large distance
3219 
3220 				double x = minX;
3221 				while ( x <= maxX )
3222 				{
3223 					double distance = pixelDistance( pos, plot, x, false );
3224 					bool insideView = QRectF(m_clipRect).contains( toPixel( realValue( plot, x, false ), ClipInfinite ) );
3225 
3226 					if ( distance < best_distance && insideView )
3227 					{
3228 						best_distance = distance;
3229 						best_x = x;
3230 					}
3231 
3232 					x += stepSize;
3233 				}
3234 
3235 				minX = best_x - stepSize;
3236 				maxX = best_x + stepSize;
3237 
3238 				stepSize *= 0.1;
3239 			}
3240 			break;
3241 		}
3242 	}
3243 
3244 	return best_x;
3245 }
3246 
3247 
pixelDistance(const QPointF & pos,const Plot & plot,double x,bool updateFunction)3248 double View::pixelDistance( const QPointF & pos, const Plot & plot, double x, bool updateFunction )
3249 {
3250 	QPointF f = realValue( plot, x, updateFunction );
3251 	QPointF df = toPixel( pos, ClipInfinite ) - toPixel( f, ClipInfinite );
3252 
3253 	return std::sqrt( df.x()*df.x() + df.y()*df.y() );
3254 }
3255 
3256 
posToString(double x,double delta,PositionFormatting format,const QColor & color) const3257 QString View::posToString( double x, double delta, PositionFormatting format, const QColor &color  ) const
3258 {
3259 	delta = qAbs(delta);
3260 	if ( delta == 0 )
3261 		delta = 1;
3262 
3263 	QString numberText;
3264 
3265 	int decimalPlaces = 1-int(log(delta)/log(10.0));
3266 
3267 	// Avoid exponential format for smallish numbers
3268 	if ( 0.01 < qAbs(x) && qAbs(x) < 10000 )
3269 		format = DecimalFormat;
3270 
3271 	switch ( format )
3272 	{
3273 		case ScientificFormat:
3274 		{
3275 			int accuracy = 1 + decimalPlaces + int(log(qAbs(x))/log(10.0));
3276 			if ( accuracy < 2 )
3277 			{
3278 				// Ensure a minimum of two significant digits
3279 				accuracy = 2;
3280 			}
3281 
3282 			QString number = QString::number( x, 'g', accuracy );
3283 			if ( number.contains( 'e' ) )
3284 			{
3285 				number.remove( "+0" );
3286 				number.remove( '+' );
3287 				number.replace( "-0", MinusSymbol );
3288 
3289 				number.replace( 'e', QChar(215) + QString("10<sup>") );
3290 				number.append( "</sup>" );
3291 			}
3292 			if ( x > 0.0 )
3293 				number.prepend('+');
3294 
3295 			numberText = QString("<html><body><span style=\"color:%1;\">").arg( color.name() ) + number + "</span></body></html>";
3296 
3297 			break;
3298 		}
3299 
3300 		case DecimalFormat:
3301 		{
3302 			if ( decimalPlaces >= 0 )
3303 				numberText = QString::number( x, 'f', decimalPlaces );
3304 			else
3305 				numberText = QString::number( x*(pow(10.0,decimalPlaces)), 'f', 0 ) + QString( -decimalPlaces, '0' );
3306 
3307 			break;
3308 		}
3309 	}
3310 
3311 	numberText.replace( '-', MinusSymbol );
3312 
3313 	return numberText;
3314 }
3315 
3316 
mouseMoveEvent(QMouseEvent * e)3317 void View::mouseMoveEvent(QMouseEvent *e)
3318 {
3319 	if ( m_previousMouseMovePos != e->globalPos() )
3320 	{
3321 		m_AccumulatedDelta = 0;
3322 	}
3323 	m_previousMouseMovePos = e->globalPos();
3324 	m_AccumulatedDelta = 0;
3325 	if ( m_isDrawing || !e)
3326 		return;
3327 
3328 	bool inBounds = updateCrosshairPosition();
3329 	if ( !m_haveRoot )
3330 		setStatusBar( QString(), RootSection );
3331 
3332 	QString sx, sy;
3333 
3334 	if ( inBounds )
3335 	{
3336 		sx = i18n( "x = %1", posToString( m_crosshairPosition.x(), (m_xmax-m_xmin)/m_clipRect.width(), View::DecimalFormat ).replace('.', QLocale().decimalPoint()) );
3337 		sy = i18n( "y = %1", posToString( m_crosshairPosition.y(), (m_ymax-m_ymin)/m_clipRect.width(), View::DecimalFormat ).replace('.', QLocale().decimalPoint()) );
3338 	}
3339 	else
3340 		sx = sy = "";
3341 
3342 	setStatusBar( sx, XSection );
3343 	setStatusBar( sy, YSection );
3344 
3345 	if ( e->buttons() & Qt::LeftButton )
3346 	{
3347 		if ( m_zoomMode == ZoomIn )
3348 		{
3349 			m_zoomMode = ZoomInDrawing;
3350 			m_zoomRectangleStart = e->pos();
3351 		}
3352 		else if ( m_zoomMode == ZoomOut )
3353 		{
3354 			m_zoomMode = ZoomOutDrawing;
3355 			m_zoomRectangleStart = e->pos();
3356 		}
3357 		else if ( ((m_zoomMode == AboutToTranslate) || (m_zoomMode == Translating)) &&
3358 						  (e->pos() != m_prevDragMousePos) )
3359 		{
3360 			m_zoomMode = Translating;
3361 			QPoint d = m_prevDragMousePos - e->pos();
3362 			m_prevDragMousePos = e->pos();
3363 			translateView( d.x(), d.y() );
3364 		}
3365 	}
3366 
3367 	if ( (m_zoomMode == Normal) &&
3368 			 (m_popupMenuStatus != NoPopup) &&
3369 			 !m_popupMenu->isVisible() )
3370 	{
3371 		if ( m_popupMenuStatus==Popup)
3372 			m_currentPlot.setFunctionID( -1 );
3373 		m_popupMenuStatus = NoPopup;
3374 	}
3375 
3376 	update();
3377 	updateCursor();
3378 }
3379 
3380 
leaveEvent(QEvent *)3381 void View::leaveEvent(QEvent *)
3382 {
3383 	setStatusBar( "", XSection );
3384 	setStatusBar( "", YSection );
3385 
3386 	updateCrosshairPosition();
3387 	update();
3388 }
3389 
3390 
wheelEvent(QWheelEvent * e)3391 void View::wheelEvent(QWheelEvent *e)
3392 {
3393 	m_AccumulatedDelta += e->angleDelta().y();
3394 
3395 	if (e->modifiers() & Qt::ControlModifier)
3396 	{
3397 		if (m_AccumulatedDelta >= QWheelEvent::DefaultDeltasPerStep)
3398 		{
3399 			zoomIn( e->pos(), double(Settings::zoomInStep())/100.0 );
3400 			m_AccumulatedDelta = 0;
3401 		}
3402 		else if (m_AccumulatedDelta <= -QWheelEvent::DefaultDeltasPerStep)
3403 		{
3404 			zoomIn( e->pos(), (double(Settings::zoomOutStep())/100.0) + 1.0 );
3405 			m_AccumulatedDelta = 0;
3406 		}
3407 		e->accept();
3408 		return;
3409 	}
3410 	else
3411 	{
3412 		m_AccumulatedDelta = 0;
3413 	}
3414 	QWidget::wheelEvent(e);
3415 }
3416 
3417 
updateCrosshairPosition()3418 bool View::updateCrosshairPosition()
3419 {
3420 	QPointF mousePos = mapFromGlobal( QCursor::pos() );
3421 
3422 	bool out_of_bounds = false; // for the ypos
3423 
3424 	m_crosshairPosition = toReal( mousePos );
3425 
3426 	m_currentPlot.updateFunction();
3427 	Function * it = m_currentPlot.function();
3428 
3429 	if ( it && crosshairPositionValid( it ) && (m_popupMenuStatus != Popup) )
3430 	{
3431 		// The user currently has a plot selected, with the mouse in a valid position
3432 
3433 		if ( (it->type() == Function::Parametric) ||
3434 					(it->type() == Function::Polar) )
3435 		{
3436 
3437 			// Should we increase or decrease t to get closer to the mouse?
3438 			double dx[2] = { -0.00001, +0.00001 };
3439 			double d[] = { 0.0, 0.0 };
3440 			for ( int i = 0; i < 2; ++ i )
3441 				d[i] = pixelDistance( m_crosshairPosition, m_currentPlot, m_trace_x + dx[i], false );
3442 
3443 			double prev_best = pixelDistance( m_crosshairPosition, m_currentPlot, m_trace_x, false );
3444 			double current_dx = dx[(d[0] < d[1]) ? 0 : 1]*1e3;
3445 
3446 			while ( true )
3447 			{
3448 				double new_distance = pixelDistance( m_crosshairPosition, m_currentPlot, m_trace_x + current_dx, false );
3449 				if ( new_distance < prev_best )
3450 				{
3451 					prev_best = new_distance;
3452 					m_trace_x += current_dx;
3453 				}
3454 				else
3455 				{
3456 					if ( qAbs(current_dx) > 9e-10 )
3457 						current_dx *= 0.1;
3458 					else
3459 						break;
3460 				}
3461 			}
3462 
3463 			double min = getXmin( it );
3464 			double max = getXmax( it );
3465 
3466 			if ( m_trace_x > max )
3467 				m_trace_x  = max;
3468 
3469 			else if ( m_trace_x < min )
3470 				m_trace_x = min;
3471 
3472 			m_crosshairPosition = realValue( m_currentPlot, m_trace_x, false );
3473 		}
3474 		else if ( it->type() == Function::Implicit )
3475 		{
3476 			double x = m_crosshairPosition.x();
3477 			double y = m_crosshairPosition.y();
3478 			findRoot( & x, & y, m_currentPlot, PreciseRoot );
3479 			m_crosshairPosition = QPointF( x, y );
3480 		}
3481 		else
3482 		{
3483 			// cartesian or differential plot
3484 
3485 			m_crosshairPosition.setY( value( m_currentPlot, 0, m_crosshairPosition.x(), false ) );
3486 			mousePos.setY( yToPixel( m_crosshairPosition.y() ));
3487 
3488 			if ( m_crosshairPosition.y()<m_ymin || m_crosshairPosition.y()>m_ymax) //the ypoint is not visible
3489 			{
3490 				out_of_bounds = true;
3491 			}
3492 			else if ( (fabs(yToReal(mousePos.y())) < (m_ymax-m_ymin)/80) && (it->type() == Function::Cartesian || it->type() == Function::Differential) )
3493 			{
3494 				double x0 = m_crosshairPosition.x();
3495 				if ( !m_haveRoot && findRoot( &x0, m_currentPlot, PreciseRoot ) )
3496 				{
3497 					QString str="  ";
3498 					str += i18nc("%1 is a subscript zero symbol", "root: x%1 = ", SubscriptZeroSymbol);
3499 					setStatusBar( str+QLocale().toString( x0, 'f', 5 ), RootSection );
3500 					m_haveRoot=true;
3501 					emit updateRootValue( true, x0 );
3502 				}
3503 			}
3504 			else
3505 			{
3506 				m_haveRoot=false;
3507 				emit updateRootValue( false, 0 );
3508 			}
3509 		}
3510 
3511 		// For Cartesian plots, only adjust the cursor position if it is not at the ends of the view
3512 		if ( ((it->type() != Function::Cartesian) && (it->type() != Function::Differential)) || m_clipRect.contains( mousePos.toPoint() ) )
3513 		{
3514 			mousePos = toPixel( m_crosshairPosition, ClipAll, mousePos );
3515 			QPoint globalPos = mapToGlobal( mousePos.toPoint() );
3516 			QCursor::setPos( globalPos );
3517 		}
3518 	}
3519 
3520 	m_crosshairPixelCoords = mousePos;
3521 
3522 	return !out_of_bounds && m_clipRect.contains( mousePos.toPoint() );
3523 }
3524 
3525 
mouseReleaseEvent(QMouseEvent * e)3526 void View::mouseReleaseEvent ( QMouseEvent * e )
3527 {
3528 	bool doDrawPlot = false;
3529 
3530 	// avoid zooming in if the zoom rectangle is very small and the mouse was
3531 	// just pressed, which suggests that the user dragged the mouse accidentally
3532 	QRect zoomRect = QRect( m_zoomRectangleStart, e->pos() ).normalized();
3533 	int area = zoomRect.width() * zoomRect.height();
3534 
3535 	if ( (area <= 500) && (m_mousePressTimer->elapsed() < QApplication::startDragTime()) )
3536 	{
3537 		if ( m_zoomMode == ZoomInDrawing )
3538 			m_zoomMode = ZoomIn;
3539 		else if ( m_zoomMode == ZoomOutDrawing )
3540 			m_zoomMode = ZoomOut;
3541 	}
3542 
3543 	switch ( m_zoomMode )
3544 	{
3545 		case Normal:
3546 		case AnimatingZoom:
3547 		case AboutToTranslate:
3548 			break;
3549 
3550 		case Translating:
3551 			doDrawPlot = true;
3552 			Settings::self()->save();
3553 			MainDlg::self()->requestSaveCurrentState();
3554 			break;
3555 
3556 		case ZoomIn:
3557 			zoomIn( e->pos(), double(Settings::zoomInStep())/100.0 );
3558 			break;
3559 
3560 		case ZoomOut:
3561 			zoomIn( e->pos(), (double(Settings::zoomOutStep())/100.0) + 1.0 );
3562 			break;
3563 
3564 		case ZoomInDrawing:
3565 			zoomIn( zoomRect );
3566 			break;
3567 
3568 		case ZoomOutDrawing:
3569 			zoomOut( zoomRect );
3570 			break;
3571 	}
3572 
3573 	m_zoomMode = Normal;
3574 
3575 	if ( doDrawPlot )
3576 		drawPlot();
3577 	else
3578 		update();
3579 
3580 	updateCursor();
3581 }
3582 
3583 
zoomIn(const QPoint & mousePos,double zoomFactor)3584 void View::zoomIn( const QPoint & mousePos, double zoomFactor )
3585 {
3586 	QPointF real = toReal( mousePos );
3587 
3588 	double diffx = (m_xmax-m_xmin)*zoomFactor;
3589 	double diffy = (m_ymax-m_ymin)*zoomFactor;
3590 
3591 	animateZoom( QRectF( real.x()-diffx, real.y()-diffy, 2.0*diffx, 2.0*diffy ) );
3592 }
3593 
3594 
zoomIn(const QRectF & zoomRect)3595 void View::zoomIn( const QRectF & zoomRect )
3596 {
3597 	QPointF p = zoomRect.topLeft();
3598 	double real1x = xToReal(p.x() );
3599 	double real1y = yToReal(p.y() );
3600 	p = zoomRect.bottomRight();
3601 	double real2x = xToReal(p.x() );
3602 	double real2y = yToReal(p.y() );
3603 
3604 	if ( real1x > real2x )
3605 		qSwap( real1x, real2x );
3606 	if ( real1y > real2y )
3607 		qSwap( real1y, real2y );
3608 
3609 	animateZoom( QRectF( QPointF( real1x, real1y ), QSizeF( real2x-real1x, real2y-real1y ) ) );
3610 }
3611 
3612 
zoomOut(const QRectF & zoomRect)3613 void View::zoomOut( const QRectF & zoomRect )
3614 {
3615 	QPointF p = zoomRect.topLeft();
3616 	double _real1x = xToReal(p.x() );
3617 	double _real1y = yToReal(p.y() );
3618 	p = zoomRect.bottomRight();
3619 	double _real2x = xToReal(p.x() );
3620 	double _real2y = yToReal(p.y() );
3621 
3622 	double kx = (_real1x-_real2x)/(m_xmin-m_xmax);
3623 	double lx = _real1x - (kx * m_xmin);
3624 
3625 	double ky = (_real1y-_real2y)/(m_ymax-m_ymin);
3626 	double ly = _real1y - (ky * m_ymax);
3627 
3628 	double real1x = (m_xmin-lx)/kx;
3629 	double real2x = (m_xmax-lx)/kx;
3630 
3631 	double real1y = (m_ymax-ly)/ky;
3632 	double real2y = (m_ymin-ly)/ky;
3633 
3634 	animateZoom( QRectF( QPointF( real1x, real1y ), QSizeF( real2x-real1x, real2y-real1y ) ) );
3635 }
3636 
3637 
animateZoom(const QRectF & _newCoords)3638 void View::animateZoom( const QRectF & _newCoords )
3639 {
3640 	QRectF oldCoords( m_xmin, m_ymin, m_xmax-m_xmin, m_ymax-m_ymin );
3641 	QRectF newCoords( _newCoords.normalized() );
3642 
3643 	if ( newCoords.left() == m_xmin &&
3644 			newCoords.right() == m_xmax &&
3645 			newCoords.top() == m_ymin &&
3646 			newCoords.bottom() == m_ymax )
3647 		return;
3648 
3649 	m_zoomMode = AnimatingZoom;
3650 
3651 	if ( style()->styleHint(QStyle::SH_Widget_Animate) && m_viewportAnimation->state() == QAbstractAnimation::Stopped )
3652 	{
3653 		m_viewportAnimation->setDuration( 150 );
3654 		m_viewportAnimation->setEasingCurve( QEasingCurve::OutCubic );
3655 		m_viewportAnimation->setStartValue( oldCoords );
3656 		m_viewportAnimation->setEndValue( newCoords );
3657 		m_viewportAnimation->start();
3658 		connect(m_viewportAnimation, &QPropertyAnimation::finished, [this, newCoords]
3659 		{
3660 			finishAnimation( newCoords );
3661 		});
3662 	}
3663 	else
3664         {
3665 		finishAnimation( newCoords );
3666 	}
3667 	Settings::self()->save();
3668 }
3669 
finishAnimation(const QRectF & rect)3670 void View::finishAnimation( const QRectF & rect )
3671 {
3672 	m_xmin = rect.left();
3673 	m_xmax = rect.right();
3674 	m_ymin = rect.top();
3675 	m_ymax = rect.bottom();
3676 
3677 	Settings::setXMin( Parser::number( m_xmin ) );
3678 	Settings::setXMax( Parser::number( m_xmax ) );
3679 	Settings::setYMin( Parser::number( m_ymin ) );
3680 	Settings::setYMax( Parser::number( m_ymax ) );
3681 	MainDlg::self()->coordsDialog()->updateXYRange();
3682 	MainDlg::self()->requestSaveCurrentState();
3683 
3684 	drawPlot(); //update all graphs
3685 
3686 	m_zoomMode = Normal;
3687 }
3688 
getViewport()3689 const QRectF View::getViewport()
3690 {
3691 	return m_animateZoomRect;
3692 }
3693 
setViewport(const QRectF & rect)3694 void View::setViewport( const QRectF & rect )
3695 {
3696 	m_animateZoomRect = rect;
3697 	repaint();
3698 }
3699 
3700 
translateView(int dx,int dy)3701 void View::translateView( int dx, int dy )
3702 {
3703 	double rdx = xToReal( dx ) - xToReal( 0.0 );
3704 	double rdy = yToReal( dy ) - yToReal( 0.0 );
3705 
3706 	m_xmin += rdx;
3707 	m_xmax += rdx;
3708 	m_ymin += rdy;
3709 	m_ymax += rdy;
3710 
3711 	Settings::setXMin( Parser::number( m_xmin ) );
3712 	Settings::setXMax( Parser::number( m_xmax ) );
3713 	Settings::setYMin( Parser::number( m_ymin ) );
3714 	Settings::setYMax( Parser::number( m_ymax ) );
3715 	MainDlg::self()->coordsDialog()->updateXYRange();
3716 
3717 	drawPlot(); //update all graphs
3718 }
3719 
3720 
stopDrawing()3721 void View::stopDrawing()
3722 {
3723 	if (m_isDrawing)
3724 		m_stopCalculating = true;
3725 }
3726 
3727 
findMinMaxValue(const Plot & plot,ExtremaType type,double dmin,double dmax)3728 QPointF View::findMinMaxValue( const Plot & plot, ExtremaType type, double dmin, double dmax )
3729 {
3730 	Function * ufkt = plot.function();
3731 	assert( (ufkt->type() == Function::Cartesian) || (ufkt->type() == Function::Differential) );
3732 	Q_UNUSED(ufkt);
3733 
3734 	plot.updateFunction();
3735 
3736 	Plot differentiated = plot;
3737 	differentiated.differentiate();
3738 	QList<double> roots = findRoots( differentiated, dmin, dmax, RoughRoot );
3739 
3740 	// The minimum / maximum might occur at the end points
3741 	roots << dmin << dmax;
3742 
3743 	double best = (type == Maximum) ? -HUGE_VAL : +HUGE_VAL;
3744 	QPointF bestPoint;
3745 
3746 	for ( double root : qAsConst(roots) )
3747 	{
3748 		QPointF rv = realValue( plot, root, false );
3749 		if ( (type == Maximum && rv.y() > best) || (type == Minimum && rv.y() < best) )
3750 		{
3751 			best = rv.y();
3752 			bestPoint = QPointF(rv.x(), rv.y());
3753 		}
3754 	}
3755 
3756 	return bestPoint;
3757 }
3758 
3759 
keyPressEvent(QKeyEvent * e)3760 void View::keyPressEvent( QKeyEvent * e )
3761 {
3762 	// if a zoom operation is in progress, assume that the key press is to cancel it
3763 	if ( m_zoomMode != Normal )
3764 	{
3765 		m_zoomMode = Normal;
3766 		update();
3767 		updateCursor();
3768 		return;
3769 	}
3770 
3771 	if (m_isDrawing)
3772 	{
3773 		m_stopCalculating=true;
3774 		return;
3775 	}
3776 
3777 	if ( m_currentPlot.functionID() == -1 )
3778 		return;
3779 
3780 	QMouseEvent * event = 0;
3781 	if (e->key() == Qt::Key_Left )
3782 		event = new QMouseEvent( QEvent::MouseMove, m_crosshairPixelCoords.toPoint() - QPoint(1,1), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
3783 	else if (e->key() == Qt::Key_Right )
3784 		event = new QMouseEvent( QEvent::MouseMove, m_crosshairPixelCoords.toPoint() + QPoint(1,1), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
3785 	else if (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down) //switch graph in trace mode
3786 	{
3787 		/// \todo reimplement moving between plots
3788 #if 0
3789 		QMap<int, Function*>::iterator it = XParser::self()->m_ufkt.find( m_currentPlot.functionID );
3790 		int const ke=(*it)->parameters.count();
3791 		if (ke>0)
3792 		{
3793 			m_currentFunctionParameter++;
3794 			if (m_currentFunctionParameter >= ke)
3795 				m_currentFunctionParameter=0;
3796 		}
3797 		if (m_currentFunctionParameter==0)
3798 		{
3799 			int const old_m_currentPlot.functionID=m_currentPlot.functionID;
3800 			Function::PMode const old_m_currentPlot.plotMode = m_currentPlot.plotMode;
3801 			bool start = true;
3802 			bool found = false;
3803 			while ( 1 )
3804 			{
3805 				if ( old_m_currentPlot.functionID==m_currentPlot.functionID && !start)
3806 				{
3807 					m_currentPlot.plotMode=old_m_currentPlot.plotMode;
3808 					break;
3809 				}
3810 				qDebug() << "m_currentPlot.functionID: " << m_currentPlot.functionID;
3811 				switch ( (*it)->type() )
3812 				{
3813 					case Function::Parametric:
3814 				case Function::Polar:
3815 					break;
3816 				default:
3817 				{
3818 					//going through the function, the first and the second derivative
3819 					for ( m_currentPlot.plotMode = (Function::PMode)0; m_currentPlot.plotMode < 3; m_currentPlot.plotMode = (Function::PMode)(m_currentPlot.plotMode+1) )
3820 // 					for (m_currentPlot.plotMode=0;m_currentPlot.plotMode<3;m_currentPlot.plotMode++)
3821 					{
3822 							if (start)
3823 							{
3824 								if ( m_currentPlot.plotMode==Function::Derivative2)
3825 									m_currentPlot.plotMode=Function::Derivative0;
3826 								else
3827 									m_currentPlot.plotMode = (Function::PMode)(old_m_currentPlot.plotMode+1);
3828 								start=false;
3829 							}
3830 						qDebug() << "   m_currentPlot.plotMode: " << (int)m_currentPlot.plotMode;
3831 
3832 						if ( (*it)->plotAppearance( m_currentPlot.plotMode ).visible )
3833 							found = true;
3834 
3835 							if (found)
3836 								break;
3837 						}
3838 						break;
3839 					}
3840 				}
3841 				if (found)
3842 					break;
3843 
3844 				if ( ++it == XParser::self()->m_ufkt.end())
3845 					it = XParser::self()->m_ufkt.begin();
3846 				m_currentPlot.functionID = (*it)->id();
3847 			}
3848 		}
3849 
3850 		qDebug() << "************************";
3851 		qDebug() << "m_currentPlot.functionID: " << (int)m_currentPlot.functionID;
3852 		qDebug() << "m_currentPlot.plotMode: " << (int)m_currentPlot.plotMode;
3853 		qDebug() << "m_currentFunctionParameter: " << m_currentFunctionParameter;
3854 
3855 		setStatusBar( (*it)->prettyName( m_currentPlot.plotMode ), FunctionSection );
3856 
3857 		event = new QMouseEvent( QEvent::MouseMove, m_crosshairPixelCoords.toPoint(), Qt::LeftButton, Qt::LeftButton, 0 );
3858 #else
3859 		return;
3860 #endif
3861 	}
3862 	else if ( e->key() == Qt::Key_Space  )
3863 	{
3864 		event = new QMouseEvent( QEvent::MouseButtonPress, QCursor::pos(), Qt::RightButton, Qt::RightButton, Qt::NoModifier );
3865 		mousePressEvent(event);
3866 		delete event;
3867 		return;
3868 	}
3869 	else
3870 	{
3871 		event = new QMouseEvent( QEvent::MouseButtonPress, m_crosshairPixelCoords.toPoint(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
3872 		mousePressEvent(event);
3873 		delete event;
3874 		return;
3875 	}
3876 	mouseMoveEvent(event);
3877 	delete event;
3878 }
3879 
3880 
areaUnderGraph(IntegralDrawSettings s)3881 double View::areaUnderGraph( IntegralDrawSettings s )
3882 {
3883 	int sign = 1;
3884 	if ( s.dmax < s.dmin )
3885 	{
3886 		qSwap( s.dmin, s.dmax );
3887 		sign = -1;
3888 	}
3889 
3890 	else if ( s.dmax == s.dmin )
3891 		return 0;
3892 
3893 	Function * ufkt = s.plot.function();
3894 	assert( ufkt );
3895 
3896 	double dx = (s.dmax-s.dmin)/m_clipRect.width();
3897 	if ( s.plot.plotMode == Function::Integral )
3898 	{
3899 		double max_dx = ufkt->eq[0]->differentialStates.step().value();
3900 		if ( dx > max_dx )
3901 			dx = max_dx;
3902 	}
3903 
3904 	// Make sure that we calculate the exact area (instead of missing out a
3905 	// vertical slither at the end) by making sure dx tiles the x-range
3906 	// a whole number of times
3907 	int intervals = qRound( (s.dmax-s.dmin)/dx );
3908 	dx = (s.dmax-s.dmin) / intervals;
3909 
3910 	double calculated_area=0;
3911 	double x = s.dmin;
3912 
3913 	s.plot.updateFunction();
3914 
3915 	for ( int i = 0; i <= intervals; ++i )
3916 	{
3917 		double y = value( s.plot, 0, x, false );
3918 
3919 		// Trapezoid rule for integrals: only add on half for the first and last value
3920 		if ( (i == 0) || (i == intervals) )
3921 			calculated_area += 0.5*dx*y;
3922 		else
3923 			calculated_area += dx*y;
3924 
3925 		x=x+dx;
3926 	}
3927 
3928 	m_integralDrawSettings = s;
3929 	m_integralDrawSettings.draw = true;
3930 	drawPlot();
3931 	m_integralDrawSettings.draw = false;
3932 	return calculated_area * sign;
3933 }
3934 
isCalculationStopped()3935 bool View::isCalculationStopped()
3936 {
3937 	if ( m_stopCalculating)
3938 	{
3939 		m_stopCalculating = false;
3940 		return true;
3941 	}
3942 	else
3943 		return false;
3944 }
3945 
updateSliders()3946 void View::updateSliders()
3947 {
3948 	bool needSliderWindow = false;
3949 	for ( Function * it : qAsConst(XParser::self()->m_ufkt) )
3950 	{
3951 		if ( it->m_parameters.useSlider && !it->allPlotsAreHidden() )
3952 		{
3953 			needSliderWindow = true;
3954 			break;
3955 		}
3956 	}
3957 
3958 	if ( !needSliderWindow )
3959 	{
3960 		if ( m_sliderWindow )
3961 			m_sliderWindow->hide();
3962 		m_menuSliderAction->setChecked( false );
3963 		return;
3964 	}
3965 
3966 	if ( !m_sliderWindow )
3967 	{
3968 		m_sliderWindow = new KSliderWindow( this );
3969 		connect( m_sliderWindow, &KSliderWindow::valueChanged, this, QOverload<>::of(&View::drawPlot) );
3970 		connect( m_sliderWindow, &KSliderWindow::windowClosed, this, &View::sliderWindowClosed );
3971 		connect( m_sliderWindow, &KSliderWindow::finished, this, &View::sliderWindowClosed );
3972 	}
3973 	if ( m_menuSliderAction->isChecked() )
3974 		m_sliderWindow->show();
3975 }
3976 
sliderWindowClosed()3977 void View::sliderWindowClosed()
3978 {
3979 	m_menuSliderAction->setChecked( false );  //set the slider-item in the menu
3980 }
3981 
functionRemoved(int id)3982 void View::functionRemoved( int id )
3983 {
3984 	if ( id == m_currentPlot.functionID() )
3985 	{
3986 		m_currentPlot.setFunctionID( -1 );
3987 		setStatusBar( QString(), RootSection );
3988 		setStatusBar( QString(), FunctionSection );
3989 	}
3990 }
3991 
hideCurrentFunction()3992 void View::hideCurrentFunction()
3993 {
3994 	if ( m_currentPlot.functionID() == -1 )
3995       return;
3996 
3997 	Function * ufkt = m_currentPlot.function();
3998 	ufkt->plotAppearance( m_currentPlot.plotMode ).visible = false;
3999 
4000 	MainDlg::self()->functionEditor()->functionsChanged();
4001 	drawPlot();
4002 	MainDlg::self()->requestSaveCurrentState();
4003 	updateSliders();
4004 	if ( m_currentPlot.functionID() == -1 )
4005 		return;
4006 	if ( ufkt->allPlotsAreHidden() )
4007 	{
4008 		m_currentPlot.setFunctionID( -1 );
4009 		QMouseEvent *event = new QMouseEvent( QMouseEvent::KeyPress, QCursor::pos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
4010 		mousePressEvent(event); //leave trace mode
4011 		delete event;
4012 		return;
4013 	}
4014 	else
4015 	{
4016 		QKeyEvent *event = new QKeyEvent( QKeyEvent::KeyPress, Qt::Key_Up, Qt::NoModifier );
4017 		keyPressEvent(event); //change selected graph
4018 		delete event;
4019 		return;
4020 	}
4021 }
removeCurrentPlot()4022 void View::removeCurrentPlot()
4023 {
4024 	if ( m_currentPlot.functionID() == -1 )
4025       return;
4026 
4027 	Function * ufkt = m_currentPlot.function();
4028 	Function::Type function_type = ufkt->type();
4029 	if (!XParser::self()->removeFunction( ufkt ))
4030 		return;
4031 
4032 	if ( m_currentPlot.functionID() != -1 ) // if trace mode is enabled
4033 	{
4034 		m_currentPlot.setFunctionID( -1 );
4035 		QMouseEvent *event = new QMouseEvent( QMouseEvent::KeyPress, QCursor::pos(), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier );
4036 		mousePressEvent(event); //leave trace mode
4037 		delete event;
4038 	}
4039 
4040 	drawPlot();
4041 	if ( function_type == Function::Cartesian )
4042 		updateSliders();
4043 	MainDlg::self()->requestSaveCurrentState();
4044 }
4045 
4046 
animateFunction()4047 void View::animateFunction()
4048 {
4049 	Function * f = m_currentPlot.function();
4050 	if ( !f )
4051 		return;
4052 
4053 	ParameterAnimator * anim = new ParameterAnimator( this, f );
4054 	anim->show();
4055 }
4056 
4057 
editCurrentPlot()4058 void View::editCurrentPlot()
4059 {
4060 	MainDlg::self()->functionEditor()->setCurrentFunction( m_currentPlot.functionID() );
4061 }
4062 
4063 
zoomIn()4064 void View::zoomIn()
4065 {
4066 	m_zoomMode = ZoomIn;
4067 	updateCursor();
4068 }
4069 
4070 
zoomOut()4071 void View::zoomOut()
4072 {
4073 	m_zoomMode = ZoomOut;
4074 	updateCursor();
4075 }
4076 
4077 
zoomToTrigonometric()4078 void View::zoomToTrigonometric()
4079 {
4080 	double rpau = XParser::self()->radiansPerAngleUnit();
4081 	animateZoom( QRectF( -2*M_PI/rpau, -4.0, 4*M_PI/rpau, 8.0 ) );
4082 }
4083 
4084 
updateCursor()4085 void View::updateCursor()
4086 {
4087 	Cursor newCursor = m_prevCursor;
4088 
4089 	if ( m_isDrawing && (m_zoomMode != Translating) )
4090 		newCursor = CursorWait;
4091 
4092 	else switch (m_zoomMode)
4093 	{
4094 		case AnimatingZoom:
4095 			newCursor = CursorArrow;
4096 			break;
4097 
4098 		case Normal:
4099 			if ( shouldShowCrosshairs() )
4100 			{
4101 				// Don't show any cursor if we're tracing a function or the crosshairs should be shown
4102 				newCursor = CursorBlank;
4103 			}
4104 			else
4105 				newCursor = CursorArrow;
4106 			break;
4107 
4108 		case ZoomIn:
4109 		case ZoomInDrawing:
4110 			newCursor = CursorMagnify;
4111 			break;
4112 
4113 		case ZoomOut:
4114 		case ZoomOutDrawing:
4115 			newCursor = CursorLessen;
4116 			break;
4117 
4118 		case AboutToTranslate:
4119 		case Translating:
4120 			newCursor = CursorMove;
4121 			break;
4122 	}
4123 
4124 	if ( newCursor == m_prevCursor )
4125 		return;
4126 	m_prevCursor = newCursor;
4127 
4128 	switch ( newCursor )
4129 	{
4130 		case CursorWait:
4131 			setCursor( Qt::WaitCursor );
4132 			break;
4133 		case CursorBlank:
4134 			setCursor( Qt::BlankCursor );
4135 			break;
4136 		case CursorArrow:
4137 			setCursor( Qt::ArrowCursor );
4138 			break;
4139 		case CursorCross:
4140 			setCursor( Qt::CrossCursor );
4141 			break;
4142 		case CursorMagnify:
4143 			setCursor( QCursor( QIcon::fromTheme( "zoom-in").pixmap(48), 22, 15 ) );
4144 			break;
4145 		case CursorLessen:
4146 			setCursor( QCursor( QIcon::fromTheme( "zoom-out").pixmap(48), 22, 15 ) );
4147 			break;
4148 		case CursorMove:
4149 			setCursor( Qt::SizeAllCursor );
4150 
4151 	}
4152 }
4153 
4154 
shouldShowCrosshairs() const4155 bool View::shouldShowCrosshairs() const
4156 {
4157 	switch ( m_zoomMode )
4158 	{
4159 		case Normal:
4160 		case ZoomIn:
4161 		case ZoomOut:
4162 			break;
4163 
4164 		case AnimatingZoom:
4165 		case ZoomInDrawing:
4166 		case ZoomOutDrawing:
4167 		case AboutToTranslate:
4168 		case Translating:
4169 			return false;
4170 	}
4171 
4172 	if ( m_popupMenuStatus != NoPopup )
4173 		return false;
4174 
4175 	Function * it = m_currentPlot.function();
4176 
4177 	return ( underMouse() && (!it || crosshairPositionValid( it )) );
4178 }
4179 
4180 
event(QEvent * e)4181 bool View::event( QEvent * e )
4182 {
4183 	if ( e->type() == QEvent::WindowDeactivate && m_isDrawing)
4184 	{
4185 		m_stopCalculating = true;
4186 		return true;
4187 	}
4188 	return QWidget::event(e); //send the information further
4189 }
4190 
4191 
setStatusBar(const QString & t,StatusBarSection section)4192 void View::setStatusBar( const QString & t, StatusBarSection section )
4193 {
4194 	QString text;
4195 	if ( section == FunctionSection )
4196 		text = ' ' + t + ' ';
4197 	else
4198 		text = t;
4199 
4200 	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
4201 	{
4202 		m_statusBarText[ section ] = text;
4203 
4204 		QString text;
4205 		for ( int i = 0; i < 4; ++i )
4206 		{
4207 			if ( m_statusBarText[i].isEmpty() )
4208 				continue;
4209 
4210 			if ( !text.isEmpty() )
4211 				text.append( "  |  " );
4212 
4213 			text.append( m_statusBarText[i] );
4214 		}
4215 
4216 		emit setStatusBarText(text);
4217 	}
4218 	else
4219 	{
4220 		QDBusReply<void> reply = QDBusInterface( QDBusConnection::sessionBus().baseService(), "/kmplot", "org.kde.kmplot.KmPlot" ).call( QDBus::NoBlock, "setStatusBarText", text, (int)section );
4221 	}
4222 }
4223 
setPrintHeaderTable(bool status)4224 void View::setPrintHeaderTable( bool status )
4225 {
4226 	m_printHeaderTable = status;
4227 }
4228 
setPrintBackground(bool status)4229 void View::setPrintBackground( bool status )
4230 {
4231 	m_printBackground = status;
4232 }
4233 
setPrintWidth(double width)4234 void View::setPrintWidth( double width )
4235 {
4236 	m_printWidth = width;
4237 }
4238 
setPrintHeight(double height)4239 void View::setPrintHeight( double height )
4240 {
4241 	m_printHeight = height;
4242 }
4243 
getCrosshairPosition() const4244 QPointF View::getCrosshairPosition() const
4245 {
4246 	return m_crosshairPosition;
4247 }
4248 
4249 //END class View
4250 
4251 
4252 
4253 //BEGIN class IntegralDrawSettings
IntegralDrawSettings()4254 IntegralDrawSettings::IntegralDrawSettings()
4255 {
4256 	dmin = dmax = 0.0;
4257 	draw = false;
4258 }
4259 //END class IntegralDrawSettings
4260