//============================================================================ // // KPendulum screen saver for KDE // // The screen saver displays a physically realistic simulation of a two-part // pendulum. // // Developed by Georg Drenkhahn, georg-d@users.sourceforge.net // // $Id$ // /* * Copyright (C) 2004 Georg Drenkhahn * * KRotation is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License version 2 as published by the * Free Software Foundation. * * KRotation 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., 59 Temple * Place, Suite 330, Boston, MA 02110-1301 USA */ //============================================================================ // std. C++ headers #include // TQt headers #include #include #include #include #include #include // KDE headers #include #include #include #include #include "sspreviewarea.h" // pendulum.tqmoc includes pendulum.h #include "pendulum.moc" #define KPENDULUM_VERSION "1.1" // libkscreensaver interface extern "C" { /// application name for libkscreensaver interface KDE_EXPORT const char *kss_applicationName = "kpendulum.kss"; /// application description for libkscreensaver interface KDE_EXPORT const char *kss_description = I18N_NOOP("Simulation of\ a two-part pendulum"); /// application version for libkscreensaver interface KDE_EXPORT const char *kss_version = KPENDULUM_VERSION; /// function to create screen saver object KDE_EXPORT KScreenSaver* kss_create(WId id) { return new KPendulumSaver(id); } /// function to create setup dialog for screen saver KDE_EXPORT TQDialog* kss_setup() { return new KPendulumSetup(); } } //----------------------------------------------------------------------------- // PendulumOdeSolver //----------------------------------------------------------------------------- PendulumOdeSolver::PendulumOdeSolver( const double &_t, const double &_dt, std::valarray &_y, const double &_eps, const double &_m1, const double &_m2, const double &_l1, const double &_l2, const double &_g) : RkOdeSolver(_t,_y,_dt,_eps), A(1.0/(_m2*_l1*_l1)), B1(_m2*_l1*_l2), // constants for faster numeric calculation B(1.0/B1), // derived from m1,m2,l1,l2,g C((_m1+_m2)/(_m2*_m2*_l2*_l2)), D(_g*(_m1+_m2)*_l1), E(_g*_m2*_l2), M((_m1+_m2)/_m2) { } std::valarray PendulumOdeSolver::f( const double &x, const std::valarray &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 - cosDq*cosDq); // invers denominator const double dq1dt = (A*p1 - B*cosDq*p2)*iden; const double dq2dt = (C*p2 - B*cosDq*p1)*iden; std::valarray ypr(y.size()); ypr[0] = dq1dt; ypr[1] = dq2dt; const double K = B1 * dq1dt*dq2dt * std::sin(q1-q2); ypr[2] = -K - D * std::sin(q1); ypr[3] = K - E * std::sin(q2); return ypr; } //----------------------------------------------------------------------------- // Rotation: screen saver widget //----------------------------------------------------------------------------- PendulumGLWidget::PendulumGLWidget(TQWidget* parent, const char* name) : TQGLWidget(parent, name), eyeR(30), // eye coordinates (polar) eyeTheta(M_PI*0.45), eyePhi(0), lightR(eyeR), // light coordinates (polar) lightTheta(M_PI*0.25), lightPhi(M_PI*0.25), quadM1(gluNewQuadric()), m_barColor(KPendulumSaver::barColorDefault), m_m1Color(KPendulumSaver::m1ColorDefault), m_m2Color(KPendulumSaver::m2ColorDefault) { } PendulumGLWidget::~PendulumGLWidget(void) { gluDeleteQuadric(quadM1); } void PendulumGLWidget::setEyePhi(double phi) { eyePhi = phi; while (eyePhi < 0) eyePhi += 2.*M_PI; while (eyePhi > 2*M_PI) eyePhi -= 2.*M_PI; // get the view port static GLint vp[4]; glGetIntegerv(GL_VIEWPORT, vp); // calc new perspective, a resize event is simulated here resizeGL(static_cast(vp[2]), static_cast(vp[3])); } void PendulumGLWidget::setAngles(const double& q1, const double& q2) { ang1 = static_cast(q1*180./M_PI); ang2 = static_cast(q2*180./M_PI); } void PendulumGLWidget::setMasses(const double& m1, const double& m2) { sqrtm1 = static_cast(sqrt(m1)); sqrtm2 = static_cast(sqrt(m2)); } void PendulumGLWidget::setLengths(const double& _l1, const double& _l2) { l1 = static_cast(_l1); l2 = static_cast(_l2); } void PendulumGLWidget::setBarColor(const TQColor& c) { if (c.isValid()) { m_barColor = c; } } void PendulumGLWidget::setM1Color(const TQColor& c) { if (c.isValid()) { m_m1Color = c; } } void PendulumGLWidget::setM2Color(const TQColor& c) { if (c.isValid()) { m_m2Color = c; } } /* --------- protected methods ----------- */ void PendulumGLWidget::initializeGL(void) { qglClearColor(TQColor(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 positon of light0 GLfloat lightPos[4]= {lightR * sin(lightTheta) * sin(lightPhi), lightR * sin(lightTheta) * cos(lightPhi), lightR * cos(lightTheta), 0}; glLightfv(GL_LIGHT0, GL_POSITION, lightPos); // set positon of light1 lightPos[0] = lightR * sin(lightTheta) * sin(lightPhi+M_PI); lightPos[1] = lightR * sin(lightTheta) * cos(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(); static const GLfloat width = 2.0; static const GLfloat masswidth = 1.0; static const int noOfSlices = 20; // top axis, left (x>0) glTranslatef(0.5*width, 0, 0); glRotatef(90, 0, 1, 0); qglColor(m_barColor); gluCylinder(quadM1, 0.2, 0.2, 5, 10, 1); gluSphere(quadM1, 0.2, 10, 10); // top axis, right glLoadIdentity(); glTranslatef(-0.5*width, 0, 0); glRotatef(-90, 0, 1, 0); gluCylinder(quadM1, 0.2, 0.2, 5, 10, 1); gluSphere(quadM1, 0.2, 10, 10); // 1. part, left glLoadIdentity(); glRotatef(ang1, 1, 0, 0); glPushMatrix(); glTranslatef(0.5*width, 0, -l1); gluCylinder(quadM1, 0.2, 0.2, l1, 10, 1); glPopMatrix(); // 1. part, right glPushMatrix(); glTranslatef(-0.5*width, 0, -l1); gluCylinder(quadM1, 0.2, 0.2, l1, 10, 1); // 1. part, bottom glRotatef(90, 0, 1, 0); gluSphere(quadM1, 0.2, 10, 10); // bottom corner 1 gluCylinder(quadM1, 0.2, 0.2, width, 10, 1); // connection glTranslatef(0, 0, 0.5*(width-masswidth)); qglColor(m_m1Color); gluCylinder(quadM1, sqrtm1, sqrtm1, masswidth, noOfSlices, 1); // mass 1 gluQuadricOrientation(quadM1, GLU_INSIDE); gluDisk(quadM1, 0, sqrtm1, noOfSlices,1); // bottom of mass gluQuadricOrientation(quadM1, GLU_OUTSIDE); glTranslatef(0, 0, masswidth); gluDisk(quadM1, 0, sqrtm1, noOfSlices,1); // top of mass glTranslatef(0, 0, 0.5*(width-masswidth)); qglColor(m_barColor); gluSphere(quadM1, 0.2, 10, 10); // bottom corner 2 glPopMatrix(); // 2. pendulum bar glLoadIdentity(); glTranslatef(0, l1*std::sin(ang1*M_PI/180.), -l1*std::cos(ang1*M_PI/180.)); glRotatef(ang2, 1, 0, 0); glTranslatef(0, 0, -l2); qglColor(m_barColor); gluCylinder(quadM1, 0.2, 0.2, l2, 10, 1); // mass 2 glRotatef(90, 0, 1, 0); glTranslatef(0, 0, -0.5*masswidth); qglColor(m_m2Color); gluCylinder(quadM1, sqrtm2, sqrtm2, masswidth, noOfSlices, 1); gluQuadricOrientation(quadM1, GLU_INSIDE); gluDisk(quadM1, 0, sqrtm2, noOfSlices,1); // bottom of mass gluQuadricOrientation(quadM1, GLU_OUTSIDE); glTranslatef(0, 0, masswidth); gluDisk(quadM1, 0, sqrtm2, noOfSlices,1); // top of mass glFlush(); } void PendulumGLWidget::resizeGL(int w, int h) { // Prevent a divide by zero if (h == 0) h = 1; // set the new view port glViewport(0, 0, static_cast(w), static_cast(h)); // set up projection matrix glMatrixMode(GL_PROJECTION); glLoadIdentity(); // Perspective view gluPerspective(40.0f, static_cast(w)/static_cast(h), 1.0, 100.0f); // Viewing transformation, position for better view // Theta is polar angle 0setEyePhi(eyePhiDefault); readSettings(); // read global settings into pars initData(); // init solver and glArea with read settings embed(glArea); // embed gl widget and resize it glArea->show(); // show gl widget // set up and start cyclic timer timer = new TQTimer(this); timer->start(deltaT, TRUE); connect(timer, TQT_SIGNAL(timeout()), this, TQT_SLOT(doTimeStep())); } KPendulumSaver::~KPendulumSaver() { // time, rotation are automatically deleted with parent KPendulumSaver delete solver; } void KPendulumSaver::readSettings() { // read configuration settings from config file KConfig *config = KGlobal::config(); config->setGroup("Settings"); // internal saver parameters are set to stored values or left at their // default values if stored values are out of range setMassRatio( config->readDoubleNumEntry( "mass ratio", KPendulumSaver::massRatioDefault)); setLengthRatio( config->readDoubleNumEntry( "length ratio", KPendulumSaver::lengthRatioDefault)); setG( config->readDoubleNumEntry( "g", KPendulumSaver::gDefault)); setE( config->readDoubleNumEntry( "E", KPendulumSaver::EDefault)); setPersChangeInterval( config->readUnsignedNumEntry( "perspective change interval", KPendulumSaver::persChangeIntervalDefault)); // set the colours setBarColor(config->readColorEntry("bar color", &barColorDefault)); setM1Color( config->readColorEntry("m1 color", &m1ColorDefault)); setM2Color( config->readColorEntry("m2 color", &m2ColorDefault)); } void KPendulumSaver::initData() { const double m1plusm2 = 2; // m1+m2 const double m2 = m_massRatio * m1plusm2; const double m1 = m1plusm2 - m2; glArea->setMasses(m1, m2); glArea->setAngles(0, 0); const double l1plusl2 = 9; // l1+l2 const double l2 = m_lengthRatio * l1plusl2; const double l1 = l1plusl2 - l2; 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 = sqrt(2.*kin_energy/((m1+m2)*l1*l1 + m2*l2*l2 + m2*l1*l2)); // assemble initial y for solver std::valarray y(4); 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 (solver!=0) delete solver; // init new solver 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 TQColor& c) { glArea->setBarColor(c); } TQColor KPendulumSaver::barColor(void) const { return glArea->barColor(); } const TQColor KPendulumSaver::barColorDefault(255, 255, 127); void KPendulumSaver::setM1Color(const TQColor& c) { glArea->setM1Color(c); } TQColor KPendulumSaver::m1Color(void) const { return glArea->m1Color(); } const TQColor KPendulumSaver::m1ColorDefault(170, 0, 127); void KPendulumSaver::setM2Color(const TQColor& c) { glArea->setM2Color(c); } TQColor KPendulumSaver::m2Color(void) const { return glArea->m2Color(); } const TQColor KPendulumSaver::m2ColorDefault( 85, 170, 127); void KPendulumSaver::setMassRatio(const double& massRatio) { // range check is not neccessary in normal operation because validators check // the values at input. But the validators do not check for corrupted // settings read from disk. if (massRatio >= massRatioLimitLower && massRatio <= massRatioLimitUpper && m_massRatio != massRatio) { m_massRatio = massRatio; if (timer!=0) { initData(); } } } const double KPendulumSaver::massRatioLimitLower = 0.01; const double KPendulumSaver::massRatioLimitUpper = 0.99; const double KPendulumSaver::massRatioDefault = 0.5; void KPendulumSaver::setLengthRatio(const double& lengthRatio) { if (lengthRatio >= lengthRatioLimitLower && lengthRatio <= lengthRatioLimitUpper && m_lengthRatio != lengthRatio) { m_lengthRatio = lengthRatio; if (timer!=0) { initData(); } } } const double KPendulumSaver::lengthRatioLimitLower = 0.01; const double KPendulumSaver::lengthRatioLimitUpper = 0.99; const double KPendulumSaver::lengthRatioDefault = 0.5; void KPendulumSaver::setG(const double& g) { if (g >= gLimitLower && g <= gLimitUpper && m_g != g) { m_g = g; if (timer!=0) { initData(); } } } const double KPendulumSaver::gLimitLower = 0.1; const double KPendulumSaver::gLimitUpper = 300.0; const double KPendulumSaver::gDefault = 40.0; void KPendulumSaver::setE(const double& E) { if (E >= ELimitLower && E <= ELimitUpper && m_E != E) { m_E = E; if (timer!=0) { initData(); } } } const double KPendulumSaver::ELimitLower = 0.0; const double KPendulumSaver::ELimitUpper = 5.0; const double KPendulumSaver::EDefault = 1.2; void KPendulumSaver::setPersChangeInterval( const unsigned int& persChangeInterval) { if (persChangeInterval >= persChangeIntervalLimitLower && persChangeInterval <= persChangeIntervalLimitUpper && m_persChangeInterval != persChangeInterval) { m_persChangeInterval = persChangeInterval; // do not restart simulation here } } const double KPendulumSaver::eyePhiDefault = 0.25*M_PI; void KPendulumSaver::doTimeStep() { /* time (in seconds) of perspective change. * - t<0: no change yet * - t=0: change starts * - 0integrate(0.001*deltaT); // read new y from solver const std::valarray y = solver->Y(); // tell glArea the new coordinates/angles of the pendulum glArea->setAngles(y[0], y[1]); // handle perspective change persChangeTime += 0.001*deltaT; if (persChangeTime > 0) { // phi value at the start of a perspective change static double eyePhi0 = 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.*sqrt(fabs(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; eyePhi1M_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; } glArea->setEyePhi(eyePhi); // set new perspective } glArea->updateGL(); // repaint scenery timer->start(deltaT, TRUE); // restart 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(TQResizeEvent* e) { glArea->resize(e->size()); } //----------------------------------------------------------------------------- // KPendulumSetup: dialog to setup screen saver parameters //----------------------------------------------------------------------------- KPendulumSetup::KPendulumSetup(TQWidget* parent, const char* name) : KPendulumSetupUi(parent, name), // create saver and give it the WinID of the preview area saver(new KPendulumSaver(preview->winId())) { // the dialog should block, no other control center input should be possible // until the dialog is closed setModal(TRUE); // create input validators mEdit->setValidator(new TQDoubleValidator( KPendulumSaver::massRatioLimitLower, KPendulumSaver::massRatioLimitUpper, 5, mEdit)); lEdit->setValidator(new TQDoubleValidator( KPendulumSaver::lengthRatioLimitLower, KPendulumSaver::lengthRatioLimitUpper, 5, lEdit)); gEdit->setValidator(new TQDoubleValidator( KPendulumSaver::gLimitLower, KPendulumSaver::gLimitUpper, 5, gEdit)); eEdit->setValidator(new TQDoubleValidator( KPendulumSaver::ELimitLower, KPendulumSaver::ELimitUpper, 5, eEdit)); // set input limits for the perspective change interval time persSpinBox->setMinValue(KPendulumSaver::persChangeIntervalLimitLower); persSpinBox->setMaxValue(KPendulumSaver::persChangeIntervalLimitUpper); // set tool tips of editable fields TQToolTip::add( mEdit, i18n("Ratio of 2nd mass to sum of both masses.\nValid values from %1 to %2.") .arg(KPendulumSaver::massRatioLimitLower, 0, 'f', 2) .arg(KPendulumSaver::massRatioLimitUpper, 0, 'f', 2)); TQToolTip::add( lEdit, i18n("Ratio of 2nd pendulum part length to the sum of both part lengths.\nValid values from %1 to %2.") .arg(KPendulumSaver::lengthRatioLimitLower, 0, 'f', 2) .arg(KPendulumSaver::lengthRatioLimitUpper, 0, 'f', 2)); TQToolTip::add( gEdit, i18n("Gravitational constant in arbitrary units.\nValid values from %1 to %2.") .arg(KPendulumSaver::gLimitLower, 0, 'f', 2) .arg(KPendulumSaver::gLimitUpper, 0, 'f', 2)); TQToolTip::add( eEdit, i18n("Energy in units of the maximum potential energy of the given configuration.\nValid values from %1 to %2.") .arg(KPendulumSaver::ELimitLower, 0, 'f', 2) .arg(KPendulumSaver::ELimitUpper, 0, 'f', 2)); TQToolTip::add( persSpinBox, i18n("Time in seconds after which a random perspective change occurs.\nValid values from %1 to %2.") .arg(KPendulumSaver::persChangeIntervalLimitLower) .arg(KPendulumSaver::persChangeIntervalLimitUpper)); // init preview area preview->setBackgroundColor(black); preview->show(); // otherwise saver does not get correct size // 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 TQString text; text.setNum(saver->massRatio()); mEdit->setText(text); text.setNum(saver->lengthRatio()); lEdit->setText(text); text.setNum(saver->g()); gEdit->setText(text); text.setNum(saver->E()); eEdit->setText(text); persSpinBox->setValue(saver->persChangeInterval()); barColorButton->setPaletteBackgroundColor(saver->barColor()); m1ColorButton->setPaletteBackgroundColor(saver->m1Color()); m2ColorButton->setPaletteBackgroundColor(saver->m2Color()); // if the preview area is resized it emmits the resized() event which is // caught by the saver. The embedded GlArea is resized to fit into the // preview area. connect(preview, TQT_SIGNAL(resized(TQResizeEvent*)), saver, TQT_SLOT(resizeGlArea(TQResizeEvent*))); } KPendulumSetup::~KPendulumSetup() { delete saver; } // Ok pressed - save settings and exit void KPendulumSetup::okButtonClickedSlot() { KConfig* config = KGlobal::config(); config->setGroup("Settings"); config->writeEntry("mass ratio", saver->massRatio()); config->writeEntry("length ratio", saver->lengthRatio()); config->writeEntry("g", saver->g()); config->writeEntry("E", saver->E()); config->writeEntry("perspective change interval", saver->persChangeInterval()); config->writeEntry("bar color", saver->barColor()); config->writeEntry("m1 color", saver->m1Color()); config->writeEntry("m2 color", saver->m2Color()); config->sync(); accept(); } void KPendulumSetup::aboutButtonClickedSlot() { KMessageBox::about(this, i18n("\

