Logo Search packages:      
Sourcecode: kdeedu-kde4 version File versions  Download package

view.cpp

/*
* KmPlot - a math. function plotter for the KDE-Desktop
*
* Copyright (C) 1998, 1999, 2000, 2002  Klaus-Dieter Möller <kd.moeller@t-online.de>
*                     2006 David Saxton <david@bluehaze.org>
*
* This file is part of the KDE Project.
* KmPlot is part of the KDE-EDU Project.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*
*/

#include "view.h"

// Qt includes
#include <QAbstractTextDocumentLayout>
#include <qbitmap.h>
#include <qcursor.h>
#include <qdatastream.h>
#include <QPainter>
#include <qslider.h>
#include <qtimer.h>
#include <QPixmap>
#include <QPaintEvent>
#include <QKeyEvent>
#include <QEvent>
#include <QList>
#include <QResizeEvent>
#include <QTextEdit>
#include <QTime>
#include <QMouseEvent>

// KDE includes
#include <kaction.h>
#include <kactioncollection.h>
#include <kdebug.h>
#include <kglobal.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kmenu.h>

// local includes
#include "functioneditor.h"
#include "functiontools.h"
#include "settings.h"
#include "ksliderwindow.h"
#include "maindlg.h"
#include "parameteranimator.h"
#include "viewadaptor.h"
#include "xparser.h"


// other includes
#include <assert.h>
#include <cmath>

#if defined(Q_CC_MINGW)
using namespace __gnu_cxx;
#endif

//BEGIN nan & inf

#if defined(__APPLE__) || defined(_MSC_VER)
// work around an OSX <cmath> bug; is there a proper way to fix this?
#ifndef isnan
extern "C" int isnan(double);
#endif
#ifndef isinf
extern "C" int isinf(double);
#endif
#endif


#ifdef __osf__
#include <nan.h>
#define isnan(x) IsNAN(x)
#define isinf(x) IsINF(X)
#endif

#ifdef USE_SOLARIS
#include <ieeefp.h>
int isinf(double x)
{
      return !finite(x) && x==x;
}
#endif
// only msvc needs this (sometimes)
#ifdef _MSC_VER
#ifndef KDEWIN32_VERSION
int isinf(double x)
{
        return !finite(x) && x==x;
}
#endif  // KDEWIN32_VERSION
#endif //END _WIN32
//END nan & inf


// does for real numbers what "%" does for integers
double realModulo( double x, double mod )
{
      return x - floor(x/mod)*mod;
}


//BEGIN class View
View * View::m_self = 0;

00123 View::View( bool readOnly, KMenu * functionPopup, QWidget* parent )
      : QWidget( parent ),
        buffer( width(), height() ),
        m_popupMenu( functionPopup ),
        m_readonly( readOnly )
{
      assert( !m_self ); // this class should only be constructed once
      m_self = this;
    setAttribute( Qt::WA_StaticContents );
      
      m_haveRoot = false;
      m_xmin = m_xmax = m_ymin = m_ymax = 0.0;
      m_printHeaderTable = false;
      m_printBackground = false;
      m_printWidth = 0.0;
      m_printHeight = 0.0;
      m_stopCalculating = false;
      m_isDrawing = false;
      m_popupMenuStatus = NoPopup;
      m_zoomMode = Normal;
      m_prevCursor = CursorArrow;
      m_backgroundColor = Settings::backgroundcolor();
      
      m_textEdit = new QTextEdit;
      m_textEdit->setWordWrapMode( QTextOption::NoWrap );
      m_textEdit->setLineWrapMode( QTextEdit::NoWrap );
      m_textDocument = m_textEdit->document();

      m_mousePressTimer = new QTime();

    new ViewAdaptor(this);
    QDBusConnection::sessionBus().registerObject("/view", this);

      setMouseTracking( true );
      m_sliderWindow = 0;
      
      m_popupMenuTitle = m_popupMenu->addTitle( "" );
}


View::~View()
{
      m_textEdit->deleteLater();
      delete XParser::self();
      delete m_mousePressTimer;
}


00171 void View::initDrawLabels()
{
      m_labelFont = Settings::labelFont();
      
      for ( int i = 0; i < LabelGridSize; ++i )
            for ( int j = 0; j < LabelGridSize; ++j )
                  m_usedDiagramArea[i][j] = false;
      
      // Add the axis
      double x = xToPixel( 0 );
      double y = yToPixel( 0 );
      
      double x0 = xToPixel( m_xmin );
      double x1 = xToPixel( m_xmax );
      double y0 = yToPixel( m_ymin );
      double y1 = yToPixel( m_ymax );
      
      // x-axis
      markDiagramAreaUsed( QRectF( x-20, y0, 40, y1-y0 ) );
      
      // y-axis
      markDiagramAreaUsed( QRectF( x0, y-20, x1-x0, 40 ) );
}


00196 double View::niceTicSpacing( double length_mm, double range )
{
      Q_ASSERT_X( range > 0, "View::niceTicSpacing", "Range must be positive" );
      
      if ( length_mm <= 0 )
      {
            // Don't assert, as we can at least handle this situation - and it can
            // happen with extreme zooms
            
            kWarning() << "Non-positive length: length_mm="<<length_mm;
            length_mm = 120;
      }
      
      // Custom case for trigonemtric scaled
      if ( qFuzzyCompare( range, 4*M_PI ) )
            return M_PI/2;
      
      // Aim to space the tics by around 16 mm
      double target = range * 16.0 / length_mm;
      
      // The scaling required to bring target to a number between 1 and 10
      double scale = pow( 10, -std::floor(log(target)/log(10)) );
      
      // Calculate the first digit of target, e.g. if target is 0.0352, then leading will be set to 3
      int leading = int(target * scale);
      
      if ( leading == 1 )
            return 1/scale;
      else if ( leading >= 2 && leading <= 4 )
            return 2/scale;
      else
            return 5/scale;
}


00231 void View::initDrawing( QPaintDevice * device, PlotMedium medium )
{
      switch ( medium )
      {
            case SVG:
            case Screen:
            {
                  m_clipRect = QRect( 0, 0, width(), height() );
                  break;
            }

            case Printer:
            {
                  double inchesPerMeter = 100.0/2.54;
                  
                  int pixels_x = int(m_printWidth * device->logicalDpiX() * inchesPerMeter);
                  int pixels_y = int(m_printHeight * device->logicalDpiY() * inchesPerMeter);
                  
                  m_clipRect = QRect( 0, 0, pixels_x, pixels_y );
                  break;
            }
            
            case Pixmap:
            {
                  QPixmap * pic = static_cast<QPixmap*>(device);
                  m_clipRect = pic->rect();
                  break;
            }
      }
      
      
      if ( m_clipRect.width() <= 0 || m_clipRect.height() <= 0 )
      {
            kWarning() << "Invalid clip rect: m_clipRect="<<m_clipRect;
            return;
      }
            
      
      //BEGIN get X/Y range
      m_xmin = XParser::self()->eval( Settings::xMin() );
      m_xmax = XParser::self()->eval( Settings::xMax() );
      
      if ( m_xmax <= m_xmin || !finite(m_xmin) || !finite(m_xmax) )
      {
            m_xmin = -8;
            m_xmax = +8;
      }
      
      m_ymin = XParser::self()->eval( Settings::yMin() );
      m_ymax = XParser::self()->eval( Settings::yMax() );
      if ( m_ymax <= m_ymin || !finite(m_ymin) || !finite(m_ymax) )
      {
            m_ymin = -8;
            m_ymax = +8;
      }
      //END get X/Y range
      
      
      //BEGIN calculate scaling matrices
      m_realToPixel.reset();
      m_realToPixel.scale( m_clipRect.width()/(m_xmax-m_xmin), m_clipRect.height()/(m_ymin-m_ymax) );
      m_realToPixel.translate( -m_xmin, -m_ymax );
      
      m_pixelToReal = m_realToPixel.inverted();
      //END calculate scaling matrices
      
      
      
      //BEGIN get Tic Separation
      if ( Settings::xScalingMode() == 0 )
      {
            double length = pixelsToMillimeters( xToPixel( m_xmax ), device );
            double spacing = niceTicSpacing( length, m_xmax-m_xmin );
            ticSepX.updateExpression( spacing );
      }
      else
            ticSepX.updateExpression( Settings::xScaling() );

      if ( Settings::yScalingMode() == 0 )
      {
            double length = pixelsToMillimeters( yToPixel( m_ymin ), device );
            double spacing = niceTicSpacing( length, m_ymax-m_ymin );
            ticSepY.updateExpression( spacing );
      }
      else
            ticSepY.updateExpression( Settings::yScaling() );
      
      ticStartX = ceil(m_xmin/ticSepX.value())*ticSepX.value();
      ticStartY = ceil(m_ymin/ticSepY.value())*ticSepY.value();
      //END get Tic Separation
      
      
      
      //BEGIN get colours
      m_backgroundColor = Settings::backgroundcolor();
      if ( !m_backgroundColor.isValid() )
            m_backgroundColor = Qt::white;
      //END get colours

      
      XParser::self()->setAngleMode( (Parser::AngleMode)Settings::anglemode() );
      
      initDrawLabels();
}


00337 void View::draw( QPaintDevice * dev, PlotMedium medium )
{
      if ( m_isDrawing )
            return;
      
      m_isDrawing = true;
      updateCursor();
      initDrawing( dev, medium );
      
      QPainter painter( dev );

      switch ( medium )
      {
            case SVG:
            case Screen:
                  break;

            case Printer:
            {
                  if ( m_printHeaderTable )
                        drawHeaderTable( &painter );
                  
                  if ( m_printBackground )
                        painter.fillRect( m_clipRect,  m_backgroundColor); //draw a colored background
                  
                  break;
            }
            
            case Pixmap:
            {
                  QPixmap * pic = static_cast<QPixmap*>(dev);
                  pic->fill(m_backgroundColor);
                  break;
            }
      }
      
      painter.setClipRect( m_clipRect );

      
      //BEGIN draw diagram background stuff
      painter.setRenderHint( QPainter::Antialiasing, true );
      
      drawGrid( &painter );
      if ( Settings::showAxes() )
            drawAxes( &painter );
      if( Settings::showLabel() )
            drawLabels( &painter );
      //END draw diagram background stuff
      
      
      //BEGIN draw the functions
      m_stopCalculating = false;

      // Antialiasing slows down rendering a lot, so turn it off if we are
      // sliding the view about
      painter.setRenderHint( QPainter::Antialiasing, m_zoomMode != Translating );
      
      double numPlots = XParser::self()->m_ufkt.size();
      double at = -1;
      foreach ( Function * function, XParser::self()->m_ufkt )
      {
            at += 1;
            
            if ( m_stopCalculating )
                  break;
            
//          QDBusInterface( QDBus::sessionBus().baseService(), "/kmplot", "org.kde.kmplot.KmPlot" ).call( QDBus::Block, "setDrawProgress", at/numPlots );

            if ( function->type() == Function::Implicit )
                  drawImplicit( function, & painter );
            else
                  drawFunction( function, & painter );
      }
//    QDBusInterface( QDBus::sessionBus().baseService(), "/kmplot", "org.kde.kmplot.KmPlot" ).call( QDBus::Block, "setDrawProgress", 1.0 );
      
      drawFunctionInfo( & painter );
      
      m_isDrawing=false;
      //END draw the functions
      
      // Reset are stuff back to the screen stuff
      initDrawing( & buffer, Screen );

      updateCursor();
}


//BEGIN coordinate mapping functions
QPointF View::toPixel( const QPointF & real, ClipBehaviour clipBehaviour, const QPointF & pixelIfNaN )
{
      xclipflg = false;
      yclipflg = false;
      
      QPointF pixel = m_realToPixel.map( real );
      double x = pixel.x();
      double y = pixel.y();
      
      if ( isnan(x) )
      {
            xclipflg = true;
            x = pixelIfNaN.x();
      }
      else if ( clipBehaviour == ClipAll )
      {
            if ( x<0 )
            {
                  xclipflg = true;
                  x = 0;
            }
            else if ( x > m_clipRect.right() )
            {
                  xclipflg = true;
                  x = m_clipRect.right();
            }
      }
      else
      {
            if ( isinf(x) == -1 )
                  x = 0;
                  
            else if ( isinf(x) == 1 )
                  x = m_clipRect.right();
      }
      
      if ( isnan(y) )
      {
            yclipflg = true;
            y = pixelIfNaN.y();
      }
      else if ( clipBehaviour == ClipAll )
      {
            if ( y<0 )
            {
                  yclipflg = true;
                  y = 0;
            }
            else if ( y > m_clipRect.bottom() )
            {
                  yclipflg = true;
                  y = m_clipRect.bottom();
            }
      }
      else
      {
            if ( isinf(y) == -1 )
                  y = 0;
            
            else if ( isinf(y) == 1 )
                  y = m_clipRect.bottom();
      }
      
      // Make sure that x and y are *reasonably* bounded at least, even if they're not infinite
      double min_x = -1e3 * m_clipRect.width();
      double max_x = +1e3 * m_clipRect.width();
      double min_y = -1e3 * m_clipRect.height();
      double max_y = +1e3 * m_clipRect.height();
      
      if ( x < min_x )
            x = min_x;
      else if ( x > max_x )
            x = max_x;
      
      if ( y < min_y )
            y = min_y;
      else if ( y > max_y )
            y = max_y;
      
      return QPointF( x, y );
}

double View::xToPixel( double x, ClipBehaviour clipBehaviour, double xIfNaN )
{
      return toPixel( QPointF( x, 0 ), clipBehaviour, QPointF( xIfNaN, 0 ) ).x();
}

double View::yToPixel( double y, ClipBehaviour clipBehaviour, double yIfNaN )
{
      return toPixel( QPointF( 0, y ), clipBehaviour, QPointF( 0, yIfNaN ) ).y();
}


QPointF View::toReal( const QPointF & pixel )
{
      return m_pixelToReal.map( pixel );
}

double View::xToReal( double x )
{
      return toReal( QPointF( x, 0 ) ).x();
}

double View::yToReal( double y )
{
      return toReal( QPointF( 0, y ) ).y();
}
//END coordinate mapping functions


00535 void View::drawAxes( QPainter* painter )
{
      double axesLineWidth = millimetersToPixels( Settings::axesLineWidth(), painter->device() );
      double ticWidth = millimetersToPixels( Settings::ticWidth(), painter->device() );
      double ticLength = millimetersToPixels( Settings::ticLength(), painter->device() );
      QColor axesColor = Settings::axesColor();
      
      painter->save();
      
      double arrowWidth = ticLength*1.4;
      double arrowLength = arrowWidth*2.8;
      
      painter->setPen( QPen( axesColor, axesLineWidth ) );
      painter->setBrush( axesColor );
      
      //BEGIN draw x axis
      double a = m_clipRect.right()-ticLength;
      double b = yToPixel(0.);
      
      double b_max = m_clipRect.bottom() - ticLength;
      if ( b < ticLength )
            b = ticLength;
      else if ( b > b_max )
            b = b_max;
      
      // horizontal line
      painter->Lineh(ticLength, b, a);
      
      // arrow head
      if ( Settings::showArrows())
      {
            a = m_clipRect.right();
            
            QPolygonF p(3);
            p[0] = QPointF( a, b );
            p[1] = QPointF( a-arrowLength, b+arrowWidth );
            p[2] = QPointF( a-arrowLength, b-arrowWidth );
            painter->drawPolygon( p );
      }
      //END draw x axis

      
      //BEGIN draw y axis
      a = xToPixel(0.);
      b = ticLength;
      
      double a_max = m_clipRect.right() - ticLength;
      if ( a < ticLength )
            a = ticLength;
      else if ( a > a_max )
            a = a_max;
      
      // vertical line
      painter->Linev(a, m_clipRect.bottom()-ticLength, b);
      
      // arrow head
      if ( Settings::showArrows() )
      {
            b = 0;
            
            QPolygonF p(3);
            p[0] = QPointF( a, b );
            p[1] = QPointF( a-arrowWidth, b+arrowLength );
            p[2] = QPointF( a+arrowWidth, b+arrowLength );
            painter->drawPolygon( p );
      }
      //END draw y axis
      
      painter->restore();
      
      painter->setPen( QPen( axesColor, ticWidth ) );
      
      double da = yToPixel(0)-ticLength;
      double db = yToPixel(0)+ticLength;
      double d = ticStartX;
      if (da<0)
      {
            a = 0;
            b = 2*ticLength;
      }
      else if(db>(double)m_clipRect.bottom())
      {
            b = m_clipRect.bottom();
            a = m_clipRect.bottom()-2*ticLength;
      }
      else
      {
            a = da;
            b = db;
      }

      while(d<m_xmax-ticSepX.value()/2.)
      {
            double d_pixel = xToPixel(d);
            if ( d_pixel > ticLength )
                  painter->Linev(xToPixel(d), a, b);
            d+=ticSepX.value();
      }

      da = xToPixel(0)-ticLength;
      db = xToPixel(0)+ticLength;
      d = ticStartY;
      if (da<0)
      {
            a = 0;
            b = 2*ticLength;
      }
      else if(db>(double)m_clipRect.right())
      {
            b = m_clipRect.right();
            a = m_clipRect.right()-2*ticLength;
      }
      else
      {
            a = da;
            b = db;
      }

      while(d<m_ymax-ticSepY.value()/2.)
      {
            double d_pixel = yToPixel(d);
            if ( d_pixel < m_clipRect.bottom()-ticLength )
                  painter->Lineh(a, d_pixel, b);
            d+=ticSepY.value();
      }
}


