File indexing completed on 2024-04-14 03:40:26
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 } 0405 0406 #include "moc_ExerciseMixedNumbers.cpp"