File indexing completed on 2022-11-29 18:12:04

0001 /*
0002     SPDX-FileCopyrightText: 2004 Sebastian Stein <seb.kde@hpfsc.de>
0003     SPDX-FileCopyrightText: 2008 Tadeu Araujo <tadeu.araujo@ltia.fc.unesp.br>
0004     SPDX-FileCopyrightText: 2008 Danilo Balzaque <danilo.balzaque@ltia.fc.unesp.br>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "ExerciseConvert.h"
0010 
0011 /* these includes are needed for KDE support */
0012 #include <KMessageBox>
0013 #include <KLocalizedString>
0014 
0015 /* these includes are needed for Qt support */
0016 #include <QApplication>
0017 #include <QIntValidator>
0018 #include <QLineEdit>
0019 #include <QLocale>
0020 #include <QPushButton>
0021 #include <QFrame>
0022 #include <QRandomGenerator>
0023 
0024 #ifdef DEBUG
0025 #include <QDebug>
0026 #endif
0027 
0028 /* standard C++ library includes */
0029 #include <cstdlib>
0030 
0031 #include "RationalWidget.h"
0032 #include "ResultWidget.h"
0033 #include "settingsclass.h"
0034 
0035 /* ----- public member functions ----- */
0036 
0037 /* constructor */
0038 ExerciseConvert::ExerciseConvert(QWidget * parent) :
0039     ExerciseBase(parent)
0040 {
0041 #ifdef DEBUG
0042     qDebug() << QStringLiteral("constructor ExerciseConvert()");
0043 #endif
0044 
0045     /* create a new task */
0046     QApplication::setOverrideCursor(Qt::WaitCursor);  /* show the sand clock */
0047     createTask();
0048     QApplication::restoreOverrideCursor(); /* show the normal cursor */
0049 
0050     // to validate, that the input is an int
0051     QIntValidator *valnum = new QIntValidator(this);
0052 
0053     QFont defaultFont = SettingsClass::taskFont();
0054     defaultFont.setBold(true);
0055     defaultFont.setPointSize(18);
0056 
0057     m_currentState = _CHECK_TASK;
0058 
0059     // Create layout
0060     taskWidget = new QWidget(this);
0061     taskWidget->setObjectName(QStringLiteral("taskWidget"));
0062     checkWidget = new QWidget(this);
0063     checkWidget->setObjectName(QStringLiteral("checkWidget"));
0064 
0065     baseGrid = new QGridLayout(this);
0066     baseGrid->setObjectName(QStringLiteral("baseGrid"));
0067     baseGrid->setColumnStretch(0, 1);
0068 
0069     baseGrid->addWidget(taskWidget, 0, 0);
0070     baseGrid->addWidget(checkWidget, 0, 1);
0071 
0072     taskLayout = new QGridLayout(taskWidget);
0073     taskLayout->setObjectName(QStringLiteral("taskLayout"));
0074     taskLayout->setRowStretch(0, 1);
0075     taskLayout->setRowStretch(4, 1);
0076     taskLayout->setColumnStretch(0, 1);
0077     taskLayout->setColumnStretch(3, 1);
0078 
0079     checkLayout = new QGridLayout(checkWidget);
0080     checkLayout->setObjectName(QStringLiteral("checkLayout"));
0081 
0082     // first left is the rational widget
0083     m_rationalWidget = new RationalWidget(taskWidget, m_number, m_periodStart, m_periodLength);
0084     m_rationalWidget->setObjectName(QStringLiteral("m_rationalWidget"));
0085     taskLayout->addWidget(m_rationalWidget, 1, 1, 3, 1);
0086 
0087     /* add input box so the user can enter numerator */
0088     numer_edit = new QLineEdit(taskWidget);
0089     numer_edit->setObjectName(QStringLiteral("numer_edit"));
0090     numer_edit->setValidator(valnum);   // use the int validator
0091     numer_edit->setToolTip(i18n("Enter the numerator of your result"));
0092     numer_edit->setFixedSize(85, 42);
0093     numer_edit->setAlignment(Qt::AlignHCenter);
0094     numer_edit->setFont(defaultFont);
0095     QObject::connect(numer_edit, &QLineEdit::returnPressed, this, &ExerciseConvert::numeratorReturnPressed);
0096     taskLayout->addWidget(numer_edit, 1, 2);
0097 
0098     /* add a line between the edit boxes */
0099     edit_line = new QFrame(taskWidget);
0100     edit_line->setGeometry(QRect(100, 100, 20, 20));
0101     edit_line->setFrameStyle(QFrame::HLine | QFrame::Sunken);
0102     taskLayout->addWidget(edit_line, 2, 2);
0103 
0104     /* add input box so the user can enter denominator */
0105     deno_edit = new QLineEdit(taskWidget);
0106     deno_edit->setObjectName(QStringLiteral("deno_edit"));
0107     deno_edit->setValidator(valnum);   // use the int validator
0108     deno_edit->setToolTip(i18n("Enter the denominator of your result"));
0109     deno_edit->setFixedSize(85, 42);
0110     deno_edit->setAlignment(Qt::AlignHCenter);
0111     deno_edit->setFont(defaultFont);
0112     QObject::connect(deno_edit, &QLineEdit::returnPressed, this, &ExerciseConvert::denominatorReturnPressed);
0113     taskLayout->addWidget(deno_edit, 3, 2);
0114 
0115     // next is the result widget
0116     m_resultWidget = new ResultWidget(checkWidget, m_result);
0117     m_resultWidget->setObjectName(QStringLiteral("m_resultWidget"));
0118     checkLayout->addWidget(m_resultWidget, 0, 0, 1, 2);
0119 
0120     defaultFont.setPointSize(10);
0121 
0122     // the right aligned button
0123     m_checkButton = new QPushButton(checkWidget);
0124     m_checkButton->setObjectName(QStringLiteral("m_checkButton"));
0125     m_checkButton->setText(i18n("&Check"));
0126     m_checkButton->setDefault(true);  // is the default button of the dialog
0127     m_checkButton->setToolTip(i18n("Click on this button to check your result. The button will not work if you have not entered a result yet."));
0128     m_checkButton->setFont(defaultFont);
0129     QObject::connect(m_checkButton, &QPushButton::clicked, this, &ExerciseConvert::slotCheckButtonClicked);
0130     checkLayout->addWidget(m_checkButton, 1, 0);
0131 
0132     // the right aligned button
0133     m_skipButton = new QPushButton(checkWidget);
0134     m_skipButton->setObjectName(QStringLiteral("m_skipButton"));
0135     m_skipButton->setText(i18n("&Skip"));
0136     m_skipButton->setToolTip(i18n("Click on this button to skip this question."));
0137     m_skipButton->setFont(defaultFont);
0138     QObject::connect(m_skipButton, &QPushButton::clicked, this, &ExerciseConvert::slotSkipButtonClicked);
0139     checkLayout->addWidget(m_skipButton, 1, 1);
0140 
0141     setLayout(baseGrid);
0142     taskWidget->setLayout(taskLayout);
0143     checkWidget->setLayout(checkLayout);
0144 
0145     // add tooltip and qwhatsthis help to the widget
0146     setToolTip(i18n("In this exercise you have to convert a number into a fraction."));
0147     setWhatsThis(i18n("In this exercise you have to convert a given number into a fraction by entering a numerator and denominator. Do not forget to reduce the result."));
0148 }
0149 
0150 /* destructor */
0151 ExerciseConvert::~ExerciseConvert()
0152 {
0153 #ifdef DEBUG
0154     qDebug() << QStringLiteral("destructor ExerciseConvert()");
0155 #endif
0156 
0157     /* no need to delete any child widgets, Qt does it by itself */
0158 }
0159 
0160 /** resets the current state, creates a new task and count the last task as
0161  * wrong, if it wasn't solved (in _NEXT_TASK state) yet
0162  * mainly used after changing the task parameters */
0163 void ExerciseConvert::forceNewTask()
0164 {
0165 #ifdef DEBUG
0166     qDebug() << QStringLiteral("forceNewTask ExerciseConvert()");
0167 #endif
0168 
0169     if (m_currentState == _CHECK_TASK) {
0170         // emit the signal for skipped
0171         Q_EMIT signalExerciseSkipped();
0172     }
0173     m_currentState = _CHECK_TASK;
0174     m_checkButton->setText(i18n("&Check"));
0175 
0176     // generate next task
0177     (void) nextTask();
0178 }
0179 
0180 
0181 /* ------ public slots ------ */
0182 
0183 void ExerciseConvert::update()
0184 {
0185     // call update of components
0186     m_rationalWidget->updateAndRepaint();
0187     m_resultWidget->updateAndRepaint();
0188 
0189     // update for itself
0190     ((QWidget *) this)->update();
0191 }
0192 
0193 
0194 /* ------ private member functions ------ */
0195 
0196 void ExerciseConvert::createTask()
0197 {
0198     // the tasks are hardcoded here; there are some algorithms to convert
0199     // rational numbers to fractions, but it is not worth the effort here
0200     switch (QRandomGenerator::global()->bounded(19)) {
0201     case  0 :   m_number = QLocale().toString(0.5, 'f', 1);
0202         m_periodStart = 2;
0203         m_periodLength = 0;
0204         m_result = Ratio(1, 2);
0205         break;
0206     case  1 :   m_number = QLocale().toString(0.3, 'f', 1);
0207         m_periodStart = 2;
0208         m_periodLength = 1;
0209         m_result = Ratio(1, 3);
0210         break;
0211     case  2 :   m_number = QLocale().toString(0.6, 'f', 1);
0212         m_periodStart = 2;
0213         m_periodLength = 1;
0214         m_result = Ratio(2, 3);
0215         break;
0216     case  3 :   m_number = QLocale().toString(0.25, 'f', 2);
0217         m_periodStart = 2;
0218         m_periodLength = 0;
0219         m_result = Ratio(1, 4);
0220         break;
0221     case  4 :   m_number = QLocale().toString(0.75, 'f', 2);
0222         m_periodStart = 2;
0223         m_periodLength = 0;
0224         m_result = Ratio(3, 4);
0225         break;
0226     case  5 :   m_number = QLocale().toString(0.2, 'f', 1);
0227         m_periodStart = 2;
0228         m_periodLength = 0;
0229         m_result = Ratio(1, 5);
0230         break;
0231     case  6 :   m_number = QLocale().toString(0.4, 'f', 1);
0232         m_periodStart = 2;
0233         m_periodLength = 0;
0234         m_result = Ratio(2, 5);
0235         break;
0236     case  7 :   m_number = QLocale().toString(0.6, 'f', 1);
0237         m_periodStart = 2;
0238         m_periodLength = 0;
0239         m_result = Ratio(3, 5);
0240         break;
0241     case  8 :   m_number = QLocale().toString(0.8, 'f', 1);
0242         m_periodStart = 2;
0243         m_periodLength = 0;
0244         m_result = Ratio(4, 5);
0245         break;
0246     case  9 :   m_number = QLocale().toString(0.16, 'f', 2);
0247         m_periodStart = 3;
0248         m_periodLength = 1;
0249         m_result = Ratio(1, 6);
0250         break;
0251     case 10 :   m_number = QLocale().toString(0.142857, 'f', 6);
0252         m_periodStart = 2;
0253         m_periodLength = 6;
0254         m_result = Ratio(1, 7);
0255         break;
0256     case 11 :   m_number = QLocale().toString(0.125, 'f', 3);
0257         m_periodStart = 2;
0258         m_periodLength = 0;
0259         m_result = Ratio(1, 8);
0260         break;
0261     case 12 :   m_number = QLocale().toString(0.375, 'f', 3);
0262         m_periodStart = 2;
0263         m_periodLength = 0;
0264         m_result = Ratio(3, 8);
0265         break;
0266     case 13 :   m_number = QLocale().toString(0.1, 'f', 1);
0267         m_periodStart = 2;
0268         m_periodLength = 1;
0269         m_result = Ratio(1, 9);
0270         break;
0271     case 14 :   m_number = QLocale().toString(0.1, 'f', 1);
0272         m_periodStart = 2;
0273         m_periodLength = 0;
0274         m_result = Ratio(1, 10);
0275         break;
0276     case 15 :   m_number = QLocale().toString(0.05, 'f', 2);
0277         m_periodStart = 2;
0278         m_periodLength = 0;
0279         m_result = Ratio(1, 20);
0280         break;
0281     case 16 :   m_number = QLocale().toString(0.01, 'f', 2);
0282         m_periodStart = 2;
0283         m_periodLength = 0;
0284         m_result = Ratio(1, 100);
0285         break;
0286     case 17 :   m_number = QLocale().toString(0.83, 'f', 2);
0287         m_periodStart = 3;
0288         m_periodLength = 1;
0289         m_result = Ratio(5, 6);
0290         break;
0291     default :
0292     case 18 :   m_number = QLocale().toString(0.001, 'f', 3);
0293         m_periodStart = 2;
0294         m_periodLength = 0;
0295         m_result = Ratio(1, 1000);
0296         break;
0297     }
0298 
0299     return;
0300 }
0301 
0302 /** - checks, if the user solved the task correctly
0303         - emits signals if task was solved correctly or wrong */
0304 void ExerciseConvert::showResult()
0305 {
0306     Ratio entered_result;
0307 
0308     // change the tooltip of the check button
0309     m_checkButton->setToolTip(i18n("Click on this button to get to the next question."));
0310 
0311     numer_edit->setEnabled(false);
0312     deno_edit->setEnabled(false);
0313     m_skipButton->setEnabled(false);
0314 
0315     // an empty numerator field will be interpreted as 0
0316     if (numer_edit->text().isEmpty() == true)
0317         numer_edit->setText(QStringLiteral("0"));
0318 
0319     // an empty denominator field will be interpreted as 1
0320     if (deno_edit->text().isEmpty() == true)
0321         deno_edit->setText(QStringLiteral("1"));
0322 
0323     /* store the entered result to check it, but without reducing */
0324     entered_result.setNumerator(numer_edit->text().toInt(), false);
0325     entered_result.setDenominator(deno_edit->text().toInt(), false);
0326 
0327     // check the entered result; 0/1 == 0/5 -> true,
0328     // but 0/1 == 0/0 -> false
0329     // a 0 for denominator is never allowed (always counted as wrong)
0330     //
0331     // we have to get the 0 directly from the input field, because
0332     // Ratio::setDenominator(0, false) will set the denominator to 1 to ensure
0333     // the Ratio is valid
0334     if ((deno_edit->text().toInt() != 0) && ((entered_result == m_result) ||
0335             (m_result.numerator() == 0 && entered_result.numerator() == 0))) {
0336         // emit the signal for correct
0337         Q_EMIT signalExerciseSolvedCorrect();
0338 
0339         /* yes, the user entered the correct result */
0340         m_resultWidget->setResult(m_result, 1);
0341     } else {
0342         // emit the signal for wrong
0343         Q_EMIT signalExerciseSolvedWrong();
0344 
0345         /* no, the user entered the wrong result */
0346         m_resultWidget->setResult(m_result, 0);
0347 
0348         // if the user entered a 0 for the denominator (division by 0) we have to
0349         // get the 0 directly from the input field, because
0350         // Ratio::setDenominator(0, true) will set the denominator to 1 to ensure
0351         // the Ratio is valid
0352         if (deno_edit->text().toInt() == 0) {
0353             KMessageBox::information(this,
0354                                      i18n("You entered a 0 as the denominator. This means division by zero, which is not allowed. This question will be counted as not correctly solved."));
0355         } else {
0356             /* maybe the entered ratio was not reduced */
0357             entered_result.reduce();
0358             if (entered_result == m_result)
0359                 KMessageBox::information(this,
0360                                          i18n("You entered the correct result, but not reduced.\nAlways enter your results as reduced. This question will be counted as not correctly solved."));
0361         }
0362     } /* if (entered_result == result) */
0363 
0364     return;
0365 }
0366 
0367 /** generate the next task and show it to the user */
0368 void ExerciseConvert::nextTask()
0369 {
0370     // change the tooltip of the check button
0371     m_checkButton->setToolTip(i18n("Click on this button to check your result. The button will not work if you have not entered a result yet."));
0372 
0373     numer_edit->setEnabled(true);
0374     deno_edit->setEnabled(true);
0375     m_skipButton->setEnabled(true);
0376 
0377     m_resultWidget->setResult(m_result, -1);
0378 
0379     /* clear user input */
0380     deno_edit->clear();
0381     numer_edit->clear();
0382     numer_edit->setFocus();
0383 
0384     /* create a new task */
0385     QApplication::setOverrideCursor(Qt::WaitCursor);  /* show the sand clock */
0386     createTask();
0387     QApplication::restoreOverrideCursor(); /* show the normal cursor */
0388 
0389     // update the task widget
0390     m_rationalWidget->setRational(m_number, m_periodStart, m_periodLength);
0391 
0392     return;
0393 }
0394 
0395 /* ------ private slots ------ */
0396 
0397 void ExerciseConvert::slotCheckButtonClicked()
0398 {
0399     if (m_currentState == _CHECK_TASK) {
0400         // if nothing has been entered by the user, we don't check the result yet
0401         if (numer_edit->text().isEmpty() == true && deno_edit->text().isEmpty() ==
0402                 true)
0403             return;
0404         m_currentState = _NEXT_TASK;
0405         m_checkButton->setText(i18n("N&ext"));
0406         (void) showResult();
0407     } else {
0408         m_currentState = _CHECK_TASK;
0409         m_checkButton->setText(i18n("&Check"));
0410         (void) nextTask();
0411     }
0412 
0413     return;
0414 }
0415 
0416 void ExerciseConvert::slotSkipButtonClicked()
0417 {
0418     forceNewTask();
0419 }
0420 
0421 /* ------ private slots ------ */
0422 
0423 void ExerciseConvert::numeratorReturnPressed()
0424 {
0425     deno_edit->setFocus();
0426 }
0427 
0428 void ExerciseConvert::denominatorReturnPressed()
0429 {
0430     slotCheckButtonClicked();
0431 }
0432 
0433 /* ------ protected events ------ */
0434 void ExerciseConvert::showEvent(QShowEvent *)
0435 {
0436 
0437     // that the user can start typing without moving the focus
0438     numer_edit->setFocus();
0439 
0440 }