File indexing completed on 2024-04-21 03:41:50
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 } 0462 0463 #include "moc_TaskView.cpp"