00663 void View::drawGrid( QPainter* painter )
{
      QColor gridColor = Settings::gridColor();
      
      double gridLineWidth = millimetersToPixels( Settings::gridLineWidth(), painter->device() );
      QPen pen( gridColor, gridLineWidth );

      painter->setPen(pen);
      
      enum GridStyle
      {
            GridNone,
            GridLines,
            GridCrosses,
            GridPolar
      };
      GridStyle gridMode = (GridStyle)Settings::gridStyle();
      
      switch ( gridMode )
      {
            case GridNone:
                  break;
                  
            case GridLines:
            {
                  for ( double d = ticStartX; d <= m_xmax; d += ticSepX.value() )
                        painter->Linev(xToPixel(d), m_clipRect.bottom(), 0);
                  
                  for ( double d = ticStartY; d <= m_ymax; d += ticSepY.value() )
                        painter->Lineh(0, yToPixel(d), m_clipRect.right());
                  
                  break;
            }
            
            case GridCrosses:
            {
                  int const dx = 5;
                  int const dy = 5;

                  for( double x = ticStartX; x<m_xmax; x+=ticSepX.value() )
                  {
                        double a = xToPixel(x);
                        for( double y=ticStartY; y<m_ymax; y+=ticSepY.value())
                        {
                              double b = yToPixel(y);
                              painter->Lineh(a-dx, b, a+dx);
                              painter->Linev(a, b-dy, b+dy);
                        }
                  }
                  
                  break;
            }
            
            case GridPolar:
            {
                  // Note: 1.42 \approx sqrt(2)
                  
                  double xMax = qMax( qAbs(m_xmin), qAbs(m_xmax) ) * 1.42;
                  double yMax = qMax( qAbs(m_ymin), qAbs(m_ymax) ) * 1.42;
                  double rMax = qMax( xMax, yMax );
                  
                  // The furthest pixel away from the origin
                  double pixelMax = qMax( xMax*m_realToPixel.m11(), yMax*m_realToPixel.m22() );
                  
                  double ticSep = qMin( ticSepX.value(), ticSepY.value() );
                  double r = ticSep;
                  
                  while ( r < rMax )
                  {
                        QRectF rect;
                        rect.setTopLeft(        toPixel( QPointF( -r, r ), ClipInfinite ) ); 
                        rect.setBottomRight(    toPixel( QPointF( r, -r ), ClipInfinite ) );
                        painter->drawEllipse( rect );
                        r += ticSep;
                  }
                  
                  for ( double theta = 0; theta < 2.0*M_PI; theta += M_PI/12.0 )
                  {
                        QPointF start = toPixel( QPointF( 0, 0 ), ClipInfinite );
                        QPointF end = start + QPointF( pixelMax * cos(theta), pixelMax * sin(theta) );
                        
                        painter->drawLine( start, end );
                  }
                  
                  break;
            }
      }
}


00753 void View::drawLabels( QPainter *painter )
{
      // Determine which letters to use for the axes
      QString xLabel;
      QString yLabel;
      bool xLabelsIdentical = true;
      bool yLabelsIdentical = true;
      
      foreach ( Function * function, XParser::self()->m_ufkt )
      {
            QString x, y;
            
            switch ( function->type() )
            {
                  case Function::Cartesian:
                  case Function::Differential:
                        x = function->eq[0]->variables().isEmpty() ? QString() : function->eq[0]->variables()[0];
                        y = function->eq[0]->name();
                        break;
                        
                  case Function::Implicit:
                        x = function->eq[0]->variables().isEmpty()  ? QString() : function->eq[0]->variables()[0];
                        y = (function->eq[0]->variables().size()<2) ? QString() : function->eq[0]->variables()[1];
                        break;
                        
                  case Function::Parametric:
                  case Function::Polar:
                        continue;
            }
            
            if ( xLabel.isEmpty() )
                  xLabel = x;
            else if ( xLabel != x )
                  xLabelsIdentical = false;
            
            if ( yLabel.isEmpty() )
                  yLabel = y;
            else if ( yLabel != y )
                  yLabelsIdentical = false;
      }
      
      if ( !xLabelsIdentical || xLabel.isEmpty() )
            xLabel = "x";
      if ( !yLabelsIdentical || yLabel.isEmpty() )
            yLabel = "y";
      
      
      QColor axesColor = Settings::axesColor();
      int const dx=10;
      int const dy=15;
      QFont const font = Settings::axesFont();
      painter->setFont(font);
      m_textDocument->setDefaultFont( font );
      
      double const x=xToPixel(0.);
      double const y=yToPixel(0.);
      

      QRectF drawRect;
      
      // Whether the x-axis is along the top of the view
      // and the y-axis is along the right edge of the view
      bool axesInTopRight = ( m_ymax<ticSepY.value() && m_xmax<ticSepX.value() );
      
      // for "x" label
      double endLabelWidth = 0;
      
      int flags = Qt::AlignVCenter|Qt::TextDontClip|Qt::AlignRight;
      
      // Draw x label
      if ( axesInTopRight )
            drawRect = QRectF( xToPixel(m_xmax)-(3*dx), y+dy, 0, 0 );
      else if (m_ymin>-ticSepY.value())
            drawRect = QRectF( xToPixel(m_xmax)-dx, y-dy, 0, 0 );
      else
            drawRect = QRectF( xToPixel(m_xmax)-dx, y+dy, 0, 0 );
      painter->drawText( drawRect, flags, xLabel );
      endLabelWidth = m_clipRect.right() - painter->boundingRect( drawRect, flags, xLabel ).right();
      
      
      // Draw y label
      if ( axesInTopRight )
            drawRect = QRectF( x-dx, yToPixel(m_ymax)+(2*dy), 0, 0 );
      else if (m_xmin>-ticSepX.value())
            drawRect = QRectF( x+(2*dx), yToPixel(m_ymax)+dy, 0, 0 );
      else
            drawRect = QRectF( x-dx, yToPixel(m_ymax)+dy, 0, 0 );
      painter->drawText( drawRect, flags, yLabel );
      
      
      
      // Draw the numbers on the axes
      drawXAxisLabels( painter, pixelsToMillimeters( endLabelWidth, painter->device() ) );
      drawYAxisLabels( painter );
}


/**
 * If \p d is a rational multiple of pi, then return a string appropriate for
 * displaying it in fraction form.
 */
QString tryPiFraction( double d, double sep )
{
      // Avoid strange bug where get pi at large separation
      if ( sep > 10 )
            return QString();
      
      bool positive = d > 0;
      
      d /= M_PI;
      if ( !positive )
            d = -d;
      
      if ( d < 1e-2 )
            return QString();
      
      // Try denominators from 1 to 6
      for ( int denom = 1; denom <= 6; ++denom )
      {
            if ( realModulo( d * denom, 1 ) > 1e-3*sep )
                  continue;
            
            int num = qRound( d * denom );
            
            QString s = positive ? "+" : QString(MinusSymbol);
            if ( num != 1 )
                  s += QString::number( num );
            
            s += PiSymbol;
            
            if ( denom != 1 )
                  s += '/' + QString::number( denom );
            
            return s;
      }
      
      return QString();
}


00893 void View::drawXAxisLabels( QPainter *painter, double endLabelWidth_mm )
{
      QColor axesColor = Settings::axesColor();
      int const dy = 8;
      
      double const y = yToPixel(0.);
      
      // Used to ensure that labels aren't drawn too closely together
      // These numbers contain the pixel position of the left and right endpoints of the last label
      double last_x_start = -1e3; // (just a large negative number)
      double last_x_end = -1e3; // (just a large negative number)
      
      
      // The strange label drawing order here is so that the labels eitherside
      // of zero are always drawn, and then the other labels are drawn if there
      // is enough space
      
      bool first = true;
      bool forwards = true;
      double d;
      
      while ( true )
      {
            if ( first )
            {
                  // Draw x>0 first
                  d = qMax( ticSepX.value(), ticStartX );
                  last_x_end = xToPixel(0);
                  first = false;
            }
            else
            {
                  if ( forwards )
                  {
                        d += ticSepX.value();
                        if ( d > m_xmax )
                        {
                              // Continue on other side
                              d = qMin( -ticSepX.value(), ticStartX + floor((m_xmax-m_xmin)/ticSepX.value())*ticSepX.value() );
                              last_x_start = xToPixel(0);
                              forwards = false;
                        }
                  }
                  else
                  {
                        d -= ticSepX.value();
                        if ( d < m_xmin )
                              return;
                  }
            }
            
            // Don't draw too close to the left edge if the y axis is there
            if ( m_xmin >= -ticSepX.value() && (d-m_xmin) <= ticSepX.value() )
                  continue;
            
            QString s = tryPiFraction( d, ticSepX.value() );
            
            if ( s.isEmpty() )
                  s = posToString( d, ticSepX.value()*5, View::ScientificFormat, axesColor );
                  
            m_textDocument->setHtml( s );
            double idealWidth = m_textDocument->idealWidth();
            double idealHeight = m_textDocument->size().height();
                  
            double x_pos = xToPixel(d)-(idealWidth/2)-4;
            if ( x_pos < 0 )
                  continue;
            
            double y_pos = y+dy;
            if ( (y_pos+idealHeight) > m_clipRect.bottom() )
                  y_pos = y-dy-idealHeight;
            
            double x_start = x_pos;
            double x_end = x_start + idealWidth;
            
            // Use a minimum spacing between labels
            if ( (last_x_start < x_start) && pixelsToMillimeters( x_start - last_x_end, painter->device() ) < 7 )
                  continue;
            if ( (last_x_start > x_start) && pixelsToMillimeters( last_x_start - x_end, painter->device() ) < 7 )
                  continue;
            
            // Don't draw too close to the right edge (has x axis label)
            if ( pixelsToMillimeters( m_clipRect.right()-x_end, painter->device() ) < endLabelWidth_mm+3 )
                  continue;
            
            last_x_start = x_start;
            last_x_end = x_end;
            
            QPointF drawPoint( x_pos, y_pos );
            painter->translate( drawPoint );
            m_textDocument->documentLayout()->draw( painter, QAbstractTextDocumentLayout::PaintContext() ); 
            painter->translate( -drawPoint );
      }
}


00989 void View::drawYAxisLabels( QPainter *painter )
{
      QColor axesColor = Settings::axesColor();
      int const dx = 12;
      
      double const x=xToPixel(0.);
      
      double d = ticStartY;
      long long n = (long long)ceil(m_ymin/ticSepY.value());
      for( ; d<m_ymax; d += ticSepY.value(), ++n )
      {
            // Don't draw zero
            if ( n == 0 )
                  continue;
            
            // Don't draw too close to top
            if ( (m_ymax-d) <= ticSepY.value()*0.6 )
                  continue;
            
            // Don't draw too close to bottom if the x axis is there
            if ( m_ymin > -ticSepY.value() && (d-m_ymin) <= ticSepY.value() )
                  continue;

            QString s = tryPiFraction( d, ticSepY.value() );
            
            if ( s.isEmpty() )
                  s = posToString( d, ticSepY.value()*5, View::ScientificFormat, axesColor );
            
            m_textDocument->setHtml( s );
                  
            double idealWidth = m_textDocument->idealWidth();
            double idealHeight = m_textDocument->size().height();
                  
            QPointF drawPoint( 0, yToPixel(d)-(idealHeight/2) );
                  
            if ( m_xmin > -ticSepX.value() )
            {
                  drawPoint.setX( x+dx );
            }
            else
            {
                  drawPoint.setX( x-dx-idealWidth );
                        
                  if ( drawPoint.x() < 0 )
                  {
                        // Don't draw off the left edge of the screen
                        drawPoint.setX( 0 );
                  }
            }
            
            // Shouldn't have the label cut off by the bottom of the view
            if ( drawPoint.y() + idealHeight > m_clipRect.height() )
                  continue;
                  
            painter->translate( drawPoint );
            m_textDocument->documentLayout()->draw( painter, QAbstractTextDocumentLayout::PaintContext() );
            painter->translate( -drawPoint );
      }
}


01050 double View::h( const Plot & plot ) const
{
      if ( (plot.plotMode == Function::Integral) || (plot.function()->type() == Function::Differential) )
            return plot.function()->eq[0]->differentialStates.step().value();
      
      double dx = (m_xmax-m_xmin)/m_clipRect.width();
      double dy = (m_ymax-m_ymin)/m_clipRect.height();
      
      switch ( plot.function()->type() )
      {
            case Function::Cartesian:
            case Function::Differential:
                  return dx;
                  
            case Function::Polar:
            case Function::Parametric:
            case Function::Implicit:
                  return qMin( dx, dy );
      }
      
      kWarning() << "Unknown coord\n";
      return qMin( dx, dy );
}


01075 double View::value( const Plot & plot, int eq, double x, bool updateFunction )
{
      Function * function = plot.function();
      assert( function );
      
      if ( updateFunction )
            plot.updateFunction();
      
      Equation * equation = function->eq[eq];
      
      double dx = h( plot );
      DifferentialState * state = plot.state();
      
      return XParser::self()->derivative( plot.derivativeNumber(), equation, state, x, dx );
}


01092 QPointF View::realValue( const Plot & plot, double x, bool updateFunction )
{
      Function * function = plot.function();
      assert( function );

      switch ( function->type() )
      {
            case Function::Differential:
            case Function::Cartesian:
            {
                  double y = value( plot, 0, x, updateFunction );
                  return QPointF( x, y );
            }

            case Function::Polar:
            {
                  double y = value( plot, 0, x, updateFunction );
                  return QPointF( y * lcos(x), y * lsin(x) );
            }

            case Function::Parametric:
            {
                  double X = value( plot, 0, x, updateFunction );
                  double Y = value( plot, 1, x, updateFunction );
                  return QPointF( X, Y );
            }
            
            case Function::Implicit:
            {
                  // Can only calculate the value when either x or y is fixed.
                  assert( function->m_implicitMode != Function::UnfixedXY );
                  
                  double val = value( plot, 0, x, updateFunction );
                  
                  if ( function->m_implicitMode == Function::FixedX )
                        return QPointF( function->x, val );
                  else
                        return QPointF( val, function->y );
            }
      }

      kWarning() << "Unknown function type!\n";
      return QPointF();
}


01138 double View::getXmin( Function * function, bool overlapEdge  )
{
      switch ( function->type() )
      {
            case Function::Parametric:
            case Function::Polar:
                  return function->dmin.value();
                  
            case Function::Implicit:
                  kWarning() << "You probably don't want to do this!\n";
                  // fall through
                  
            case Function::Differential:
            case Function::Cartesian:
            {
                  double min = m_xmin;
                  if ( overlapEdge )
                        min -= (m_xmax-m_xmin)*0.02;
                  
                  if ( function->usecustomxmin )
                        return qMax( min, function->dmin.value() );
                  else
                        return min;
            }
      }
      
      return 0;
}


