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

0001 /*
0002     StatisticsView.cpp  -  the statistic window
0003     SPDX-FileCopyrightText: 2001-2004 Sebastian Stein <seb.kde@hpfsc.de>
0004     SPDX-FileCopyrightText: 2008 Tadeu Araujo <tadeu.araujo@ltia.fc.unesp.br>
0005     SPDX-FileCopyrightText: 2008 Danilo Balzaque <danilo.balzaque@ltia.fc.unesp.br>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "TaskView.h"
0011 
0012 /* these includes are needed for KDE support */
0013 #include <KLocalizedString>
0014 #include <KMessageBox>
0015 
0016 /* these includes are needed for Qt support */
0017 #include <QApplication>
0018 #include <QGridLayout>
0019 #include <QIntValidator>
0020 #include <QLineEdit>
0021 #include <QPushButton>
0022 #include <QFrame>
0023 
0024 #ifdef DEBUG
0025 #include <QDebug>
0026 #endif
0027 
0028 #include "settingsclass.h"
0029 
0030 /* standard C++ library includes */
0031 #include <cmath>
0032 
0033 /* ----- public member functions ----- */
0034 
0035 /* constructor */
0036 TaskView::TaskView(QWidget * parent,
0037                    bool padd_add, bool padd_div, bool padd_mult, bool padd_sub,
0038                    unsigned int pnr_ratios, unsigned int pmax_md) :
0039     ExerciseBase(parent), m_addAdd(padd_add), m_addDiv(padd_div),
0040     m_addMult(padd_mult), m_addSub(padd_sub),
0041     nr_ratios(pnr_ratios), max_md(pmax_md)
0042 {
0043 #ifdef DEBUG
0044     qDebug() << QStringLiteral("constructor TaskView()");
0045 #endif
0046     curr_nr_ratios = nr_ratios;
0047 
0048     /* create a new task */
0049     QApplication::setOverrideCursor(Qt::WaitCursor);  /* show the sand clock */
0050     current_task.create_task(max_md, nr_ratios, m_addAdd, m_addDiv, m_addMult, m_addSub);
0051     QApplication::restoreOverrideCursor(); /* show the normal cursor */
0052 
0053     // to validate, that the input is an int
0054     QIntValidator *valnum = new QIntValidator(this);
0055 
0056     // the next thing to do on a button click would be to check the entered
0057     // result
0058     m_currentState = _CHECK_TASK;
0059 
0060     // Create the base of Widget
0061     taskWidget = new QWidget(this);
0062     taskWidget->setObjectName(QStringLiteral("taskWidget"));
0063     checkWidget = new QWidget(this);
0064     checkWidget->setObjectName(QStringLiteral("checkWidget"));
0065 
0066     baseGrid = new QGridLayout(this);
0067     baseGrid->setObjectName(QStringLiteral("baseGrid"));
0068     baseGrid->setColumnStretch(0, 1);
0069 
0070     baseGrid->addWidget(taskWidget, 0, 0);
0071     baseGrid->addWidget(checkWidget, 0, 1);
0072 
0073     taskLayout = new QGridLayout(taskWidget);
0074     taskLayout->setObjectName(QStringLiteral("taskLayout"));
0075     taskLayout->setRowStretch(0, 1);
0076     taskLayout->setRowStretch(4, 1);
0077     taskLayout->setColumnStretch(0, 1);
0078     taskLayout->setColumnStretch(5, 1);
0079 
0080     checkLayout = new QGridLayout(checkWidget);
0081     checkLayout->setObjectName(QStringLiteral("checkLayout"));
0082 
0083     QFont defaultFont = SettingsClass::taskFont();
0084     defaultFont.setBold(true);
0085     defaultFont.setPointSize(18);
0086 
0087     // first left is the task widget
0088     m_taskWidget = new TaskWidget(taskWidget, current_task);
0089     m_taskWidget->setObjectName(QStringLiteral("m_taskWidget"));
0090     taskLayout->addWidget(m_taskWidget, 1, 1, 3, 1);
0091 
0092     /* add input box so the user can enter the integer par of the fraction */
0093     integer_edit = new QLineEdit(taskWidget);
0094     integer_edit->setObjectName(QStringLiteral("integer_edit"));
0095     integer_edit->setValidator(valnum);   // use the int validator
0096     integer_edit->setToolTip(i18n("Enter the integer part of the fraction"));
0097     integer_edit->setFont(defaultFont);
0098     integer_edit->setFixedSize(85, 42);
0099     integer_edit->setAlignment(Qt::AlignHCenter);
0100     QObject::connect(integer_edit, &QLineEdit::returnPressed, this, &TaskView::integerReturnPressed);
0101     taskLayout->addWidget(integer_edit, 1, 3, 3, 1, Qt::AlignVCenter | Qt::AlignRight);
0102 
0103 
0104     /* add input box so the user can enter numerator */
0105     numer_edit = new QLineEdit(taskWidget);
0106     numer_edit->setObjectName(QStringLiteral("numer_edit"));
0107     numer_edit->setValidator(valnum);   // use the int validator
0108     numer_edit->setToolTip(i18n("Enter the numerator of your result"));
0109     numer_edit->setFont(defaultFont);
0110     numer_edit->setFixedSize(85, 42);
0111     numer_edit->setAlignment(Qt::AlignHCenter);
0112     QObject::connect(numer_edit, &QLineEdit::returnPressed, this, &TaskView::numeratorReturnPressed);
0113     taskLayout->addWidget(numer_edit, 1, 4);
0114 
0115     /* add a line between the edit boxes */
0116     edit_line = new QFrame(taskWidget);
0117     edit_line->setGeometry(QRect(100, 100, 20, 20));
0118     edit_line->setFrameStyle(QFrame::HLine | QFrame::Sunken);
0119     taskLayout->addWidget(edit_line, 2, 4);
0120 
0121     /* add input box so the user can enter denominator */
0122     deno_edit = new QLineEdit(taskWidget);
0123     deno_edit->setObjectName(QStringLiteral("deno_edit"));
0124     deno_edit->setValidator(valnum);   // use the int validator
0125     deno_edit->setToolTip(i18n("Enter the denominator of your result"));
0126     deno_edit->setFont(defaultFont);
0127     deno_edit->setFixedSize(85, 42);
0128     deno_edit->setAlignment(Qt::AlignHCenter);
0129     QObject::connect(deno_edit, &QLineEdit::returnPressed, this, &TaskView::denominatorReturnPressed);
0130     taskLayout->addWidget(deno_edit, 3, 4);
0131 
0132     // next is the result widget
0133     m_resultWidget = new ResultWidget(checkWidget, Ratio());
0134     m_resultWidget->setObjectName(QStringLiteral("m_resultWidget"));
0135     checkLayout->addWidget(m_resultWidget, 0, 0, 1, 2);
0136 
0137     defaultFont.setPointSize(10);
0138 
0139     // the right aligned button
0140     m_checkButton = new QPushButton(checkWidget);
0141     m_checkButton->setObjectName(QStringLiteral("m_checkButton"));
0142     m_checkButton->setText(i18n("&Check"));
0143     m_checkButton->setDefault(true);  // is the default button of the dialog
0144     m_checkButton->setToolTip(i18n("Click this button to check your result. The button will not work if you have not entered a result yet."));
0145     m_checkButton->setFont(defaultFont);
0146     QObject::connect(m_checkButton, &QPushButton::clicked, this, &TaskView::slotCheckButtonClicked);
0147     checkLayout->addWidget(m_checkButton, 1, 0);
0148 
0149     // the right aligned button
0150     m_skipButton = new QPushButton(checkWidget);
0151     m_skipButton->setObjectName(QStringLiteral("m_skipButton"));
0152     m_skipButton->setText(i18n("&Skip"));
0153     m_skipButton->setToolTip(i18n("Click this button to skip this question."));
0154     m_skipButton->setFont(defaultFont);
0155     QObject::connect(m_skipButton, &QPushButton::clicked, this, &TaskView::slotSkipButtonClicked);
0156     checkLayout->addWidget(m_skipButton, 1, 1);
0157 
0158     // show the whole layout
0159     m_taskWidget->show();
0160 
0161     // add tooltip and qwhatsthis help to the widget
0162     setToolTip(i18n("In this exercise you have to solve a given question with fractions."));
0163     setWhatsThis(i18n("In this exercise you have to solve the generated question. You have to enter the integer part of the fraction and the numerator and the denominator. You can adjust the difficulty of the question in the options window part. Do not forget to reduce the result, if the use of the reduced form is forced."));
0164 }
0165 
0166 /* destructor */
0167 TaskView::~TaskView()
0168 {
0169 #ifdef DEBUG
0170     qDebug() << QStringLiteral("destructor TaskView()");
0171 #endif
0172 
0173     /* no need to delete any child widgets, Qt does it by itself */
0174 }
0175 
0176 void TaskView::setReducedForm(bool value)
0177 {
0178     m_reducedForm = value;
0179 }
0180 
0181 void TaskView::setQuestionMixed(bool value)
0182 {
0183 #ifdef DEBUG
0184     qDebug() << "TaskView::setQuestionMixed()";
0185 #endif
0186     m_taskWidget->setQuestionMixed(value);
0187 }
0188 
0189 void TaskView::setAnswerMixed(bool value)
0190 {
0191 #ifdef DEBUG
0192     qDebug() << "TaskView::setAnswerMixed()";
0193 #endif
0194     m_answerMixed = value;
0195     integer_edit->setVisible(value);
0196     m_resultWidget->setAnswerMixed(value);
0197 }
0198 
0199 /** the parameters of task generation can be set with this function */
0200 void TaskView::setTaskParameters(bool padd_add, bool padd_div,
0201                                  bool padd_mult, bool padd_sub,
0202                                  unsigned int pnr_ratios, unsigned int pmax_md)
0203 {
0204     // at least one operation must be enabled
0205     //if ((padd_add == false) && (padd_div == false) && (padd_mult == false) && (padd_sub == false))
0206     //  padd_add = true;
0207 
0208     // we need at least 2 ratios
0209     if (pnr_ratios < 2)
0210         pnr_ratios = 2;
0211 
0212     // we can only visualize 5 ratios, so we have to limit it
0213     if (pnr_ratios > 5)
0214         pnr_ratios = 5;
0215 
0216     // the main denominator must be at least 2^pnr_ratios
0217     if (pow(2.0, (double) pnr_ratios) > pmax_md)
0218         pmax_md = (unsigned int) pow(2.0, (double) pnr_ratios);
0219 
0220     // so everything seems to be fine, lets set the internal values to the given
0221     // ones
0222     m_addSub = padd_sub;
0223     m_addAdd = padd_add;
0224     m_addMult = padd_mult;
0225     m_addDiv = padd_div;
0226     max_md = pmax_md;
0227 
0228     nr_ratios = pnr_ratios;
0229 
0230     return;
0231 }
0232 
0233 /** resets the current state, creates a new task and count the last task as
0234  * wrong, if it wasn't solved (in _NEXT_TASK state) yet
0235  * mainly used after changing the task parameters */
0236 void TaskView::forceNewTask()
0237 {
0238 #ifdef DEBUG
0239     qDebug() << "forceNewTask TaskView()";
0240 #endif
0241 
0242     if (m_currentState == _CHECK_TASK) {
0243         // emit the signal for skipped
0244         Q_EMIT signalTaskSkipped();
0245     }
0246     m_currentState = _CHECK_TASK;
0247     m_checkButton->setText(i18n("&Check"));
0248 
0249     // generate next task
0250     (void) nextTask();
0251 }
0252 
0253 
0254 /* ------ private member functions ------ */
0255 
0256 /** - checks the entered result and compares it to the task's result
0257         - shows the correct result and informs the user if he was right or wrong
0258         - if the user entered the result unreduced, he will be informed about it
0259         - if the user entered a 0 for the denominator, he will be informed about
0260           it (division by zero)
0261         - emits signals if task was solved right or wrong */
0262 void TaskView::showResult()
0263 {
0264     bool wrong = false;
0265 
0266     // change the tooltip of the check button
0267     m_checkButton->setToolTip(i18n("Click this button to get the next question."));
0268 
0269     numer_edit->setEnabled(false);
0270     deno_edit->setEnabled(false);
0271     integer_edit->setEnabled(false);
0272     m_skipButton->setEnabled(false);
0273 
0274     // an empty numerator field will be interpreted as 0
0275     if (numer_edit->text().isEmpty() == true)
0276         numer_edit->setText(QStringLiteral("0"));
0277     int int_numerator = numer_edit->text().toInt();
0278 
0279     // an empty denominator field will be interpreted as 1
0280     if (deno_edit->text().isEmpty() == true)
0281         deno_edit->setText(QStringLiteral("1"));
0282     int int_denominator = deno_edit->text().toInt();
0283 
0284     // get the par (integer) value in case mixed input is enabled
0285     int int_mixed = 0;
0286     if (m_answerMixed) {
0287         // an empty par (integer) field  will be interpreted as 0
0288         if (integer_edit->text().isEmpty() == true)
0289             integer_edit->setText(QStringLiteral("0"));
0290         int_mixed = integer_edit->text().toInt();
0291     }
0292 
0293     // get the solution for the current task
0294     solution = current_task.solve();
0295 
0296     // calculate the combined numerator and preserve prefix sign
0297     int tmp_num = qAbs(int_mixed * int_denominator) + qAbs(int_numerator);
0298     if (int_mixed < 0)
0299         tmp_num *= -1;
0300     if (int_numerator < 0)
0301         tmp_num *= -1;
0302 
0303     /* store the entered result to check it, but without reducing it */
0304     entered_result.setNumerator(tmp_num, false);
0305     entered_result.setDenominator(int_denominator, false);
0306 
0307     /* check whether we expect the result in reduced form
0308      * if not, we reduce it for the user
0309      *
0310      * we also have to update some temp variables to be reduced
0311      *
0312      * we don't recalculate the mixed part, because this should not change
0313      * by reducing it */
0314     if (!m_reducedForm) {
0315         entered_result.reduce();
0316         int_numerator = entered_result.numerator() % entered_result.denominator();
0317         int_denominator = entered_result.denominator();
0318 
0319     }
0320 
0321     /* compare entered result and solution ratio
0322      * if they are equal it still might be that the user didn't entered a mixed result */
0323     if (!(entered_result == solution))
0324         wrong = true;
0325 
0326     /* we do not allow entering a denominator of 0 (division by zero!) */
0327     if (deno_edit->text().toInt() == 0)
0328         wrong = true;
0329 
0330     /* now we have to check if the solution was entered as a mixed number if required
0331      *
0332      * we only have to do that in case the answer is not already marked as wrong
0333      *
0334      * fortunately, we don't have to care about the prefix sign, because that is already
0335      * handled above */
0336     if (m_answerMixed == true && wrong == false) {
0337         int int_solution_mixed = qAbs(int (solution.numerator() / solution.denominator()));
0338         int int_solution_numerator = qAbs(solution.numerator() % solution.denominator());
0339 
0340         if (!(int_solution_mixed == qAbs(int_mixed) && int_solution_numerator == qAbs(int_numerator)))
0341             wrong = true;
0342     }
0343 
0344     // in case the user entered the wrong result, try to give some hints
0345     if (wrong == true) {
0346         // emit the signal for wrong
0347         Q_EMIT signalTaskSolvedWrong();
0348         m_resultWidget->setResult(solution, 0);
0349 
0350         // if the user entered a 0 for the denominator (division by 0) we have to
0351         // get the 0 directly from the input field, because
0352         // Ratio::setDenominator(0, true) will set the denominator to 1 to ensure
0353         // the Ratio is valid
0354         if (deno_edit->text().toInt() == 0) {
0355             KMessageBox::information(this,
0356                                      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."));
0357         } else {
0358             /* maybe the entered ratio was not reduced */
0359             Ratio tmp_result = Ratio(entered_result);
0360             tmp_result.reduce();
0361             if ((tmp_result == solution) && !(tmp_result == entered_result)) {
0362                 KMessageBox::information(this,
0363                                          i18n("You entered the correct result, but not reduced. This question will be counted as not correctly solved."));
0364             } else if (tmp_result == solution && tmp_result == entered_result) {
0365                 KMessageBox::information(this,
0366                                          i18n("You entered the correct result, but not in the mixed number notation. This question will be counted as not correctly solved."));
0367             }
0368         }
0369     } else {
0370         // emit the signal for correct
0371         Q_EMIT signalTaskSolvedCorrect();
0372         m_resultWidget->setResult(solution, 1);
0373 
0374     }
0375 
0376     m_resultWidget->show();
0377 }
0378 
0379 /** generate the next task and show it to the user */
0380 void TaskView::nextTask()
0381 {
0382     // change the tooltip of the check button
0383     m_checkButton->setToolTip(i18n("Click this button to check your result. The button will not work if you have not entered a result yet."));
0384 
0385     numer_edit->setEnabled(true);
0386     deno_edit->setEnabled(true);
0387     integer_edit->setEnabled(true);
0388     if (m_answerMixed == true)
0389         integer_edit->setEnabled(true);
0390     m_skipButton->setEnabled(true);
0391 
0392     m_resultWidget->setResult(solution, -1);
0393 
0394     /* clear user input */
0395     deno_edit->clear();
0396     numer_edit->clear();
0397     integer_edit->clear();
0398     if (m_answerMixed == true)
0399         integer_edit->setFocus();
0400     else
0401         numer_edit->setFocus();
0402 
0403     /* create a new task */
0404     QApplication::setOverrideCursor(Qt::WaitCursor);  /* show the sand clock */
0405 
0406     current_task.create_task(max_md, nr_ratios, m_addAdd, m_addDiv, m_addMult, m_addSub);
0407     QApplication::restoreOverrideCursor(); /* show the normal cursor */
0408 
0409     // update the task widget
0410     m_taskWidget->setTask((const Task)(current_task));
0411 }
0412 
0413 /* ------ private slots ------ */
0414 
0415 void TaskView::integerReturnPressed()
0416 {
0417     numer_edit->setFocus();
0418 }
0419 
0420 void TaskView::numeratorReturnPressed()
0421 {
0422     deno_edit->setFocus();
0423 }
0424 
0425 void TaskView::denominatorReturnPressed()
0426 {
0427     slotCheckButtonClicked();
0428 }
0429 
0430 void TaskView::slotCheckButtonClicked()
0431 {
0432     if (m_currentState == _CHECK_TASK) {
0433         // if nothing has been entered by the user, we don't check the result yet
0434         if (numer_edit->text().isEmpty() == true && deno_edit->text().isEmpty() == true && integer_edit->text().isEmpty() == true)
0435             return;
0436         m_currentState = _NEXT_TASK;
0437         m_checkButton->setText(i18n("N&ext"));
0438         (void) showResult();
0439     } else {
0440         m_currentState = _CHECK_TASK;
0441         m_checkButton->setText(i18n("&Check"));
0442         (void) nextTask();
0443     }
0444 }
0445 
0446 void TaskView::slotSkipButtonClicked()
0447 {
0448     forceNewTask();
0449 }
0450 
0451 /* ------ protected events ------ */
0452 void TaskView::showEvent(QShowEvent *)
0453 {
0454 
0455     // that the user can start typing without moving the focus
0456     if (m_answerMixed == true)
0457         integer_edit->setFocus();
0458     else
0459         numer_edit->setFocus();
0460 
0461 }