Commit a0d359d0 authored by David Saxton's avatar David Saxton

Added parameter animator. The value of a function parameter can now be

automatically stepped through.

svn path=/trunk/KDE/kdeedu/kmplot/; revision=554985
parent 631992af
......@@ -137,3 +137,8 @@ kmplot/plotstylewidget.ui
kmplot/parameterswidget.cpp
kmplot/parameterswidget.h
kmplot/parameterswidget.ui
kmplot/animateparameter.cpp
kmplot/animateparameter.h
kmplot/parameteranimator.cpp
kmplot/parameteranimator.h
kmplot/parameteranimator.ui
......@@ -23,8 +23,6 @@ TODO
* An export dialog where you can set the size and enable/disable monocrome.
* More printing options.
* Move Coordinate System toolbar buttons to one drop-down list
* Make Appearance widget more powerful - add gradient for parameters, etc.
* Option to show extreme points of (Cartesian) functions (accessible via right-click, where else?..). This would replace the find min/max dialog box.
IN PROGRESS
=========================
......@@ -33,6 +31,8 @@ IN PROGRESS
DONE
=========================
* Option to show extreme points of (Cartesian) functions (accessible via right-click, where else?..). This would replace the find min/max dialog box.
* Make Appearance widget more powerful - add gradient for parameters, etc.
* Give parameters to all types of plots
* Intrinsic plots: f(x,y) = 0
* Use a better(faster!) algorithm for drawing integrals numeric. It's not urgent anymore since the implemention of Euler's method is better now.
......
......@@ -24,7 +24,9 @@ set(kmplotpart_PART_SRCS
kmplotio.cpp
ksliderwindow.cpp
parameterswidget.cpp
coordsconfigdialog.cpp )
coordsconfigdialog.cpp
parameteranimator.cpp
)
kde4_automoc(${kmplotpart_PART_SRCS})
......@@ -41,6 +43,7 @@ kde4_add_ui_files(kmplotpart_PART_SRCS
settingspagegeneral.ui
sliderwindow.ui
parameterswidget.ui
parameteranimator.ui
)
kde4_add_kcfg_files(kmplotpart_PART_SRCS settings.kcfgc )
......
......@@ -330,6 +330,10 @@ void MainDlg::setupActions()
m_popupmenu->addSeparator();
KAction * animateFunction = new KAction( i18n("Animate Function"), actionCollection(), "animateFunction" );
connect( animateFunction, SIGNAL(triggered(bool)), View::self(), SLOT( animateFunction() ) );
m_popupmenu->addAction( animateFunction );
View::self()->m_showFunctionExtrema = new KToggleAction( i18n( "Show Extrema" ), actionCollection(), "showExtrema" );
View::self()->m_showFunctionExtrema->setIcon( KIcon( "minimum" ) );
connect( View::self()->m_showFunctionExtrema, SIGNAL(triggered(bool)), View::self(), SLOT(showExtrema(bool)) );
......
......@@ -58,7 +58,7 @@
#include "settings.h"
#include "ksliderwindow.h"
#include "MainDlg.h"
#include "parameteranimator.h"
#include "View.h"
#include "viewadaptor.h"
......@@ -270,13 +270,17 @@ void View::draw( QPaintDevice * dev, PlotMedium medium )
}
double View::h() const
{
return qMin( (m_xmax-m_xmin)/area.width(), (m_ymax-m_ymin)/area.height() ) * 1e-1;
}
double View::value( const Plot & plot, int eq, double x, bool updateParameter )
{
Function * function = plot.function();
assert( function );
double dx = (m_xmax-m_xmin)/area.width();
if ( updateParameter )
plot.updateFunctionParameter();
......@@ -288,13 +292,13 @@ double View::value( const Plot & plot, int eq, double x, bool updateParameter )
return XParser::self()->fkt( equation, x );
case Function::Derivative1:
return XParser::self()->derivative( 1, equation, x, dx );
return XParser::self()->derivative( 1, equation, x, h() );
case Function::Derivative2:
return XParser::self()->derivative( 2, equation, x, dx );
return XParser::self()->derivative( 2, equation, x, h() );
case Function::Integral:
return XParser::self()->integral( equation, x, dx );
return XParser::self()->integral( equation, x, h() );
}
kWarning() << k_funcinfo << "Unknown mode!\n";
......@@ -685,6 +689,17 @@ void View::plotFunction(Function *ufkt, QPainter *pDC)
{
plot.updateFunctionParameter();
bool setAliased = false;
if ( plot.parameter.type() == Parameter::Animated )
{
// Don't use antialiasing, so that rendering is speeded up
if ( pDC->renderHints() & QPainter::Antialiasing )
{
setAliased = true;
pDC->setRenderHint( QPainter::Antialiasing, false );
}
}
pDC->setPen( penForPlot( plot, pDC->renderHints() & QPainter::Antialiasing ) );
bool drawIntegral = m_drawIntegral && (m_integralDrawSettings.plot == plot);
......@@ -803,6 +818,9 @@ void View::plotFunction(Function *ufkt, QPainter *pDC)
x=x+dx;
}
}
if ( setAliased )
pDC->setRenderHint( QPainter::Antialiasing, true );
}
}
......@@ -1598,6 +1616,8 @@ double View::pixelNormal( const Plot & plot, double x, double y )
double dx = 0;
double dy = 0;
double h = this->h();
switch ( f->type() )
{
case Function::Cartesian:
......@@ -1608,8 +1628,8 @@ double View::pixelNormal( const Plot & plot, double x, double y )
case Function::Implicit:
{
dx = XParser::self()->partialDerivative( 1, 0, f->eq[0], x, y, 1e-5, 1e-5 ) / sx;
dy = XParser::self()->partialDerivative( 0, 1, f->eq[0], x, y, 1e-5, 1e-5 ) / sy;
dx = XParser::self()->partialDerivative( 1, 0, f->eq[0], x, y, h, h ) / sx;
dy = XParser::self()->partialDerivative( 0, 1, f->eq[0], x, y, h, h ) / sy;
double theta = -arctan( dy / dx );
......@@ -1623,8 +1643,8 @@ double View::pixelNormal( const Plot & plot, double x, double y )
case Function::Polar:
{
double r = XParser::self()->derivative( 0, f->eq[0], x, 1e-5 );
double dr = XParser::self()->derivative( 1, f->eq[0], x, 1e-5 );
double r = XParser::self()->derivative( 0, f->eq[0], x, h );
double dr = XParser::self()->derivative( 1, f->eq[0], x, h );
dx = (dr * cos(x) - r * sin(x)) * sx;
dy = (dr * sin(x) + r * cos(x)) * sy;
......@@ -1633,8 +1653,8 @@ double View::pixelNormal( const Plot & plot, double x, double y )
case Function::Parametric:
{
dx = XParser::self()->derivative( 1, f->eq[0], x, 1e-5 ) * sx;
dy = XParser::self()->derivative( 1, f->eq[1], x, 1e-5 ) * sy;
dx = XParser::self()->derivative( 1, f->eq[0], x, h ) * sx;
dy = XParser::self()->derivative( 1, f->eq[1], x, h ) * sy;
break;
}
}
......@@ -1662,6 +1682,8 @@ double View::pixelCurvature( const Plot & plot, double x, double y )
double fddy = 0;
double fdxy = 0;
double h = this->h();
switch ( f->type() )
{
case Function::Cartesian:
......@@ -1669,17 +1691,17 @@ double View::pixelCurvature( const Plot & plot, double x, double y )
fdx = sx;
fddx = 0;
fdy = XParser::self()->derivative( 1, f->eq[0], x, (m_xmax-m_xmin)/1e5 ) * sy;
fddy = XParser::self()->derivative( 2, f->eq[0], x, (m_xmax-m_xmin)/1e5 ) * sy;
fdy = XParser::self()->derivative( 1, f->eq[0], x, h ) * sy;
fddy = XParser::self()->derivative( 2, f->eq[0], x, h) * sy;
break;
}
case Function::Polar:
{
double r = XParser::self()->derivative( 0, f->eq[0], x, 1e-5 );
double dr = XParser::self()->derivative( 1, f->eq[0], x, 1e-5 );
double ddr = XParser::self()->derivative( 2, f->eq[0], x, 1e-5 );
double r = XParser::self()->derivative( 0, f->eq[0], x, h );
double dr = XParser::self()->derivative( 1, f->eq[0], x, h );
double ddr = XParser::self()->derivative( 2, f->eq[0], x, h );
fdx = (dr * cos(x) - r * sin(x)) * sx;
fdy = (dr * sin(x) + r * cos(x)) * sy;
......@@ -1692,24 +1714,24 @@ double View::pixelCurvature( const Plot & plot, double x, double y )
case Function::Parametric:
{
fdx = XParser::self()->derivative( 1, f->eq[0], x, 1e-5 ) * sx;
fdy = XParser::self()->derivative( 1, f->eq[1], x, 1e-5 ) * sy;
fdx = XParser::self()->derivative( 1, f->eq[0], x, h ) * sx;
fdy = XParser::self()->derivative( 1, f->eq[1], x, h ) * sy;
fddx = XParser::self()->derivative( 2, f->eq[0], x, 1e-5 ) * sx;
fddy = XParser::self()->derivative( 2, f->eq[1], x, 1e-5 ) * sy;
fddx = XParser::self()->derivative( 2, f->eq[0], x, h ) * sx;
fddy = XParser::self()->derivative( 2, f->eq[1], x, h ) * sy;
break;
}
case Function::Implicit:
{
fdx = XParser::self()->partialDerivative( 1, 0, f->eq[0], x, y, 1e-5, 1e-5 ) / sx;
fdy = XParser::self()->partialDerivative( 0, 1, f->eq[0], x, y, 1e-5, 1e-5 ) / sy;
fdx = XParser::self()->partialDerivative( 1, 0, f->eq[0], x, y, h, h ) / sx;
fdy = XParser::self()->partialDerivative( 0, 1, f->eq[0], x, y, h, h ) / sy;
fddx = XParser::self()->partialDerivative( 2, 0, f->eq[0], x, y, 1e-5, 1e-5 ) / (sx*sx);
fddy = XParser::self()->partialDerivative( 0, 2, f->eq[0], x, y, 1e-5, 1e-5 ) / (sy*sy);
fddx = XParser::self()->partialDerivative( 2, 0, f->eq[0], x, y, h, h ) / (sx*sx);
fddy = XParser::self()->partialDerivative( 0, 2, f->eq[0], x, y, h, h ) / (sy*sy);
fdxy = XParser::self()->partialDerivative( 1, 1, f->eq[0], x, y, 1e-5, 1e-5 ) / (sx*sy);
fdxy = XParser::self()->partialDerivative( 1, 1, f->eq[0], x, y, h, h ) / (sx*sy);
break;
......@@ -2047,7 +2069,9 @@ double View::pixelDistance( const QPointF & pos, const Plot & plot, double x, bo
QString View::posToString( double x, double delta, PositionFormatting format, QColor color ) const
{
assert( delta != 0.0 );
// assert( delta != 0.0 );
if ( delta == 0 )
delta = 1;
QString numberText;
......@@ -2929,6 +2953,17 @@ void View::mnuRemove_clicked()
}
void View::animateFunction()
{
Function * f = m_currentPlot.function();
if ( !f )
return;
ParameterAnimator * anim = new ParameterAnimator( this, f );
anim->show();
}
void View::showExtrema( bool show )
{
Function * f = m_currentPlot.function();
......
......@@ -188,6 +188,7 @@ class View : public QWidget
void mnuRemove_clicked();
void mnuEdit_clicked();
void showExtrema( bool show );
void animateFunction();
///Slots for the zoom menu
void mnuZoomIn_clicked();
void mnuZoomOut_clicked();
......@@ -224,6 +225,10 @@ class View : public QWidget
virtual void focusInEvent( QFocusEvent * );
private:
/**
* \return an appropriate value to use in numerical differentiation.
*/
double h() const;
/**
* Print out table with additional information. Only for printing.
*/
......
......@@ -29,6 +29,7 @@
#include <QApplication>
#include <QStyleOptionFrame>
#include <assert.h>
//BEGIN class EquationHighlighter
EquationHighlighter::EquationHighlighter( EquationEdit * parent )
......@@ -122,6 +123,14 @@ void EquationEdit::setInputType( InputType type )
}
double EquationEdit::value( )
{
assert( m_inputType == Expression ); // Can't really get a value of a function as that requires an input
return XParser::self()->eval( text() );
}
void EquationEdit::slotTextChanged( )
{
emit textChanged( text() );
......
......@@ -95,6 +95,10 @@ class EquationEdit : public QTextEdit
void setValidatePrefix( const QString & prefix );
QString text() const { return toPlainText(); }
/**
* Attempts to evaluate the text and return it.
*/
double value();
signals:
void editingFinished();
......
......@@ -501,7 +501,9 @@ QList< Plot > Function::allPlots( ) const
bool usedParameter = false;
if ( m_parameters.useSlider )
// Don't use slider or list parameters if animating
if ( !m_parameters.animating && m_parameters.useSlider )
{
Parameter param( Parameter::Slider );
param.setSliderID( m_parameters.sliderID );
......@@ -511,7 +513,7 @@ QList< Plot > Function::allPlots( ) const
usedParameter = true;
}
if ( m_parameters.useList )
if ( !m_parameters.animating && m_parameters.useList )
{
int pos = 0;
foreach ( Value v, m_parameters.list )
......@@ -525,6 +527,12 @@ QList< Plot > Function::allPlots( ) const
}
}
if ( m_parameters.animating )
{
Parameter param( Parameter::Animated );
plot.parameter = param;
}
if ( !usedParameter )
list << plot;
}
......@@ -538,6 +546,7 @@ QList< Plot > Function::allPlots( ) const
//BEGIN class ParameterSettings
ParameterSettings::ParameterSettings()
{
animating = false;
useSlider = false;
sliderID = 0;
useList = false;
......@@ -642,6 +651,12 @@ void Plot::updateFunctionParameter() const
k = m_function->m_parameters.list[ parameter.listPos() ].value();
break;
}
case Parameter::Animated:
{
// Don't adjust the current function parameter
return;
}
}
m_function->setParameter( k );
......
......@@ -209,6 +209,7 @@ class Equation
bool operator == ( const ParameterSettings & other ) const;
bool operator != ( const ParameterSettings & other ) const { return !((*this) == other); }
bool animating; ///< if true, then useSlider and useList are ignored, parameter value is assumed to be updated
bool useSlider;
int sliderID;
bool useList;
......@@ -223,7 +224,7 @@ class Equation
class Parameter
{
public:
enum Type { Unknown, Slider, List };
enum Type { Unknown, Animated, Slider, List };
Parameter( Type type = Unknown );
Type type() const { return m_type; }
......
......@@ -45,10 +45,9 @@
class ParameterValueList;
KParameterEditor::KParameterEditor(XParser *m, QList<Value> *l, QWidget *parent )
KParameterEditor::KParameterEditor( QList<Value> *l, QWidget *parent )
: KDialog( parent ),
m_parameter(l),
m_parser(m)
m_parameter(l)
{
setCaption( i18n( "Parameter Editor" ) );
setButtons( Ok | Cancel );
......@@ -147,8 +146,8 @@ void KParameterEditor::saveCurrentValue()
bool KParameterEditor::checkValueValid()
{
QString valueText = m_mainWidget->value->text();
(double) m_parser->eval( valueText );
bool valid = (m_parser->parserError( false ) == 0);
(double) XParser::self()->eval( valueText );
bool valid = (XParser::self()->parserError( false ) == 0);
m_mainWidget->valueInvalidLabel->setVisible( !valueText.isEmpty() && !valid );
return valid;
}
......@@ -190,8 +189,8 @@ void KParameterEditor::cmdImport_clicked()
line = stream.readLine();
if (line.isEmpty())
continue;
m_parser->eval( line );
if ( m_parser->parserError(false) == 0)
XParser::self()->eval( line );
if ( XParser::self()->parserError(false) == 0)
{
if ( !checkTwoOfIt(line) )
{
......
......@@ -43,7 +43,7 @@ class KParameterEditor : public KDialog
{
Q_OBJECT
public:
KParameterEditor(XParser *, QList<Value> *, QWidget *parent = 0 );
KParameterEditor( QList<Value> *, QWidget *parent = 0 );
~KParameterEditor();
public slots:
......@@ -68,7 +68,6 @@ private:
/// Check so that it doesn't exist two equal values
bool checkTwoOfIt( const QString & text);
QList<Value> *m_parameter;
XParser *m_parser;
QParameterEditor * m_mainWidget;
};
......
/*
* KmPlot - a math. function plotter for the KDE-Desktop
*
* Copyright (C) 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 "parameteranimator.h"
#include "ui_parameteranimator.h"
#include "View.h"
#include <kicon.h>
#include <QTimer>
#include <assert.h>
#include <cmath>
using namespace std;
class ParameterAnimatorWidget : public QWidget, public Ui::ParameterAnimator
{
public:
ParameterAnimatorWidget( QWidget * parent = 0 )
: QWidget( parent )
{ setupUi(this); }
};
//BEGIN class ParameterAnimator
ParameterAnimator::ParameterAnimator( QWidget * parent, Function * function )
: KDialog( parent ),
m_function( function )
{
m_widget = new ParameterAnimatorWidget( this );
m_widget->layout()->setMargin( 0 );
setMainWidget( m_widget );
setCaption( i18n("Parameter Animator") );
setButtons( Ok );
m_mode = Paused;
m_currentValue = 0;
m_function->m_parameters.animating = true;
m_function->k = m_currentValue;
m_timer = new QTimer( this );
connect( m_timer, SIGNAL(timeout()), this, SLOT(step()) );
m_widget->gotoInitial->setIcon( KIcon( "2leftarrow" ) );
m_widget->gotoFinal->setIcon( KIcon( "2rightarrow" ) );
m_widget->stepBackwards->setIcon( KIcon( "1leftarrow" ) );
m_widget->stepForwards->setIcon( KIcon( "1rightarrow" ) );
m_widget->pause->setIcon( KIcon( "player_pause" ) );
connect( m_widget->gotoInitial, SIGNAL(clicked()), this, SLOT(gotoInitial()) );
connect( m_widget->gotoFinal, SIGNAL(clicked()), this, SLOT(gotoFinal()) );
connect( m_widget->stepBackwards, SIGNAL(toggled(bool)), this, SLOT(stepBackwards(bool)) );
connect( m_widget->stepForwards, SIGNAL(toggled(bool)), this, SLOT(stepForwards(bool)) );
connect( m_widget->pause, SIGNAL(clicked()), this, SLOT(pause()) );
connect( m_widget->speed, SIGNAL(valueChanged(int)), this, SLOT(updateSpeed()) );
updateUI();
}
ParameterAnimator::~ ParameterAnimator()
{
m_function->m_parameters.animating = false;
/// \todo need to update the view when closing, but destructor might be called from closing kmplot, which causes crash
// View::self()->drawPlot();
}
void ParameterAnimator::step()
{
// This function shouldn't get called when we aren't actually stepping
assert( m_mode != Paused );
if ( m_mode == StepBackwards )
{
if ( m_currentValue <= m_widget->initial->value() )
{
stopStepping();
return;
}
m_currentValue -= m_widget->step->value();
}
else
{
if ( m_currentValue >= m_widget->final->value() )
{
stopStepping();
return;
}
m_currentValue += m_widget->step->value();
}
updateUI();
updateFunctionParameter();
}
void ParameterAnimator::updateFunctionParameter()
{
m_function->k = m_currentValue;
View::self()->drawPlot();
}
void ParameterAnimator::gotoInitial()
{
m_currentValue = m_widget->initial->value();
updateUI();
updateFunctionParameter();
}
void ParameterAnimator::gotoFinal()
{
m_currentValue = m_widget->final->value();
updateUI();
updateFunctionParameter();
}
void ParameterAnimator::stepBackwards( bool step )
{
if ( !step )
{
pause();
return;
}
m_mode = StepBackwards;
startStepping();
updateUI();
}
void ParameterAnimator::stepForwards( bool step )
{
if ( !step )
{
pause();
return;
}
m_mode = StepForwards;
startStepping();
updateUI();
}
void ParameterAnimator::pause()
{
m_mode = Paused;
m_timer->stop();
updateUI();
}
void ParameterAnimator::updateUI()
{
switch ( m_mode )
{
case StepBackwards:
m_widget->stepBackwards->setChecked( true );
m_widget->stepForwards->setChecked( false );
break;
case StepForwards:
m_widget->stepBackwards->setChecked( false );
m_widget->stepForwards->setChecked( true );
break;
case Paused:
m_widget->stepBackwards->setChecked( false );
m_widget->stepForwards->setChecked( false );
break;
}
m_widget->currentValue->setText( View::self()->posToString( m_currentValue, m_widget->step->value() * 1e-2, View::DecimalFormat ) );
}