01168 double View::getXmax( Function * function, bool overlapEdge )
{
      switch ( function->type() )
      {
            case Function::Parametric:
            case Function::Polar:
                  return function->dmax.value();
                  
            case Function::Implicit:
                  kWarning() << "You probably don't want to do this!\n";
                  // fall through
                  
            case Function::Differential:
            case Function::Cartesian:
            {
                  double max = m_xmax;
                  if ( overlapEdge )
                        max += (m_xmax-m_xmin)*0.02;
                  
                  if ( function->usecustomxmax )
                        return qMin( max, function->dmax.value() );
                  else
                        return max;
            }
      }
      
      return 0;
}

// #define DEBUG_IMPLICIT

#ifdef DEBUG_IMPLICIT
// Used in profiling root finding
int root_find_iterations;
int root_find_requests;
#endif


/**
 * For comparing points where two points close together are considered equal.
 */
01209 class FuzzyPoint
{
      public:
            FuzzyPoint( const QPointF & point )
            {
                  x = point.x();
                  y = point.y();
            }
            
            
            FuzzyPoint( double x, double y )
            {
                  FuzzyPoint::x = x;
                  FuzzyPoint::y = y;
            }
            
            
            bool operator < ( const FuzzyPoint & other ) const
            {
                  double du = qAbs(other.x - x) / dx;
                  double dv = qAbs(other.y - y) / dy;
                  
                  bool x_eq = (du < 1); // Whether the x coordinates are considered equal
                  bool y_eq = (dv < 1); // Whether the y coordinates are considered equal
                  
                  if ( x_eq && y_eq )
                  {
                        // Points are close together.
                        return false;
                  }
                  
                  bool x_lt = !x_eq && (x < other.x);
                  bool y_lt = !y_eq && (y < other.y);
                  
                  return ( x_lt || (x_eq && y_lt) );
            }
            
            double x;
            double y;
            
            static double dx;
            static double dy;
};
typedef QMap< FuzzyPoint, QPointF > FuzzyPointMap;

double FuzzyPoint::dx = 0;
double FuzzyPoint::dy = 0;


double SegmentMin = 0.1;
double SegmentMax = 6.0;


// The viewable area is divided up into square*squares squares, and the curve
// is traced around in each square.
// NOTE: it is generally a good idea to make this number prime
int squares = 19;

01267 void View::drawImplicit( Function * function, QPainter * painter )
{
      assert( function->type() == Function::Implicit );
      
#ifdef DEBUG_IMPLICIT
      QTime t;
      t.start();
      
      painter->setPen( Qt::black );
      
      for ( double i = 0; i <= squares; ++i )
      {
            double x = m_xmin + i * (m_xmax-m_xmin)/squares;
            double y = m_ymin + i * (m_ymax-m_ymin)/squares;
            
            painter->drawLine( toPixel( QPointF( m_xmin, y ), ClipInfinite ), toPixel( QPointF( m_xmax, y ), ClipInfinite ) );
            painter->drawLine( toPixel( QPointF( x, m_ymin ), ClipInfinite ), toPixel( QPointF( x, m_ymax ), ClipInfinite ) );
      }
      
      root_find_iterations = 0;
      root_find_requests = 0;
#endif
      
      // Need another function for investigating singular points
      Plot circular;
      QString fname( "f(x)=0" );
      XParser::self()->fixFunctionName( fname, Equation::Cartesian, -1 );
      circular.setFunctionID( XParser::self()->Parser::addFunction( fname, 0, Function::Cartesian ) );
      assert( circular.function() );
      
      const QList< Plot > plots = function->plots();
      foreach ( const Plot &plot, plots )
      {
            bool setAliased = false;
            if ( plot.parameter.type() == Parameter::Animated )
            {
                  // Don't use antialiasing, so that rendering is speeded up
                  if ( painter->renderHints() & QPainter::Antialiasing )
                  {
                        setAliased = true;
                        painter->setRenderHint( QPainter::Antialiasing, false );
                  }
            }
            
            painter->setPen( penForPlot( plot, painter ) );
            
            QList<QPointF> singular;
            
            for ( int i = 0; i <= squares; ++i )
            {
                  double y = m_ymin + i*(m_ymax-m_ymin)/double(squares);
                  
                  function->y = y;
                  function->m_implicitMode = Function::FixedY;
                  QList<double> roots = findRoots( plot, m_xmin, m_xmax, RoughRoot );
                  
                  foreach ( double x, roots )
                  {
#ifdef DEBUG_IMPLICIT
                        painter->setPen( QPen( Qt::red, painter->pen().width() ) );
#endif
                        drawImplicitInSquare( plot, painter, x, y, Qt::Horizontal, & singular );
                  }
                  
                  
                  
                  double x = m_xmin + i*(m_xmax-m_xmin)/double(squares);
                  
                  function->x = x;
                  function->m_implicitMode = Function::FixedX;
                  roots = findRoots( plot, m_ymin, m_ymax, RoughRoot );
                  
                  foreach ( double y, roots )
                  {     
#ifdef DEBUG_IMPLICIT
                        painter->setPen( QPen( Qt::blue, painter->pen().width() ) );
#endif
                        drawImplicitInSquare( plot, painter, x, y, Qt::Vertical, & singular );
                  }
            }
            
            // Sort out the imlpicit points
            FuzzyPointMap singularSorted;
            FuzzyPoint::dx = (m_xmax-m_xmin) * SegmentMin * 0.1 / m_clipRect.width();
            FuzzyPoint::dy = (m_ymax-m_ymin) * SegmentMin * 0.1 / m_clipRect.height();
            foreach ( const QPointF &point, singular )
                  singularSorted.insert( point, point );
            singular = singularSorted.values();
            
            foreach ( const QPointF &point, singular )
            {
                  // radius of circle around singular point
                  double epsilon = qMin( FuzzyPoint::dx, FuzzyPoint::dy );
                  
                  QString fstr;
                  fstr = QString("%1(x)=%2(%3+%6*cos(x),%4+%6*sin(x)%5)")
                              .arg( circular.function()->eq[0]->name() )
                              .arg( function->eq[0]->name() )
                              .arg( XParser::self()->number( point.x() ) )
                              .arg( XParser::self()->number( point.y() ) )
                              .arg( function->eq[0]->usesParameter() ? ',' + XParser::self()->number( function->k ) : QString() )
                              .arg( XParser::self()->number( epsilon ) );
                  
                  bool setFstrOk = circular.function()->eq[0]->setFstr( fstr );
                  assert( setFstrOk );
                  
                  QList<double> roots = findRoots( circular, 0, 2*M_PI / XParser::self()->radiansPerAngleUnit(), PreciseRoot );
                  
#ifdef DEBUG_IMPLICIT
                  kDebug() << "Singular point at (x,y)=("<<point.x()<<','<<point.y()<<")\n";
                  kDebug() << "fstr is    " << fstr;
                  kDebug() << "Found " << roots.size() << " roots.\n";
#endif
                  
                  foreach ( double t, roots )
                  {     
#ifdef DEBUG_IMPLICIT
                        painter->setPen( QPen( Qt::green, painter->pen().width() ) );
#endif
                        double x = point.x() + epsilon * lcos(t);
                        double y = point.y() + epsilon * lsin(t);
                        drawImplicitInSquare( plot, painter, x, y, 0, & singular );
                  }
            }
            
            
            if ( setAliased )
                  painter->setRenderHint( QPainter::Antialiasing, true );
      }
      
#ifdef DEBUG_IMPLICIT
      if ( root_find_requests != 0 )
            kDebug() << "Average iterations in root finding was " << root_find_iterations/root_find_requests;
      kDebug() << "Time taken was " << t.elapsed();
#endif
      
      XParser::self()->removeFunction( circular.functionID() );
}


// static
01408 double View::maxSegmentLength( double curvature )
{
      // Use a circle angle of 4 degrees to determine the maximum segment length
      // Also, limit the length to be between 0.1 and 6 pixels.
      
      double arc = 4 * (M_PI / 180);
      
      if ( curvature < 0 )
            curvature = -curvature;
      
      if ( curvature < 1e-20 )
            return SegmentMax; // very large circle
      
      double radius = 1.0/curvature;
      
      double segment = arc * radius;
      if ( segment < SegmentMin )
            segment = SegmentMin;
      else if ( segment > SegmentMax )
            segment = SegmentMax;
       
      return segment;
}


01433 void View::drawImplicitInSquare( const Plot & plot, QPainter * painter, double x, double y, Qt::Orientations orientation, QList<QPointF> * singular )
{
      plot.updateFunction();
      Plot diff1 = plot;
      diff1.differentiate();
      Plot diff2 = diff1;
      diff2.differentiate();
      
#ifdef DEBUG_IMPLICIT
      painter->save();
      painter->setPen( QPen( Qt::black, painter->pen().width() ) );
      QPointF tl = toPixel( QPointF( x, y ), ClipInfinite ) - QPoint( 2, 2 );
      painter->drawRect( QRectF( tl, QSizeF( 4, 4 ) ) );
      painter->restore();
#endif
      
      double x_side = (m_xmax-m_xmin)/squares;
      double y_side = (m_ymax-m_ymin)/squares;
      
      // Use a square around the root to bound the tracing
      // To start with, assume that tracing will go up,right. But this
      // might not be so, so the upper/lower boundaries may be adjusted depending
      // on where the tracing ends up
      double x_lower, x_upper, y_lower, y_upper;
      if ( orientation & Qt::Vertical )
      {
            x_lower = x;
            x_upper = x + x_side;
      }
      else
      {
            double x_prop = (x-m_xmin)/(m_xmax-m_xmin);
            x_lower = std::floor( x_prop * squares ) * x_side + m_xmin;
            x_upper = x_lower + x_side;
      }
      if ( orientation & Qt::Horizontal )
      {
            y_lower = y;
            y_upper = y + y_side;
      }
      else
      {
            double y_prop = (y-m_ymin)/(m_ymax-m_ymin);
            y_lower = std::floor( y_prop * squares ) * y_side + m_ymin;
            y_upper = y_lower + y_side;
      }
      
      // If during tracing, the root could not be found, then this will be set to true,
      // the route will be retraced using a smaller step size and it will attempt to find
      // a root again. If it fails for a second time, then tracing is finished.
      bool foundRootPreviously = true;
      
      // Used for focal points.
      double prevAngle = 0;
      int switchCount = 0;
      
      // This is so that the algorithm can "look ahead" to see what is coming up,
      // before drawing or committing itself to anything potentially bad
      QPointF prev2 = toPixel( QPointF( x, y ), ClipInfinite );
      QPointF prev1 = prev2;
      
      // Allow us to doubly retrace
      double prev_diff_x = 0;
      double prev_diff_y = 0;
      
      for ( int i = 0; i < 500; ++i ) // allow a maximum of 500 traces (to prevent possibly infinite loop)
      {
            if ( i == 500 - 1 )
            {
                  kDebug() << "Implicit: got to last iteration!\n";
            }
            
            // (dx, dy) is perpendicular to curve
            
            plot.function()->x = x;
            plot.function()->y = y;
            
            plot.function()->m_implicitMode = Function::FixedY;
            double dx = value( diff1, 0, x, false );
            
            plot.function()->m_implicitMode = Function::FixedX;
            double dy = value( diff1, 0, y, false );
            
            double k = pixelCurvature( plot, x, y );
            double segment_step = maxSegmentLength( k ) * pow( 0.5, switchCount );
            
            // If we couldn't find a root in the previous iteration, it was possibly
            // because we were using too large a step size. So reduce the step size
            // and try again.
            if ( !foundRootPreviously )
                  segment_step = qMin( segment_step/4, SegmentMin );
            
//          kDebug() << "k="<<k<<" segment_step="<<segment_step;
            
            QPointF p1 = toPixel( QPointF( x, y ),                ClipInfinite ) * painter->matrix();
            QPointF p2 = toPixel( QPointF( x+dx, y+dy ),    ClipInfinite ) * painter->matrix();
            double l = QLineF( p1, p2 ).length() / segment_step;
            
            if ( l == 0 )
            {
                  kDebug() << "length is zero!\n";
                  break;
            }
            
            // (tx, ty) is tangent to the curve in the direction that we are tracing
            double tx = -dy/l;
            double ty = dx/l;
            
            double angle = atan(ty/tx) + ((tx<0) ? M_PI : 0);
            double diff = realModulo( angle-prevAngle, 2*M_PI );
            
            bool switchedDirection = (i > 0) && (diff > (3./4.)*M_PI) && (diff < (5./4.)*M_PI);
            if ( switchedDirection )
            {
                  // Why do I care about suddenly changing the direction?
                  // Because the chances are, a attracting or repelling point has been reached.
                  // Even if not, it suggests that a smaller step size is needed. If we have
                  // switched direction and are already at the smallest step size, then note
                  // the dodgy point for further investigation and give up for now
                  
//                kDebug() << "Switched direction: x="<<x<<" switchCount="<<switchCount<<" segment_step="<<segment_step<<" i="<<i;
                  
                  // Use a step size much smaller than segment min to obtain good accuracy,
                  // needed for investigating the point further
                  if ( segment_step <= SegmentMin * 0.01 )
                  {
                        // Give up. Tell our parent function to investigate the point further
                        *singular << QPointF( x, y );
                        break;
                  }
                  
                  // Rewind the last tangent addition as well
                  x -= prev_diff_x;
                  y -= prev_diff_y;
                  
                  prev_diff_x = 0;
                  prev_diff_y = 0;
                  
                  switchCount += 2;
                  continue;
            }
            else
            {
                  // Reset the stepping adjustment
                  switchCount = qMax( 0, switchCount-1 );
                  prevAngle = angle;
//                kDebug() << "Didn't switch - x="<<x<<" segment_step="<<segment_step;
            }
            
            if ( i == 0 )
            {
                  // First trace; does the bounding square need adjusting?
                  
                  if ( (tx < 0) && (orientation & Qt::Vertical) )
                  {
                        x_lower -= x_side;
                        x_upper -= x_side;
                  }
                  
                  if ( (ty < 0) && (orientation & Qt::Horizontal) )
                  {
                        y_lower -= y_side;
                        y_upper -= y_side;
                  }
            }
            
            
            // The maximum tangent length before we end up outside our bounding square
            double max_tx, max_ty;
            if ( tx > 0 )
                  max_tx = x_upper - x;
            else
                  max_tx = x - x_lower;
            if ( ty > 0 )
                  max_ty = y_upper - y;
            else
                  max_ty = y - y_lower;
            
            
            // Does (tx,ty) need to be scaled to make sure the tangent stays inside the square?
            double scale = qMax( (tx==0) ? 0 : qAbs(tx)/max_tx, (ty==0) ? 0 : qAbs(ty)/max_ty );
            bool outOfBounds = scale > 1;
            if ( outOfBounds )
            {
                  tx /= scale;
                  ty /= scale;
            }
            
            double x0 = x;
            double y0 = y;
            
            x += tx;
            y += ty;
            
            plot.function()->x = x;
            plot.function()->y = y;
            
            double * coord = 0;
            if ( qAbs(tx) > qAbs(ty) )
            {
                  plot.function()->m_implicitMode = Function::FixedX;
                  coord = & y;
            }
            else
            {
                  plot.function()->m_implicitMode = Function::FixedY;
                  coord = & x;
            }
                              
            bool found = findRoot( coord, plot, RoughRoot );
            if ( !found )
            {
                  if ( foundRootPreviously )
                  {
#ifdef DEBUG_IMPLICIT
                        kDebug() << "Could not find root!\n";
#endif
                        
                        // Retrace our steps
                        x = x0;
                        y = y0;
                        prev_diff_x = 0;
                        prev_diff_y = 0;
                        foundRootPreviously = false;
                        continue;
                  }
                  else
                  {
                        kDebug() << "Couldn't find root - giving up.\n";
                        break;
                  }
            }
            else
                  foundRootPreviously = true;
            
            prev_diff_x = x - x0;
            prev_diff_y = y - y0;
            
            painter->drawLine( prev2, prev1 );
            prev2 = prev1;
            prev1 = toPixel( QPointF( x, y ), ClipInfinite );
            markDiagramPointUsed( prev1 );
            
            if ( outOfBounds )
                  break;
      }
      
      // and the final line
      painter->drawLine( prev2, prev1 );
}


