Back to index

kdeartwork  4.3.2
pendulum.cpp
Go to the documentation of this file.
00001 
00023 #define QT_NO_COMPAT
00024 
00025 // std. C++ headers
00026 #include <cstdlib>
00027 
00028 // Qt headers
00029 #include <QLineEdit>
00030 #include <QSpinBox>
00031 #include <QValidator>
00032 #include <QColorDialog>
00033 #include <QPushButton>
00034 #include <QToolTip>
00035 #include <QResizeEvent>
00036 
00037 // KDE headers
00038 #include <KLocale>
00039 #include <KGlobal>
00040 #include <KConfig>
00041 #include <KDebug>
00042 #include <KMessageBox>
00043 
00044 // Eigen2 from KDE support
00045 #include <Eigen/Core>
00046 #include <Eigen/Geometry>
00047 // import most common Eigen types
00048 USING_PART_OF_NAMESPACE_EIGEN
00049 
00050 // the screen saver preview area class
00051 #include "sspreviewarea.h"
00052 
00053 #include "pendulum.h"           // own interfaces
00054 #include <kcolordialog.h>
00055 #include "pendulum.moc"
00056 
00057 #define KPENDULUM_VERSION "2.0"
00058 
00059 #ifndef M_PI
00060 #define M_PI 3.14159265358979323846
00061 #endif
00062 
00063 // libkscreensaver interface
00064 class KPendulumSaverInterface : public KScreenSaverInterface
00065 {
00066    public:
00068       virtual KAboutData* aboutData()
00069       {
00070          return new KAboutData(
00071             "kpendulum.kss", 0,
00072             ki18n("Simulation of a two-part pendulum"),
00073             KPENDULUM_VERSION,
00074             ki18n("Simulation of a two-part pendulum"));
00075       }
00076 
00078       virtual KScreenSaver* create(WId id)
00079       {
00080          return new KPendulumSaver(id);
00081       }
00082 
00084       virtual QDialog* setup()
00085       {
00086          return new KPendulumSetup();
00087       }
00088 };
00089 
00090 int main( int argc, char *argv[] )
00091 {
00092    KPendulumSaverInterface kss;
00093    return kScreenSaverMain(argc, argv, kss);
00094 }
00095 
00096 //-----------------------------------------------------------------------------
00097 // PendulumOdeSolver
00098 //-----------------------------------------------------------------------------
00099 
00100 PendulumOdeSolver::PendulumOdeSolver(
00101    const double&   t,
00102    const double&   dt,
00103    const Vector4d& y,
00104    const double&   eps,
00105    const double&   m1,
00106    const double&   m2,
00107    const double&   l1,
00108    const double&   l2,
00109    const double&   g)
00110    : RkOdeSolver<double,4>(t, y, dt, eps),
00111      // constants for faster numeric calculation, derived from m1,m2,l1,l2,g
00112      m_A(1.0/(m2*l1*l1)),
00113      m_B1(m2*l1*l2),
00114      m_B(1.0/m_B1),
00115      m_C((m1+m2)/(m2*m2*l2*l2)),
00116      m_D(g*(m1+m2)*l1),
00117      m_E(g*m2*l2),
00118      m_M((m1+m2)/m2)
00119 {
00120 }
00121 
00122 Vector4d PendulumOdeSolver::f(const double& x, const Vector4d& y) const
00123 {
00124    (void)x; // unused
00125 
00126    const double& q1 = y[0];
00127    const double& q2 = y[1];
00128    const double& p1 = y[2];
00129    const double& p2 = y[3];
00130 
00131    const double cosDq = std::cos(q1-q2);
00132    const double iden  = 1.0/(m_M - cosDq*cosDq); // invers denominator
00133    const double dq1dt = (m_A*p1 - m_B*cosDq*p2)*iden;
00134    const double dq2dt = (m_C*p2 - m_B*cosDq*p1)*iden;
00135 
00136    Vector4d ypr;
00137    ypr[0] = dq1dt;
00138    ypr[1] = dq2dt;
00139 
00140    const double K = m_B1 * dq1dt*dq2dt * std::sin(q1-q2);
00141    ypr[2] = -K - m_D * std::sin(q1);
00142    ypr[3] =  K - m_E * std::sin(q2);
00143 
00144    return ypr;
00145 }
00146 
00147 //-----------------------------------------------------------------------------
00148 // Rotation: screen saver widget
00149 //-----------------------------------------------------------------------------
00150 
00151 PendulumGLWidget::PendulumGLWidget(QWidget* parent)
00152    : QGLWidget(parent),
00153      m_eyeR(30),                  // eye coordinates (polar)
00154      m_eyeTheta(M_PI*0.45),
00155      m_eyePhi(0),
00156      m_lightR(m_eyeR),              // light coordinates (polar)
00157      m_lightTheta(M_PI*0.25),
00158      m_lightPhi(M_PI*0.25),
00159      m_quadM1(gluNewQuadric()),
00160      m_barColor(KPendulumSaver::sm_barColorDefault),
00161      m_m1Color(KPendulumSaver::sm_m1ColorDefault),
00162      m_m2Color(KPendulumSaver::sm_m2ColorDefault)
00163 {
00164 }
00165 
00166 PendulumGLWidget::~PendulumGLWidget(void)
00167 {
00168    gluDeleteQuadric(m_quadM1);
00169 }
00170 
00171 void PendulumGLWidget::setEyePhi(double phi)
00172 {
00173    m_eyePhi = phi;
00174    while (m_eyePhi < 0)
00175    {
00176       m_eyePhi += 2.*M_PI;
00177    }
00178    while (m_eyePhi > 2*M_PI)
00179    {
00180       m_eyePhi -= 2.*M_PI;
00181    }
00182 
00183    // get the view port
00184    GLint vp[4];
00185    glGetIntegerv(GL_VIEWPORT, vp);
00186 
00187    // calc new perspective, a resize event is simulated here
00188    resizeGL(static_cast<int>(vp[2]), static_cast<int>(vp[3]));
00189 }
00190 
00191 void PendulumGLWidget::setAngles(const double& q1, const double& q2)
00192 {
00193    m_ang1 = static_cast<GLfloat>(q1*180./M_PI);
00194    m_ang2 = static_cast<GLfloat>(q2*180./M_PI);
00195 }
00196 
00197 void PendulumGLWidget::setMasses(const double& m1, const double& m2)
00198 {
00199    m_sqrtm1 = static_cast<GLfloat>(std::sqrt(m1));
00200    m_sqrtm2 = static_cast<GLfloat>(std::sqrt(m2));
00201 }
00202 
00203 void PendulumGLWidget::setLengths(const double& l1, const double& l2)
00204 {
00205    m_l1 = static_cast<GLfloat>(l1);
00206    m_l2 = static_cast<GLfloat>(l2);
00207 }
00208 
00209 void PendulumGLWidget::setBarColor(const QColor& c)
00210 {
00211    if (c.isValid())
00212    {
00213       m_barColor = c;
00214    }
00215 }
00216 
00217 void PendulumGLWidget::setM1Color(const QColor& c)
00218 {
00219    if (c.isValid())
00220    {
00221       m_m1Color = c;
00222    }
00223 }
00224 void PendulumGLWidget::setM2Color(const QColor& c)
00225 {
00226    if (c.isValid())
00227    {
00228       m_m2Color = c;
00229    }
00230 }
00231 
00232 /* --------- protected methods ----------- */
00233 
00234 void PendulumGLWidget::initializeGL(void)
00235 {
00236    qglClearColor(QColor(Qt::black)); // set color to clear the background
00237 
00238    glClearDepth(1);             // depth buffer setup
00239    glEnable(GL_DEPTH_TEST);     // depth testing
00240    glDepthFunc(GL_LEQUAL);      // type of depth test
00241 
00242    glShadeModel(GL_SMOOTH);     // smooth color shading in poygons
00243 
00244    // nice perspective calculation
00245    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
00246 
00247    // set up the light
00248    glEnable(GL_LIGHTING);
00249    glEnable(GL_LIGHT0);
00250    glEnable(GL_LIGHT1);
00251 
00252    glMatrixMode(GL_MODELVIEW);           // select modelview matrix
00253    glLoadIdentity();
00254    // set position of light0
00255    GLfloat lightPos[4]=
00256       {m_lightR * std::sin(m_lightTheta) * std::sin(m_lightPhi),
00257        m_lightR * std::sin(m_lightTheta) * std::cos(m_lightPhi),
00258        m_lightR * std::cos(m_lightTheta),
00259        0};
00260    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
00261    // set position of light1
00262    lightPos[0] = m_lightR * std::sin(m_lightTheta) * std::sin(m_lightPhi+M_PI);
00263    lightPos[1] = m_lightR * std::sin(m_lightTheta) * std::cos(m_lightPhi+M_PI);
00264    glLightfv(GL_LIGHT1, GL_POSITION, lightPos);
00265 
00266    // only for lights #>0
00267    GLfloat spec[] = {1,1,1,1};
00268    glLightfv(GL_LIGHT1, GL_SPECULAR, spec);
00269    glLightfv(GL_LIGHT1, GL_DIFFUSE, spec);
00270 
00271    // enable setting the material colour by glColor()
00272    glEnable(GL_COLOR_MATERIAL);
00273 
00274    GLfloat emi[4] = {.13, .13, .13, 1};
00275    glMaterialfv(GL_FRONT, GL_EMISSION, emi);
00276 }
00277 
00278 void PendulumGLWidget::paintGL(void)
00279 {
00280    // clear color and depth buffer
00281    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
00282 
00283    glMatrixMode(GL_MODELVIEW);           // select modelview matrix
00284 
00285    glLoadIdentity();
00286 
00287    const GLfloat width     = 2.0;
00288    const GLfloat masswidth = 1.0;
00289    const int noOfSlices    = 20;
00290 
00291    // top axis, left (x>0)
00292    glTranslatef(0.5*width, 0, 0);
00293    glRotatef(90, 0, 1, 0);
00294    qglColor(m_barColor);
00295    gluCylinder(m_quadM1, 0.2, 0.2, 5, 10, 1);
00296    gluSphere(m_quadM1, 0.2, 10, 10);
00297    // top axis, right
00298    glLoadIdentity();
00299    glTranslatef(-0.5*width, 0, 0);
00300    glRotatef(-90, 0, 1, 0);
00301    gluCylinder(m_quadM1, 0.2, 0.2, 5, 10, 1);
00302    gluSphere(m_quadM1, 0.2, 10, 10);
00303    // 1. part, left
00304    glLoadIdentity();
00305    glRotatef(m_ang1, 1, 0, 0);
00306    glPushMatrix();
00307    glTranslatef(0.5*width, 0, -m_l1);
00308    gluCylinder(m_quadM1, 0.2, 0.2, m_l1, 10, 1);
00309    glPopMatrix();
00310 
00311    // 1. part, right
00312    glPushMatrix();
00313    glTranslatef(-0.5*width, 0, -m_l1);
00314    gluCylinder(m_quadM1, 0.2, 0.2, m_l1, 10, 1);
00315    // 1. part, bottom
00316    glRotatef(90, 0, 1, 0);
00317    gluSphere(m_quadM1, 0.2, 10, 10); // bottom corner 1
00318    gluCylinder(m_quadM1, 0.2, 0.2, width, 10, 1); // connection
00319    glTranslatef(0, 0, 0.5*(width-masswidth));
00320    qglColor(m_m1Color);
00321    gluCylinder(m_quadM1, m_sqrtm1, m_sqrtm1, masswidth, noOfSlices, 1); // mass 1
00322    gluQuadricOrientation(m_quadM1, GLU_INSIDE);
00323    gluDisk(m_quadM1, 0, m_sqrtm1, noOfSlices, 1); // bottom of mass
00324    gluQuadricOrientation(m_quadM1, GLU_OUTSIDE);
00325    glTranslatef(0, 0, masswidth);
00326    gluDisk(m_quadM1, 0, m_sqrtm1, noOfSlices, 1); // top of mass
00327 
00328    glTranslatef(0, 0, 0.5*(width-masswidth));
00329    qglColor(m_barColor);
00330    gluSphere(m_quadM1, 0.2, 10, 10); // bottom corner 2
00331    glPopMatrix();
00332 
00333    // 2. pendulum bar
00334    glLoadIdentity();
00335    glTranslatef(0, m_l1*std::sin(m_ang1*M_PI/180.), -m_l1*std::cos(m_ang1*M_PI/180.));
00336    glRotatef(m_ang2, 1, 0, 0);
00337    glTranslatef(0, 0, -m_l2);
00338    qglColor(m_barColor);
00339    gluCylinder(m_quadM1, 0.2, 0.2, m_l2, 10, 1);
00340 
00341    // mass 2
00342    glRotatef(90, 0, 1, 0);
00343    glTranslatef(0, 0, -0.5*masswidth);
00344    qglColor(m_m2Color);
00345    gluCylinder(m_quadM1, m_sqrtm2, m_sqrtm2, masswidth, noOfSlices, 1);
00346    gluQuadricOrientation(m_quadM1, GLU_INSIDE);
00347    gluDisk(m_quadM1, 0, m_sqrtm2, noOfSlices, 1); // bottom of mass
00348    gluQuadricOrientation(m_quadM1, GLU_OUTSIDE);
00349    glTranslatef(0, 0, masswidth);
00350    gluDisk(m_quadM1, 0, m_sqrtm2, noOfSlices, 1); // top of mass
00351 
00352    glFlush();
00353 }
00354 
00355 void PendulumGLWidget::resizeGL(int w, int h)
00356 {
00357    kDebug() << "w=" << w << ", h=" << h << "\n";
00358 
00359    // prevent a divide by zero
00360    if (h == 0)
00361    {
00362       return;
00363    }
00364 
00365    // set the new view port
00366    glViewport(0, 0, static_cast<GLint>(w), static_cast<GLint>(h));
00367 
00368    // set up projection matrix
00369    glMatrixMode(GL_PROJECTION);
00370    glLoadIdentity();
00371    // Perspective view
00372    gluPerspective(
00373       40.0f,
00374       static_cast<GLdouble>(w)/static_cast<GLdouble>(h),
00375       1.0, 100.0f);
00376 
00377    // Viewing transformation, position for better view
00378    // Theta is polar angle 0<Theta<Pi
00379    gluLookAt(
00380       m_eyeR * std::sin(m_eyeTheta) * std::sin(m_eyePhi),
00381       m_eyeR * std::sin(m_eyeTheta) * std::cos(m_eyePhi),
00382       m_eyeR * std::cos(m_eyeTheta),
00383       0,0,0,
00384       0,0,1);
00385 }
00386 
00387 //-----------------------------------------------------------------------------
00388 // KPendulumSaver: screen saver class
00389 //-----------------------------------------------------------------------------
00390 
00391 // class methods
00392 
00393 KPendulumSaver::KPendulumSaver(WId id) :
00394    KScreenSaver(id),
00395    m_solver(0),
00396    m_massRatio(sm_massRatioDefault),
00397    m_lengthRatio(sm_lengthRatioDefault),
00398    m_g(sm_gDefault),
00399    m_E(sm_EDefault),
00400    m_persChangeInterval(sm_persChangeIntervalDefault)
00401 {
00402    // no need to set our parent widget's background here, the GL widget sets its
00403    // own background color
00404 
00405 
00406    m_glArea = new PendulumGLWidget(); // create gl widget w/o parent
00407    m_glArea->setEyePhi(sm_eyePhiDefault);
00408    readSettings();              // read global settings into pars
00409 
00410 
00411    // init m_glArea with read settings, construct and init m_solver
00412    initData();
00413 
00414    embed(m_glArea);               // embed gl widget and resize it
00415    m_glArea->show();              // show embedded gl widget
00416 
00417    // set up and start cyclic timer
00418    m_timer = new QTimer(this);
00419    m_timer->start(sm_deltaT);
00420    connect(m_timer, SIGNAL(timeout()), this, SLOT(doTimeStep()));
00421 }
00422 
00423 KPendulumSaver::~KPendulumSaver()
00424 {
00425    m_timer->stop();
00426 
00427    // m_timer is automatically deleted with parent KPendulumSaver
00428    delete m_solver;
00429    delete m_glArea;
00430 }
00431 
00432 
00433 void KPendulumSaver::readSettings()
00434 {
00435    // read configuration settings from config file
00436    KConfigGroup config(KGlobal::config(), "Settings");
00437 
00438    // internal saver parameters are set to stored values or left at their
00439    // default values if stored values are out of range
00440    setMassRatio(
00441       config.readEntry(
00442          "mass ratio",
00443          KPendulumSaver::sm_massRatioDefault));
00444    setLengthRatio(
00445       config.readEntry(
00446          "length ratio",
00447          KPendulumSaver::sm_lengthRatioDefault));
00448    setG(
00449       config.readEntry(
00450          "g",
00451          KPendulumSaver::sm_gDefault));
00452    setE(
00453       config.readEntry(
00454          "E",
00455          KPendulumSaver::sm_EDefault));
00456    setPersChangeInterval(
00457       config.readEntry(
00458          "perspective change interval",
00459          (uint)KPendulumSaver::sm_persChangeIntervalDefault));
00460 
00461    // set the colours
00462    setBarColor(config.readEntry("bar color", sm_barColorDefault));
00463    setM1Color( config.readEntry("m1 color",  sm_m1ColorDefault));
00464    setM2Color( config.readEntry("m2 color",  sm_m2ColorDefault));
00465 }
00466 
00467 void KPendulumSaver::initData()
00468 {
00469    const double m1plusm2 = 2;   // m1+m2
00470    const double m2       = m_massRatio * m1plusm2;
00471    const double m1       = m1plusm2 - m2;
00472    m_glArea->setMasses(m1, m2);
00473    m_glArea->setAngles(0, 0);
00474 
00475    const double l1plusl2 = 9;   // l1+l2
00476    const double l2       = m_lengthRatio * l1plusl2;
00477    const double l1       = l1plusl2 - l2;
00478    m_glArea->setLengths(l1, l2);
00479 
00480    // kinetic energy of m2 and m1
00481    const double kin_energy = m_E * m_g * (l1*m1 + (m1+m2)*(l1+l2));
00482    // angular velocity for 1. and 2. pendulum
00483    const double qp = std::sqrt(2.*kin_energy/((m1+m2)*l1*l1 + m2*l2*l2 + m2*l1*l2));
00484 
00485    // assemble initial y for solver
00486    Vector4d y;
00487    y[0] = 0;                                  // q1
00488    y[1] = 0;                                  // q2
00489    y[2] = (m1+m2)*l1*l1*qp + 0.5*m2*l1*l2*qp; // p1
00490    y[3] = m2*l2*l2*qp + 0.5*m2*l1*l2*qp;      // p2
00491 
00492    // delete old solver
00493    if (m_solver!=0)
00494    {
00495       delete m_solver;
00496    }
00497    // init new solver
00498    m_solver = new PendulumOdeSolver(
00499       0.0,                      // t
00500       0.01,                     // first dt step size estimation
00501       y,
00502       1e-5,                     // eps
00503       m1,
00504       m2,
00505       l1,
00506       l2,
00507       m_g);
00508 }
00509 
00510 
00511 void KPendulumSaver::setBarColor(const QColor& c)
00512 {
00513    m_glArea->setBarColor(c);
00514 }
00515 QColor KPendulumSaver::barColor(void) const
00516 {
00517    return m_glArea->barColor();
00518 }
00519 
00520 void KPendulumSaver::setM1Color(const QColor& c)
00521 {
00522    m_glArea->setM1Color(c);
00523 }
00524 QColor KPendulumSaver::m1Color(void) const
00525 {
00526    return m_glArea->m1Color();
00527 }
00528 
00529 void KPendulumSaver::setM2Color(const QColor& c)
00530 {
00531    m_glArea->setM2Color(c);
00532 }
00533 QColor KPendulumSaver::m2Color(void) const
00534 {
00535    return m_glArea->m2Color();
00536 }
00537 
00538 
00539 void KPendulumSaver::setMassRatio(const double& massRatio)
00540 {
00541    // range check is not necessary in normal operation because validators check
00542    // the values at input.  But the validators do not check for corrupted
00543    // settings read from disk.
00544    if ((massRatio >= sm_massRatioLimitLower)
00545        && (massRatio <= sm_massRatioLimitUpper)
00546        && (m_massRatio != massRatio))
00547    {
00548       m_massRatio = massRatio;
00549       if (m_timer!=0)
00550       {
00551          initData();
00552       }
00553    }
00554 }
00555 
00556 void KPendulumSaver::setLengthRatio(const double& lengthRatio)
00557 {
00558    if ((lengthRatio >= sm_lengthRatioLimitLower)
00559        && (lengthRatio <= sm_lengthRatioLimitUpper)
00560        && (m_lengthRatio != lengthRatio))
00561    {
00562       m_lengthRatio = lengthRatio;
00563       if (m_timer!=0)
00564       {
00565          initData();
00566       }
00567    }
00568 }
00569 
00570 void KPendulumSaver::setG(const double& g)
00571 {
00572    if ((g >= sm_gLimitLower)
00573        && (g <= sm_gLimitUpper)
00574        && (m_g != g))
00575    {
00576       m_g = g;
00577       if (m_timer!=0)
00578       {
00579          initData();
00580       }
00581    }
00582 }
00583 
00584 void KPendulumSaver::setE(const double& E)
00585 {
00586    if ((E >= sm_ELimitLower)
00587        && (E <= sm_ELimitUpper)
00588        && (m_E != E))
00589    {
00590       m_E = E;
00591       if (m_timer!=0)
00592       {
00593          initData();
00594       }
00595    }
00596 }
00597 
00598 void KPendulumSaver::setPersChangeInterval(
00599    const unsigned int& persChangeInterval)
00600 {
00601    if ((persChangeInterval >= sm_persChangeIntervalLimitLower)
00602        && (persChangeInterval <= sm_persChangeIntervalLimitUpper)
00603        && (m_persChangeInterval != persChangeInterval))
00604    {
00605       m_persChangeInterval = persChangeInterval;
00606       // do not restart simulation here
00607    }
00608 }
00609 
00610 void KPendulumSaver::doTimeStep()
00611 {
00612    /* time (in seconds) of perspective change.
00613     * - t<0: no change yet
00614     * - t=0: change starts
00615     * - 0<t<moving time: change takes place
00616     * - t=moving time: end of the change */
00617    static double persChangeTime = -5;
00618 
00619    // integrate a step ahead
00620    m_solver->integrate(0.001 * sm_deltaT);
00621 
00622    // read new y from solver
00623    const Vector4d& y = m_solver->Y();
00624 
00625    // tell glArea the new coordinates/angles of the pendulum
00626    m_glArea->setAngles(y[0], y[1]);
00627 
00628    // handle perspective change
00629    persChangeTime += 0.001 * sm_deltaT;
00630    if (persChangeTime > 0)
00631    {
00632       // phi value at the start of a perspective change
00633       static double eyePhi0     = sm_eyePhiDefault;
00634       // phi value at the end of a perspective change
00635       static double eyePhi1     = 0.75*M_PI;
00636       static double deltaEyePhi = eyePhi1-eyePhi0;
00637 
00638       // movement acceleration/deceleration
00639       const double a = 3;
00640       // duration of the change period
00641       const double movingTime = 2.*std::sqrt(std::abs(deltaEyePhi)/a);
00642 
00643       // new current phi of eye
00644       double eyePhi = persChangeTime < 0.5*movingTime ?
00645          // accelerating phase
00646          eyePhi0 + (deltaEyePhi>0?1:-1)
00647          * 0.5*a*persChangeTime*persChangeTime:
00648          // decellerating phase
00649          eyePhi1 - (deltaEyePhi>0?1:-1)
00650          * 0.5*a*(movingTime-persChangeTime)*(movingTime-persChangeTime);
00651 
00652       if (persChangeTime > movingTime)
00653       {  // perspective change has finished
00654          // set new time till next change
00655          persChangeTime = -double(m_persChangeInterval);
00656          eyePhi0 = eyePhi = eyePhi1;
00657          // find new phi value with angleLimit < phi < Pi-angleLimit or
00658          // Pi+angleLimit < phi < 2*Pi-angleLimit
00659          const double angleLimit = M_PI*0.2;
00660          for (eyePhi1 = 0;
00661               (eyePhi1<angleLimit)
00662                  || ((eyePhi1<M_PI+angleLimit) && (eyePhi1>M_PI-angleLimit))
00663                  || (eyePhi1>2*M_PI-angleLimit);
00664               eyePhi1 = double(rand())/RAND_MAX * 2*M_PI)
00665          {
00666          }
00667          // new delta phi for next change
00668          deltaEyePhi = eyePhi1 - eyePhi0;
00669          // find shortest perspective change
00670          if (deltaEyePhi < -M_PI)
00671          {
00672             deltaEyePhi += 2*M_PI;
00673          }
00674       }
00675 
00676       m_glArea->setEyePhi(eyePhi); // set new perspective
00677    }
00678 
00679    m_glArea->updateGL();          // repaint scenery
00680 
00681    // restarting timer not necessary here, it is a cyclic timer
00682 }
00683 
00684 // public slot of KPendulumSaver, forward resize event to public slot of glArea
00685 // to allow the resizing of the gl area withing the setup dialog
00686 void KPendulumSaver::resizeGlArea(QResizeEvent* e)
00687 {
00688    m_glArea->resize(e->size());
00689 }
00690 
00691 // public static class member variables
00692 
00693 const QColor KPendulumSaver::sm_barColorDefault(255, 255, 127);
00694 const QColor KPendulumSaver::sm_m1ColorDefault( 170,   0, 127);
00695 const QColor KPendulumSaver::sm_m2ColorDefault(  85, 170, 127);
00696 
00697 const double KPendulumSaver::sm_massRatioLimitLower                =   0.01;
00698 const double KPendulumSaver::sm_massRatioLimitUpper                =   0.99;
00699 const double KPendulumSaver::sm_massRatioDefault                   =   0.5;
00700 
00701 const double KPendulumSaver::sm_lengthRatioLimitLower              =   0.01;
00702 const double KPendulumSaver::sm_lengthRatioLimitUpper              =   0.99;
00703 const double KPendulumSaver::sm_lengthRatioDefault                 =   0.5;
00704 
00705 const double KPendulumSaver::sm_gLimitLower                        =   0.1;
00706 const double KPendulumSaver::sm_gLimitUpper                        = 300.0;
00707 const double KPendulumSaver::sm_gDefault                           =  40.0;
00708 
00709 const double KPendulumSaver::sm_ELimitLower                        =   0.0;
00710 const double KPendulumSaver::sm_ELimitUpper                        =   5.0;
00711 const double KPendulumSaver::sm_EDefault                           =   1.2;
00712 
00713 const unsigned int KPendulumSaver::sm_persChangeIntervalLimitLower =   5;
00714 const unsigned int KPendulumSaver::sm_persChangeIntervalLimitUpper = 600;
00715 const unsigned int KPendulumSaver::sm_persChangeIntervalDefault    =  15;
00716 
00717 // private static class member variables
00718 
00719 const unsigned int KPendulumSaver::sm_deltaT                       =  20;
00720 const double KPendulumSaver::sm_eyePhiDefault = 0.25 * M_PI;
00721 
00722 //-----------------------------------------------------------------------------
00723 // KPendulumSetup: dialog to setup screen saver parameters
00724 //-----------------------------------------------------------------------------
00725 
00726 KPendulumSetup::KPendulumSetup(QWidget* parent)
00727    : QDialog(parent)
00728 {
00729    setupUi(this);
00730 
00731    // the dialog should block, no other control center input should be possible
00732    // until the dialog is closed
00733    setModal(true);
00734 
00735    // create input validators
00736    m_mEdit->setValidator(
00737       new QDoubleValidator(
00738          KPendulumSaver::sm_massRatioLimitLower,
00739          KPendulumSaver::sm_massRatioLimitUpper,
00740          5, m_mEdit));
00741    m_lEdit->setValidator(
00742       new QDoubleValidator(
00743          KPendulumSaver::sm_lengthRatioLimitLower,
00744          KPendulumSaver::sm_lengthRatioLimitUpper,
00745          5, m_lEdit));
00746    m_gEdit->setValidator(
00747       new QDoubleValidator(
00748          KPendulumSaver::sm_gLimitLower,
00749          KPendulumSaver::sm_gLimitUpper,
00750          5, m_gEdit));
00751    m_eEdit->setValidator(
00752       new QDoubleValidator(
00753          KPendulumSaver::sm_ELimitLower,
00754          KPendulumSaver::sm_ELimitUpper,
00755          5, m_eEdit));
00756 
00757    // set input limits for the perspective change interval time
00758    m_persSpinBox->setMinimum(KPendulumSaver::sm_persChangeIntervalLimitLower);
00759    m_persSpinBox->setMaximum(KPendulumSaver::sm_persChangeIntervalLimitUpper);
00760 
00761    // set tool tips of editable fields
00762    m_mEdit->setToolTip(
00763       ki18n("Ratio of 2nd mass to sum of both masses.\nValid values from %1 to %2.")
00764       .subs(KPendulumSaver::sm_massRatioLimitLower, 0, 'f', 2)
00765       .subs(KPendulumSaver::sm_massRatioLimitUpper, 0, 'f', 2)
00766       .toString());
00767    m_lEdit->setToolTip(
00768       ki18n("Ratio of 2nd pendulum part length to the sum of both part lengths.\nValid values from %1 to %2.")
00769       .subs(KPendulumSaver::sm_lengthRatioLimitLower, 0, 'f', 2)
00770       .subs(KPendulumSaver::sm_lengthRatioLimitUpper, 0, 'f', 2)
00771       .toString());
00772    m_gEdit->setToolTip(
00773       ki18n("Gravitational constant in arbitrary units.\nValid values from %1 to %2.")
00774       .subs(KPendulumSaver::sm_gLimitLower, 0, 'f', 2)
00775       .subs(KPendulumSaver::sm_gLimitUpper, 0, 'f', 2)
00776       .toString());
00777    m_eEdit->setToolTip(
00778       ki18n("Energy in units of the maximum potential energy of the given configuration.\nValid values from %1 to %2.")
00779       .subs(KPendulumSaver::sm_ELimitLower, 0, 'f', 2)
00780       .subs(KPendulumSaver::sm_ELimitUpper, 0, 'f', 2)
00781       .toString());
00782    m_persSpinBox->setToolTip(
00783       ki18n("Time in seconds after which a random perspective change occurs.\nValid values from %1 to %2.")
00784       .subs(KPendulumSaver::sm_persChangeIntervalLimitLower)
00785       .subs(KPendulumSaver::sm_persChangeIntervalLimitUpper)
00786       .toString());
00787 
00788    // init preview area
00789    QPalette palette;
00790    palette.setColor(m_preview->backgroundRole(), Qt::black);
00791    m_preview->setPalette(palette);
00792    m_preview->setAutoFillBackground(true);
00793    m_preview->show();    // otherwise saver does not get correct size
00794 
00795    // create saver and give it the WinID of the preview area
00796    m_saver = new KPendulumSaver(m_preview->winId());
00797 
00798    // read settings from saver and update GUI elements with these values, saver
00799    // has read settings in its constructor
00800 
00801    // set editable fields with stored values as defaults
00802    QString text;
00803    text.setNum(m_saver->massRatio());
00804    m_mEdit->setText(text);
00805    text.setNum(m_saver->lengthRatio());
00806    m_lEdit->setText(text);
00807    text.setNum(m_saver->g());
00808    m_gEdit->setText(text);
00809    text.setNum(m_saver->E());
00810    m_eEdit->setText(text);
00811 
00812    m_persSpinBox->setValue(m_saver->persChangeInterval());
00813 
00814    palette.setColor(m_barColorButton->backgroundRole(), m_saver->barColor());
00815    m_barColorButton->setPalette(palette);
00816    palette.setColor(m_m1ColorButton->backgroundRole(), m_saver->m1Color());
00817    m_m1ColorButton->setPalette(palette);
00818    palette.setColor(m_m2ColorButton->backgroundRole(), m_saver->m2Color());
00819    m_m2ColorButton->setPalette(palette);
00820 
00821    // if the preview area is resized it emits the resized() event which is
00822    // caught by m_saver.  The embedded GLArea is resized to fit into the preview
00823    // area.
00824    connect(m_preview, SIGNAL(resized(QResizeEvent*)),
00825            m_saver,   SLOT(resizeGlArea(QResizeEvent*)));
00826 
00827    connect(m_okButton,       SIGNAL(clicked()),         this, SLOT(okButtonClickedSlot()));
00828    connect( m_cancelButton,  SIGNAL(clicked()),         this, SLOT(reject() ) );
00829    connect(m_aboutButton,    SIGNAL(clicked()),         this, SLOT(aboutButtonClickedSlot()));
00830 
00831    connect(m_lEdit,          SIGNAL(lostFocus()),       this, SLOT(lEditLostFocusSlot()));
00832    connect(m_gEdit,          SIGNAL(lostFocus()),       this, SLOT(gEditLostFocusSlot()));
00833    connect(m_eEdit,          SIGNAL(lostFocus()),       this, SLOT(eEditLostFocusSlot()));
00834    connect(m_persSpinBox,    SIGNAL(valueChanged(int)), this, SLOT(persChangeEnteredSlot(int)));
00835    connect(m_mEdit,          SIGNAL(lostFocus()),       this, SLOT(mEditLostFocusSlot()));
00836    connect(m_barColorButton, SIGNAL(clicked()),         this, SLOT(barColorButtonClickedSlot()));
00837    connect(m_m1ColorButton,  SIGNAL(clicked()),         this, SLOT(m1ColorButtonClickedSlot()));
00838    connect(m_m2ColorButton,  SIGNAL(clicked()),         this, SLOT(m2ColorButtonClickedSlot()));
00839 }
00840 
00841 KPendulumSetup::~KPendulumSetup()
00842 {
00843    delete m_saver;
00844 }
00845 
00846 // Ok pressed - save settings and exit
00847 void KPendulumSetup::okButtonClickedSlot()
00848 {
00849    KConfigGroup config(KGlobal::config(), "Settings");
00850 
00851    config.writeEntry("mass ratio",   m_saver->massRatio());
00852    config.writeEntry("length ratio", m_saver->lengthRatio());
00853    config.writeEntry("g",            m_saver->g());
00854    config.writeEntry("E",            m_saver->E());
00855    config.writeEntry("perspective change interval",
00856                       m_saver->persChangeInterval());
00857    config.writeEntry("bar color",    m_saver->barColor());
00858    config.writeEntry("m1 color",     m_saver->m1Color());
00859    config.writeEntry("m2 color",     m_saver->m2Color());
00860 
00861    config.sync();
00862    accept();
00863 }
00864 
00865 void KPendulumSetup::aboutButtonClickedSlot()
00866 {
00867    KMessageBox::about(this, i18n("\
00868 <h3>KPendulum Screen Saver for KDE</h3>\
00869 <p>Simulation of a two-part pendulum</p>\
00870 <p>Copyright (c) Georg&nbsp;Drenkhahn 2004</p>\
00871 <p><tt>Georg.Drenkhahn@gmx.net</tt></p>"));
00872 }
00873 
00874 void KPendulumSetup::mEditLostFocusSlot(void)
00875 {
00876    if (m_mEdit->hasAcceptableInput())
00877    {
00878       m_saver->setMassRatio(m_mEdit->text().toDouble());
00879    }
00880    else
00881    {  // write current setting back into input field
00882       QString text;
00883       text.setNum(m_saver->massRatio());
00884       m_mEdit->setText(text);
00885    }
00886 }
00887 void KPendulumSetup::lEditLostFocusSlot(void)
00888 {
00889    if (m_lEdit->hasAcceptableInput())
00890    {
00891       m_saver->setLengthRatio(m_lEdit->text().toDouble());
00892    }
00893    else
00894    {  // write current setting back into input field
00895       QString text;
00896       text.setNum(m_saver->lengthRatio());
00897       m_lEdit->setText(text);
00898    }
00899 }
00900 void KPendulumSetup::gEditLostFocusSlot(void)
00901 {
00902    if (m_gEdit->hasAcceptableInput())
00903    {
00904       m_saver->setG(m_gEdit->text().toDouble());
00905    }
00906    else
00907    {  // write current setting back into input field
00908       QString text;
00909       text.setNum(m_saver->g());
00910       m_gEdit->setText(text);
00911    }
00912 }
00913 void KPendulumSetup::eEditLostFocusSlot(void)
00914 {
00915    if (m_eEdit->hasAcceptableInput())
00916    {
00917       m_saver->setE(m_eEdit->text().toDouble());
00918    }
00919    else
00920    {  // write current setting back into input field
00921       QString text;
00922       text.setNum(m_saver->E());
00923       m_eEdit->setText(text);
00924    }
00925 }
00926 void KPendulumSetup::persChangeEnteredSlot(int t)
00927 {
00928    m_saver->setPersChangeInterval(t);
00929 }
00930 
00931 void KPendulumSetup::barColorButtonClickedSlot(void)
00932 {
00933     QColor color = m_saver->barColor();
00934     if ( KColorDialog::getColor( color, this) )
00935     {
00936         if (color.isValid())
00937         {
00938             m_saver->setBarColor(color);
00939             QPalette palette;
00940             palette.setColor(m_barColorButton->backgroundRole(), color);
00941             m_barColorButton->setPalette(palette);
00942         }
00943     }
00944 }
00945 
00946 void KPendulumSetup::m1ColorButtonClickedSlot(void)
00947 {
00948     QColor color =m_saver->m1Color();
00949     if ( KColorDialog::getColor( color, this) )
00950     {
00951         if (color.isValid())
00952         {
00953             m_saver->setM1Color(color);
00954             QPalette palette;
00955             palette.setColor(m_m1ColorButton->backgroundRole(), color);
00956             m_m1ColorButton->setPalette(palette);
00957         }
00958     }
00959 }
00960 void KPendulumSetup::m2ColorButtonClickedSlot(void)
00961 {
00962     QColor color = m_saver->m2Color();
00963     if ( KColorDialog::getColor( color, this) )
00964     {
00965         if (color.isValid())
00966         {
00967             m_saver->setM2Color(color);
00968             QPalette palette;
00969             palette.setColor(m_m2ColorButton->backgroundRole(), color);
00970             m_m2ColorButton->setPalette(palette);
00971         }
00972     }
00973 }