File indexing completed on 2022-12-06 18:52:30

0001 /*
0002     KBruch - exercise to convert mixed numbers in ratios and vice versa
0003     SPDX-FileCopyrightText: 2011 Sebastian Stein <seb.kde@hpfsc.de>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "ExerciseMixedNumbers.h"
0009 
0010 /* these includes are needed for KDE support */
0011 #include <QLineEdit>
0012 #include <KLocalizedString>
0013 #include <KMessageBox>
0014 
0015 /* these includes are needed for Qt support */
0016 #include <QApplication>
0017 #include <QGridLayout>
0018 #include <QIntValidator>
0019 #include <QPushButton>
0020 #include <QWidget>
0021 #include <QRandomGenerator>
0022 
0023 #ifdef DEBUG
0024 #include <QDebug>
0025 #endif
0026 
0027 /* KBruch includes */
0028 #include "settingsclass.h"
0029 
0030 
0031 /* constructor; build UI for this exercise */
0032 ExerciseMixedNumbers::ExerciseMixedNumbers(QWidget * parent) :
0033     ExerciseBase(parent)
0034 {
0035 #ifdef DEBUG
0036     qDebug() << QStringLiteral("constructor ExerciseMixedNumbers()");
0037 #endif
0038 
0039     // define initial state
0040     m_isMixedTask = true;
0041     m_currentState = _CHECK_TASK;
0042 
0043     // create a new task
0044     QApplication::setOverrideCursor(Qt::WaitCursor);    // show the sand clock
0045     createTask();
0046     QApplication::restoreOverrideCursor(); // show the normal cursor
0047 
0048     // create layout
0049     //
0050     // create 2 base widgets
0051     m_tmpTaskWidget = new QWidget(this);
0052     m_tmpTaskWidget->setObjectName(QStringLiteral("m_tmpTaskWidget"));
0053     m_checkWidget = new QWidget(this);
0054     m_checkWidget->setObjectName(QStringLiteral("m_checkWidget"));
0055 
0056     // add base widgets to base grid layout
0057     m_baseGrid = new QGridLayout(this);
0058     m_baseGrid->setObjectName(QStringLiteral("m_baseGrid"));
0059     m_baseGrid->setColumnStretch(0, 1);
0060     m_baseGrid->addWidget(m_tmpTaskWidget, 0, 0);
0061     m_baseGrid->addWidget(m_checkWidget, 0, 1);
0062 
0063     // prepare task layout
0064     m_taskLayout = new QGridLayout(m_tmpTaskWidget);
0065     m_taskLayout->setObjectName(QStringLiteral("m_taskLayout"));
0066     m_taskLayout->setRowStretch(0, 1);
0067     m_taskLayout->setRowStretch(4, 1);
0068     m_taskLayout->setColumnStretch(0, 1);
0069     m_taskLayout->setColumnStretch(5, 1);
0070 
0071     // prepare check layout
0072     m_checkLayout = new QGridLayout(m_checkWidget);
0073     m_checkLayout->setObjectName(QStringLiteral("m_checkLayout"));
0074 
0075     // set up task layout
0076     //
0077     // create task widget
0078     m_taskWidget = new TaskWidget(m_tmpTaskWidget, m_task);
0079     m_taskWidget->setObjectName(QStringLiteral("m_taskWidget"));
0080     m_taskLayout->addWidget(m_taskWidget, 1, 1, 3, 1);
0081 
0082     // int input validator
0083     QIntValidator * intValidator = new QIntValidator(this);
0084 
0085     // default font
0086     QFont defaultFont = SettingsClass::taskFont();
0087     defaultFont.setBold(true);
0088     defaultFont.setPointSize(18);
0089 
0090     // input fields for solution
0091     //
0092     // integer input
0093     m_integerEdit = new QLineEdit(m_tmpTaskWidget);
0094     m_integerEdit->setObjectName(QStringLiteral("m_integerEdit"));
0095     m_integerEdit->setValidator(intValidator);
0096     m_integerEdit->setToolTip(i18n("Enter the integer part of the fraction"));
0097     m_integerEdit->setFont(defaultFont);
0098     m_integerEdit->setFixedSize(85, 42);
0099     m_integerEdit->setAlignment(Qt::AlignHCenter);
0100     QObject::connect(m_integerEdit, &QLineEdit::returnPressed, this, &ExerciseMixedNumbers::integerReturnPressed);
0101     m_taskLayout->addWidget(m_integerEdit, 1, 3, 3, 1, Qt::AlignVCenter |
0102                             Qt::AlignRight);
0103     m_integerEdit->setEnabled(false);
0104     m_integerEdit->hide();
0105 
0106     // numerator input
0107     m_numerEdit = new QLineEdit(m_tmpTaskWidget);
0108     m_numerEdit->setObjectName(QStringLiteral("m_numerEdit"));
0109     m_numerEdit->setValidator(intValidator);
0110     m_numerEdit->setToolTip(i18n("Enter the numerator of the fraction"));
0111     m_numerEdit->setFont(defaultFont);
0112     m_numerEdit->setFixedSize(85, 42);
0113     m_numerEdit->setAlignment(Qt::AlignHCenter);
0114     QObject::connect(m_numerEdit, &QLineEdit::returnPressed, this, &ExerciseMixedNumbers::numerReturnPressed);
0115     m_taskLayout->addWidget(m_numerEdit, 1, 4);
0116 
0117     // add a line between the input boxes
0118     m_editLine = new QFrame(m_tmpTaskWidget);
0119     m_editLine->setGeometry(QRect(100, 100, 20, 20));
0120     m_editLine->setFrameStyle(QFrame::HLine | QFrame::Sunken);
0121     m_taskLayout->addWidget(m_editLine, 2, 4);
0122 
0123     // denominator input
0124     m_denoEdit = new QLineEdit(m_tmpTaskWidget);
0125     m_denoEdit->setObjectName(QStringLiteral("m_numerEdit"));
0126     m_denoEdit->setValidator(intValidator);
0127     m_denoEdit->setToolTip(i18n("Enter the denominator of the fraction"));
0128     m_denoEdit->setFont(defaultFont);
0129     m_denoEdit->setFixedSize(85, 42);
0130     m_denoEdit->setAlignment(Qt::AlignHCenter);
0131     QObject::connect(m_denoEdit, &QLineEdit::returnPressed, this, &ExerciseMixedNumbers::denoReturnPressed);
0132     m_taskLayout->addWidget(m_denoEdit, 3, 4);
0133 
0134 
0135     // set up check layout
0136     //
0137     // add result widget
0138     m_resultWidget = new ResultWidget(m_checkWidget, Ratio());
0139     m_resultWidget->setObjectName(QStringLiteral("m_resultWidget"));
0140     m_checkLayout->addWidget(m_resultWidget, 0, 0, 1, 2);
0141 
0142     // new font size
0143     defaultFont.setPointSize(10);
0144 
0145     // check button
0146     m_checkButton = new QPushButton(m_checkWidget);
0147     m_checkButton->setObjectName(QStringLiteral("m_checkButton"));
0148     m_checkButton->setText(i18n("&Check"));
0149     m_checkButton->setDefault(true);    // is the default button of the dialog
0150     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."));
0151     m_checkButton->setFont(defaultFont);
0152     QObject::connect(m_checkButton, &QPushButton::clicked, this, &ExerciseMixedNumbers::slotCheckButtonClicked);
0153     m_checkLayout->addWidget(m_checkButton, 1, 0);
0154 
0155     // skip button
0156     m_skipButton = new QPushButton(m_checkWidget);
0157     m_skipButton->setObjectName(QStringLiteral("m_skipButton"));
0158     m_skipButton->setText(i18n("&Skip"));
0159     m_skipButton->setToolTip(i18n("Click on this button to skip this question."));
0160     m_skipButton->setFont(defaultFont);
0161     QObject::connect(m_skipButton, &QPushButton::clicked, this, &ExerciseMixedNumbers::slotSkipButtonClicked);
0162     m_checkLayout->addWidget(m_skipButton, 1, 1);
0163 
0164     // add tooltip and qwhatsthis help to the exercise widget
0165     setToolTip(i18n("In this exercise you have to convert a mixed number into a ratio and vice versa."));
0166     setWhatsThis(i18n("In this exercise you have to convert a mixed number into a ratio and vice versa. Do not forget to reduce the result."));
0167 }
0168 
0169 /* destructor */
0170 ExerciseMixedNumbers::~ExerciseMixedNumbers()
0171 {
0172 #ifdef DEBUG
0173     qDebug() << QStringLiteral("destructor ExerciseMixedNumbers()");
0174 #endif
0175 }
0176 
0177 /* handle user action to show a new task */
0178 void ExerciseMixedNumbers::forceNewTask()
0179 {
0180 #ifdef DEBUG
0181     qDebug() << QStringLiteral("forceNewTask ExerciseMixedNumbers()");
0182 #endif
0183 
0184     if (m_currentState == _CHECK_TASK) {
0185         // emit the signal for skipped
0186         Q_EMIT signalExerciseSkipped();
0187     }
0188     m_currentState = _CHECK_TASK;
0189     m_checkButton->setText(i18n("&Check"));
0190 
0191     // generate next task
0192     (void) nextTask();
0193 }
0194 
0195 /* generate new task */
0196 void ExerciseMixedNumbers::createTask()
0197 {
0198     // generate ratio; constraints:
0199     // - reduced
0200     // - numerator is larger than denominator
0201     // - denominator is not 1
0202     Ratio tmpRatio = Ratio();
0203     int numerator = 0;
0204     int denominator = 1;
0205     do {
0206         // numerator should be between 1..15
0207         numerator = QRandomGenerator::global()->bounded(15) + 1;
0208 
0209         // denominator should be between 1..(numerator-1)
0210         denominator = QRandomGenerator::global()->bounded(numerator);
0211 
0212         // eventually make ratio negative
0213         if (QRandomGenerator::global()->bounded(2) == 1) {
0214             numerator *= -1;
0215         }
0216         tmpRatio.setRatio(numerator, denominator);
0217     } while (tmpRatio.denominator() == 1);
0218 
0219     // store new task
0220     m_task = Task();
0221     m_task.add_ratio(tmpRatio);
0222 
0223     return;
0224 }
0225 
0226 /* show next task to user */
0227 void ExerciseMixedNumbers::nextTask()
0228 {
0229     // create a new task
0230     //
0231     // alternate between converting ratio into mixed number and vice versa
0232     m_isMixedTask = ! m_isMixedTask;
0233     QApplication::setOverrideCursor(Qt::WaitCursor);    // show the sand clock
0234     createTask();
0235     QApplication::restoreOverrideCursor(); // show the normal cursor
0236 
0237     // update the task widget
0238     m_taskWidget->setQuestionMixed(m_isMixedTask);
0239     m_taskWidget->setTask((const Task) m_task);
0240 
0241     // hide result widget
0242     m_resultWidget->setResult(Ratio(), -1);
0243 
0244     // change check button
0245     m_checkButton->setToolTip(i18n("Click this button to check your result. The button will not work if you have not entered a result yet."));
0246     m_checkButton->setText(i18n("&Check"));
0247 
0248     // clear user input and restore input fields
0249     m_denoEdit->clear();
0250     m_numerEdit->clear();
0251     m_integerEdit->clear();
0252     m_numerEdit->setEnabled(true);
0253     m_denoEdit->setEnabled(true);
0254     m_skipButton->setEnabled(true);
0255     if (m_isMixedTask) {
0256         m_integerEdit->hide();
0257         m_integerEdit->setEnabled(false);
0258         m_numerEdit->setFocus();
0259     } else {
0260         m_integerEdit->setEnabled(true);
0261         m_integerEdit->show();
0262         m_integerEdit->setFocus();
0263     }
0264 
0265     return;
0266 }
0267 
0268 /* exercise gets shown */
0269 void ExerciseMixedNumbers::showEvent(QShowEvent *)
0270 {
0271     // that the user can start typing without moving the focus
0272     if (m_isMixedTask) {
0273         m_numerEdit->setFocus();
0274     } else {
0275         m_integerEdit->setFocus();
0276     }
0277     m_taskWidget->setQuestionMixed(m_isMixedTask);
0278 }
0279 
0280 /* check entered result and show solution */
0281 void ExerciseMixedNumbers::showResult()
0282 {
0283     bool wrong = false;
0284 
0285     // update UI while solution is shown
0286     m_checkButton->setToolTip(i18n("Click this button to get the next question."));
0287     m_checkButton->setText(i18n("N&ext"));
0288     m_integerEdit->setEnabled(false);
0289     m_numerEdit->setEnabled(false);
0290     m_denoEdit->setEnabled(false);
0291     m_skipButton->setEnabled(false);
0292 
0293     // an empty numerator field is interpreted as 0
0294     if (m_numerEdit->text().isEmpty()) {
0295         m_numerEdit->setText(QStringLiteral("0"));
0296     }
0297     int resultNumerator = m_numerEdit->text().toInt();
0298 
0299     // an empty denominator field is interpreted as 1
0300     if (m_denoEdit->text().isEmpty()) {
0301         m_denoEdit->setText(QStringLiteral("1"));
0302     }
0303     int resultDenominator = m_denoEdit->text().toInt();
0304     if (resultDenominator == 0) {
0305         // don't allow denominator to be 0
0306         wrong = true;
0307     }
0308 
0309     // get integer if user had to input it
0310     int resultInteger = 0;
0311     if (! m_isMixedTask) {
0312         resultInteger = m_integerEdit->text().toInt();
0313 
0314         // in mixed notation, numerator must be smaller than denominator
0315         if (qAbs(resultNumerator) >= qAbs(resultDenominator)) {
0316             wrong = true;
0317         }
0318     }
0319 
0320     // create result ratio, but don't try to reduce it yet
0321     Ratio resultRatio = Ratio();
0322     resultRatio.setRatio(resultInteger, resultNumerator, resultDenominator,
0323                          false);
0324 
0325     // check for correct solution
0326     Ratio solutionRatio = m_task.get_ratio_n(0);
0327     if (!(resultRatio == solutionRatio)) {
0328         wrong = true;
0329     }
0330 
0331     // wrong solution, try to give some hints what might be wrong
0332     if (wrong) {
0333         // emit the signal for wrong
0334         Q_EMIT signalExerciseSolvedWrong();
0335         m_resultWidget->setAnswerMixed(! m_isMixedTask);
0336         m_resultWidget->setResult(solutionRatio, 0);
0337 
0338         // check if user entered 0 as denominator
0339         if (resultDenominator == 0) {
0340             KMessageBox::information(this, i18n("You entered 0 as the denominator. This means division by zero, which is not allowed. This question will be counted as not correctly solved."));
0341         } else {
0342             // first reduce entered result to identify more problems
0343             resultRatio.reduce();
0344             if ((! m_isMixedTask) && (resultRatio == solutionRatio) &&
0345                     (qAbs(resultNumerator) >= qAbs(resultDenominator))) {
0346                 // maybe didn't enter mixed number notation
0347                 KMessageBox::information(this, i18n("You entered the correct result, but not in the mixed number notation. This question will be counted as not correctly solved."));
0348             } else if (resultRatio == solutionRatio) {
0349                 // maybe the user entered the correct result but not reduced
0350                 KMessageBox::information(this, i18n("You entered the correct result, but not reduced. This question will be counted as not correctly solved."));
0351             }
0352         }
0353     } else {
0354         // emit the signal for correct
0355         Q_EMIT signalExerciseSolvedCorrect();
0356         m_resultWidget->setResult(solutionRatio, 1);
0357     }
0358 
0359     return;
0360 }
0361 
0362 /* handle check button */
0363 void ExerciseMixedNumbers::slotCheckButtonClicked()
0364 {
0365     // button is used to check result and get to next task
0366     if (m_currentState == _CHECK_TASK) {
0367         // don't check result if nothing was entered yet
0368         if (m_numerEdit->text().isEmpty() && m_denoEdit->text().isEmpty()) {
0369             return;
0370         }
0371 
0372         m_currentState = _NEXT_TASK;
0373         (void) showResult();
0374     } else {
0375         m_currentState = _CHECK_TASK;
0376         (void) nextTask();
0377     }
0378 
0379     return;
0380 }
0381 
0382 /* handle skip button */
0383 void ExerciseMixedNumbers::slotSkipButtonClicked()
0384 {
0385     forceNewTask();
0386 }
0387 
0388 /* handle integer edit */
0389 void ExerciseMixedNumbers::integerReturnPressed()
0390 {
0391     m_numerEdit->setFocus();
0392 }
0393 
0394 /* handle numerator edit */
0395 void ExerciseMixedNumbers::numerReturnPressed()
0396 {
0397     m_denoEdit->setFocus();
0398 }
0399 
0400 /* handle denominator edit */
0401 void ExerciseMixedNumbers::denoReturnPressed()
0402 {
0403     slotCheckButtonClicked();
0404 }