diff --git a/memorybase/all_types.mdem b/memorybase/all_types.mdem index a3e72ea..3347abc 100644 --- a/memorybase/all_types.mdem +++ b/memorybase/all_types.mdem @@ -1,29 +1,9 @@ - -- What is the capital of Latvia? > - - Rīga - -- [cap_est] What is the capital of Estonia? > - - Tallin - -- What countries reside in Europe? > - + Latvia - - Peru - + Poland - - China - - Arrange these events in the order they occurred > - -^ The Fall of the Roman Empire (476 AD) - -^ The Renaissance (14th\-17th century) - -^ The Industrial Revolution (18th\-19th century) + -^ The Fall of the Roman Empire + -^ The Renaissance + -^ The Industrial Revolution -- [scient_method_order] Place the following steps of the scientific method in the correct order > - - Ask a Question - - Form a Hypothesis - -^ Conduct an Experiment - - Analyze Data - - -- Match Planets to Their Characteristics > +- [planet_characteristics] Match Planets to Their Characteristics > - Earth: - Contains Life - Mars: @@ -40,3 +20,26 @@ - Neptune: - Farthest from the Sun - Has Rings + +- What countries reside in Europe? > + + Latvia + - Peru + + Poland + - China + + + +- What is the capital of Latvia? > + - Rīga + +- [cap_est] What is the capital of Estonia? > + - Tallin + + + +- [scient_method_order] Place the following steps of the scientific method in the correct order > + - Ask a Question + - Form a Hypothesis + -^ Conduct an Experiment + - Analyze Data + diff --git a/memorybase/another_mfile.mdem b/memorybase/large/another_mfile.mdem similarity index 100% rename from memorybase/another_mfile.mdem rename to memorybase/large/another_mfile.mdem diff --git a/src/cpp/qtapp/main.cpp b/src/cpp/qtapp/main.cpp index 7fca3e5..bfa8655 100644 --- a/src/cpp/qtapp/main.cpp +++ b/src/cpp/qtapp/main.cpp @@ -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(); diff --git a/src/cpp/qtapp/trainWindow.cpp b/src/cpp/qtapp/trainWindow.cpp index bc26405..77cbf89 100644 --- a/src/cpp/qtapp/trainWindow.cpp +++ b/src/cpp/qtapp/trainWindow.cpp @@ -2,9 +2,15 @@ #include #include #include +#include #include #include +#include +#include +#include +#include #include +#include #include #include #include @@ -20,10 +26,18 @@ #include #include #include +#include +#include #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 groupModels; -std::default_random_engine rng; +// Questions & State +std::vector trainQuestions = std::vector(); +int32_t currentQuestionIndex = -1; +std::vector 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 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(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(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 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 trainQuestions = std::vector(); -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 questions) { - trainQuestions = questions; - if (questions.size() <= 0) { - return; - } - - // setupFirstQuestion - currentQuestionIndex = 0; - if (auto *question = dynamic_cast(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(trainQuestions[currentQuestionIndex])) { - setupGroupQuestion(question); - } -} diff --git a/tasks.md b/tasks.md index 9caf4d6..f33241e 100644 --- a/tasks.md +++ b/tasks.md @@ -1,3 +1,18 @@ +- [x] Augment the lexer for Order and match question; +- [x] Parse the questions accordingly; +- [x] Escape character; +- [ ] Implement order question checking; +- [ ] Implement multichoice question checking; +- [ ] Implement group question checking; +- [ ] Ensure no duplicates in questions where it matters. + We will need this for checking the answers; +- [ ] +- [ ] +- [ ] +- [ ] + + + ## 1-N Answer question - kfoewf > @@ -30,8 +45,4 @@ - fewjpfe - fioewf -- [ ] Augment the lexer for Order and match question; -- [ ] Parse the questions accordingly; -- [ ] Escape character; -