01685 void View::drawFunction( Function * function, QPainter * painter )
{
      if ( (function->type() == Function::Differential) &&
                   (function->eq[0]->order() == 1) &&
                  function->plotAppearance( Function::Derivative0 ).showTangentField )
      {
            QList<Plot> plots = function->plots( Function::PlotCombinations(Function::AllCombinations) & ~Function::DifferentInitialStates );
            foreach ( const Plot &plot, plots )
                  drawTangentField( plot, painter );
      }
      
      QList<Plot> plots = function->plots();
      foreach ( const Plot &plot, plots )
            drawPlot( plot, painter );
      
}


01703 void View::drawTangentField( const Plot & plot, QPainter * painter )
{
      plot.updateFunction();
      Function * function = plot.function();
      
      assert( function->type() == Function::Differential );
      // Can only draw tangent fields for first order differential equations
      assert( function->eq[0]->order() == 1 );
      
      painter->setPen( penForPlot( plot, painter ) );
      
      bool useParameter = function->eq[0]->usesParameter();
      Vector v( useParameter ? 3 : 2 );
      
      if ( useParameter )
            v[1] = function->k;
      
      // For converting from real to pixels
      double sx = m_clipRect.width() / (m_xmax - m_xmin);
      double sy = m_clipRect.height() / (m_ymax - m_ymin);
      
      for ( double x = ticStartX; x <= m_xmax; x += ticSepX.value() )
      {
            v[0] = x;
            for ( double y = ticStartY; y <= m_ymax; y += ticSepY.value() )
            {
                  v[ useParameter ? 2 : 1 ] = y;
                  
                  double df = XParser::self()->fkt( function->eq[0], v ) * (sy / sx);
                  double theta = std::atan( df );
                  double dx = std::cos( theta ) * (ticSepX.value() / 8.0);
                  double dy = std::sin( theta ) * (ticSepY.value() / 8.0);
                  
                  QPointF mid( x, y );
                  QPointF diff( dx, dy );
                  
                  painter->drawLine( toPixel( mid-diff ), toPixel( mid+diff ) );
            }
      }
}


/**
 * Convenience function for drawing lines. Unfortunately, QPainter::drawPolyline
 * takes a long time to draw the line joins, which is only necessary when we are
 * using a fat pen. Therefore, draw each line individually if we are using a
 * thin pen to save time.
 */
void drawPolyline( QPainter * painter, const QPolygonF & points )
{
      if ( painter->pen().width() > 5 )
            painter->drawPolyline( points );
      else if ( points.size() >= 2 )
      {
            QPointF prev = points.first();
            for ( int i = 1; i < points.size(); ++i )
            {
//                QPen pen( painter->pen() );
//                pen.setColor( (i%2==0) ? Qt::red : Qt::blue );
//                painter->setPen( pen );
                  
                  QPointF next = points[i];
                  painter->drawLine( prev, next );
                  prev = next;
            }
      }
}

/**
 * Speed up drawing by only drawing one line between each straightish section of the curve
 * These variable are used to determine when the curve can no longer be approximate by a
 * straight line as the new angle has changed too much
 */
01776 class CurveApproximator
{
      public:
            CurveApproximator( const QPolygonF & points )
            {
                  assert( points.size() >= 2 );
                  reset();
                  
                  QPointF diff = points[ points.size() - 2 ] - points.last();
                  currentAngle = atan2( diff.y(), diff.x() ); 
                  approximatingCurve = true;
            }
            
            CurveApproximator() { reset(); }
            
            void reset()
            {
                  currentAngle = 0;
                  maxClockwise = 0;
                  maxAnticlockwise = 0;
                  maxDistance = 0;
                  approximatingCurve = false;
            }
            
            
            bool shouldDraw() const
            {
                  return ((maxAnticlockwise + maxClockwise) * maxDistance) >= 0.5;
            }
            
            
            void update( const QPolygonF & points )
            {
                  // Should have at least two points in the list
                  assert( points.size() >= 2 );
                  
                  QPointF p1 = points[ points.size() - 2 ];
                  QPointF p2 = points.last();
                  
                  QPointF diff = p1 - p2;
                  double angle = atan2( diff.y(), diff.x() ); 
                  
                  double lineLength = QLineF( p1, p2 ).length();
                  if ( lineLength > maxDistance )
                        maxDistance = lineLength;
                  
                  double clockwise =            realModulo( currentAngle-angle, 2*M_PI );
                  double anticlockwise =  realModulo( angle-currentAngle, 2*M_PI );
                  
                  bool goingClockwise = (clockwise < anticlockwise);
                  
                  if ( goingClockwise )
                  {
                        // anti-clockwise
                        if ( clockwise > maxClockwise )
                              maxClockwise = clockwise;
                  }
                  else
                  {
                        // clockwise
                        if ( anticlockwise > maxAnticlockwise )
                              maxAnticlockwise = anticlockwise;
                  }
            }
            
            double currentAngle;
            double maxClockwise;
            double maxAnticlockwise;
            double maxDistance;
            bool approximatingCurve;
};


01849 void View::drawPlot( const Plot & plot, QPainter *painter )
{
      plot.updateFunction();
      Function * function = plot.function();
      
      // should use drawImplicit for implicit functions
      assert( function->type() != Function::Implicit );
      
      double dmin = getXmin( function, true );
      double dmax = getXmax( function, true );
      
      if ( dmin >= dmax )
            return;
      
      painter->save();
      
      // Bug in Qt 4.2 TP - QPainter::drawPolyline draws the background as well while printing
      // So for testing printing, use a brush where one can see the function being drawn
      painter->setBrush( Qt::white );
      
      if ( (plot.parameter.type() == Parameter::Animated) && (painter->renderHints() & QPainter::Antialiasing) )
      {
            // Don't use antialiasing, so that rendering is speeded up
            painter->setRenderHint( QPainter::Antialiasing, false );
      }
      
      painter->setPen( penForPlot( plot, painter ) );
      
      // the 'middle' dx, which may be increased or decreased
      double max_dx = (dmax-dmin)/m_clipRect.width();
      if ( (function->type() == Function::Parametric) || (function->type() == Function::Polar) )
            max_dx *= 0.01;

      // Increase speed while translating the view
      bool quickDraw = ( m_zoomMode == Translating );
      if ( quickDraw )
            max_dx *= 4.0;
      
      double dx = max_dx;
      
      double maxLength = quickDraw ? 8.0 : (function->plotAppearance( plot.plotMode ).style == Qt::SolidLine) ? 4.0 : 1.5;
      double minLength = maxLength * 0.5;
      
      bool drawIntegral = m_integralDrawSettings.draw && (m_integralDrawSettings.plot == plot);
      double totalLength = 0.0; // total pixel length; used for drawing dotted lines
      
      bool p1Set = false;
      QPointF p1, p2;
      
      CurveApproximator approximator;
      QPolygonF drawPoints;
      
      double x = dmin;
      double prevX = x; // the value of x before last adding dx to it
      do
      {     
            QPointF rv = realValue( plot, x, false );
                  
            // If we are currently plotting a differential equation, and it became infinite,
            // then skip x forward to a point where it is finite
            if ( function->type() == Function::Differential && !XParser::self()->differentialFinite )
            {
                  double new_x = XParser::self()->differentialDiverge;
                  if ( new_x > x )
                  {
                        x = new_x;
                        prevX = x;
                        
                        continue;
                  }
            }
                  
            p2 = toPixel( rv, ClipInfinite );

            if ( xclipflg || yclipflg )
            {
                  prevX = x;
                  x += dx;
                  
                  p1Set = false; // p1 wouldn't be finite (if we had set it)
                  continue;
            }
            
            if ( !p1Set )
            {
                  prevX = x;
                  x += dx;
                  
                  p1 = p2;
                  p1Set = true;
                  continue;
            }
            
            
            //BEGIN adjust dx
            QRectF bound = QRectF( p1, QSizeF( (p2-p1).x(), (p2-p1).y() ) ).normalized();
            double length = QLineF( p1, p2 ).length();
            totalLength += length;

            double min_mod = (function->type() == Function::Cartesian || function->type() == Function::Differential) ? 1e-2 : 5e-4;
            bool dxAtMinimum = (dx <= max_dx*min_mod);
            bool dxAtMaximum = (dx >= max_dx);
            bool dxTooBig = false;
            bool dxTooSmall = false;
            
            if ( QRectF(m_clipRect).intersects( bound ) )
            {
                  dxTooBig = !dxAtMinimum && (length > maxLength);
                  dxTooSmall = !dxAtMaximum && (length < minLength);
            }
            else
                  dxTooSmall = !dxAtMaximum;
                  
            if ( dxTooBig )
            {
                  dx *= 0.5;
                  x = prevX + dx;
                  totalLength -= length;
                  continue;
            }
            
            if ( dxTooSmall )
                  dx *= 2.0;
            //END adjust dx
            
            
            if ( drawIntegral && (x >= m_integralDrawSettings.dmin) && (x <= m_integralDrawSettings.dmax) )
            {
                  double y0 = yToPixel( 0 );

                  /// \todo should draw the shape in one go
                  
                  QPointF points[4];
                  points[0] = QPointF( p1.x(), y0 );
                  points[1] = QPointF( p2.x(), y0 );
                  points[2] = QPointF( p2.x(), p2.y() );
                  points[3] = QPointF( p1.x(), p1.y() );

                  painter->drawPolygon( points, 4 );
            }
            else if ( penShouldDraw( totalLength, plot ) )
            {
                  if ( drawPoints.isEmpty() )
                  {
                        drawPoints << p1;
                  }
                  else if ( drawPoints.last() != p1 )
                  {
                        drawPolyline( painter, drawPoints );
                        drawPoints.clear();
                        drawPoints << p1;
                        approximator.reset();
                  }
                  
                  // The above code should guarantee that drawPoints isn't empty
                  // But check it now in case I do something stupid
                  assert( !drawPoints.isEmpty() );
                  
                  if ( !approximator.approximatingCurve )
                  {
                        // Cool, about to add another point. This defines the working angle of the line
                        // approximation
                        drawPoints << p2;
                        approximator = CurveApproximator( drawPoints );
                  }
                  else
                  {
                        QPointF prev = drawPoints.last();
                        drawPoints.last() = p2;
                        approximator.update( drawPoints );
                        
                        // Allow a maximum deviation (in pixels)
                        if ( approximator.shouldDraw() )
                        {
                              // The apprimxation is too bad; will have to start again now
                              drawPoints.last() = prev;
                              drawPoints << p2;
                              approximator = CurveApproximator( drawPoints );
                        }
                  }
            }
            
            markDiagramPointUsed( p2 );
            
            p1 = p2;
            
            Q_ASSERT( dx > 0 );
            prevX = x;
            x += dx;
      }
      while ( x <= dmax );
      
//    kDebug() << "drawPoints.size()="<<drawPoints.size();
      drawPolyline( painter, drawPoints );
      
      painter->restore();
}


02048 void View::drawFunctionInfo( QPainter * painter )
{
      // Don't draw info if translating the view
      if ( m_zoomMode == Translating )
            return;
      
      // The names of the plots are drawn around the edge of the view, in a clockwise
      // direction, starting from the top-right. Picture the positions like this:
      // 
      //   7  8  9  0
      //   6        1
      //   5  4  3  2
      
      // Used for determining where to draw the next label indicating the plot name
      int plotNameAt = 0;
      
      foreach ( Function * function, XParser::self()->m_ufkt )
      {
            if ( m_stopCalculating )
                  break;
            
            foreach ( const Plot &plot, function->plots() )
            {
                  plot.updateFunction();
                  
                  // Draw extrema points?
                  if ( (function->type() == Function::Cartesian) && function->plotAppearance( plot.plotMode ).showExtrema )
                  {
                        QList<QPointF> stationaryPoints = findStationaryPoints( plot );
                        foreach ( const QPointF &realValue, stationaryPoints )
                        {
                              painter->setPen( QPen( Qt::black, millimetersToPixels( 1.5, painter->device() ) ) );
                              painter->drawPoint( toPixel( realValue ) );
                              
                              QString x = posToString( realValue.x(), (m_xmax-m_xmin)/m_clipRect.width(), View::DecimalFormat );
                              QString y = posToString( realValue.y(), (m_ymax-m_ymin)/m_clipRect.width(), View::DecimalFormat );
                              
                              drawLabel( painter, plot.color(), realValue, QString( "x = %1   y = %2" ).arg(x).arg(y) );
                        }
                  }
                  
                  // Show the name of the plot?
                  if ( function->plotAppearance( plot.plotMode ).showPlotName )
                  {
                        double x, y;
                        
                        double xmin = m_xmin + 0.1 * (m_xmax-m_xmin);
                        double xmax = m_xmax - 0.1 * (m_xmax-m_xmin);
                        double ymin = m_ymin + 0.1 * (m_ymax-m_ymin);
                        double ymax = m_ymax - 0.1 * (m_ymax-m_ymin);
                        
                        // Find out where on the outer edge of the view to draw it
                        if ( 0 <= plotNameAt && plotNameAt <= 2 )
                        {
                              x = xmax;
                              y = ymax - (ymax-ymin)*plotNameAt/2;
                        }
                        else if ( 3 <= plotNameAt && plotNameAt <= 5 )
                        {
                              x = xmax - (xmax-xmin)*(plotNameAt-2)/3;
                              y = ymin;
                        }
                        else if ( 6 <= plotNameAt && plotNameAt <= 7 )
                        {
                              x = xmin;
                              y = ymin + (ymax-ymin)*(plotNameAt-5)/2;
                        }
                        else
                        {
                              x = xmin + (xmax-xmin)*(plotNameAt-7)/3;
                              y = ymax;
                        }
                        
                        plotNameAt = (plotNameAt+1) % 10;
                        
                        QPointF realPos;
                        
                        if ( function->type() == Function::Implicit )
                        {
                              findRoot( & x, & y, plot, RoughRoot );
                              realPos = QPointF( x, y );
                        }
                        else
                        {
                              double t = getClosestPoint( QPointF( x, y ), plot );
                              realPos = realValue( plot, t, false );
                        }
                        
                        // If the closest point isn't in the view, then don't draw the label
                        if ( realPos.x() < m_xmin || realPos.x() > m_xmax || realPos.y() < m_ymin || realPos.y() > m_ymax )
                              continue;
                        
                        drawLabel( painter, plot.color(), realPos, plot.name() );
                  }
            }
      }
}


02147 void View::drawLabel( QPainter * painter, const QColor & color, const QPointF & realPos, const QString & text )
{
      QPalette palette;
      QColor outline = color;
      QColor background = outline.light( 500 );
      background.setAlpha( 127 );
      
      
      QPointF pixelCenter = toPixel( realPos );
      QRectF rect( pixelCenter, QSizeF( 1, 1 ) );
      
      int flags = Qt::TextSingleLine | Qt::AlignLeft | Qt::AlignTop;
      rect = painter->boundingRect( rect, flags, text ).adjusted( -7, -3, 4, 2 );
      
      // Try and find a nice place for inserting the rectangle
      int bestCost = int(1e7);
      QPointF bestCenter = realPos;
      for ( double x = pixelCenter.x() - 300; x <= pixelCenter.x() + 300; x += 20 )
      {
            for ( double y = pixelCenter.y() - 300; y <= pixelCenter.y() + 300; y += 20 )
            {
                  QPointF center( x, y ) ;
                  rect.moveCenter( center );
                  double length = (x-pixelCenter.x())*(x-pixelCenter.x()) + (y-pixelCenter.y())*(y-pixelCenter.y());
                  int cost = rectCost( rect ) + int(length)/100;
                  
                  if ( cost < bestCost )
                  {
                        bestCenter = center;
                        bestCost = cost;
                  }
            }
      }
      
      rect.moveCenter( bestCenter );
      
      markDiagramAreaUsed( rect );
      
      painter->setBrush( background );
      painter->setPen( outline );
      painter->drawRoundRect( rect, int(1000/rect.width()), int(1000/rect.height()) );
      
      
      // If the rectangle does not lie over realPos, then draw a line to realPos from the rectangle
      if ( ! rect.contains( pixelCenter ) )
      {
            QPointF lineStart = bestCenter;
            QLineF line( pixelCenter, bestCenter );
            
            QPointF intersect = bestCenter;
            
            // Where does line intersect the rectangle?
            if ( QLineF( rect.topLeft(), rect.topRight() ).intersect( line, & intersect ) == QLineF::BoundedIntersection )
                  lineStart = intersect;
            else if ( QLineF( rect.topRight(), rect.bottomRight() ).intersect( line, & intersect ) == QLineF::BoundedIntersection )
                  lineStart = intersect;
            else if ( QLineF( rect.bottomRight(), rect.bottomLeft() ).intersect( line, & intersect ) == QLineF::BoundedIntersection )
                  lineStart = intersect;
            else if ( QLineF( rect.bottomLeft(), rect.topLeft() ).intersect( line, & intersect ) == QLineF::BoundedIntersection )
                  lineStart = intersect;
            
            painter->drawLine( lineStart, pixelCenter );
      }
      
      
      painter->setFont( m_labelFont );
      painter->setPen( Qt::black );
      painter->drawText( rect.adjusted( 7, 3, -4, -2 ), flags, text );
}


