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 #ifndef View_included 15 #define View_included 16 17 #include "function.h" 18 19 // Qt includes 20 #include <QDebug> 21 #include <QEasingCurve> 22 #include <QEvent> 23 #include <QKeyEvent> 24 #include <QMouseEvent> 25 #include <QPixmap> 26 #include <QPointer> 27 #include <QPropertyAnimation> 28 #include <QResizeEvent> 29 30 // KDE includes 31 #include <KToggleAction> 32 #include <KTextEdit> 33 34 class KSliderWindow; 35 class MainDlg; 36 class QPaintEvent; 37 class QTextDocument; 38 class QMenu; 39 class QElapsedTimer; 40 41 //@{ 42 /// Some abbreviations for horizontal and vertical lines. 43 #define Line drawLine 44 #define Lineh(x1, y, x2) drawLine( QPointF(x1, y), QPointF(x2, y) ) 45 #define Linev(x, y1, y2) drawLine( QPointF(x, y1), QPointF(x, y2) ) 46 //@} 47 48 49 /** 50 * For drawing the area of a (Cartesian) plot. 51 */ 52 class IntegralDrawSettings 53 { 54 public: 55 IntegralDrawSettings(); 56 57 Plot plot; 58 double dmin, dmax; 59 /// Set to true when calculating the area under the graph 60 bool draw; 61 }; 62 63 64 /** 65 * @short This class contains the plots. 66 * 67 * It is the central widget of MainDlg. 68 * @see MainDlg, MainDlg::view 69 */ 70 class View : public QWidget 71 { 72 Q_OBJECT 73 Q_PROPERTY( QRectF viewport READ getViewport WRITE setViewport ) 74 public: 75 /// Constructor 76 View( bool readOnly, QMenu * functionPopup, QWidget* parent ); 77 virtual ~View(); 78 79 /// There is only one view. self()80 static View * self() { return m_self; } 81 82 enum PlotMedium 83 { 84 Screen, 85 Printer, 86 SVG, 87 Pixmap 88 }; 89 /** 90 * Draw the plot to \p dev, which is of the given \p medium. 91 */ 92 void draw( QPaintDevice * dev, PlotMedium medium ); 93 94 enum ExtremaType { Minimum, Maximum }; 95 /** 96 * Finding the minimum or maximum value. 97 * \return The (x,y) coordinates of the extrema point. 98 */ 99 QPointF findMinMaxValue( const Plot & plot, ExtremaType type, double dmin, double dmax ); 100 /** 101 * Calculates the area between the given plot and the x-axis 102 * (from x = \p dmin to x = \p dmax). The area will also be colored in. 103 * \return the area. 104 */ 105 double areaUnderGraph( IntegralDrawSettings settings ); 106 /** 107 * \return if the calculation was cancelled by the user. 108 */ 109 bool isCalculationStopped(); 110 /** 111 * Used in posToString for requesting how the position string is to be 112 * created. 113 */ 114 enum PositionFormatting 115 { 116 DecimalFormat, ///< Plain text, using no scientific notation; just decimal expansion. 117 ScientificFormat ///< Rich text possibly using scientific notation (mult x 10 ^ exp). 118 }; 119 /** 120 * @return a string for displaying the x or y coordinate in the statusbar. 121 * \param x The number to convert to a string. 122 * \param delta is the amount by which the value varies over one pixel in 123 * the view. This is for choosing an appropriate number of decimals so that 124 * moving the cursor shows a nice change in the string. 125 * \param format How the number should be represented as a string. 126 * \param color If using scientific mode, the color to format the text. 127 */ 128 QString posToString( double x, double delta, PositionFormatting format, const QColor &color = Qt::black ) const; 129 130 /// Slider controlling parameter values 131 QPointer<KSliderWindow> m_sliderWindow; 132 /// Menu actions for the sliders 133 KToggleAction * m_menuSliderAction; 134 void updateSliders(); /// show only needed sliders 135 136 /** 137 * Convert \p width_mm (millimeters) to the equivalent length when 138 * drawing using \p painter. 139 */ 140 double millimetersToPixels( double width_mm, QPaintDevice * device ) const; 141 /** 142 * The inverse of millimetersToPixels(). 143 */ 144 double pixelsToMillimeters( double width_pixels, QPaintDevice * device ) const; 145 146 /** Current plot x-range. */ 147 double m_xmin; 148 double m_xmax; 149 /** Current plot y-range. */ 150 double m_ymin; 151 double m_ymax; 152 153 /// trace mode stuff, must be accessible in FunctionTools 154 Plot m_currentPlot; 155 /** 156 * Convenience function for calculating the value of \p eq using the 157 * given \p mode 158 */ 159 double value( const Plot & plot, int eq, double x, bool updateFunction ); 160 /** 161 * \return the real position of the function (similar to calling 162 * value(), but returns both coordinates). 163 */ 164 QPointF realValue( const Plot & plot, double x, bool updateFunction ); 165 /** 166 * \return the (signed) curvature (in screen coordinates) of the plot 167 * at \p x (and \p y for implicit functions). 168 */ 169 double pixelCurvature( const Plot & plot, double x, double y = 0 ); 170 /** 171 * \return the angle of the normal (in radians) of the plot when viewed 172 * on the screen. 173 */ 174 double pixelNormal( const Plot & plot, double x, double y = 0 ); 175 /** 176 * Animates zooming from the current zoom rect to the one given (in real 177 * coordinates) 178 */ 179 void animateZoom( const QRectF & newCoords ); 180 181 ///Methods for the Print Dialog to set options for drawing 182 void setPrintHeaderTable( bool status ); 183 void setPrintBackground( bool status ); 184 void setPrintWidth( double width ); 185 void setPrintHeight( double height ); 186 187 /** 188 * Which part of the status bar. 189 */ 190 enum StatusBarSection 191 { 192 XSection = 0, 193 YSection = 1, 194 RootSection = 2, 195 FunctionSection = 3, 196 SectionCount = 4 197 }; 198 199 /** 200 * Crosshair position in real coordinates 201 */ 202 QPointF getCrosshairPosition() const; 203 204 public slots: 205 /// Called when the user want to cancel the drawing 206 void stopDrawing(); 207 208 /// Called when the graph should be updated 209 void drawPlot(); 210 /// Called when a function is deleted 211 void functionRemoved( int id ); 212 ///Slots for the three first items in popup menu 213 void hideCurrentFunction(); 214 void removeCurrentPlot(); 215 void editCurrentPlot(); 216 void animateFunction(); 217 ///Slots for the zoom menu 218 void zoomIn(); 219 void zoomOut(); 220 void zoomToTrigonometric(); 221 222 protected slots: 223 /// Unchecks menu item when the slider window is closed 224 void sliderWindowClosed(); 225 /// Restore the mouse cursor when a drawing is finished 226 void updateCursor(); 227 228 signals: 229 void setStatusBarText(const QString &); 230 void updateRootValue(bool haveRoot, double rootValue); 231 232 protected: 233 /// called when focus is lost 234 void focusOutEvent( QFocusEvent * ) Q_DECL_OVERRIDE; 235 /// called when focus is gained 236 void focusInEvent( QFocusEvent * ) Q_DECL_OVERRIDE; 237 void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE; 238 void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; 239 /// Updating the cross hair. 240 void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE; 241 /// Clearing the cross hair. 242 void leaveEvent(QEvent *) Q_DECL_OVERRIDE; 243 /// Toggles the trace mode if the cursor is near to a plot. 244 void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE; 245 /// when a key is pressed and the graph widget has focus 246 void keyPressEvent(QKeyEvent * ) Q_DECL_OVERRIDE; 247 /// called when a mouse key is released 248 void mouseReleaseEvent ( QMouseEvent * e ) Q_DECL_OVERRIDE; 249 /// called for zooming with Ctrl+mouse wheel 250 void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE; 251 /// Is needed to be reimplement so that the user can stop a preview-drawing 252 bool event( QEvent * e ) Q_DECL_OVERRIDE; 253 /** 254 * Updates csxpos and csypos from the current mouse position. 255 * @return whether the crosshair is within the bounds of the diagram. 256 */ 257 bool updateCrosshairPosition(); 258 259 private: 260 /** 261 * Fills the popup menu according to the currently selected plot. 262 */ 263 void fillPopupMenu(); 264 /** 265 * For using in automatic tic spacing. Given \p range (e.g. x_max-x_min) 266 * and the \p length_mm (in millimeters), it aims to find a "nice" 267 * spacing distance that is visually pleasing and also fits the base 10 268 * number system in use (i.e. is a decimal multiple of 1, 2 or 5). 269 */ 270 double niceTicSpacing( double length_mm, double range ); 271 /** 272 * For using in manual tic spacing. Given a size in screen \p pixels for 273 * the given \p range (e.g. x_max-x_min), make sure the \p spacing distance 274 * is at least \p minPixels large. If zooming out too far, the spacing is 275 * doubled until this conditions is met, effectively omitting tics. If zooming in 276 * too far, the spacing is halved until at least two tics are visible, effectively 277 * inserting additional tics. 278 */ 279 double validatedTicSpacing( double spacing, double range, double pixels, double minPixels ); 280 /** 281 * When zoomed in on part of a circle, it looks nearly straight. KmPlot 282 * uses this to quickly draw curves that are mostly straight. Given the 283 * curvature, this function returns the maximum length of line that can 284 * be used to draw a part of a curve with the given curvature without 285 * the curve starting to look jagged. 286 */ 287 static double maxSegmentLength( double curvature ); 288 /** 289 * \return an appropriate value to use in numerical differentiation. 290 */ 291 double h( const Plot & plot ) const; 292 /** 293 * Initializes the size, scaling, etc variables from the given paint 294 * device. 295 */ 296 void initDrawing( QPaintDevice * device, PlotMedium medium ); 297 /** 298 * Print out table with additional information. Only for printing. 299 */ 300 void drawHeaderTable(QPainter *); 301 /// Draw the grid. 302 void drawGrid( QPainter* ); 303 /** 304 * Draw the axes. 305 */ 306 void drawAxes( QPainter *painter ); 307 /** 308 * Draw the axes' labels. 309 */ 310 void drawLabels( QPainter *painter ); 311 /** 312 * Draw the labels for the x-axis (this function is called from 313 * drawLabels). 314 * \a painter The QPainter to draw the labels with 315 * \a endLabelWidth_mm the distance of the "x" label from the edge. 316 */ 317 void drawXAxisLabels( QPainter *painter, double endLabelWidth_mm ); 318 /** 319 * Draw the labels for the y-axis (this function is called from 320 * drawLabels). 321 */ 322 void drawYAxisLabels( QPainter *painter ); 323 /** 324 * Draw a non-implicit function. 325 */ 326 void drawFunction( Function * function, QPainter * painter ); 327 /** 328 * Draw the function plots (other than implicit). 329 */ 330 void drawPlot( const Plot & plot, QPainter*); 331 /** 332 * Draw the tangent field (for a differential function). 333 */ 334 void drawTangentField( const Plot & plot, QPainter * painter ); 335 /** 336 * Draw an implicit function. 337 */ 338 void drawImplicit( Function * function, QPainter * ); 339 /** 340 * Draw the extrema points, function names, etc. This needs to be done 341 * after the functions have all been drawn so that the label positioning 342 * knows where the plots have been drawn. 343 */ 344 void drawFunctionInfo( QPainter * painter ); 345 /** 346 * Initializes for the drawLabel function, called before drawing has 347 * started. 348 */ 349 void initDrawLabels(); 350 /** 351 * Draw text (e.g. showing the value of an extrema point or a function 352 * name) at the given (real) position. 353 */ 354 void drawLabel( QPainter * painter, const QColor & color, const QPointF & realPos, const QString & text ); 355 /** 356 * Used by plotImplicit to draw the plot in the square associated with 357 * the given point. 358 */ 359 void drawImplicitInSquare( const Plot & plot, QPainter *, double x, double y, Qt::Orientations orientation, QList<QPointF> * singular ); 360 /** 361 * \return whether should draw the pixel from the given line length, 362 * according to the given pen style (used in plotfkt). 363 */ 364 bool penShouldDraw( double totalLength, const Plot & plot ); 365 /** 366 * \return An appropriate pen for drawing the plot. 367 */ 368 QPen penForPlot( const Plot & plot, QPainter * painter ) const; 369 /** 370 * Used in findRoot. 371 */ 372 enum RootAccuracy 373 { 374 PreciseRoot, ///< Will potential spend a long time finding a root to a high degree of accuracy 375 RoughRoot ///< Won't spend too long making a root accurate, giving up quickly if failed to find root 376 }; 377 /** 378 * Used in trace mode. Attempts to find the root of equation \p eq near 379 * \p x (which is then set to the exact root if found). 380 * \returns whether a root was found. 381 */ 382 bool findRoot( double * x, const Plot & plot, RootAccuracy accuracy ); 383 /** 384 * Equivalent function as above for implicit functions. 385 */ 386 bool findRoot( double * x, double * y, const Plot & plot, RootAccuracy accuracy ); 387 /** 388 * For use in the findRoot functions. 389 * \p max_k maximum number of iterations 390 * \p max_f the largest value of y which is deemed a root found 391 */ 392 void setupFindRoot( const Plot & plot, RootAccuracy accuracy, double * max_k, double * max_f, int * n ); 393 /** 394 * Finds the list of points (in function coordinates) at which the 395 * derivative of the given plot is zero in the range of the currently 396 * viewable segment of the plot. 397 */ 398 QList<QPointF> findStationaryPoints( const Plot & plot ); 399 /** 400 * Find all roots (at which the given plot is zero) in the range 401 * [min,max]. 402 */ 403 QList<double> findRoots( const Plot & plot, double min, double max, RootAccuracy accuracy ); 404 /** 405 * Changes the text in the statusbar. 406 */ 407 void setStatusBar( const QString &text, StatusBarSection section ); 408 /** 409 * \return whether the crosshairs should be shown for the current mouse 410 * position, zoom mode, etc. 411 */ 412 bool shouldShowCrosshairs() const; 413 /** 414 * Zooms in by amount \p zoomFactor (which will zooming out if less than 1) 415 * from clicking at \p mousePos (in widget coordinates). 416 */ 417 void zoomIn( const QPoint & mousePos, double zoomFactor ); 418 /** 419 * Zooms in from having drawn \p zoomRect (which is in widget coordinates). 420 */ 421 void zoomIn( const QRectF & zoomRect ); 422 /** 423 * Zooms out from havoutg drawn \p zoomRect (which is out widget 424 * coordinates). 425 */ 426 void zoomOut( const QRectF & zoomRect ); 427 /** 428 * Translates the view by \p dx, \p dy (in widget coordinates). 429 */ 430 void translateView( int dx, int dy ); 431 /** 432 * Finds the plot (if any) under the last mouse pos as recorded by 433 * updateCrosshairPosition(). This sets csmode, cstype, csparam. If no plot 434 * was found, then csmode is set to -1. 435 * \return the function position of the closest plot if one was found. 436 */ 437 QPointF getPlotUnderMouse(); 438 /** 439 * Finds the closest point to \p pos (which is in real coordinates) to 440 * the given function. 441 * \return the parametization (angle, x or t) that gives the closest 442 * point. 443 */ 444 double getClosestPoint( const QPointF & pos, const Plot & plot ); 445 /** 446 * Calculates the pixel distance from \p pos to the display point of the 447 * given function at \p x. 448 */ 449 double pixelDistance( const QPointF & pos, const Plot & plot, double x, bool updateFunction ); 450 /** 451 * \param overlapEdge whether to give values that are slightly either 452 * side of the view; this is useful for thick pens 453 * \return an appropriate xmin value for the given function 454 * plotting. 455 */ 456 double getXmin( Function * function, bool overlapEdge = false ); 457 /** 458 * \param overlapEdge whether to give values that are slightly either 459 * side of the view; this is useful for thick pens 460 * \return an appropriate xmax value for the given function for 461 * plotting. 462 */ 463 double getXmax( Function * function, bool overlapEdge = false ); 464 465 /** 466 * How to behave in the *ToPixel functions. 467 */ 468 enum ClipBehaviour 469 { 470 ClipAll, ///< Clips any points going over the edge of the diagram 471 ClipInfinite ///< Clips only infinite and NaN points going over the edge 472 }; 473 /** 474 * @name Transformations 475 * These functions convert real coordinates to pixel coordinates and vice 476 * versa. 477 */ 478 double xToPixel( double x, ClipBehaviour clipBehaviour = ClipAll, double xIfNaN = 0 ); 479 double yToPixel( double y, ClipBehaviour clipBehaviour = ClipAll, double yIfNaN = 0 ); 480 QPointF toPixel( const QPointF & real, ClipBehaviour clipBehaviour = ClipAll, const QPointF & pixelIfNaN = QPointF() ); 481 double xToReal( double x ); 482 double yToReal( double y ); 483 QPointF toReal( const QPointF & pixel ); 484 bool xclipflg; ///< clipflg is set to 1 if the plot is out of the plot area. 485 bool yclipflg; ///< clipflg is set to 1 if the plot is out of the plot area. 486 /** 487 * Contains the settings for drawing the area under a graph (when 488 * calculating the area from function tools. 489 */ 490 IntegralDrawSettings m_integralDrawSettings; 491 /** 492 * Separation distance between the grid lines. 493 */ 494 Value ticSepX, ticSepY; 495 /** 496 * Positions of the first grid line. 497 */ 498 double ticStartX, ticStartY; 499 500 /** 501 * Mouse pointer previous for zooming. 502 */ 503 QPoint m_previousMouseMovePos; 504 505 QPointF m_crosshairPixelCoords; 506 QPointF m_crosshairPosition; ///< in real coordinates 507 508 /** 509 * The t- or x- (angle) coordinate of the traced curve - when tracing a 510 * polar or parametric curve. 511 */ 512 double m_trace_x; 513 /** 514 * When tracing a Cartesian plot and the trace position nears the 515 * x-axis, an attempt to find a root will be found. If found, this will 516 * be set to true, and no further attempts will be made at finding a 517 * root. Once the plot position moves away from the x-axis again, this 518 * will be set to false. 519 */ 520 bool m_haveRoot; 521 522 /// @return whether cspos is in the range of the view or in the custom range for the given \p plot 523 bool crosshairPositionValid( Function * plot ) const; 524 525 /// represents the Printer options set by user in the Print Dialog 526 /// @see KPrinterDlg 527 bool m_printHeaderTable; 528 bool m_printBackground; 529 double m_printWidth; 530 double m_printHeight; 531 /// if stop_calculating is true, the user has canceled drawing of an integral graph 532 bool m_stopCalculating; 533 /// the background color of the graph 534 QColor m_backgroundColor; 535 ///buffer the current window so all functions don't need to be re-drawn 536 QPixmap buffer; 537 /// the popup menu 538 QMenu *m_popupMenu; 539 /// The pointer to the popup menu's title 540 QAction *m_popupMenuTitle; 541 /// is set to true if an integral is calculated 542 bool m_isDrawing; 543 /** 544 * Describes the state of the popup menu. 545 */ 546 enum PopupStatus 547 { 548 NoPopup, 549 Popup, 550 PopupDuringTrace 551 }; 552 ///status of the popup menu 553 PopupStatus m_popupMenuStatus; 554 /// False if KmPlot is started as a program, otherwise true 555 bool const m_readonly; 556 /// For drawing diagram labels 557 QFont m_labelFont; 558 /** 559 * The resolution of label positioning. 560 */ 561 static const int LabelGridSize = 50; 562 /** 563 * Indicate which parts of the diagram have content (e.g. axis or 564 * plots), so that they can be avoided when drawing diagram labels 565 */ 566 bool m_usedDiagramArea[LabelGridSize][LabelGridSize]; 567 /** 568 * Marks the given diagram rectangle (in screen coords) as 'used'. 569 */ 570 void markDiagramAreaUsed( const QRectF & rect ); 571 /** 572 * Marks the given diagram point (in screen coords) as 'used'. 573 */ 574 void markDiagramPointUsed( const QPointF & point ); 575 /** 576 * \return the m_usedDiagramArea coords for the screen rect. 577 */ 578 QRect usedDiagramRect( const QRectF & rect ) const; 579 /** 580 * \return the cost of occupying the given rectangle (as in whether it 581 * overlaps other diagram content, etc). 582 */ 583 int rectCost( QRectF rect ) const; 584 585 enum ZoomMode 586 { 587 Normal, ///< no zooming 588 AnimatingZoom, ///< animating a current zooming 589 ZoomIn, ///< zoom in 590 ZoomOut, ///< zoom out 591 ZoomInDrawing, ///< drawing a rectangle for zooming in 592 ZoomOutDrawing, ///< drawing a rectangle for zooming out 593 AboutToTranslate, ///< user has clicked on an empty spot, but hasn't moved the mouse yet 594 Translating ///< dragging the view with the mouse 595 }; 596 597 /// The current editing status 598 ZoomMode m_zoomMode; 599 /// for zoom-mode 600 QPoint m_zoomRectangleStart; 601 /// for animating zoom; contains the rectangle (in real coordinates) to draw 602 QRectF m_animateZoomRect; 603 /// for translating the view via dragging 604 QPoint m_prevDragMousePos; 605 /// timer that is started when the mouse is pressed 606 QElapsedTimer * m_mousePressTimer; 607 /** Current plot viewport. */ 608 const QRectF getViewport(); 609 610 /** 611 * The rectangle (in painter, and hence pixel, coordinates) that the 612 * plots must be in. This is also the size of the image being drawn to, 613 * since the painter remains untransformed. 614 */ 615 QRect m_clipRect; 616 /** 617 * This matrix transforms from real coordinates to painter coordinates. 618 * (Note that the painter does not have any transformation applied). 619 */ 620 QMatrix m_realToPixel; 621 /** 622 * The inverse matrix of m_realToPixel; it maps from pixel coordinates 623 * to real X-Y coordinates. 624 */ 625 QMatrix m_pixelToReal; 626 627 QString m_statusBarText[4]; 628 629 enum Cursor { CursorWait, CursorBlank, CursorArrow, CursorCross, CursorMagnify, CursorLessen, CursorMove }; 630 Cursor m_prevCursor; 631 632 static View * m_self; 633 634 KTextEdit * m_textEdit; ///< Contains m_textDocument 635 QTextDocument * m_textDocument; ///< Used for layout of axis labels 636 637 /// Accumulates mouse or trackpad scrolling to enable Ctrl+mouse wheel scaling on faulty devices 638 int m_AccumulatedDelta; 639 640 /// Animation for the viewport 641 QPropertyAnimation * m_viewportAnimation; 642 643 /// Finishes animation of the viewport and get the View back to the Normal mode 644 void finishAnimation( const QRectF & rect ); 645 646 private slots: 647 void setViewport( const QRectF & rect ); 648 }; 649 650 #endif // View_included 651