kde-workspace/kscreensaver/kdesavers/pendulum.cpp
2014-12-10 18:09:09 +00:00

980 lines
29 KiB
C++

/** @file
*
* KPendulum screen saver for KDE
*
* The screen saver displays a physically realistic simulation of a two-part
* pendulum.
*
* Copyright (C) 2004 Georg Drenkhahn, Georg.Drenkhahn@gmx.net
*
* 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) version 3 or
* any later version accepted by the membership of KDE e.V. (or its successor
* approved by the membership of KDE e.V.), which shall act as a proxy defined
* in Section 14 of version 3 of the license.
*
* 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.
*/
#define QT_NO_COMPAT
// std. C++ headers
#include <cstdlib>
// Qt headers
#include <QLineEdit>
#include <QSpinBox>
#include <QValidator>
#include <QColorDialog>
#include <QPushButton>
#include <QToolTip>
#include <QResizeEvent>
// KDE headers
#include <KLocale>
#include <KGlobal>
#include <KConfig>
#include <KDebug>
#include <KMessageBox>
// Eigen2 from KDE support
#include <Eigen/Core>
#include <Eigen/Geometry>
// import most common Eigen types
using namespace Eigen;
// the screen saver preview area class
#include "sspreviewarea.h"
#include "pendulum.h" // own interfaces
#include <kcolordialog.h>
#include "pendulum.moc"
#define KPENDULUM_VERSION "2.0"
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
// libkscreensaver interface
class KPendulumSaverInterface : public KScreenSaverInterface
{
public:
/// aboutdata instance for libkscreensaver interface
virtual KAboutData* aboutData()
{
return new KAboutData(
"kpendulum.kss", 0,
ki18n("Simulation of a two-part pendulum"),
KPENDULUM_VERSION,
ki18n("Simulation of a two-part pendulum"));
}
/// function to create screen saver object
virtual KScreenSaver* create(WId id)
{
return new KPendulumSaver(id);
}
/// function to create setup dialog for screen saver
virtual QDialog* setup()
{
return new KPendulumSetup();
}
};
int main( int argc, char *argv[] )
{
KPendulumSaverInterface kss;
return kScreenSaverMain(argc, argv, kss);
}
//-----------------------------------------------------------------------------
// PendulumOdeSolver
//-----------------------------------------------------------------------------
PendulumOdeSolver::PendulumOdeSolver(
const double& t,
const double& dt,
const Vector4d& y,
const double& eps,
const double& m1,
const double& m2,
const double& l1,
const double& l2,
const double& g)
: RkOdeSolver<double,4>(t, y, dt, eps),
// constants for faster numeric calculation, derived from m1,m2,l1,l2,g
m_A(1.0/(m2*l1*l1)),
m_B1(m2*l1*l2),
m_B(1.0/m_B1),
m_C((m1+m2)/(m2*m2*l2*l2)),
m_D(g*(m1+m2)*l1),
m_E(g*m2*l2),
m_M((m1+m2)/m2)
{
}
Vector4d PendulumOdeSolver::f(const double& x, const Vector4d& y) const
{
(void)x; // unused
const double& q1 = y[0];
const double& q2 = y[1];
const double& p1 = y[2];
const double& p2 = y[3];
const double cosDq = std::cos(q1-q2);
const double iden = 1.0/(m_M - cosDq*cosDq); // invers denominator
const double dq1dt = (m_A*p1 - m_B*cosDq*p2)*iden;
const double dq2dt = (m_C*p2 - m_B*cosDq*p1)*iden;
Vector4d ypr;
ypr[0] = dq1dt;
ypr[1] = dq2dt;
const double K = m_B1 * dq1dt*dq2dt * std::sin(q1-q2);
ypr[2] = -K - m_D * std::sin(q1);
ypr[3] = K - m_E * std::sin(q2);
return ypr;
}
//-----------------------------------------------------------------------------
// Rotation: screen saver widget
//-----------------------------------------------------------------------------
PendulumGLWidget::PendulumGLWidget(QWidget* parent)
: QGLWidget(parent),
m_eyeR(30), // eye coordinates (polar)
m_eyeTheta(M_PI*0.45),
m_eyePhi(0),
m_lightR(m_eyeR), // light coordinates (polar)
m_lightTheta(M_PI*0.25),
m_lightPhi(M_PI*0.25),
m_quadM1(gluNewQuadric()),
m_barColor(KPendulumSaver::sm_barColorDefault),
m_m1Color(KPendulumSaver::sm_m1ColorDefault),
m_m2Color(KPendulumSaver::sm_m2ColorDefault)
{
}
PendulumGLWidget::~PendulumGLWidget(void)
{
gluDeleteQuadric(m_quadM1);
}
void PendulumGLWidget::setEyePhi(double phi)
{
m_eyePhi = phi;
while (m_eyePhi < 0)
{
m_eyePhi += 2.*M_PI;
}
while (m_eyePhi > 2*M_PI)
{
m_eyePhi -= 2.*M_PI;
}
// get the view port
GLint vp[4];
glGetIntegerv(GL_VIEWPORT, vp);
// calc new perspective, a resize event is simulated here
resizeGL(static_cast<int>(vp[2]), static_cast<int>(vp[3]));
}
void PendulumGLWidget::setAngles(const double& q1, const double& q2)
{
m_ang1 = static_cast<GLfloat>(q1*180./M_PI);
m_ang2 = static_cast<GLfloat>(q2*180./M_PI);
}
void PendulumGLWidget::setMasses(const double& m1, const double& m2)
{
m_sqrtm1 = static_cast<GLfloat>(std::sqrt(m1));
m_sqrtm2 = static_cast<GLfloat>(std::sqrt(m2));
}
void PendulumGLWidget::setLengths(const double& l1, const double& l2)
{
m_l1 = static_cast<GLfloat>(l1);
m_l2 = static_cast<GLfloat>(l2);
}
void PendulumGLWidget::setBarColor(const QColor& c)
{
if (c.isValid())
{
m_barColor = c;
}
}
void PendulumGLWidget::setM1Color(const QColor& c)
{
if (c.isValid())
{
m_m1Color = c;
}
}
void PendulumGLWidget::setM2Color(const QColor& c)
{
if (c.isValid())
{
m_m2Color = c;
}
}
/* --------- protected methods ----------- */
void PendulumGLWidget::initializeGL(void)
{
qglClearColor(QColor(Qt::black)); // set color to clear the background
glClearDepth(1); // depth buffer setup
glEnable(GL_DEPTH_TEST); // depth testing
glDepthFunc(GL_LEQUAL); // type of depth test
glShadeModel(GL_SMOOTH); // smooth color shading in poygons
// nice perspective calculation
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
// set up the light
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHT1);
glMatrixMode(GL_MODELVIEW); // select modelview matrix
glLoadIdentity();
// set position of light0
GLfloat lightPos[4]=
{GLfloat(m_lightR * std::sin(m_lightTheta) * std::sin(m_lightPhi)),
GLfloat(m_lightR * std::sin(m_lightTheta) * std::cos(m_lightPhi)),
GLfloat(m_lightR * std::cos(m_lightTheta)),
0};
glLightfv(GL_LIGHT0, GL_POSITION, lightPos);
// set position of light1
lightPos[0] = m_lightR * std::sin(m_lightTheta) * std::sin(m_lightPhi+M_PI);
lightPos[1] = m_lightR * std::sin(m_lightTheta) * std::cos(m_lightPhi+M_PI);
glLightfv(GL_LIGHT1, GL_POSITION, lightPos);
// only for lights #>0
GLfloat spec[] = {1,1,1,1};
glLightfv(GL_LIGHT1, GL_SPECULAR, spec);
glLightfv(GL_LIGHT1, GL_DIFFUSE, spec);
// enable setting the material colour by glColor()
glEnable(GL_COLOR_MATERIAL);
GLfloat emi[4] = {.13, .13, .13, 1};
glMaterialfv(GL_FRONT, GL_EMISSION, emi);
}
void PendulumGLWidget::paintGL(void)
{
// clear color and depth buffer
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
glMatrixMode(GL_MODELVIEW); // select modelview matrix
glLoadIdentity();
const GLfloat width = 2.0;
const GLfloat masswidth = 1.0;
const int noOfSlices = 20;
// top axis, left (x>0)
glTranslatef(0.5*width, 0, 0);
glRotatef(90, 0, 1, 0);
qglColor(m_barColor);
gluCylinder(m_quadM1, 0.2, 0.2, 5, 10, 1);
gluSphere(m_quadM1, 0.2, 10, 10);
// top axis, right
glLoadIdentity();
glTranslatef(-0.5*width, 0, 0);
glRotatef(-90, 0, 1, 0);
gluCylinder(m_quadM1, 0.2, 0.2, 5, 10, 1);
gluSphere(m_quadM1, 0.2, 10, 10);
// 1. part, left
glLoadIdentity();
glRotatef(m_ang1, 1, 0, 0);
glPushMatrix();
glTranslatef(0.5*width, 0, -m_l1);
gluCylinder(m_quadM1, 0.2, 0.2, m_l1, 10, 1);
glPopMatrix();
// 1. part, right
glPushMatrix();
glTranslatef(-0.5*width, 0, -m_l1);
gluCylinder(m_quadM1, 0.2, 0.2, m_l1, 10, 1);
// 1. part, bottom
glRotatef(90, 0, 1, 0);
gluSphere(m_quadM1, 0.2, 10, 10); // bottom corner 1
gluCylinder(m_quadM1, 0.2, 0.2, width, 10, 1); // connection
glTranslatef(0, 0, 0.5*(width-masswidth));
qglColor(m_m1Color);
gluCylinder(m_quadM1, m_sqrtm1, m_sqrtm1, masswidth, noOfSlices, 1); // mass 1
gluQuadricOrientation(m_quadM1, GLU_INSIDE);
gluDisk(m_quadM1, 0, m_sqrtm1, noOfSlices, 1); // bottom of mass
gluQuadricOrientation(m_quadM1, GLU_OUTSIDE);
glTranslatef(0, 0, masswidth);
gluDisk(m_quadM1, 0, m_sqrtm1, noOfSlices, 1); // top of mass
glTranslatef(0, 0, 0.5*(width-masswidth));
qglColor(m_barColor);
gluSphere(m_quadM1, 0.2, 10, 10); // bottom corner 2
glPopMatrix();
// 2. pendulum bar
glLoadIdentity();
glTranslatef(0, m_l1*std::sin(m_ang1*M_PI/180.), -m_l1*std::cos(m_ang1*M_PI/180.));
glRotatef(m_ang2, 1, 0, 0);
glTranslatef(0, 0, -m_l2);
qglColor(m_barColor);
gluCylinder(m_quadM1, 0.2, 0.2, m_l2, 10, 1);
// mass 2
glRotatef(90, 0, 1, 0);
glTranslatef(0, 0, -0.5*masswidth);
qglColor(m_m2Color);
gluCylinder(m_quadM1, m_sqrtm2, m_sqrtm2, masswidth, noOfSlices, 1);
gluQuadricOrientation(m_quadM1, GLU_INSIDE);
gluDisk(m_quadM1, 0, m_sqrtm2, noOfSlices, 1); // bottom of mass
gluQuadricOrientation(m_quadM1, GLU_OUTSIDE);
glTranslatef(0, 0, masswidth);
gluDisk(m_quadM1, 0, m_sqrtm2, noOfSlices, 1); // top of mass
glFlush();
}
void PendulumGLWidget::resizeGL(int w, int h)
{
kDebug() << "w=" << w << ", h=" << h << "\n";
// prevent a divide by zero
if (h == 0)
{
return;
}
// set the new view port
glViewport(0, 0, static_cast<GLint>(w), static_cast<GLint>(h));
// set up projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
// Perspective view
gluPerspective(
40.0f,
static_cast<GLdouble>(w)/static_cast<GLdouble>(h),
1.0, 100.0f);
// Viewing transformation, position for better view
// Theta is polar angle 0<Theta<Pi
gluLookAt(
m_eyeR * std::sin(m_eyeTheta) * std::sin(m_eyePhi),
m_eyeR * std::sin(m_eyeTheta) * std::cos(m_eyePhi),
m_eyeR * std::cos(m_eyeTheta),
0,0,0,
0,0,1);
}
//-----------------------------------------------------------------------------
// KPendulumSaver: screen saver class
//-----------------------------------------------------------------------------
// class methods
KPendulumSaver::KPendulumSaver(WId id) :
KScreenSaver(id),
m_solver(0),
m_massRatio(sm_massRatioDefault),
m_lengthRatio(sm_lengthRatioDefault),
m_g(sm_gDefault),
m_E(sm_EDefault),
m_persChangeInterval(sm_persChangeIntervalDefault)
{
// no need to set our parent widget's background here, the GL widget sets its
// own background color
m_glArea = new PendulumGLWidget(); // create gl widget w/o parent
m_glArea->setEyePhi(sm_eyePhiDefault);
readSettings(); // read global settings into pars
// init m_glArea with read settings, construct and init m_solver
initData();
embed(m_glArea); // embed gl widget and resize it
m_glArea->show(); // show embedded gl widget
// set up and start cyclic timer
m_timer = new QTimer(this);
m_timer->start(sm_deltaT);
connect(m_timer, SIGNAL(timeout()), this, SLOT(doTimeStep()));
}
KPendulumSaver::~KPendulumSaver()
{
m_timer->stop();
// m_timer is automatically deleted with parent KPendulumSaver
delete m_solver;
delete m_glArea;
}
void KPendulumSaver::readSettings()
{
// read configuration settings from config file
KConfigGroup config(KGlobal::config(), "Settings");
// internal saver parameters are set to stored values or left at their
// default values if stored values are out of range
setMassRatio(
config.readEntry(
"mass ratio",
KPendulumSaver::sm_massRatioDefault));
setLengthRatio(
config.readEntry(
"length ratio",
KPendulumSaver::sm_lengthRatioDefault));
setG(
config.readEntry(
"g",
KPendulumSaver::sm_gDefault));
setE(
config.readEntry(
"E",
KPendulumSaver::sm_EDefault));
setPersChangeInterval(
config.readEntry(
"perspective change interval",
(uint)KPendulumSaver::sm_persChangeIntervalDefault));
// set the colours
setBarColor(config.readEntry("bar color", sm_barColorDefault));
setM1Color( config.readEntry("m1 color", sm_m1ColorDefault));
setM2Color( config.readEntry("m2 color", sm_m2ColorDefault));
}
void KPendulumSaver::initData()
{
const double m1plusm2 = 2; // m1+m2
const double m2 = m_massRatio * m1plusm2;
const double m1 = m1plusm2 - m2;
m_glArea->setMasses(m1, m2);
m_glArea->setAngles(0, 0);
const double l1plusl2 = 9; // l1+l2
const double l2 = m_lengthRatio * l1plusl2;
const double l1 = l1plusl2 - l2;
m_glArea->setLengths(l1, l2);
// kinetic energy of m2 and m1
const double kin_energy = m_E * m_g * (l1*m1 + (m1+m2)*(l1+l2));
// angular velocity for 1. and 2. pendulum
const double qp = std::sqrt(2.*kin_energy/((m1+m2)*l1*l1 + m2*l2*l2 + m2*l1*l2));
// assemble initial y for solver
Vector4d y;
y[0] = 0; // q1
y[1] = 0; // q2
y[2] = (m1+m2)*l1*l1*qp + 0.5*m2*l1*l2*qp; // p1
y[3] = m2*l2*l2*qp + 0.5*m2*l1*l2*qp; // p2
// delete old solver
if (m_solver!=0)
{
delete m_solver;
}
// init new solver
m_solver = new PendulumOdeSolver(
0.0, // t
0.01, // first dt step size estimation
y,
1e-5, // eps
m1,
m2,
l1,
l2,
m_g);
}
void KPendulumSaver::setBarColor(const QColor& c)
{
m_glArea->setBarColor(c);
}
QColor KPendulumSaver::barColor(void) const
{
return m_glArea->barColor();
}
void KPendulumSaver::setM1Color(const QColor& c)
{
m_glArea->setM1Color(c);
}
QColor KPendulumSaver::m1Color(void) const
{
return m_glArea->m1Color();
}
void KPendulumSaver::setM2Color(const QColor& c)
{
m_glArea->setM2Color(c);
}
QColor KPendulumSaver::m2Color(void) const
{
return m_glArea->m2Color();
}
void KPendulumSaver::setMassRatio(const double& massRatio)
{
// range check is not necessary in normal operation because validators check
// the values at input. But the validators do not check for corrupted
// settings read from disk.
if ((massRatio >= sm_massRatioLimitLower)
&& (massRatio <= sm_massRatioLimitUpper)
&& (m_massRatio != massRatio))
{
m_massRatio = massRatio;
if (m_timer!=0)
{
initData();
}
}
}
void KPendulumSaver::setLengthRatio(const double& lengthRatio)
{
if ((lengthRatio >= sm_lengthRatioLimitLower)
&& (lengthRatio <= sm_lengthRatioLimitUpper)
&& (m_lengthRatio != lengthRatio))
{
m_lengthRatio = lengthRatio;
if (m_timer!=0)
{
initData();
}
}
}
void KPendulumSaver::setG(const double& g)
{
if ((g >= sm_gLimitLower)
&& (g <= sm_gLimitUpper)
&& (m_g != g))
{
m_g = g;
if (m_timer!=0)
{
initData();
}
}
}
void KPendulumSaver::setE(const double& E)
{
if ((E >= sm_ELimitLower)
&& (E <= sm_ELimitUpper)
&& (m_E != E))
{
m_E = E;
if (m_timer!=0)
{
initData();
}
}
}
void KPendulumSaver::setPersChangeInterval(
const unsigned int& persChangeInterval)
{
if ((persChangeInterval >= sm_persChangeIntervalLimitLower)
&& (persChangeInterval <= sm_persChangeIntervalLimitUpper)
&& (m_persChangeInterval != persChangeInterval))
{
m_persChangeInterval = persChangeInterval;
// do not restart simulation here
}
}
void KPendulumSaver::doTimeStep()
{
/* time (in seconds) of perspective change.
* - t<0: no change yet
* - t=0: change starts
* - 0<t<moving time: change takes place
* - t=moving time: end of the change */
static double persChangeTime = -5;
// integrate a step ahead
m_solver->integrate(0.001 * sm_deltaT);
// read new y from solver
const Vector4d& y = m_solver->Y();
// tell glArea the new coordinates/angles of the pendulum
m_glArea->setAngles(y[0], y[1]);
// handle perspective change
persChangeTime += 0.001 * sm_deltaT;
if (persChangeTime > 0)
{
// phi value at the start of a perspective change
static double eyePhi0 = sm_eyePhiDefault;
// phi value at the end of a perspective change
static double eyePhi1 = 0.75*M_PI;
static double deltaEyePhi = eyePhi1-eyePhi0;
// movement acceleration/deceleration
const double a = 3;
// duration of the change period
const double movingTime = 2.*std::sqrt(std::abs(deltaEyePhi)/a);
// new current phi of eye
double eyePhi = persChangeTime < 0.5*movingTime ?
// accelerating phase
eyePhi0 + (deltaEyePhi>0?1:-1)
* 0.5*a*persChangeTime*persChangeTime:
// decellerating phase
eyePhi1 - (deltaEyePhi>0?1:-1)
* 0.5*a*(movingTime-persChangeTime)*(movingTime-persChangeTime);
if (persChangeTime > movingTime)
{ // perspective change has finished
// set new time till next change
persChangeTime = -double(m_persChangeInterval);
eyePhi0 = eyePhi = eyePhi1;
// find new phi value with angleLimit < phi < Pi-angleLimit or
// Pi+angleLimit < phi < 2*Pi-angleLimit
const double angleLimit = M_PI*0.2;
for (eyePhi1 = 0;
(eyePhi1<angleLimit)
|| ((eyePhi1<M_PI+angleLimit) && (eyePhi1>M_PI-angleLimit))
|| (eyePhi1>2*M_PI-angleLimit);
eyePhi1 = double(rand())/RAND_MAX * 2*M_PI)
{
}
// new delta phi for next change
deltaEyePhi = eyePhi1 - eyePhi0;
// find shortest perspective change
if (deltaEyePhi < -M_PI)
{
deltaEyePhi += 2*M_PI;
}
}
m_glArea->setEyePhi(eyePhi); // set new perspective
}
m_glArea->updateGL(); // repaint scenery
// restarting timer not necessary here, it is a cyclic timer
}
// public slot of KPendulumSaver, forward resize event to public slot of glArea
// to allow the resizing of the gl area withing the setup dialog
void KPendulumSaver::resizeGlArea(QResizeEvent* e)
{
m_glArea->resize(e->size());
}
// public static class member variables
const QColor KPendulumSaver::sm_barColorDefault(255, 255, 127);
const QColor KPendulumSaver::sm_m1ColorDefault( 170, 0, 127);
const QColor KPendulumSaver::sm_m2ColorDefault( 85, 170, 127);
const double KPendulumSaver::sm_massRatioLimitLower = 0.01;
const double KPendulumSaver::sm_massRatioLimitUpper = 0.99;
const double KPendulumSaver::sm_massRatioDefault = 0.5;
const double KPendulumSaver::sm_lengthRatioLimitLower = 0.01;
const double KPendulumSaver::sm_lengthRatioLimitUpper = 0.99;
const double KPendulumSaver::sm_lengthRatioDefault = 0.5;
const double KPendulumSaver::sm_gLimitLower = 0.1;
const double KPendulumSaver::sm_gLimitUpper = 300.0;
const double KPendulumSaver::sm_gDefault = 40.0;
const double KPendulumSaver::sm_ELimitLower = 0.0;
const double KPendulumSaver::sm_ELimitUpper = 5.0;
const double KPendulumSaver::sm_EDefault = 1.2;
const unsigned int KPendulumSaver::sm_persChangeIntervalLimitLower = 5;
const unsigned int KPendulumSaver::sm_persChangeIntervalLimitUpper = 600;
const unsigned int KPendulumSaver::sm_persChangeIntervalDefault = 15;
// private static class member variables
const unsigned int KPendulumSaver::sm_deltaT = 20;
const double KPendulumSaver::sm_eyePhiDefault = 0.25 * M_PI;
//-----------------------------------------------------------------------------
// KPendulumSetup: dialog to setup screen saver parameters
//-----------------------------------------------------------------------------
KPendulumSetup::KPendulumSetup(QWidget* parent)
: KDialog(parent)
{
// the dialog should block, no other control center input should be possible
// until the dialog is closed
setModal(true);
setCaption(i18n( "KPendulum Setup" ));
setButtons(Ok|Cancel|Help);
setDefaultButton(Ok);
setButtonText( Help, i18n( "A&bout" ) );
QWidget *main = new QWidget(this);
setMainWidget(main);
cfg = new PendulumWidget();
cfg->setupUi( main );
// create input validators
cfg->m_mEdit->setValidator(
new QDoubleValidator(
KPendulumSaver::sm_massRatioLimitLower,
KPendulumSaver::sm_massRatioLimitUpper,
5, cfg->m_mEdit));
cfg->m_lEdit->setValidator(
new QDoubleValidator(
KPendulumSaver::sm_lengthRatioLimitLower,
KPendulumSaver::sm_lengthRatioLimitUpper,
5, cfg->m_lEdit));
cfg->m_gEdit->setValidator(
new QDoubleValidator(
KPendulumSaver::sm_gLimitLower,
KPendulumSaver::sm_gLimitUpper,
5, cfg->m_gEdit));
cfg->m_eEdit->setValidator(
new QDoubleValidator(
KPendulumSaver::sm_ELimitLower,
KPendulumSaver::sm_ELimitUpper,
5, cfg->m_eEdit));
// set input limits for the perspective change interval time
cfg->m_persSpinBox->setMinimum(KPendulumSaver::sm_persChangeIntervalLimitLower);
cfg->m_persSpinBox->setMaximum(KPendulumSaver::sm_persChangeIntervalLimitUpper);
// set tool tips of editable fields
cfg->m_mEdit->setToolTip(
ki18n("Ratio of 2nd mass to sum of both masses.\nValid values from %1 to %2.")
.subs(KPendulumSaver::sm_massRatioLimitLower, 0, 'f', 2)
.subs(KPendulumSaver::sm_massRatioLimitUpper, 0, 'f', 2)
.toString());
cfg->m_lEdit->setToolTip(
ki18n("Ratio of 2nd pendulum part length to the sum of both part lengths.\nValid values from %1 to %2.")
.subs(KPendulumSaver::sm_lengthRatioLimitLower, 0, 'f', 2)
.subs(KPendulumSaver::sm_lengthRatioLimitUpper, 0, 'f', 2)
.toString());
cfg->m_gEdit->setToolTip(
ki18n("Gravitational constant in arbitrary units.\nValid values from %1 to %2.")
.subs(KPendulumSaver::sm_gLimitLower, 0, 'f', 2)
.subs(KPendulumSaver::sm_gLimitUpper, 0, 'f', 2)
.toString());
cfg->m_eEdit->setToolTip(
ki18n("Energy in units of the maximum potential energy of the given configuration.\nValid values from %1 to %2.")
.subs(KPendulumSaver::sm_ELimitLower, 0, 'f', 2)
.subs(KPendulumSaver::sm_ELimitUpper, 0, 'f', 2)
.toString());
cfg->m_persSpinBox->setToolTip(
ki18n("Time in seconds after which a random perspective change occurs.\nValid values from %1 to %2.")
.subs(KPendulumSaver::sm_persChangeIntervalLimitLower)
.subs(KPendulumSaver::sm_persChangeIntervalLimitUpper)
.toString());
// init preview area
QPalette palette;
palette.setColor(cfg->m_preview->backgroundRole(), Qt::black);
cfg->m_preview->setPalette(palette);
cfg->m_preview->setAutoFillBackground(true);
cfg->m_preview->show(); // otherwise saver does not get correct size
// create saver and give it the WinID of the preview area
m_saver = new KPendulumSaver(cfg->m_preview->winId());
// read settings from saver and update GUI elements with these values, saver
// has read settings in its constructor
// set editable fields with stored values as defaults
QString text;
text.setNum(m_saver->massRatio());
cfg->m_mEdit->setText(text);
text.setNum(m_saver->lengthRatio());
cfg->m_lEdit->setText(text);
text.setNum(m_saver->g());
cfg->m_gEdit->setText(text);
text.setNum(m_saver->E());
cfg->m_eEdit->setText(text);
cfg->m_persSpinBox->setValue(m_saver->persChangeInterval());
palette.setColor(cfg->m_barColorButton->backgroundRole(), m_saver->barColor());
cfg->m_barColorButton->setPalette(palette);
palette.setColor(cfg->m_m1ColorButton->backgroundRole(), m_saver->m1Color());
cfg->m_m1ColorButton->setPalette(palette);
palette.setColor(cfg->m_m2ColorButton->backgroundRole(), m_saver->m2Color());
cfg->m_m2ColorButton->setPalette(palette);
// if the preview area is resized it emits the resized() event which is
// caught by m_saver. The embedded GLArea is resized to fit into the preview
// area.
connect(cfg->m_preview, SIGNAL(resized(QResizeEvent*)),
m_saver, SLOT(resizeGlArea(QResizeEvent*)));
connect(this, SIGNAL(okClicked()), this, SLOT(okButtonClickedSlot()));
connect(this, SIGNAL(helpClicked()), this, SLOT(aboutButtonClickedSlot()));
connect(cfg->m_lEdit, SIGNAL(lostFocus()), this, SLOT(lEditLostFocusSlot()));
connect(cfg->m_gEdit, SIGNAL(lostFocus()), this, SLOT(gEditLostFocusSlot()));
connect(cfg->m_eEdit, SIGNAL(lostFocus()), this, SLOT(eEditLostFocusSlot()));
connect(cfg->m_persSpinBox, SIGNAL(valueChanged(int)), this, SLOT(persChangeEnteredSlot(int)));
connect(cfg->m_mEdit, SIGNAL(lostFocus()), this, SLOT(mEditLostFocusSlot()));
connect(cfg->m_barColorButton, SIGNAL(clicked()), this, SLOT(barColorButtonClickedSlot()));
connect(cfg->m_m1ColorButton, SIGNAL(clicked()), this, SLOT(m1ColorButtonClickedSlot()));
connect(cfg->m_m2ColorButton, SIGNAL(clicked()), this, SLOT(m2ColorButtonClickedSlot()));
}
KPendulumSetup::~KPendulumSetup()
{
delete m_saver;
delete cfg;
}
// Ok pressed - save settings and exit
void KPendulumSetup::okButtonClickedSlot()
{
KConfigGroup config(KGlobal::config(), "Settings");
config.writeEntry("mass ratio", m_saver->massRatio());
config.writeEntry("length ratio", m_saver->lengthRatio());
config.writeEntry("g", m_saver->g());
config.writeEntry("E", m_saver->E());
config.writeEntry("perspective change interval",
m_saver->persChangeInterval());
config.writeEntry("bar color", m_saver->barColor());
config.writeEntry("m1 color", m_saver->m1Color());
config.writeEntry("m2 color", m_saver->m2Color());
config.sync();
accept();
}
void KPendulumSetup::aboutButtonClickedSlot()
{
KMessageBox::about(this, i18n("\
<h3>KPendulum Screen Saver for KDE</h3>\
<p>Simulation of a two-part pendulum</p>\
<p>Copyright (c) Georg&nbsp;Drenkhahn 2004</p>\
<p><tt>Georg.Drenkhahn@gmx.net</tt></p>"));
}
void KPendulumSetup::mEditLostFocusSlot(void)
{
if (cfg->m_mEdit->hasAcceptableInput())
{
m_saver->setMassRatio(cfg->m_mEdit->text().toDouble());
}
else
{ // write current setting back into input field
QString text;
text.setNum(m_saver->massRatio());
cfg->m_mEdit->setText(text);
}
}
void KPendulumSetup::lEditLostFocusSlot(void)
{
if (cfg->m_lEdit->hasAcceptableInput())
{
m_saver->setLengthRatio(cfg->m_lEdit->text().toDouble());
}
else
{ // write current setting back into input field
QString text;
text.setNum(m_saver->lengthRatio());
cfg->m_lEdit->setText(text);
}
}
void KPendulumSetup::gEditLostFocusSlot(void)
{
if (cfg->m_gEdit->hasAcceptableInput())
{
m_saver->setG(cfg->m_gEdit->text().toDouble());
}
else
{ // write current setting back into input field
QString text;
text.setNum(m_saver->g());
cfg->m_gEdit->setText(text);
}
}
void KPendulumSetup::eEditLostFocusSlot(void)
{
if (cfg->m_eEdit->hasAcceptableInput())
{
m_saver->setE(cfg->m_eEdit->text().toDouble());
}
else
{ // write current setting back into input field
QString text;
text.setNum(m_saver->E());
cfg->m_eEdit->setText(text);
}
}
void KPendulumSetup::persChangeEnteredSlot(int t)
{
m_saver->setPersChangeInterval(t);
}
void KPendulumSetup::barColorButtonClickedSlot(void)
{
QColor color = m_saver->barColor();
if ( KColorDialog::getColor( color, this) )
{
if (color.isValid())
{
m_saver->setBarColor(color);
QPalette palette;
palette.setColor(cfg->m_barColorButton->backgroundRole(), color);
cfg->m_barColorButton->setPalette(palette);
}
}
}
void KPendulumSetup::m1ColorButtonClickedSlot(void)
{
QColor color =m_saver->m1Color();
if ( KColorDialog::getColor( color, this) )
{
if (color.isValid())
{
m_saver->setM1Color(color);
QPalette palette;
palette.setColor(cfg->m_m1ColorButton->backgroundRole(), color);
cfg->m_m1ColorButton->setPalette(palette);
}
}
}
void KPendulumSetup::m2ColorButtonClickedSlot(void)
{
QColor color = m_saver->m2Color();
if ( KColorDialog::getColor( color, this) )
{
if (color.isValid())
{
m_saver->setM2Color(color);
QPalette palette;
palette.setColor(cfg->m_m2ColorButton->backgroundRole(), color);
cfg->m_m2ColorButton->setPalette(palette);
}
}
}