02218 QRect View::usedDiagramRect( const QRectF & rect ) const
{
      double x0 = rect.left() / m_clipRect.width();
      double x1 = rect.right() / m_clipRect.width();
      
      double y0 = rect.top() / m_clipRect.height();
      double y1 = rect.bottom() / m_clipRect.height();
      
      int i0 = qMax( int( x0 * LabelGridSize ), 0 );
      int i1 = qMin( int( x1 * LabelGridSize ), LabelGridSize-1 );
      int j0 = qMax( int( y0 * LabelGridSize ), 0 );
      int j1 = qMin( int( y1 * LabelGridSize ), LabelGridSize-1 );
      
      return QRect( i0, j0, i1-i0+1, j1-j0+1 ) & QRect( 0, 0, LabelGridSize, LabelGridSize );
}


02235 void View::markDiagramAreaUsed( const QRectF & rect )
{
      if ( m_zoomMode == Translating )
            return;
      
      QRect r = usedDiagramRect( rect );
      
      for ( int i = r.left(); i <= r.right(); ++i )
            for ( int j = r.top(); j <= r.bottom(); ++j )
                  m_usedDiagramArea[i][j] = true;
}


02248 void View::markDiagramPointUsed( const QPointF & point )
{
      if ( m_zoomMode == Translating )
            return;
      
      double x = point.x() / m_clipRect.width();
      double y = point.y() / m_clipRect.height();
      
      int i = int( x * LabelGridSize );
      int j = int( y * LabelGridSize );
      
      if ( i<0 || i>=LabelGridSize || j<0 || j>=LabelGridSize )
            return;
      
      m_usedDiagramArea[i][j] = true;
}


02266 int View::rectCost( QRectF rect ) const
{
      rect = rect.normalized();
      
      int cost = 0;
      
      // If the rectangle goes off the edge, mark it as very high cost)
      if ( rect.intersects( m_clipRect ) )
      {
            QRectF intersect = (rect & m_clipRect);
            cost += int(rect.width() * rect.height() - intersect.width() * intersect.height());
      }
      else
      {
            // The rectangle is completely outside!
            cost += int(rect.width() * rect.height());
      }
      
      
      QRect r = usedDiagramRect( rect );
      
      for ( int i = r.left(); i <= r.right(); ++i )
            for ( int j = r.top(); j <= r.bottom(); ++j )
                  if ( m_usedDiagramArea[i][j] )
                        cost += 200;
      
      return cost;
}


02296 bool View::penShouldDraw( double length, const Plot & plot )
{
      // Always use a solid line when translating the view
      if ( m_zoomMode == Translating )
            return true;

      Function * function = plot.function();

      Qt::PenStyle style = function->plotAppearance( plot.plotMode ).style;

      double sepBig = 8.0;    // separation distance between dashes
      double sepMid = 7.0;    // separation between a dash and a dot
      double sepSmall = 6.5;  // separation distance between dots
      double dash = 9.0;            // length of a dash
      double dot = 3.5;       // length of a dot

      switch ( style )
      {
            case Qt::NoPen:
                  // *whatever*...
                  return false;

            case Qt::SolidLine:
                  return true;

            case Qt::DashLine:
                  return realModulo( length, dash + sepBig ) < dash;

            case Qt::DotLine:
                  return realModulo( length, dot + sepSmall ) < dot;

            case Qt::DashDotLine:
            {
                  double at = realModulo( length, dash + sepMid + dot + sepMid );

                  if ( at < dash )
                        return true;
                  if ( at < (dash + sepMid) )
                        return false;
                  if ( at < (dash + sepMid + dot) )
                        return true;
                  return false;
            }

            case Qt::DashDotDotLine:
            {
                  double at = realModulo( length, dash + sepMid + dot + sepSmall + dot + sepMid );

                  if ( at < dash )
                        return true;
                  if ( at < (dash + sepMid) )
                        return false;
                  if ( at < (dash + sepMid + dot) )
                        return true;
                  if ( at < (dash + sepMid + dot + sepSmall) )
                        return false;
                  if ( at < (dash + sepMid + dot + sepSmall + dot) )
                        return true;
                  return false;
            }

            case Qt::MPenStyle:
            case Qt::CustomDashLine:
            {
                  assert( ! "Don't know how to handle this style!" );
                  return true;
            }
      }

      assert( ! "Unknown pen style!" );
      return true;
}


02370 QPen View::penForPlot( const Plot & plot, QPainter * painter ) const
{
      QPen pen;
      if ( m_zoomMode == Translating )
      {
            // plot style is always a solid line when translating the view
            pen.setCapStyle( Qt::FlatCap );
      }
      else
      {
            pen.setCapStyle( Qt::RoundCap );
            // (the style will be set back to FlatCap if the plot style is a solid line)
      }
      
      pen.setColor( plot.color() );

      Function * ufkt = plot.function();
      PlotAppearance appearance = ufkt->plotAppearance( plot.plotMode );
      
      double lineWidth_mm = appearance.lineWidth;
      
      if ( appearance.style == Qt::SolidLine )
            pen.setCapStyle( Qt::FlatCap );

      double width = millimetersToPixels( lineWidth_mm, painter->device() );
      pen.setWidthF( width );
      
      return pen;
}


02401 double View::millimetersToPixels( double width_mm, QPaintDevice * device ) const
{
//    assert( device->logicalDpiX() == device->logicalDpiY() );
      return device->logicalDpiX() * (width_mm/25.4);
}


02408 double View::pixelsToMillimeters( double width_pixels, QPaintDevice * device ) const
{
//    assert( device->logicalDpiX() == device->logicalDpiY() );
      return (width_pixels * 25.4) / device->logicalDpiX();
}


02415 void View::drawHeaderTable( QPainter *painter )
{
      painter->setFont( Settings::headerTableFont() );
      
      QString alx = Settings::xMin() + i18n("  to  ") + Settings::xMax();
      QString aly = Settings::yMin() + i18n("  to  ") + Settings::yMax();
      
      QString atx = "1E = " + ticSepX.expression();
      QString aty = "1E = " + ticSepY.expression();
      
      QString text = "<table border=\"1\" cellpadding=\"4\" cellspacing=\"0\">"
                  "<tr><td><b>" + i18n("Parameters") + "</b></td><td><b>" + i18n("Plotting Range") + "</b></td><td><b>" + i18n("Axes Division") + "</b></td></tr>"
                  "<tr><td><b>" + i18n("x-Axis:") + "</b></td><td>" + alx + "</td><td>" + atx + "</td></tr>"
                  "<tr><td><b>" + i18n("y-Axis:") + "</b></td><td>" + aly + "</td><td>" + aty + "</td></tr>"
                  "</table>";
      
      text += "<br><br><b>" + i18n("Functions:") + "</b><ul>";
      
      foreach ( Function * function, XParser::self()->m_ufkt )
            text += "<li>" + function->name().replace( '\n', "<br>" ) + "</li>";
      
      text += "</ul>";
      
      m_textDocument->setHtml( text );
      m_textDocument->documentLayout()->draw( painter, QAbstractTextDocumentLayout::PaintContext() );
      
      QRectF br = m_textDocument->documentLayout()->frameBoundingRect( m_textDocument->rootFrame() );
      painter->translate( 0, br.height() );
}


02446 QList< QPointF > View::findStationaryPoints( const Plot & plot )
{
      Plot plot2 = plot;
      plot2.differentiate();

      QList< double > roots = findRoots( plot2, getXmin( plot.function() ), getXmax( plot.function() ), RoughRoot );

      plot.updateFunction();
      QList< QPointF > stationaryPoints;
      foreach ( double x, roots )
      {
            QPointF real = realValue( plot, x, false );
            if ( real.y() >= m_ymin && real.y() <= m_ymax )
                  stationaryPoints << real;
      }

      return stationaryPoints;
}


02466 QList< double > View::findRoots( const Plot & plot, double min, double max, RootAccuracy accuracy )
{
      typedef QMap< double, double > DoubleMap;
      DoubleMap roots;
      
      int count = 10; // number of points to (initially) check for roots
      
      int prevNumRoots = 0;
      while ( count < 1000 )
      {
            // Use this to detect finding the same root.
            double prevX = 0.0;
            
            double dx = (max-min) / double(count);
            for ( int i = 0; i <= count; ++i )
            {
                  double x = min + dx*i;
            
                  bool found = findRoot( & x, plot, accuracy );
                  if ( !found || x < min || x > max )
                        continue;
            
                  if ( !roots.isEmpty() )
                  {
                        // Check if already have a close root
                        if ( qAbs(x-prevX) <= (dx/4) )
                              continue;
                  
                        DoubleMap::iterator nextIt = roots.lowerBound(x);
                        if ( nextIt == roots.end() )
                              --nextIt;
                        
                        double lower, upper;
                        lower = upper = *nextIt;
                        if ( nextIt != roots.begin() )
                              lower = *(--nextIt);
                  
                        if ( (qAbs(x-lower) <= (dx/4)) || (qAbs(x-upper) <= (dx/4)) )
                              continue;
                  }
            
                  roots.insert( x, x );
                  prevX = x;
            }
            
            int newNumRoots = roots.size();
            if ( newNumRoots == prevNumRoots )
                  break;
            
            prevNumRoots = newNumRoots;
            count *= 4;
      }
      
      return roots.keys();
}


02523 void View::setupFindRoot( const Plot & plot, RootAccuracy accuracy, double * max_k, double * max_f, int * n )
{
      plot.updateFunction();
      
      if ( accuracy == PreciseRoot )
      {
            *max_k = 200;
            *max_f = 1e-14;
      }
      else
      {
            // Rough root
            *max_k = 10;
            *max_f = 1e-10;
      }
      
      *n = 1 + plot.derivativeNumber();
}


02543 bool View::findRoot( double * x, const Plot & plot, RootAccuracy accuracy )
{
#ifdef DEBUG_IMPLICIT
      root_find_requests++;
#endif
      
      double max_k, max_f;
      int n;
      setupFindRoot( plot, accuracy, & max_k, & max_f, & n );
      
      Equation * eq = plot.function()->eq[0];
      DifferentialState * state = plot.state();
      
      double h = qMin( m_xmax-m_xmin, m_ymax-m_ymin ) * 1e-5;

      double f = value( plot, 0, *x, false );
      int k;
      for ( k=0; k < max_k; ++k )
      {
            double df = XParser::self()->derivative( n, eq, state, *x, h );
            if ( qAbs(df) < 1e-20 )
                  df = 1e-20 * ((df < 0) ? -1 : 1);

            double dx = f / df;
            *x -= dx;
            f = value( plot, 0, *x, false );
            
            if ( (qAbs(f) <= max_f) && (qAbs(dx) <= (h*1e-5)) )
                  break;
      }
      
#ifdef DEBUG_IMPLICIT
      root_find_iterations += k;
#endif

      // We continue calculating until |f| < max_f; this may result in k reaching
      // max_k. However, if |f| is reasonably small (even if reaching max_k),
      // we consider it a root.
      return ( qAbs(f) < 1e-6 );
}


02585 bool View::findRoot( double * x, double * y, const Plot & plot, RootAccuracy accuracy )
{
      double max_k, max_f;
      int n;
      setupFindRoot( plot, accuracy, & max_k, & max_f, & n );
      
      Function * function = plot.function();
      Equation * eq = function->eq[0];
      DifferentialState * state = plot.state();
      
      double hx = (m_xmax-m_xmin) * 1e-5;
      double hy = (m_ymax-m_ymin) * 1e-5;

      function->y = *y;
      function->m_implicitMode = Function::FixedY;
      double f = value( plot, 0, *x, false );
      
      for ( int k=0; k < max_k; ++k )
      {
            function->x = *x;
            function->y = *y;
            
            function->m_implicitMode = Function::FixedY;
            double dfx = XParser::self()->derivative( n, eq, state, *x, hx );
            
            function->m_implicitMode = Function::FixedX;
            double dfy = XParser::self()->derivative( n, eq, state, *y, hy );
            
            double dff = dfx*dfx + dfy*dfy;
            if ( dff < 1e-20 )
                  dff = 1e-20;
            
            double dx = f * dfx / dff;
            *x -= dx;
            double dy = f * dfy / dff;
            *y -= dy;
            
            function->y = *y;
            function->m_implicitMode = Function::FixedY;
            f = value( plot, 0, *x, false );
            
            if ( (qAbs(f) <= max_f) && (qAbs(dx) <= (hx*1e-5)) && (qAbs(dy) <= (hy*1e-5)) )
                  break;
      }

      // We continue calculating until |f| < max_f; this may result in k reaching
      // max_k. However, if |f| is reasonably small (even if reaching max_k),
      // we consider it a root.
      return ( qAbs(f) < 1e-6 );
}



