File indexing completed on 2023-09-24 03:52:12
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 } 0441 0442 #include "moc_ExerciseConvert.cpp"