basic training functionality - all question types

This commit is contained in:
jorenchik
2024-10-05 16:51:16 +03:00
parent c0d54eb476
commit 14ee1e91dc
5 changed files with 437 additions and 212 deletions

View File

@@ -353,7 +353,6 @@ void loadMdem() {
questions = parseRes.value;
makePages();
SwitchPage(0);
setQuestions(questions);
std::smatch matches;
auto filename = std::regex_search(currentPath, matches, lastPathElementExp);
deckListLabel->setText(
@@ -553,9 +552,11 @@ int main(int argc, char *argv[]) {
practice,
&QToolButton::clicked,
[](bool checked) {
trainWindow->show();
trainWindow->resize(600, 300);
});
trainWindow->show();
trainWindow->resize(600, 300);
setQuestions(questions);
}
);
window.setCentralWidget(hSplitter);
window.show();

View File

@@ -2,9 +2,15 @@
#include <QWidget>
#include <QToolButton>
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <format>
#include <cstdlib>
#include <exception>
#include <format>
#include <qabstractitemview.h>
#include <qboxlayout.h>
#include <qcoreevent.h>
#include <qlabel.h>
#include <qlayoutitem.h>
#include <qlistview.h>
@@ -20,10 +26,18 @@
#include <QDropEvent>
#include <QDrag>
#include <iostream>
#include <QStyledItemDelegate>
#include <QPainter>
#include "trainWindow.h"
#include "parser.h"
enum ElementState {
NEUTRAL = 0,
CORRECT,
INCORRECT
};
const auto listStyle =
"QListView {"
" background-color: white;"
@@ -35,15 +49,48 @@ const auto listStyle =
"QListView::item {"
" color: black;"
" padding: 5px;"
" border: 1px solid gray;"
" background-color: white;"
" background-color: none;"
"}"
"QListView::item:selected {"
" background-color: transparent;"
" padding-left: 0px;"
" margin-left: 0px;"
"}"
"QListView::item:hover {"
" background-color: lightyellow;"
" padding-left: 0px;"
" margin-left: 0px;"
"}";
class CustomItemDelegate : public QStyledItemDelegate {
public:
CustomItemDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}
// Override the paint method to apply custom styling
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override {
painter->save();
Qt::GlobalColor color;
auto colorIdx = index.data(Qt::UserRole + 1).toInt() ;
switch (colorIdx) {
case NEUTRAL:
color = Qt::lightGray;
break;
case INCORRECT:
color = Qt::red;
break;
case CORRECT:
color = Qt::green;
break;
}
painter->setOpacity(0.5);
if (color) {
painter->fillRect(option.rect, color);
}
painter->restore();
QStyledItemDelegate::paint(painter, option, index);
}
};
class MultiChoiceListView : public QListView
{
Q_OBJECT
@@ -54,6 +101,7 @@ public:
setAcceptDrops(true);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setFocusPolicy(Qt::NoFocus);
setSelectionMode(QAbstractItemView::NoSelection);
}
};
@@ -65,9 +113,8 @@ public:
explicit OrderListView(QWidget *parent = nullptr) : QListView(parent) {
setStyleSheet(listStyle);
setAcceptDrops(true);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setFocusPolicy(Qt::NoFocus);
setEditTriggers(QAbstractItemView::NoEditTriggers);
setDragDropMode(QAbstractItemView::InternalMove);
}
@@ -131,10 +178,21 @@ protected:
#include "trainWindow.moc"
struct GroupView {
QWidget widget;
QVBoxLayout vWidget;
QLabel label;
QScrollArea itemScroll;
QStandardItemModel itemModel;
MoveListView itemList;
};
// Main components
QMainWindow *trainWindow;
QWidget *trainWidget;
QVBoxLayout *vTrainWidget;
CustomItemDelegate *itemDelegate;
// Question
QWidget *questionBox;
@@ -163,19 +221,312 @@ OrderListView *orderList;
QStandardItemModel *orderModel;
// Group question
QStandardItemModel *itemModel;
QListView *groupItemList;
QWidget *wGroupQuestion;
QVBoxLayout *vGroups;
QStandardItemModel *groupItemModel;
QListView *groupItemList;
QWidget *wGroupQuestion;
QVBoxLayout *vGroups;
std::vector<QStandardItemModel*> groupModels;
std::default_random_engine rng;
// Questions & State
std::vector<Question*> trainQuestions = std::vector<Question*>();
int32_t currentQuestionIndex = -1;
std::vector<GroupView*> groupViews;
QStandardItem *makeItem(std::string content, bool isCheckable) {
auto *item = new QStandardItem();
item->setText(QString::fromStdString(content));
item->setCheckable(isCheckable);
std::default_random_engine rng;
std::vector<QStandardItem*> itemPool;
/*QStandardItem *makeItem(std::string content, bool isCheckable) {*/
/* auto *item = new QStandardItem(); */
/* item->setText(QString::fromStdString(content));*/
/* item->setCheckable(isCheckable);*/
/* return item;*/
/*};*/
#define ITEM_POOL_CHUNK 200
QStandardItem* acquireItem() {
if (itemPool.size() <= 0) {
for (int i = 0; i < ITEM_POOL_CHUNK; ++i) {
itemPool.push_back(new QStandardItem);
}
}
auto item = itemPool.back();
itemPool.pop_back();
return item;
};
}
void releaseItem(QStandardItem** item) {
itemPool.push_back(*item);
(*item) = nullptr;
}
void releaseAllItems() {
std::cout <<
std::format("Starting release, currently in the pool: {}", itemPool.size()) <<
std::endl;
auto releaseAllInModel = [](QStandardItemModel *model) {
auto itemCount = model->rowCount();
for (int i = 0; i < itemCount; ++i) {
auto item = model->item(0);
model->takeRow(item->row());
releaseItem(&item);
}
};
releaseAllInModel(multiChoiceModel);
releaseAllInModel(groupItemModel);
releaseAllInModel(orderModel);
for (auto groupView: groupViews) {
releaseAllInModel(&groupView->itemModel);
}
std::cout << std::format("All items released, currently in the pool: {}", itemPool.size()) << std::endl;
}
void hideQuestionElements() {
lQuestionText->hide();
answerText->hide();
orderList->hide();
multiChoiceList->hide();
wGroupQuestion->hide();
}
void setupAnswerQuestion(MultiElementQuestion *question) {
lQuestionText->setText(
QString::fromStdString(question->QuestionText)
);
lQuestionText->show();
auto ss = std::stringstream();
for (auto answerEl: question->Choices) {
ss << std::format("- {}", answerEl.Answer) << std::endl;
}
answerText->setText(
QString::fromStdString(ss.str())
);
if (answerText->isVisible()) {
answerText->hide();
}
QObject::connect(
btnTriggerAnswer,
&QToolButton::clicked,
[](bool checked) {
answerText->show();
btnTriggerAnswer->hide();
}
);
btnTriggerAnswer->show();
}
void setupOrderQuestion(MultiElementQuestion *question) {
lQuestionText->setText(
QString::fromStdString(question->QuestionText)
);
lQuestionText->show();
orderModel->clear();
auto shuffledAnswers = question->Choices;
std::shuffle(shuffledAnswers.begin(), shuffledAnswers.end(), rng);
for (auto answerEl: shuffledAnswers) {
auto *item = acquireItem();
item->setData(NEUTRAL, Qt::UserRole + 1);
item->setData(QVariant(), Qt::CheckStateRole);
item->setCheckable(false);
item->setText(QString::fromStdString(answerEl.Answer));
orderModel->appendRow(item);
}
orderList->show();
QObject::connect(
btnTriggerAnswer,
&QToolButton::clicked,
[question](bool checked) {
for (int i = 0; i < orderModel->rowCount(); ++i) {
auto item = orderModel->item(i, 0);
auto text = item->text();
auto isCorrect = question->Choices[i].Answer.compare(text.toStdString()) == 0;
if (isCorrect) {
item->setData(CORRECT, Qt::UserRole + 1);
} else {
item->setData(INCORRECT, Qt::UserRole + 1);
}
orderList->update();
}
btnTriggerAnswer->hide();
}
);
btnTriggerAnswer->show();
}
void setupMultiChoiceQuestion(MultiElementQuestion *question) {
lQuestionText->setText(
QString::fromStdString(question->QuestionText)
);
lQuestionText->show();
multiChoiceModel->clear();
for (auto answerEl: question->Choices) {
auto *item = acquireItem();
item->setText(QString::fromStdString(answerEl.Answer));
item->setData(NEUTRAL, Qt::UserRole + 1);
item->setCheckable(true);
item->setCheckState(Qt::CheckState::Unchecked);
multiChoiceModel->appendRow(item);
}
multiChoiceList->show();
QObject::connect(
btnTriggerAnswer,
&QToolButton::clicked,
[question](bool checked) {
for (int i = 0; i < multiChoiceModel->rowCount(); ++i) {
auto item = multiChoiceModel->item(i, 0);
auto isCorrect = question->Choices[i].IsCorrect == (item->checkState() == Qt::Checked);
if (isCorrect) {
item->setData(CORRECT, Qt::UserRole + 1);
} else {
item->setData(INCORRECT, Qt::UserRole + 1);
}
multiChoiceList->update();
}
btnTriggerAnswer->hide();
}
);
btnTriggerAnswer->show();
}
void setupGroupQuestion(GroupQuestion *question) {
auto groupSpacer = new QSpacerItem(
50, 50,
QSizePolicy::Minimum,
QSizePolicy::Expanding
);
lQuestionText->setText(
QString::fromStdString(question->QuestionText)
);
lQuestionText->show();
wGroupQuestion->show();
for (auto group: question->Groups) {
for (auto itemText: group.elements) {
auto *qItem = acquireItem();
qItem->setData(NEUTRAL, Qt::UserRole + 1);
qItem->setCheckable(false);
qItem->setData(QVariant(), Qt::CheckStateRole);
qItem->setText(QString::fromStdString(itemText));
groupItemModel->appendRow(qItem);
}
}
groupItemList->update();
auto makeGroup = []() {
auto groupView = new GroupView;
vGroups->addWidget(&groupView->label);
vGroups->addWidget(&groupView->itemList);
groupView->itemList.setModel(&groupView->itemModel);
groupView->itemList.setMaximumHeight(100);
groupView->itemList.setItemDelegate(itemDelegate);
/*groupView->itemScroll.setWidget(&groupView->itemScroll);*/
groupView->widget.setLayout(&groupView->vWidget);
groupView->widget.layout()->addWidget(&groupView->label);
groupView->widget.layout()->addWidget(&groupView->itemList);
return groupView;
};
for (int k = 0; k < groupViews.size(); k++) {
groupViews[k]->widget.hide();
}
for (int i = 0; i < question->Groups.size(); i++) {
GroupView *groupView;
if (i < groupViews.size()) {
groupView = groupViews[i];
} else {
groupView = makeGroup();
groupViews.push_back(groupView);
}
groupView->label.setText(
QString::fromStdString(question->Groups[i].name)
);
vGroups->addWidget(&groupView->widget);
groupView->widget.show();
}
vGroups->addItem(groupSpacer);
QObject::connect(
btnTriggerAnswer,
&QToolButton::clicked,
[question](bool checked) {
for (int i = 0; i < groupItemModel->rowCount(); ++i) {
auto item = groupItemModel->item(i, 0);
item->setData(INCORRECT, Qt::UserRole + 1);
}
for (int i = 0; i < groupViews.size(); i++) {
auto groupView = groupViews[i];
auto group = question->Groups[i];
for (int j = 0; j < groupView->itemModel.rowCount(); ++j) {
auto item = groupView->itemModel.item(j, 0);
auto itemText = item->text().toStdString();
bool found = false;
for (int k = 0; k < group.elements.size(); ++k) {
if (group.elements[k].compare(itemText) == 0) {
found = true;
break;
}
}
if (found) {
item->setData(CORRECT, Qt::UserRole + 1);
} else {
item->setData(INCORRECT, Qt::UserRole + 1);
}
}
groupView->itemList.update();
}
btnTriggerAnswer->hide();
}
);
btnTriggerAnswer->show();
}
void setupQuestion(Question *question) {
hideQuestionElements();
releaseAllItems();
QObject::disconnect(btnTriggerAnswer, 0, 0, 0);
if (auto *question = dynamic_cast<MultiElementQuestion*>(trainQuestions[currentQuestionIndex])) {
switch (question->type) {
case MultiElementType::Order:
setupOrderQuestion(question);
break;
case MultiElementType::MultiChoice:
setupMultiChoiceQuestion(question);
break;
case MultiElementType::Regular:
setupAnswerQuestion(question);
break;
}
} else if (auto *question = dynamic_cast<GroupQuestion*>(trainQuestions[currentQuestionIndex])) {
setupGroupQuestion(question);
}
}
void updatePaginationVisibility() {
if (currentQuestionIndex == 0) {
btnPrev->hide();
} else {
btnPrev->show();
}
if (currentQuestionIndex == trainQuestions.size() - 1) {
btnNext->hide();
} else {
btnNext->show();
}
}
void setQuestions(std::vector<Question*> questions) {
trainQuestions = questions;
if (questions.size() <= 0) {
return;
}
currentQuestionIndex = 0;
updatePaginationVisibility();
setupQuestion(trainQuestions[currentQuestionIndex]);
}
void initTrainWindow() {
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
@@ -191,6 +542,8 @@ void initTrainWindow() {
vTrainWidget->setAlignment(Qt::AlignCenter);
trainWidget->setObjectName("answer-question-widget");
itemDelegate = new CustomItemDelegate(multiChoiceList);
{ // Make the question box.
questionBox = new QWidget();
vQuestionBox = new QVBoxLayout();
@@ -220,6 +573,7 @@ void initTrainWindow() {
multiChoiceList->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
questionBox->layout()->addWidget(multiChoiceList);
multiChoiceList->hide();
multiChoiceList->setItemDelegate(itemDelegate);
}
{ // Make answer text.
@@ -241,6 +595,7 @@ void initTrainWindow() {
orderList = new OrderListView();
orderList->setModel(orderModel);
orderList->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
orderList->setItemDelegate(itemDelegate);
// Connect to handle the drop event properly
QObject::connect(
@@ -276,9 +631,24 @@ void initTrainWindow() {
vButtonBox->addWidget(actionButtons);
actionButtons->setLayout(hButtons);
btnPrev->setText("Previous");
btnTriggerAnswer->setText("Show answer");
btnNext->setText("Next");
QObject::connect(btnPrev, &QToolButton::clicked, []() {
currentQuestionIndex--;
if (currentQuestionIndex > -1) {
setupQuestion(trainQuestions[currentQuestionIndex]);
}
updatePaginationVisibility();
});
btnTriggerAnswer->setText("Show answer");
btnNext->setText("Next");
QObject::connect(btnNext, &QToolButton::clicked, []() {
currentQuestionIndex++;
if (currentQuestionIndex < trainQuestions.size()) {
setupQuestion(trainQuestions[currentQuestionIndex]);
}
updatePaginationVisibility();
});
questionBox->setObjectName("question-box");
actionButtons->setStyleSheet(QString(
"QToolButton {"
@@ -301,9 +671,10 @@ void initTrainWindow() {
auto itemScroll = new QScrollArea();
auto vItems = new QVBoxLayout();
itemScroll->setLayout(vItems);
itemModel = new QStandardItemModel();
groupItemModel = new QStandardItemModel();
groupItemList = new MoveListView();
groupItemList->setModel(itemModel);
groupItemList->setItemDelegate(itemDelegate);
groupItemList->setModel(groupItemModel);
vItems->addWidget(groupItemList);
hGroupQuestion->addWidget(itemScroll);
@@ -316,6 +687,7 @@ void initTrainWindow() {
wGroupContainer->setLayout(vGroups);
hGroupQuestion->addWidget(groupScroll);
wGroupQuestion->setStyleSheet("padding: 0; margin: 0;");
wGroupQuestion->hide();
questionBox->layout()->addWidget(wGroupQuestion);
@@ -325,165 +697,3 @@ void initTrainWindow() {
vTrainWidget->addWidget(actionButtons);
}
std::vector<Question*> trainQuestions = std::vector<Question*>();
int32_t currentQuestionIndex = -1;
void hideQuestionElements() {
lQuestionText->hide();
answerText->hide();
orderList->hide();
multiChoiceList->hide();
wGroupQuestion->hide();
}
void setupAnswerQuestion(MultiElementQuestion *question) {
hideQuestionElements();
lQuestionText->setText(
QString::fromStdString(question->QuestionText)
);
lQuestionText->show();
auto ss = std::stringstream();
for (auto answerEl: question->Choices) {
ss << std::format("- {}", answerEl.Answer) << std::endl;
}
answerText->setText(
QString::fromStdString(ss.str())
);
if (answerText->isVisible()) {
answerText->hide();
}
auto showAnswer = [](bool checked) {
answerText->show();
btnTriggerAnswer->hide();
QObject::disconnect(btnTriggerAnswer, 0, 0, 0);
};
QObject::connect(
btnTriggerAnswer,
&QToolButton::clicked,
showAnswer
);
btnTriggerAnswer->show();
}
void setupOrderQuestion(MultiElementQuestion *question) {
hideQuestionElements();
lQuestionText->setText(
QString::fromStdString(question->QuestionText)
);
lQuestionText->show();
orderModel->clear();
auto shuffledAnswers = question->Choices;
std::shuffle(shuffledAnswers.begin(), shuffledAnswers.end(), rng);
for (auto answerEl: shuffledAnswers) {
orderModel->appendRow(makeItem(answerEl.Answer, false));
}
orderList->show();
auto showAnswer = [](bool checked) {
QObject::disconnect(btnTriggerAnswer, 0, 0, 0);
};
QObject::connect(
btnTriggerAnswer,
&QToolButton::clicked,
showAnswer
);
btnTriggerAnswer->show();
}
void setupMultiChoiceQuestion(MultiElementQuestion *question) {
hideQuestionElements();
lQuestionText->setText(
QString::fromStdString(question->QuestionText)
);
lQuestionText->show();
multiChoiceModel->clear();
for (auto answerEl: question->Choices) {
multiChoiceModel->appendRow(makeItem(answerEl.Answer, true));
}
multiChoiceList->show();
auto showAnswer = [](bool checked) {
QObject::disconnect(btnTriggerAnswer, 0, 0, 0);
};
QObject::connect(
btnTriggerAnswer,
&QToolButton::clicked,
showAnswer
);
btnTriggerAnswer->show();
}
void setupGroupQuestion(GroupQuestion *question) {
auto groupSpacer = new QSpacerItem(
50, 50,
QSizePolicy::Minimum,
QSizePolicy::Expanding
);
hideQuestionElements();
lQuestionText->setText(
QString::fromStdString(question->QuestionText)
);
lQuestionText->show();
wGroupQuestion->show();
for (auto group: question->Groups) {
for (auto item: group.elements) {
itemModel->appendRow(makeItem(item, false));
}
}
auto makeAGroup = [](std::string groupName) {
auto groupLabel = new QLabel(QString::fromStdString(groupName));
auto scrollArea = new QScrollArea();
auto groupModel = new QStandardItemModel();
auto groupList = new MoveListView();
vGroups->addWidget(groupLabel);
vGroups->addWidget(groupList);
groupList->setModel(groupModel);
groupList->setMaximumHeight(100);
};
for (auto group: question->Groups) {
makeAGroup(group.name);
}
vGroups->addItem(groupSpacer);
auto showAnswer = [](bool checked) {
QObject::disconnect(btnTriggerAnswer, 0, 0, 0);
};
QObject::connect(
btnTriggerAnswer,
&QToolButton::clicked,
showAnswer
);
btnTriggerAnswer->show();
}
void setQuestions(std::vector<Question*> questions) {
trainQuestions = questions;
if (questions.size() <= 0) {
return;
}
// setupFirstQuestion
currentQuestionIndex = 0;
if (auto *question = dynamic_cast<MultiElementQuestion*>(trainQuestions[currentQuestionIndex])) {
switch (question->type) {
case MultiElementType::Order:
setupOrderQuestion(question);
break;
case MultiElementType::MultiChoice:
setupMultiChoiceQuestion(question);
break;
case MultiElementType::Regular:
setupAnswerQuestion(question);
break;
}
} else if (auto *question = dynamic_cast<GroupQuestion*>(trainQuestions[currentQuestionIndex])) {
setupGroupQuestion(question);
}
}