void View::paintEvent(QPaintEvent *)
{
      // Note: it is important to have this function call before we begin painting
      // as updateCrosshairPosition may set the statusbar text
      bool inBounds = updateCrosshairPosition();

      QPainter p;
      p.begin(this);

      p.drawPixmap( QPoint( 0, 0 ), buffer );

      // the current cursor position in widget coordinates
      QPoint mousePos = mapFromGlobal( QCursor::pos() );

      if ( (m_zoomMode == ZoomInDrawing) || (m_zoomMode == ZoomOutDrawing) )
      {
            QPalette palette;
            QColor highlightColor = palette.color( QPalette::Highlight );
            QColor backgroundColor = highlightColor;
            backgroundColor.setAlpha( 63 );

            p.setPen( highlightColor );
            p.setBrush( backgroundColor );

            p.setBackgroundMode (Qt::OpaqueMode);
            p.setBackground (Qt::blue);

            QRect rect( m_zoomRectangleStart, mousePos );
            p.drawRect( rect );
      }
      else if ( m_zoomMode == AnimatingZoom )
      {
            QPointF tl( toPixel( m_animateZoomRect.topLeft() ) );
            QPointF br( toPixel( m_animateZoomRect.bottomRight() ) );
            p.drawRect( QRectF( tl, QSizeF( br.x()-tl.x(), br.y()-tl.y() ) ) );
      }
      else if ( shouldShowCrosshairs() )
      {
            Function * function = m_currentPlot.function();
            
            QPen pen;
            
            if ( function )
            {
                  QColor functionColor = m_currentPlot.color();
                  pen.setColor( functionColor );
                  p.setPen( pen );
                  p.setRenderHint( QPainter::Antialiasing, true );
                  
                  double x = m_crosshairPosition.x();
                  double y = m_crosshairPosition.y();
                  
                  //BEGIN calculate curvature, normal
                  double k = 0;
                  double normalAngle = 0;
                  
                  switch ( function->type() )
                  {
                        case Function::Parametric:
                        case Function::Polar:
                              normalAngle = pixelNormal( m_currentPlot, m_trace_x );
                              k = pixelCurvature( m_currentPlot, m_trace_x );
                              break;
                              
                        case Function::Differential:
                        case Function::Cartesian:
                        case Function::Implicit:
                              normalAngle = pixelNormal( m_currentPlot, x, y );
                              k = pixelCurvature( m_currentPlot, x, y );
                              break;
                  }
                  
                  if ( k < 0 )
                  {
                        k = -k;
                        normalAngle += M_PI;
                  }
                  //END calculate curvature, normal
                  
                  if ( k > 1e-5 && Settings::detailedTracing() && inBounds )
                  {
                        p.save();
                        
                        // Transform the painter so that the center of the osculating circle is the origin,
                        // with the normal line coming in frmo the left.
                        QPointF center = m_crosshairPixelCoords + (1/k) * QPointF( cos( normalAngle ), sin( normalAngle ) );
                        p.translate( center );
                        p.rotate( normalAngle * 180 / M_PI );
                        
                        // draw osculating circle
                        pen.setColor( functionColor );
                        p.setPen( pen );
                        p.drawEllipse( QRectF( -QPointF( 1/k, 1/k ), QSizeF( 2/k, 2/k ) ) );
                        
                        // draw normal
                        pen.setColor( functionColor );
                        p.setPen( pen );
                        p.setBrush( pen.color() );
                        p.drawLine( QLineF( -1/k, 0, 0, 0 ) );
                        
                        // draw normal arrow
                        QPolygonF arrowHead(3);
                        arrowHead[0] = QPointF( 0, 0 );
                        arrowHead[1] = QPointF( -3, -2 );
                        arrowHead[2] = QPointF( -3, +2 );
                        p.drawPolygon( arrowHead );
                        
                        // draw tangent
                        double tangent_scale = 1.2; // make the tangent look better
                        p.drawLine( QLineF( -1/k, -qMax( 1/k, 15. ) * tangent_scale, -1/k, qMax( 1/k, 15. ) * tangent_scale ) );
                        
                        // draw perpendicular symbol
                        QPolygonF perp(3);
                        perp[0] = QPointF( -1/k, 10 );
                        perp[1] = QPointF( -1/k + 10, 10 );
                        perp[2] = QPointF( -1/k + 10, 0 );
                        p.drawPolyline( perp );
                        
                        // draw intersection blob
                        p.drawRect( QRectF( -1/k-1, -1, 2, 2 ) );
                        
                        p.restore();
                        
                        // Already show osculating circle, etc, so don't draw crosshairs quite so prominently
                        functionColor.setAlpha( 63 );
                        pen.setColor( functionColor );
                  }
            }
            else
            {
                  // Use an inverted background color for contrast
                  QColor inverted = QColor( 255-m_backgroundColor.red(), 255-m_backgroundColor.green(), 255-m_backgroundColor.blue() );
                  pen.setColor( inverted );
            }
            
            p.setPen( pen );
            double x = m_crosshairPixelCoords.x();
            double y = m_crosshairPixelCoords.y();
            p.drawLine( QPointF( 0, y ), QPointF( m_clipRect.right(), y ) );
            p.drawLine( QPointF( x, 0 ), QPointF( x, m_clipRect.height() ) );
      }

      p.end();
}


02784 double View::pixelNormal( const Plot & plot, double x, double y )
{
      Function * f = plot.function();
      assert( f );
      
      plot.updateFunction();
      
      // For converting from real to pixels
      double sx = m_clipRect.width() / (m_xmax - m_xmin);
      double sy = m_clipRect.height() / (m_ymax - m_ymin);
      
      double dx = 0;
      double dy = 0;
      
      double h = this->h( plot );
      
      int d0 = plot.derivativeNumber();
      int d1 = d0+1;
      
      switch ( f->type() )
      {
            case Function::Differential:
            case Function::Cartesian:
            {
                  double df = XParser::self()->derivative( d1, f->eq[0], plot.state(), x, h );
                  return -atan( df * (sy/sx) ) - (M_PI/2);
            }
            
            case Function::Implicit:
            {
                  dx = XParser::self()->partialDerivative( d1, d0, f->eq[0], 0, x, y, h, h ) / sx;
                  dy = XParser::self()->partialDerivative( d0, d1, f->eq[0], 0, x, y, h, h ) / sy;
                  
                  double theta = -atan( dy / dx );
                  
                  if ( dx < 0 )
                        theta += M_PI;
                  
                  theta += M_PI;
                  
                  return theta;
            }
            
            case Function::Polar:
            {
                  double r =  XParser::self()->derivative( d0, f->eq[0], 0, x, h );
                  double dr = XParser::self()->derivative( d1, f->eq[0], 0, x, h );
                  
                  dx = (dr * lcos(x) - r * lsin(x) * XParser::self()->radiansPerAngleUnit()) * sx;
                  dy = (dr * lsin(x) + r * lcos(x) * XParser::self()->radiansPerAngleUnit()) * sy;
                  break;
            }
            
            case Function::Parametric:
            {
                  dx = XParser::self()->derivative( d1, f->eq[0], 0, x, h ) * sx;
                  dy = XParser::self()->derivative( d1, f->eq[1], 0, x, h ) * sy;
                  break;
            }
      }
                  
      double theta = - atan( dy / dx ) - (M_PI/2);
                  
      if ( dx < 0 )
            theta += M_PI;
                  
      return theta;
}


02854 double View::pixelCurvature( const Plot & plot, double x, double y )
{
      Function * f = plot.function();
      
      // For converting from real to pixels
      double sx = m_clipRect.width() / (m_xmax - m_xmin);
      double sy = m_clipRect.height() / (m_ymax - m_ymin);
      
      double fdx = 0;
      double fdy = 0;
      double fddx = 0;
      double fddy = 0;
      double fdxy = 0;
      
      double h = this->h( plot );
      
      int d0 = plot.derivativeNumber();
      int d1 = d0+1;
      int d2 = d0+2;
      
      switch ( f->type() )
      {
            case Function::Differential:
            case Function::Cartesian:
            {
                  DifferentialState * state = plot.state();
                  
                  fdx = sx;
                  fddx = 0;
                  
                  fdy = XParser::self()->derivative( d1, f->eq[0], state, x, h ) * sy;
                  fddy = XParser::self()->derivative( d2, f->eq[0], state, x, h) * sy;
                  
//                kDebug() << "fdy="<<fdy<<" fddy="<<fddy;
                  
                  break;
            }
            
            case Function::Polar:
            {
                  double r = XParser::self()->derivative( d0, f->eq[0], 0, x, h );
                  double dr = XParser::self()->derivative( d1, f->eq[0], 0, x, h );
                  double ddr = XParser::self()->derivative( d2, f->eq[0], 0, x, h );
                  
                  fdx = (dr * lcos(x) - r * lsin(x) * XParser::self()->radiansPerAngleUnit()) * sx;
                  fdy = (dr * lsin(x) + r * lcos(x) * XParser::self()->radiansPerAngleUnit()) * sy;
                  
                  double rpau = XParser::self()->radiansPerAngleUnit();
                  
                  fddx = (ddr * lcos(x) - 2 * dr * lsin(x) * rpau - r * lcos(x) * rpau*rpau) * sx;
                  fddy = (ddr * lsin(x) + 2 * dr * lcos(x) * rpau - r * lsin(x) * rpau*rpau) * sy;
                  
                  break;
            }
            
            case Function::Parametric:
            {
                  fdx = XParser::self()->derivative( d1, f->eq[0], 0, x, h ) * sx;
                  fdy = XParser::self()->derivative( d1, f->eq[1], 0, x, h ) * sy;
                  
                  fddx = XParser::self()->derivative( d2, f->eq[0], 0, x, h ) * sx;
                  fddy = XParser::self()->derivative( d2, f->eq[1], 0, x, h ) * sy;
                  
                  break;
            }
            
            case Function::Implicit:
            {
                  fdx =  XParser::self()->partialDerivative( d1, d0, f->eq[0], 0, x, y, h, h ) / sx;
                  fdy =  XParser::self()->partialDerivative( d0, d1, f->eq[0], 0, x, y, h, h ) / sy;
                  
                  fddx = XParser::self()->partialDerivative( d2, d0, f->eq[0], 0, x, y, h, h ) / (sx*sx);
                  fddy = XParser::self()->partialDerivative( d0, d2, f->eq[0], 0, x, y, h, h ) / (sy*sy);
                  
                  fdxy = XParser::self()->partialDerivative( d1, d1, f->eq[0], 0, x, y, h, h ) / (sx*sy);
                  
                  
                  break;
            }
      }
      
      double mod = pow( fdx*fdx + fdy*fdy, 1.5 );
      
      switch ( f->type() )
      {
            case Function::Differential:
            case Function::Cartesian:
            case Function::Parametric:
            case Function::Polar:
                  return (fdx * fddy - fdy * fddx) / mod;
                  
            case Function::Implicit:
                  return ( fdx*fdx*fddy + fdy*fdy*fddx - 2*fdx*fdy*fdxy ) / mod;
      }
      
      kError() << "Unknown function type!\n";
      return 0;
}


void View::resizeEvent(QResizeEvent *)
{
      if (m_isDrawing) //stop drawing integrals
      {
            m_stopCalculating = true; //stop drawing
            return;
      }
      buffer = QPixmap( size() );
      drawPlot();
}


02966 void View::drawPlot()
{
      if ( buffer.width() == 0 || buffer.height() == 0 )
            return;
      
      buffer.fill(m_backgroundColor);
      draw(&buffer, Screen );
      update();
}


02977 void View::focusOutEvent( QFocusEvent * )
{
      // Redraw ourself to get rid of the crosshair (if we had it)...
      QTimer::singleShot( 0, this, SLOT(update()) );
      QTimer::singleShot( 0, this, SLOT(updateCursor()) );
}


02985 void View::focusInEvent( QFocusEvent * )
{
      // Redraw ourself to get the crosshair (if we should have it)...
      QTimer::singleShot( 0, this, SLOT(update()) );
      QTimer::singleShot( 0, this, SLOT(updateCursor()) );
}


02993 bool View::crosshairPositionValid( Function * plot ) const
{
      if ( !plot )
            return false;

      // only relevant for cartesian plots - assume true for none
      if ( plot->type() != Function::Cartesian )
            return true;

      bool lowerOk = ((!plot->usecustomxmin) || (plot->usecustomxmin && m_crosshairPosition.x()>plot->dmin.value()));
      bool upperOk = ((!plot->usecustomxmax) || (plot->usecustomxmax && m_crosshairPosition.x()<plot->dmax.value()));

      return lowerOk && upperOk;
}


03009 void View::mousePressEvent(QMouseEvent *e)
{
      m_mousePressTimer->start();

      // In general, we want to update the view
      update();

      if ( m_popupMenuStatus != NoPopup )
            return;

      if (m_isDrawing)
      {
            m_stopCalculating = true; //stop drawing
            return;
      }

      if ( m_zoomMode != Normal )
      {
            // If the user clicked with the right mouse button will zooming in or out, then cancel it
            if ( (m_zoomMode == ZoomInDrawing) ||
                              (m_zoomMode == ZoomOutDrawing) )
            {
                  m_zoomMode = Normal;
            }
            updateCursor();
            return;
      }

      m_haveRoot = false;

      bool hadFunction = (m_currentPlot.functionID() != -1 );

      updateCrosshairPosition();

      if( !m_readonly && e->button()==Qt::RightButton) //clicking with the right mouse button
      {
            getPlotUnderMouse();
            if ( m_currentPlot.function() )
            {
                  if ( hadFunction )
                        m_popupMenuStatus = PopupDuringTrace;
                  else
                        m_popupMenuStatus = Popup;
                  
                  fillPopupMenu();
                  m_popupMenu->exec( QCursor::pos() );
            }
            return;
      }

      if(e->button()!=Qt::LeftButton)
            return;

      if ( m_currentPlot.functionID() >= 0 ) //disable trace mode if trace mode is enable
      {
            m_currentPlot.setFunctionID( -1 );
            setStatusBar( QString(), RootSection );
            setStatusBar( QString(), FunctionSection );
            mouseMoveEvent(e);
            return;
      }

      QPointF closestPoint = getPlotUnderMouse();
      Function * function = m_currentPlot.function();
      if ( function )
      {
            QPointF ptd( toPixel( closestPoint ) );
            QPoint globalPos = mapToGlobal( ptd.toPoint() );
            QCursor::setPos( globalPos );
            setStatusBar( m_currentPlot.name().replace( '\n', " ; " ), FunctionSection );
            return;
      }

      // user didn't click on a plot; so we prepare to enter translation mode
      m_currentPlot.setFunctionID( -1 );
      m_zoomMode = AboutToTranslate;
      m_prevDragMousePos = e->pos();
      updateCursor();
}


03090 void View::fillPopupMenu( )
{
      Function * function = m_currentPlot.function();
      if ( !function )
            return;
      
      QString popupTitle( m_currentPlot.name() );
                  
      m_popupMenu->removeAction( m_popupMenuTitle );
      m_popupMenuTitle->deleteLater();
      m_popupMenuTitle = m_popupMenu->addTitle( popupTitle, MainDlg::self()->m_firstFunctionAction );
      
      QAction *calcArea = MainDlg::self()->actionCollection()->action("grapharea");
      QAction *maxValue = MainDlg::self()->actionCollection()->action("maximumvalue");
      QAction *minValue = MainDlg::self()->actionCollection()->action("minimumvalue");
      
      m_popupMenu->removeAction(calcArea);
      m_popupMenu->removeAction(maxValue);
      m_popupMenu->removeAction(minValue);
      
      if ( function->type() == Function::Cartesian || function->type() == Function::Differential )
      {
            m_popupMenu->addAction(calcArea);
            m_popupMenu->addAction(maxValue);
            m_popupMenu->addAction(minValue);
      }
}


03119 QPointF View::getPlotUnderMouse()
{
      m_currentPlot.setFunctionID( -1 );
      m_trace_x = 0.0;

      Plot bestPlot;

      double best_distance = 1e30; // a nice large number
      QPointF best_cspos;

      foreach ( Function * function, XParser::self()->m_ufkt )
      {
            const QList< Plot > plots = function->plots();
            foreach ( const Plot &plot, plots )
            {
                  plot.updateFunction();
                  
                  double best_x = 0.0, distance;
                  QPointF cspos;
                  
                  if ( function->type() == Function::Implicit )
                  {
                        double x = m_crosshairPosition.x();
                        double y = m_crosshairPosition.y();
                        findRoot( & x, & y, plot, PreciseRoot );
                        
                        QPointF d = toPixel( QPointF( x, y ), ClipInfinite ) - toPixel( QPointF( m_crosshairPosition.x(), m_crosshairPosition.y() ), ClipInfinite );
                        
                        distance = std::sqrt( d.x()*d.x() + d.y()*d.y() );
                        cspos = QPointF( x, y );
                  }
                  else
                  {
                        best_x = getClosestPoint( m_crosshairPosition, plot );
                        distance = pixelDistance( m_crosshairPosition, plot, best_x, false );
                        cspos = realValue( plot, best_x, false );;
                  }

                  if ( distance < best_distance )
                  {
                        best_distance = distance;
                        bestPlot = plot;
                        m_trace_x = best_x;
                        best_cspos = cspos;
                  }
            }
      }
      
      if ( best_distance < 10.0 )
      {
            m_currentPlot = bestPlot;
            m_crosshairPosition = best_cspos;
            return m_crosshairPosition;
      }
      else
            return QPointF();
}