KPendulum Screen Saver for KDE

\

Simulation of a two-part pendulum

\

Copyright (c) Georg Drenkhahn 2004

\

georg-d@users.sourceforge.net

")); } void KPendulumSetup::mEditLostFocusSlot(void) { if (mEdit->hasAcceptableInput()) { saver->setMassRatio(mEdit->text().toDouble()); } else { // write current setting back into input field TQString text; text.setNum(saver->massRatio()); mEdit->setText(text); } } void KPendulumSetup::lEditLostFocusSlot(void) { if (lEdit->hasAcceptableInput()) { saver->setLengthRatio(lEdit->text().toDouble()); } else { // write current setting back into input field TQString text; text.setNum(saver->lengthRatio()); lEdit->setText(text); } } void KPendulumSetup::gEditLostFocusSlot(void) { if (gEdit->hasAcceptableInput()) { saver->setG(gEdit->text().toDouble()); } else { // write current setting back into input field TQString text; text.setNum(saver->g()); gEdit->setText(text); } } void KPendulumSetup::eEditLostFocusSlot(void) { if (eEdit->hasAcceptableInput()) { saver->setE(eEdit->text().toDouble()); } else { // write current setting back into input field TQString text; text.setNum(saver->E()); eEdit->setText(text); } } void KPendulumSetup::persChangeEnteredSlot(int t) { saver->setPersChangeInterval(t); } void KPendulumSetup::barColorButtonClickedSlot(void) { TQColor color = TQColorDialog::getColor( saver->barColor(), this, "bar color dialog"); if (color.isValid()) { saver->setBarColor(color); barColorButton->setPaletteBackgroundColor(color); } } void KPendulumSetup::m1ColorButtonClickedSlot(void) { TQColor color = TQColorDialog::getColor( saver->m1Color(), this, "mass 1 color dialog"); if (color.isValid()) { saver->setM1Color(color); m1ColorButton->setPaletteBackgroundColor(color); } } void KPendulumSetup::m2ColorButtonClickedSlot(void) { TQColor color = TQColorDialog::getColor( saver->m2Color(), this, "mass 2 color dialog"); if (color.isValid()) { saver->setM2Color(color); m2ColorButton->setPaletteBackgroundColor(color); } }