03178 double View::getClosestPoint( const QPointF & pos, const Plot & plot )
{
      plot.updateFunction();

      double best_x = 0.0;

      Function * function = plot.function();
      assert( function->type() != Function::Implicit ); // should use findRoot (3D version) for this

      switch ( function->type() )
      {
            case Function::Implicit:
                  break;
            
            case Function::Differential:
            case Function::Cartesian:
            {
                  double best_pixel_x = m_clipRect.width() / 2;
      
                  QPointF pixelPos = toPixel( pos, ClipInfinite );
      
                  double dmin = getXmin( function );
                  double dmax = getXmax( function );
      
                  double stepSize = (m_xmax-m_xmin)/m_clipRect.width();
      
                  // Algorithm in use here: Work out the shortest distance between the
                  // line joining (x0,y0) to (x1,y1) and the given point (real_x,real_y)
      
                  double x = dmin;
                  double y0 = value( plot, 0, x, false );
      
                  double best_distance = 1e20; // a large distance
                  
                  while ( x <= dmax && (xToPixel(x) < best_pixel_x+best_distance) )
                  {
                        x += stepSize;
                        
                        double y1 = value( plot, 0, x, false );
      
                        double _x0 = xToPixel( x-stepSize, ClipInfinite );
                        double _x1 = xToPixel( x, ClipInfinite );
      
                        double _y0 = yToPixel( y0, ClipInfinite );
                        double _y1 = yToPixel( y1, ClipInfinite );
      
                        double k = (_y1-_y0)/(_x1-_x0);
      
                        double closest_x, closest_y;
                        if ( k == 0 )
                        {
                              closest_x = pixelPos.x();
                              closest_y = _y0;
                        }
                        else
                        {
                              closest_x = (pixelPos.y() + pixelPos.x()/k + _x0*k - _y0) / (k + 1.0/k);
                              closest_y = (pixelPos.x() + pixelPos.y()*k + _y0/k - _x0) / (k + 1.0/k);
                        }
                        
                        bool valid = (x-1.5*stepSize <= xToReal(closest_x)) && (xToReal(closest_x) <= x+0.5*stepSize);
                        
                        double dfx = closest_x - pixelPos.x();
                        double dfy = closest_y - pixelPos.y();
      
                        double distance = sqrt( dfx*dfx + dfy*dfy );
                        bool insideView = 0 <= closest_y && closest_y <= m_clipRect.height();
                        
                        if ( distance < best_distance && insideView && valid )
                        {
                              best_distance = distance;
                              best_pixel_x = closest_x;
                        }
                        
                        y0 = y1;
                  }
      
                  best_x = xToReal( best_pixel_x );
                  break;
            }
            
            case Function::Polar:
            case Function::Parametric:
            {
                  double minX = getXmin( function );
                  double maxX = getXmax( function );
                  double stepSize = 0.001;
      
                  while ( stepSize > 0.0000009 )
                  {
                        double best_distance = 1e20; // a large distance
      
                        double x = minX;
                        while ( x <= maxX )
                        {
                              double distance = pixelDistance( pos, plot, x, false );
                              bool insideView = QRectF(m_clipRect).contains( toPixel( realValue( plot, x, false ), ClipInfinite ) );
                              
                              if ( distance < best_distance && insideView )
                              {
                                    best_distance = distance;
                                    best_x = x;
                              }
      
                              x += stepSize;
                        }
      
                        minX = best_x - stepSize;
                        maxX = best_x + stepSize;
      
                        stepSize *= 0.1;
                  }
                  break;
            }
      }
      
      return best_x;
}


03298 double View::pixelDistance( const QPointF & pos, const Plot & plot, double x, bool updateFunction )
{
      QPointF f = realValue( plot, x, updateFunction );
      QPointF df = toPixel( pos, ClipInfinite ) - toPixel( f, ClipInfinite );

      return std::sqrt( df.x()*df.x() + df.y()*df.y() );
}


03307 QString View::posToString( double x, double delta, PositionFormatting format, const QColor &color  ) const
{
      delta = qAbs(delta);
      if ( delta == 0 )
            delta = 1;

      QString numberText;

      int decimalPlaces = 1-int(log(delta)/log(10.0));
      
      // Avoid exponential format for smallish numbers
      if ( 0.01 < qAbs(x) && qAbs(x) < 10000 )
            format = DecimalFormat;

      switch ( format )
      {
            case ScientificFormat:
            {
                  int accuracy = 1 + decimalPlaces + int(log(qAbs(x))/log(10.0));
                  if ( accuracy < 2 )
                  {
                        // Ensure a minimum of two significant digits
                        accuracy = 2;
                  }

                  QString number = QString::number( x, 'g', accuracy );
                  if ( number.contains( 'e' ) )
                  {
                        number.remove( "+0" );
                        number.remove( "+" );
                        number.replace( "-0", MinusSymbol );

                        number.replace( 'e', QChar(215) + QString("10<sup>") );
                        number.append( "</sup>" );
                  }
                  if ( x > 0.0 )
                        number.prepend('+');

                  numberText = QString("<html><body><span style=\"color:%1;\">").arg( color.name() ) + number + "</span></body></html>";

                  break;
            }

            case DecimalFormat:
            {
                  if ( decimalPlaces >= 0 )
                        numberText = QString::number( x, 'f', decimalPlaces );
                  else
                        numberText = QString::number( x*(pow(10.0,decimalPlaces)), 'f', 0 ) + QString( -decimalPlaces, '0' );

                  if ( x > 0.0 )
                        numberText.prepend('+');

                  break;
            }
      }
      
      numberText.replace( '-', MinusSymbol );
      
      return numberText;
}


03370 void View::mouseMoveEvent(QMouseEvent *e)
{
      if ( m_isDrawing || !e)
            return;

      bool inBounds = updateCrosshairPosition();
      if ( !m_haveRoot )
            setStatusBar( QString(), RootSection );

      QString sx, sy;

      if ( inBounds )
      {
            sx = "x = " + posToString( m_crosshairPosition.x(), (m_xmax-m_xmin)/m_clipRect.width(), View::DecimalFormat );
            sy = "y = " + posToString( m_crosshairPosition.y(), (m_ymax-m_ymin)/m_clipRect.width(), View::DecimalFormat );
      }
      else
            sx = sy = "";

      setStatusBar( sx, XSection );
      setStatusBar( sy, YSection );

      if ( e->buttons() & Qt::LeftButton )
      {
            if ( m_zoomMode == ZoomIn )
            {
                  m_zoomMode = ZoomInDrawing;
                  m_zoomRectangleStart = e->pos();
            }
            else if ( m_zoomMode == ZoomOut )
            {
                  m_zoomMode = ZoomOutDrawing;
                  m_zoomRectangleStart = e->pos();
            }
            else if ( ((m_zoomMode == AboutToTranslate) || (m_zoomMode == Translating)) &&
                                      (e->pos() != m_prevDragMousePos) )
            {
                  m_zoomMode = Translating;
                  QPoint d = m_prevDragMousePos - e->pos();
                  m_prevDragMousePos = e->pos();
                  translateView( d.x(), d.y() );
            }
      }

      if ( (m_zoomMode == Normal) &&
                   (m_popupMenuStatus != NoPopup) &&
                   !m_popupMenu->isVisible() )
      {
            if ( m_popupMenuStatus==Popup)
                  m_currentPlot.setFunctionID( -1 );
            m_popupMenuStatus = NoPopup;;
      }

      update();
      updateCursor();
}


03428 bool View::updateCrosshairPosition()
{
      QPointF mousePos = mapFromGlobal( QCursor::pos() );

      bool out_of_bounds = false; // for the ypos
      
      m_crosshairPosition = toReal( mousePos );

      m_currentPlot.updateFunction();
      Function * it = m_currentPlot.function();

      if ( it && crosshairPositionValid( it ) && (m_popupMenuStatus != Popup) )
      {
            // The user currently has a plot selected, with the mouse in a valid position

            if ( (it->type() == Function::Parametric) ||
                              (it->type() == Function::Polar) )
            {

                  // Should we increase or decrease t to get closer to the mouse?
                  double dx[2] = { -0.00001, +0.00001 };
                  double d[] = { 0.0, 0.0 };
                  for ( int i = 0; i < 2; ++ i )
                        d[i] = pixelDistance( m_crosshairPosition, m_currentPlot, m_trace_x + dx[i], false );

                  double prev_best = pixelDistance( m_crosshairPosition, m_currentPlot, m_trace_x, false );
                  double current_dx = dx[(d[0] < d[1]) ? 0 : 1]*1e3;

                  while ( true )
                  {
                        double new_distance = pixelDistance( m_crosshairPosition, m_currentPlot, m_trace_x + current_dx, false );
                        if ( new_distance < prev_best )
                        {
                              prev_best = new_distance;
                              m_trace_x += current_dx;
                        }
                        else
                        {
                              if ( qAbs(current_dx) > 9e-10 )
                                    current_dx *= 0.1;
                              else
                                    break;
                        }
                  }

                  double min = getXmin( it );
                  double max = getXmax( it );

                  if ( m_trace_x > max )
                        m_trace_x  = max;

                  else if ( m_trace_x < min )
                        m_trace_x = min;

                  m_crosshairPosition = realValue( m_currentPlot, m_trace_x, false );
            }
            else if ( it->type() == Function::Implicit )
            {
                  double x = m_crosshairPosition.x();
                  double y = m_crosshairPosition.y();
                  findRoot( & x, & y, m_currentPlot, PreciseRoot );
                  m_crosshairPosition = QPointF( x, y );
            }
            else
            {
                  // cartesian or differential plot

                  m_crosshairPosition.setY( value( m_currentPlot, 0, m_crosshairPosition.x(), false ) );
                  mousePos.setY( yToPixel( m_crosshairPosition.y() ));

                  if ( m_crosshairPosition.y()<m_ymin || m_crosshairPosition.y()>m_ymax) //the ypoint is not visible
                  {
                        out_of_bounds = true;
                  }
                  else if ( (fabs(yToReal(mousePos.y())) < (m_ymax-m_ymin)/80) && (it->type() == Function::Cartesian || it->type() == Function::Differential) )
                  {
                        double x0 = m_crosshairPosition.x();
                        if ( !m_haveRoot && findRoot( &x0, m_currentPlot, PreciseRoot ) )
                        {
                              QString str="  ";
                              str += i18n("root") + ":  x" + SubscriptZeroSymbol + " = ";
                              setStatusBar( str+QString().sprintf("%+.5f", x0), RootSection );
                              m_haveRoot=true;
                        }
                  }
                  else
                        m_haveRoot=false;
            }

            // For Cartesian plots, only adjust the cursor position if it is not at the ends of the view
            if ( ((it->type() != Function::Cartesian) && (it->type() != Function::Differential)) || m_clipRect.contains( mousePos.toPoint() ) )
            {
                  mousePos = toPixel( m_crosshairPosition, ClipAll, mousePos );
                  QPoint globalPos = mapToGlobal( mousePos.toPoint() );
                  QCursor::setPos( globalPos );
            }
      }

      m_crosshairPixelCoords = mousePos;

      return !out_of_bounds && m_clipRect.contains( mousePos.toPoint() );
}


03532 void View::mouseReleaseEvent ( QMouseEvent * e )
{
      bool doDrawPlot = false;

      // avoid zooming in if the zoom rectangle is very small and the mouse was
      // just pressed, which suggests that the user dragged the mouse accidently
      QRect zoomRect = QRect( m_zoomRectangleStart, e->pos() ).normalized();
      int area = zoomRect.width() * zoomRect.height();

      if ( (area <= 500) && (m_mousePressTimer->elapsed() < 100) )
      {
            if ( m_zoomMode == ZoomInDrawing )
                  m_zoomMode = ZoomIn;
            else if ( m_zoomMode == ZoomOutDrawing )
                  m_zoomMode = ZoomOut;
      }

      switch ( m_zoomMode )
      {
            case Normal:
            case AnimatingZoom:
            case AboutToTranslate:
                  break;

            case Translating:
                  doDrawPlot = true;
                  Settings::self()->writeConfig();
                  MainDlg::self()->requestSaveCurrentState();
                  break;

            case ZoomIn:
                  zoomIn( e->pos(), double(Settings::zoomInStep())/100.0 );
                  break;

            case ZoomOut:
                  zoomIn( e->pos(), (double(Settings::zoomOutStep())/100.0) + 1.0 );
                  break;

            case ZoomInDrawing:
                  zoomIn( zoomRect );
                  break;

            case ZoomOutDrawing:
                  zoomOut( zoomRect );
                  break;
      }

      m_zoomMode = Normal;

      if ( doDrawPlot )
            drawPlot();
      else
            update();

      updateCursor();
}


03590 void View::zoomIn( const QPoint & mousePos, double zoomFactor )
{
      QPointF real = toReal( mousePos );

      double diffx = (m_xmax-m_xmin)*zoomFactor;
      double diffy = (m_ymax-m_ymin)*zoomFactor;

      animateZoom( QRectF( real.x()-diffx, real.y()-diffy, 2.0*diffx, 2.0*diffy ) );
}


03601 void View::zoomIn( const QRectF & zoomRect )
{
      QPointF p = zoomRect.topLeft();
      double real1x = xToReal(p.x() );
      double real1y = yToReal(p.y() );
      p = zoomRect.bottomRight();
      double real2x = xToReal(p.x() );
      double real2y = yToReal(p.y() );

      if ( real1x > real2x )
            qSwap( real1x, real2x );
      if ( real1y > real2y )
            qSwap( real1y, real2y );

      animateZoom( QRectF( QPointF( real1x, real1y ), QSizeF( real2x-real1x, real2y-real1y ) ) );
}


03619 void View::zoomOut( const QRectF & zoomRect )
{
      QPointF p = zoomRect.topLeft();
      double _real1x = xToReal(p.x() );
      double _real1y = yToReal(p.y() );
      p = zoomRect.bottomRight();
      double _real2x = xToReal(p.x() );
      double _real2y = yToReal(p.y() );

      double kx = (_real1x-_real2x)/(m_xmin-m_xmax);
      double lx = _real1x - (kx * m_xmin);

      double ky = (_real1y-_real2y)/(m_ymax-m_ymin);
      double ly = _real1y - (ky * m_ymax);

      double real1x = (m_xmin-lx)/kx;
      double real2x = (m_xmax-lx)/kx;

      double real1y = (m_ymax-ly)/ky;
      double real2y = (m_ymin-ly)/ky;

      animateZoom( QRectF( QPointF( real1x, real1y ), QSizeF( real2x-real1x, real2y-real1y ) ) );
}


03644 void View::animateZoom( const QRectF & _newCoords )
{     
      QRectF oldCoords( m_xmin, m_ymin, m_xmax-m_xmin, m_ymax-m_ymin );
      QRectF newCoords( _newCoords.normalized() );

      if ( newCoords.left() == m_xmin &&
                  newCoords.right() == m_xmax &&
                  newCoords.top() == m_ymin &&
                  newCoords.bottom() == m_ymax )
            return;

      m_zoomMode = AnimatingZoom;

      double oldCoordsArea = (m_xmax-m_xmin) * (m_ymax-m_ymin);
      double newCoordsArea = newCoords.width() * newCoords.height();

      QPointF beginTL, beginBR, endTL, endBR;

      if ( oldCoordsArea > newCoordsArea )
      {
            // zooming in
            beginTL = newCoords.topLeft();
            beginBR = newCoords.bottomRight();
            endTL = QPointF( m_xmin, m_ymin );
            endBR = QPointF( m_xmax, m_ymax );
      }
      else
      {
            // zooming out
            beginTL = QPointF( m_xmin, m_ymin );
            beginBR = QPointF( m_xmax, m_ymax );

            double kx = ( m_xmin - m_xmax ) / ( newCoords.left() - newCoords.right() );
            double ky = ( m_ymin - m_ymax ) / ( newCoords.top() - newCoords.bottom() );

            double lx = m_xmin - (kx * newCoords.left());
            double ly = m_ymin - (ky * newCoords.top());

            endTL = QPointF( (kx * m_xmin) + lx, (ky * m_ymin) + ly );
            endBR = QPointF( (kx * m_xmax) + lx, (ky * m_ymax) + ly );
      }

      double MAX = 10;
      double ms = MAX*16; // milliseconds to animate for

      for ( int i = 0; i <= MAX; ++i )
      {
            QTime t;
            t.start();

            QPointF tl = (( i*endTL) + ((MAX-i)*beginTL)) / MAX;
            QPointF br = (( i*endBR) + ((MAX-i)*beginBR)) / MAX;

            m_animateZoomRect = QRectF( tl, QSizeF( br.x()-tl.x(), br.y()-tl.y() ) );

            repaint();

            if ( i == MAX )
                  break;
            else while ( t.elapsed() < (ms/MAX) )
                  ; // do nothing
      }
      
      m_xmin = newCoords.left();
      m_xmax = newCoords.right();
      m_ymin = newCoords.top();
      m_ymax = newCoords.bottom();

      Settings::setXMin( Parser::number( m_xmin ) );
      Settings::setXMax( Parser::number( m_xmax ) );
      Settings::setYMin( Parser::number( m_ymin ) );
      Settings::setYMax( Parser::number( m_ymax ) );
      Settings::self()->writeConfig();
      MainDlg::self()->coordsDialog()->updateXYRange();
      MainDlg::self()->requestSaveCurrentState();

      drawPlot(); //update all graphs

      m_zoomMode = Normal;
}


03726 void View::translateView( int dx, int dy )
{
      double rdx = xToReal( dx ) - xToReal( 0.0 );
      double rdy = yToReal( dy ) - yToReal( 0.0 );

      m_xmin += rdx;
      m_xmax += rdx;
      m_ymin += rdy;
      m_ymax += rdy;

      Settings::setXMin( Parser::number( m_xmin ) );
      Settings::setXMax( Parser::number( m_xmax ) );
      Settings::setYMin( Parser::number( m_ymin ) );
      Settings::setYMax( Parser::number( m_ymax ) );
      MainDlg::self()->coordsDialog()->updateXYRange();

      drawPlot(); //update all graphs
}


03746 void View::stopDrawing()
{
      if (m_isDrawing)
            m_stopCalculating = true;
}


03753 QPointF View::findMinMaxValue( const Plot & plot, ExtremaType type, double dmin, double dmax )
{
      Function * ufkt = plot.function();
      assert( (ufkt->type() == Function::Cartesian) || (ufkt->type() == Function::Differential) );
      
      plot.updateFunction();
      
      Plot differentiated = plot;
      differentiated.differentiate();
      QList<double> roots = findRoots( differentiated, dmin, dmax, RoughRoot );
      
      // The minimum / maximum might occur at the end points
      roots << dmin << dmax;
      
      double best = (type == Maximum) ? -HUGE_VAL : +HUGE_VAL;
      QPointF bestPoint;
      
      foreach ( double root, roots )
      {
            QPointF rv = realValue( plot, root, false );
            if ( (type == Maximum && rv.y() > best) || (type == Minimum && rv.y() < best) )
            {
                  best = rv.y();
                  bestPoint = rv;
            }
      }
      
      return bestPoint;
}


03784 void View::keyPressEvent( QKeyEvent * e )
{
      // if a zoom operation is in progress, assume that the key press is to cancel it
      if ( m_zoomMode != Normal )
      {
            m_zoomMode = Normal;
            update();
            updateCursor();
            return;
      }

      if (m_isDrawing)
      {
            m_stopCalculating=true;
            return;
      }

      if ( m_currentPlot.functionID() == -1 )
            return;

      QMouseEvent * event = 0;
      if (e->key() == Qt::Key_Left )
            event = new QMouseEvent( QEvent::MouseMove, m_crosshairPixelCoords.toPoint() - QPoint(1,1), Qt::LeftButton, Qt::LeftButton, 0 );
      else if (e->key() == Qt::Key_Right )
            event = new QMouseEvent( QEvent::MouseMove, m_crosshairPixelCoords.toPoint() + QPoint(1,1), Qt::LeftButton, Qt::LeftButton, 0 );
      else if (e->key() == Qt::Key_Up || e->key() == Qt::Key_Down) //switch graph in trace mode
      {
            /// \todo reimplement moving between plots
#if 0
            QMap<int, Function*>::iterator it = XParser::self()->m_ufkt.find( m_currentPlot.functionID );
            int const ke=(*it)->parameters.count();
            if (ke>0)
            {
                  m_currentFunctionParameter++;
                  if (m_currentFunctionParameter >= ke)
                        m_currentFunctionParameter=0;
            }
            if (m_currentFunctionParameter==0)
            {
                  int const old_m_currentPlot.functionID=m_currentPlot.functionID;
                  Function::PMode const old_m_currentPlot.plotMode = m_currentPlot.plotMode;
                  bool start = true;
                  bool found = false;
                  while ( 1 )
                  {
                        if ( old_m_currentPlot.functionID==m_currentPlot.functionID && !start)
                        {
                              m_currentPlot.plotMode=old_m_currentPlot.plotMode;
                              break;
                        }
                        kDebug() << "m_currentPlot.functionID: " << m_currentPlot.functionID;
                        switch ( (*it)->type() )
                        {
                              case Function::Parametric:
                        case Function::Polar:
                              break;
                        default:
                        {
                              //going through the function, the first and the second derivative
                              for ( m_currentPlot.plotMode = (Function::PMode)0; m_currentPlot.plotMode < 3; m_currentPlot.plotMode = (Function::PMode)(m_currentPlot.plotMode+1) )
//                            for (m_currentPlot.plotMode=0;m_currentPlot.plotMode<3;m_currentPlot.plotMode++)
                              {
                                          if (start)
                                          {
                                                if ( m_currentPlot.plotMode==Function::Derivative2)
                                                      m_currentPlot.plotMode=Function::Derivative0;
                                                else
                                                      m_currentPlot.plotMode = (Function::PMode)(old_m_currentPlot.plotMode+1);
                                                start=false;
                                          }
                                    kDebug() << "   m_currentPlot.plotMode: " << (int)m_currentPlot.plotMode;

                                    if ( (*it)->plotAppearance( m_currentPlot.plotMode ).visible )
                                          found = true;

                                          if (found)
                                                break;
                                    }
                                    break;
                              }
                        }
                        if (found)
                              break;

                        if ( ++it == XParser::self()->m_ufkt.end())
                              it = XParser::self()->m_ufkt.begin();
                        m_currentPlot.functionID = (*it)->id();
                  }
            }

            kDebug() << "************************";
            kDebug() << "m_currentPlot.functionID: " << (int)m_currentPlot.functionID;
            kDebug() << "m_currentPlot.plotMode: " << (int)m_currentPlot.plotMode;
            kDebug() << "m_currentFunctionParameter: " << m_currentFunctionParameter;

            setStatusBar( (*it)->prettyName( m_currentPlot.plotMode ), FunctionSection );

            event = new QMouseEvent( QEvent::MouseMove, m_crosshairPixelCoords.toPoint(), Qt::LeftButton, Qt::LeftButton, 0 );
#endif
      }
      else if ( e->key() == Qt::Key_Space  )
      {
            event = new QMouseEvent( QEvent::MouseButtonPress, QCursor::pos(), Qt::RightButton, Qt::RightButton, 0 );
            mousePressEvent(event);
            delete event;
            return;
      }
      else
      {
            event = new QMouseEvent( QEvent::MouseButtonPress, m_crosshairPixelCoords.toPoint(), Qt::LeftButton, Qt::LeftButton, 0 );
            mousePressEvent(event);
            delete event;
            return;
      }
      mouseMoveEvent(event);
      delete event;
}


03903 double View::areaUnderGraph( IntegralDrawSettings s )
{
      int sign = 1;
      if ( s.dmax < s.dmin )
      {
            qSwap( s.dmin, s.dmax );
            sign = -1;
      }
      
      else if ( s.dmax == s.dmin )
            return 0;

      Function * ufkt = s.plot.function();
      assert( ufkt );

      double dx = (s.dmax-s.dmin)/m_clipRect.width();
      if ( s.plot.plotMode == Function::Integral )
      {
            double max_dx = ufkt->eq[0]->differentialStates.step().value();
            if ( dx > max_dx )
                  dx = max_dx;
      }

      // Make sure that we calculate the exact area (instead of missing out a
      // vertical slither at the end) by making sure dx tiles the x-range
      // a whole number of times
      int intervals = qRound( (s.dmax-s.dmin)/dx );
      dx = (s.dmax-s.dmin) / intervals;

      double calculated_area=0;
      double x = s.dmin;

      s.plot.updateFunction();

      for ( int i = 0; i <= intervals; ++i )
      {
            double y = value( s.plot, 0, x, false );

            // Trapezoid rule for integrals: only add on half for the first and last value
            if ( (i == 0) || (i == intervals) )
                  calculated_area += 0.5*dx*y;
            else
                  calculated_area += dx*y;

            x=x+dx;
      }

      m_integralDrawSettings = s;
      m_integralDrawSettings.draw = true;
      drawPlot();
      m_integralDrawSettings.draw = false;
      return calculated_area * sign;
}

03957 bool View::isCalculationStopped()
{
      if ( m_stopCalculating)
      {
            m_stopCalculating = false;
            return true;
      }
      else
            return false;
}

void View::updateSliders()
{
      bool needSliderWindow = false;
      foreach ( Function * it, XParser::self()->m_ufkt )
      {
            if ( it->m_parameters.useSlider && !it->allPlotsAreHidden() )
            {
                  needSliderWindow = true;
                  break;;
            }
      }
      
      m_menuSliderAction->setChecked( needSliderWindow );
      
      if ( !needSliderWindow )
      {
            if ( m_sliderWindow )
                  m_sliderWindow->hide();
            return;
      }
      
      if ( !m_sliderWindow )
      {
            m_sliderWindow = new KSliderWindow( this );
            connect( m_sliderWindow, SIGNAL( valueChanged() ), this, SLOT( drawPlot() ) );
            connect( m_sliderWindow, SIGNAL( windowClosed() ), this, SLOT( sliderWindowClosed() ) );
      }
}

void View::sliderWindowClosed()
{
      m_menuSliderAction->setChecked( false );  //set the slider-item in the menu
}

04002 void View::hideCurrentFunction()
{
      if ( m_currentPlot.functionID() == -1 )
      return;

      Function * ufkt = m_currentPlot.function();
      ufkt->plotAppearance( m_currentPlot.plotMode ).visible = false;

      MainDlg::self()->functionEditor()->functionsChanged();
      drawPlot();
      MainDlg::self()->requestSaveCurrentState();
      updateSliders();
      if ( m_currentPlot.functionID() == -1 )
            return;
      if ( ufkt->allPlotsAreHidden() )
      {
            m_currentPlot.setFunctionID( -1 );
            QMouseEvent *event = new QMouseEvent( QMouseEvent::KeyPress, QCursor::pos(), Qt::LeftButton, Qt::LeftButton, 0 );
            mousePressEvent(event); //leave trace mode
            delete event;
            return;
      }
      else
      {
            QKeyEvent *event = new QKeyEvent( QKeyEvent::KeyPress, Qt::Key_Up, 0 );
            keyPressEvent(event); //change selected graph
            delete event;
            return;
      }
}
void View::removeCurrentPlot()
{
      if ( m_currentPlot.functionID() == -1 )
      return;

      Function * ufkt = m_currentPlot.function();
      Function::Type function_type = ufkt->type();
      if (!XParser::self()->removeFunction( ufkt ))
            return;

      if ( m_currentPlot.functionID() != -1 ) // if trace mode is enabled
      {
            m_currentPlot.setFunctionID( -1 );
            QMouseEvent *event = new QMouseEvent( QMouseEvent::KeyPress, QCursor::pos(), Qt::LeftButton, Qt::LeftButton, 0 );
            mousePressEvent(event); //leave trace mode
            delete event;
      }

      drawPlot();
      if ( function_type == Function::Cartesian )
            updateSliders();
      MainDlg::self()->requestSaveCurrentState();
}


void View::animateFunction()
{
      Function * f = m_currentPlot.function();
      if ( !f )
            return;
      
      ParameterAnimator * anim = new ParameterAnimator( this, f );
      anim->show();
}


void View::editCurrentPlot()
{
      MainDlg::self()->functionEditor()->setCurrentFunction( m_currentPlot.functionID() );
}


04074 void View::zoomIn()
{
      m_zoomMode = ZoomIn;
      updateCursor();
}


void View::zoomOut()
{
      m_zoomMode = ZoomOut;
      updateCursor();
}


void View::zoomToTrigonometric()
{
      double rpau = XParser::self()->radiansPerAngleUnit();
      animateZoom( QRectF( -2*M_PI/rpau, -4.0, 4*M_PI/rpau, 8.0 ) );
}


04095 void View::updateCursor()
{
      Cursor newCursor = m_prevCursor;

      if ( m_isDrawing && (m_zoomMode != Translating) )
            newCursor = CursorWait;

      else switch (m_zoomMode)
      {
            case AnimatingZoom:
                  newCursor = CursorArrow;
                  break;

            case Normal:
                  if ( shouldShowCrosshairs() )
                  {
                        // Don't show any cursor if we're tracing a function or the crosshairs should be shown
                        newCursor = CursorBlank;
                  }
                  else
                        newCursor = CursorArrow;
                  break;

            case ZoomIn:
            case ZoomInDrawing:
                  newCursor = CursorMagnify;
                  break;

            case ZoomOut:
            case ZoomOutDrawing:
                  newCursor = CursorLessen;
                  break;

            case AboutToTranslate:
            case Translating:
                  newCursor = CursorMove;
                  break;
      }
      
      if ( newCursor == m_prevCursor )
            return;
      m_prevCursor = newCursor;

      switch ( newCursor )
      {
            case CursorWait:
                  setCursor( Qt::WaitCursor );
                  break;
            case CursorBlank:
                  setCursor( Qt::BlankCursor );
                  break;
            case CursorArrow:
                  setCursor( Qt::ArrowCursor );
                  break;
            case CursorCross:
                  setCursor( Qt::CrossCursor );
                  break;
            case CursorMagnify:
                  setCursor( QCursor( SmallIcon( "magnify", 32), 10, 10 ) );
                  break;
            case CursorLessen:
                  setCursor( QCursor( SmallIcon( "lessen", 32), 10, 10 ) );
                  break;
            case CursorMove:
                  setCursor( Qt::SizeAllCursor );

      }
}


04165 bool View::shouldShowCrosshairs() const
{
      switch ( m_zoomMode )
      {
            case Normal:
            case ZoomIn:
            case ZoomOut:
                  break;

            case AnimatingZoom:
            case ZoomInDrawing:
            case ZoomOutDrawing:
            case AboutToTranslate:
            case Translating:
                  return false;
      }
      
      if ( m_popupMenuStatus != NoPopup )
            return false;

      Function * it = m_currentPlot.function();

      QPoint mousePos = mapFromGlobal( QCursor::pos() );

      return ( underMouse() && (!it || crosshairPositionValid( it )) );
}


04193 bool View::event( QEvent * e )
{
      if ( e->type() == QEvent::WindowDeactivate && m_isDrawing)
      {
            m_stopCalculating = true;
            return true;
      }
      return QWidget::event(e); //send the information further
}


04204 void View::setStatusBar( const QString & t, StatusBarSection section )
{
      QString text;
      if ( section == FunctionSection )
            text = ' ' + t + ' ';
      else
            text = t;
      
      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
      {
            m_statusBarText[ section - 1 ] = text;
            
            QString text;
            for ( int i = 0; i < 4; ++i )
            {
                  if ( m_statusBarText[i].isEmpty() )
                        continue;
                  
                  if ( !text.isEmpty() )
                        text.append( "  |  " );
                  
                  text.append( m_statusBarText[i] );
            }
            
            emit setStatusBarText(text);
      }
      else
      {
            QDBusReply<void> reply = QDBusInterface( QDBusConnection::sessionBus().baseService(), "/kmplot", "org.kde.kmplot.KmPlot" ).call( QDBus::NoBlock, "setStatusBarText", text, (int)section );
      }
}


04237 void View::slidersWindowClosed()
{
      kDebug() ;
      m_menuSliderAction->setChecked(false);
}


04244 void View::setPrintHeaderTable( bool status )
{
      m_printHeaderTable = status;
}

void View::setPrintBackground( bool status )
{
      m_printBackground = status;
}

void View::setPrintWidth( double width )
{
      m_printWidth = width;
}

void View::setPrintHeight( double height )
{
      m_printHeight = height;
}

//END class View



//BEGIN class IntegralDrawSettings
IntegralDrawSettings::IntegralDrawSettings()
{
      dmin = dmax = 0.0;
      draw = false;
}
//END class IntegralDrawSettings

#include "view.moc"

Generated by  Doxygen 1.6.0   Back to index