mirror of
https://github.com/jorenchik/mdemory.git
synced 2026-03-22 00:26:21 +00:00
multiple improvements: multi-line buffering, editing interface
This commit is contained in:
@@ -1,16 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <QSettings>
|
||||
|
||||
#include "parser.h"
|
||||
|
||||
void update(bool isChanged = false);
|
||||
|
||||
void saveMdem();
|
||||
|
||||
struct MdemBuffer {
|
||||
std::vector<Question*> questions = std::vector<Question*>();
|
||||
time_t trainedAt = 0;
|
||||
bool error = false;
|
||||
bool isModified = false;
|
||||
};
|
||||
|
||||
void updateMdemInfo(std::string filename = "", bool isChanged = true);
|
||||
|
||||
extern QSettings *settings;
|
||||
|
||||
#define SETTING_TIMEZONE "timezone"
|
||||
#define SETTING_CHARACTER_WRAP "characterWrap"
|
||||
#define SETTING_NOT_REMEMBERED "notRemembered"
|
||||
#define SETTING_HARD "hard"
|
||||
#define SETTING_MEDIUM "medium"
|
||||
#define SETTING_EASY "easy"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <qabstractbutton.h>
|
||||
#include <qboxlayout.h>
|
||||
#include <qcombobox.h>
|
||||
@@ -24,7 +25,6 @@
|
||||
|
||||
#include <Qsci/qsciscintilla.h>
|
||||
#include <Qsci/qscilexercpp.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QMainWindow>
|
||||
#include <QVBoxLayout>
|
||||
@@ -53,6 +53,7 @@
|
||||
#include <QLineEdit>
|
||||
#include <QComboBox>
|
||||
#include <QSpinBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QCheckBox>
|
||||
#include <QPushButton>
|
||||
#include <QStandardPaths>
|
||||
@@ -69,7 +70,6 @@
|
||||
#define DISTANCE 2
|
||||
#define PER_PAGE 8
|
||||
#define MDEM_BACKGROUND "#F7F7F7"
|
||||
#define WRAP_WIDTH 50
|
||||
|
||||
struct Mdem {
|
||||
QWidget wMdem;
|
||||
@@ -107,16 +107,14 @@ QFileSystemModel *model;
|
||||
QTreeView *mdemList;
|
||||
QLabel *membaseLabel;
|
||||
|
||||
// @Improvement: make it into a hashmap with different buffers;
|
||||
MdemBuffer *mdemBuffer;
|
||||
bool isBufferModified = false;
|
||||
std::map<std::string, MdemBuffer*> buffers;
|
||||
MdemBuffer *currentMdemBuffer;
|
||||
|
||||
// Mdem scroll list.
|
||||
QList<Mdem*> mdems = QList<Mdem*>();
|
||||
QVBoxLayout *hMdemScroll;
|
||||
QSpacerItem *mdemSpacer;
|
||||
std::vector<ErrorView*> errorPool;
|
||||
std::vector<ErrorView*> errorViews;
|
||||
ErrorView* errorView;
|
||||
|
||||
// Editor
|
||||
Mdem* editMdem;
|
||||
@@ -162,8 +160,13 @@ void showBacklabels(Mdem *mdem) {
|
||||
std::string outputMdem(std::vector<Question*> questions, time_t time = 0) {
|
||||
std::stringstream ss;
|
||||
|
||||
int wrap_width = 80;
|
||||
if (settings->contains(SETTING_CHARACTER_WRAP)) {
|
||||
wrap_width = settings->value(SETTING_CHARACTER_WRAP).toInt();
|
||||
}
|
||||
|
||||
if (time > 0) {
|
||||
auto timezoneOffset = settings->value("timezone").toInt();
|
||||
auto timezoneOffset = settings->value(SETTING_TIMEZONE).toInt();
|
||||
/*time = time + 3600 * timezoneOffset;*/
|
||||
std::tm* tm = std::localtime(&time);
|
||||
char buffer[100];
|
||||
@@ -182,7 +185,7 @@ std::string outputMdem(std::vector<Question*> questions, time_t time = 0) {
|
||||
std::format("-{}{} >\n",
|
||||
cooldownPart,
|
||||
" " + escapeText(question->QuestionText)),
|
||||
WRAP_WIDTH
|
||||
wrap_width
|
||||
);
|
||||
if (MultiElementQuestion* mw = dynamic_cast<MultiElementQuestion*>(question)) {
|
||||
for (auto choice: mw->Choices) {
|
||||
@@ -204,7 +207,7 @@ std::string outputMdem(std::vector<Question*> questions, time_t time = 0) {
|
||||
orderModifier,
|
||||
escapeText(choice.Answer)
|
||||
)
|
||||
, WRAP_WIDTH);
|
||||
, wrap_width);
|
||||
}
|
||||
} else if (GroupQuestion* gq = dynamic_cast<GroupQuestion*>(question)) {
|
||||
for (auto group: gq->Groups) {
|
||||
@@ -213,14 +216,14 @@ std::string outputMdem(std::vector<Question*> questions, time_t time = 0) {
|
||||
"\t- {}:\n",
|
||||
escapeText(group.name)
|
||||
)
|
||||
, WRAP_WIDTH);
|
||||
, wrap_width);
|
||||
for (auto element: group.elements) {
|
||||
ss << wrapText(
|
||||
std::format(
|
||||
"\t\t- {}\n",
|
||||
escapeText(element)
|
||||
)
|
||||
, WRAP_WIDTH);
|
||||
, wrap_width);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -230,7 +233,7 @@ std::string outputMdem(std::vector<Question*> questions, time_t time = 0) {
|
||||
|
||||
void makePages() {
|
||||
pages.clear();
|
||||
auto len = mdemBuffer->questions.size();
|
||||
auto len = currentMdemBuffer->questions.size();
|
||||
auto pageAmount = len / PER_PAGE;
|
||||
if (len % PER_PAGE != 0) {
|
||||
pageAmount += 1;
|
||||
@@ -238,8 +241,8 @@ void makePages() {
|
||||
for (int i = 0; i < pageAmount; i++) {
|
||||
auto startingIndex = PER_PAGE * i ;
|
||||
auto amount = PER_PAGE;
|
||||
if (i == mdemBuffer->questions.size() / PER_PAGE) {
|
||||
amount = mdemBuffer->questions.size() % PER_PAGE;
|
||||
if (i == currentMdemBuffer->questions.size() / PER_PAGE) {
|
||||
amount = currentMdemBuffer->questions.size() % PER_PAGE;
|
||||
}
|
||||
pages.push_back(Page{startingIndex, startingIndex + amount});
|
||||
}
|
||||
@@ -316,15 +319,15 @@ std::string getFilename(std::string path) {
|
||||
}
|
||||
|
||||
void updateMdemInfo(std::string filename, bool isChanged) {
|
||||
isBufferModified = isBufferModified;
|
||||
currentMdemBuffer->isModified = isChanged;
|
||||
if (filename.length() > 0) {
|
||||
std::stringstream ss;
|
||||
ss << std::format("mdem: {}", filename);
|
||||
if (isChanged) {
|
||||
ss << "*";
|
||||
}
|
||||
if (mdemBuffer->trainedAt > 0) {
|
||||
std::tm* tm = std::localtime(&mdemBuffer->trainedAt);
|
||||
if (currentMdemBuffer->trainedAt > 0) {
|
||||
std::tm* tm = std::localtime(¤tMdemBuffer->trainedAt);
|
||||
char buffer[100];
|
||||
std::strftime(buffer, sizeof(buffer), "%d.%m.%Y %H:%M", tm);
|
||||
ss << std::endl << "Last practiced: " << std::string(buffer);
|
||||
@@ -388,9 +391,9 @@ Mdem* makeMdem() {
|
||||
[mdem](bool checked) {
|
||||
if (mdem->question) {
|
||||
Question* deleted = nullptr;
|
||||
for (int i = 0; i < mdemBuffer->questions.size(); ++i) {
|
||||
if (mdemBuffer->questions[i] == mdem->question) {
|
||||
mdemBuffer->questions.erase(mdemBuffer->questions.begin() + i);
|
||||
for (int i = 0; i < currentMdemBuffer->questions.size(); ++i) {
|
||||
if (currentMdemBuffer->questions[i] == mdem->question) {
|
||||
currentMdemBuffer->questions.erase(currentMdemBuffer->questions.begin() + i);
|
||||
delete mdem->question;
|
||||
mdem->question = nullptr;
|
||||
updateMdemInfo(getFilename(currentMdem), true);
|
||||
@@ -417,7 +420,7 @@ Mdem* makeMdem() {
|
||||
|
||||
// Add Back Content
|
||||
for (int i = 0; i < 20; ++i) {
|
||||
// @Fix: back label pooling
|
||||
// @Improve: back label pooling
|
||||
QLabel *elBackText = new QLabel();
|
||||
mdem->hBack.addWidget(elBackText);
|
||||
mdem->backLabels.append(elBackText);
|
||||
@@ -547,7 +550,6 @@ void SwitchPage(int pageIdx) {
|
||||
prevButton->hide();
|
||||
}
|
||||
|
||||
// Handle page slice
|
||||
Page page;
|
||||
if (pages.size() <= 0) {
|
||||
page = Page();
|
||||
@@ -562,67 +564,64 @@ void SwitchPage(int pageIdx) {
|
||||
}
|
||||
|
||||
std::vector<Question*> pageSlice(
|
||||
mdemBuffer->questions.begin() + page.start,
|
||||
mdemBuffer->questions.begin() + page.end
|
||||
currentMdemBuffer->questions.begin() + page.start,
|
||||
currentMdemBuffer->questions.begin() + page.end
|
||||
);
|
||||
CreateMdems(pageSlice);
|
||||
}
|
||||
|
||||
void reloadMdem(std::string path) {
|
||||
if (path == "") {
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorView *makeErrorView() {
|
||||
auto errorView = new ErrorView;
|
||||
errorView->box.setObjectName("error-box");
|
||||
errorView->box.setLayout(&errorView->layout);
|
||||
errorView->box.setMinimumHeight(30);
|
||||
errorView->box.setStyleSheet(
|
||||
QString(
|
||||
"QWidget#error-box {"
|
||||
"border: 1px solid red;"
|
||||
"background: %2;"
|
||||
"}"
|
||||
).arg(MDEM_BACKGROUND)
|
||||
);
|
||||
errorView->layout.addWidget(&errorView->label);
|
||||
errorView->label.setWordWrap(true);
|
||||
return errorView;
|
||||
};
|
||||
auto toRemove = std::vector<std::string>();
|
||||
for (auto it = buffers.begin(); it != buffers.end(); ++it) {
|
||||
auto pair = *it;
|
||||
if (currentMdem.compare(path) != 0 &&
|
||||
(!pair.second->isModified || pair.second->error)) {
|
||||
toRemove.push_back(pair.first);
|
||||
}
|
||||
}
|
||||
for (auto key: toRemove) {
|
||||
buffers.erase(key);
|
||||
}
|
||||
|
||||
ErrorView* acquireError() {
|
||||
if (errorPool.size() <= 0) {
|
||||
for (int i = 0; i < ERROR_POOL_CHUNK; ++i) {
|
||||
auto errorView = makeErrorView();
|
||||
errorPool.push_back(errorView);
|
||||
hMdemScroll->addWidget(&errorView->box);
|
||||
MdemBuffer *buffer;
|
||||
auto filename = getFilename(path);
|
||||
if (currentMdem.compare(path) == 0) {
|
||||
buffer = currentMdemBuffer;
|
||||
currentMdem = path;
|
||||
if (buffers.contains(path)) {
|
||||
buffers.erase(path);
|
||||
}
|
||||
} else if (!buffers.contains(path)) {
|
||||
buffer = new MdemBuffer;
|
||||
buffers[path] = buffer;
|
||||
currentMdemBuffer = buffer;
|
||||
currentMdem = path;
|
||||
} else {
|
||||
buffer = buffers[path];
|
||||
currentMdemBuffer = buffer;
|
||||
makePages();
|
||||
SwitchPage(0);
|
||||
updateMdemInfo(getFilename(filename), buffer->isModified);
|
||||
currentMdem = path;
|
||||
errorView->box.hide();
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto item = errorPool.back();
|
||||
errorPool.pop_back();
|
||||
errorViews.push_back(item);
|
||||
std::cout << std::format("Acquired, current pool size: {}\n", errorPool.size());
|
||||
return item;
|
||||
}
|
||||
|
||||
void releaseError(ErrorView** item) {
|
||||
errorPool.push_back(*item);
|
||||
(*item) = nullptr;
|
||||
std::cout << std::format("Released, current pool size: {}\n", errorPool.size());
|
||||
}
|
||||
|
||||
void reloadMdem() {
|
||||
auto file = std::ifstream(currentMdem);
|
||||
auto file = std::ifstream(path);
|
||||
std::string content;
|
||||
|
||||
// Reset the mdem list.
|
||||
for (auto mdem: mdems) {
|
||||
mdem->wMdem.hide();
|
||||
}
|
||||
for (auto question: mdemBuffer->questions) {
|
||||
for (auto question: currentMdemBuffer->questions) {
|
||||
delete question;
|
||||
}
|
||||
mdemBuffer->questions.clear();
|
||||
|
||||
auto filename = getFilename(currentMdem);
|
||||
currentMdemBuffer->questions.clear();
|
||||
|
||||
if (file) {
|
||||
std::stringstream buffer;
|
||||
@@ -630,28 +629,20 @@ void reloadMdem() {
|
||||
content = buffer.str();
|
||||
auto res = transpile(content, true);
|
||||
|
||||
while (errorViews.size() > 0) {
|
||||
auto errorView = errorViews.back();
|
||||
errorViews.pop_back();
|
||||
errorView->box.hide();
|
||||
releaseError(&errorView);
|
||||
}
|
||||
|
||||
currentMdemBuffer->error = res.error.length() > 0;
|
||||
if (res.error == "") {
|
||||
if (res.value.lastTrainedAt == 0) {
|
||||
mdemBuffer->trainedAt = 0;
|
||||
currentMdemBuffer->trainedAt = 0;
|
||||
} else {
|
||||
auto timezoneOffset = settings->value("timezone").toInt();
|
||||
mdemBuffer->trainedAt = res.value.lastTrainedAt - 3600 * timezoneOffset;
|
||||
currentMdemBuffer->trainedAt = res.value.lastTrainedAt - 3600 * timezoneOffset;
|
||||
}
|
||||
std::cout << std::format("Last trained at: {}", mdemBuffer->trainedAt) << std::endl;
|
||||
std::cout << std::format("Last trained at: {}", currentMdemBuffer->trainedAt) << std::endl;
|
||||
|
||||
mdemBuffer->questions = res.value.questions;
|
||||
makePages();
|
||||
SwitchPage(0);
|
||||
updateMdemInfo(filename, false);
|
||||
currentMdemBuffer->questions = res.value.questions;
|
||||
errorView->box.hide();
|
||||
} else {
|
||||
mdemBuffer->trainedAt = 0;
|
||||
currentMdemBuffer->trainedAt = 0;
|
||||
std::cout << std::format("Compilation error: {}", res.error) << std::endl;
|
||||
|
||||
for (auto question: res.value.questions) {
|
||||
@@ -660,7 +651,6 @@ void reloadMdem() {
|
||||
|
||||
// Show errors.
|
||||
hMdemScroll->removeItem(mdemSpacer);
|
||||
auto errorView = acquireError();
|
||||
errorView->label.setText(
|
||||
QString::fromStdString(
|
||||
std::format(
|
||||
@@ -673,9 +663,13 @@ void reloadMdem() {
|
||||
)
|
||||
);
|
||||
errorView->box.show();
|
||||
|
||||
hMdemScroll->addWidget(&errorView->box);
|
||||
hMdemScroll->addItem(mdemSpacer);
|
||||
}
|
||||
makePages();
|
||||
SwitchPage(0);
|
||||
updateMdemInfo(filename, false);
|
||||
} else {
|
||||
std::cout << std::format("Could not open the file: {}", currentPath) << std::endl;
|
||||
}
|
||||
@@ -703,14 +697,13 @@ void pickDirectory(QString directory) {
|
||||
)
|
||||
));
|
||||
|
||||
currentMdem = "";
|
||||
reloadMdem();
|
||||
reloadMdem("");
|
||||
}
|
||||
|
||||
void setupEditorSave() {
|
||||
auto res = transpile(editor->text().toStdString(), true);
|
||||
if (res.error.length() > 0) {
|
||||
mdemBuffer->trainedAt = 0;
|
||||
currentMdemBuffer->trainedAt = 0;
|
||||
for (auto question: res.value.questions) {
|
||||
delete question;
|
||||
}
|
||||
@@ -730,11 +723,11 @@ void setupEditorSave() {
|
||||
} else if (res.value.questions.size() == 1) {
|
||||
auto oldQuestion = editMdem->question;
|
||||
editMdem->question = res.value.questions[0];
|
||||
for (int i = 0; i < mdemBuffer->questions.size(); ++i) {
|
||||
if (mdemBuffer->questions[i] == oldQuestion) {
|
||||
mdemBuffer->questions.erase(mdemBuffer->questions.begin() + i);
|
||||
for (int i = 0; i < currentMdemBuffer->questions.size(); ++i) {
|
||||
if (currentMdemBuffer->questions[i] == oldQuestion) {
|
||||
currentMdemBuffer->questions.erase(currentMdemBuffer->questions.begin() + i);
|
||||
delete oldQuestion;
|
||||
mdemBuffer->questions.insert(mdemBuffer->questions.begin() + i, res.value.questions[0]);
|
||||
currentMdemBuffer->questions.insert(currentMdemBuffer->questions.begin() + i, res.value.questions[0]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -757,8 +750,8 @@ void setupEditorSave() {
|
||||
"There are no questions in your input."
|
||||
);
|
||||
} else {
|
||||
mdemBuffer->questions.insert(
|
||||
mdemBuffer->questions.begin(),
|
||||
currentMdemBuffer->questions.insert(
|
||||
currentMdemBuffer->questions.begin(),
|
||||
res.value.questions.begin(),
|
||||
res.value.questions.end()
|
||||
);
|
||||
@@ -774,12 +767,12 @@ void setupEditorSave() {
|
||||
void saveMdem() {
|
||||
auto filename = getFilename(currentMdem);
|
||||
std::ofstream out(currentMdem);
|
||||
out << outputMdem(mdemBuffer->questions, mdemBuffer->trainedAt);
|
||||
out << outputMdem(currentMdemBuffer->questions, currentMdemBuffer->trainedAt);
|
||||
updateMdemInfo(getFilename(currentMdem), false);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
mdemBuffer = new MdemBuffer;
|
||||
|
||||
QApplication app(argc, argv);
|
||||
QMainWindow window;
|
||||
|
||||
@@ -825,50 +818,60 @@ int main(int argc, char *argv[]) {
|
||||
auto formLayout = new QFormLayout;
|
||||
|
||||
auto characterWrap = new QSpinBox;
|
||||
characterWrap->setRange(50, 150);
|
||||
formLayout->addRow("Character wrap in code gen:", characterWrap);
|
||||
characterWrap->setRange(30, 150);
|
||||
formLayout->addRow("Character wrap in code gen [30-150]:", characterWrap);
|
||||
|
||||
auto* timezone = new QSpinBox;
|
||||
timezone->setRange(-12, 12);
|
||||
formLayout->addRow("Timezone as number (e.g. +2 as 2):", timezone);
|
||||
|
||||
auto* notRemembered = new QSpinBox;
|
||||
auto* notRemembered = new QDoubleSpinBox;
|
||||
notRemembered->setRange(0, 100);
|
||||
formLayout->addRow("Not remembered:", notRemembered);
|
||||
|
||||
auto* hard = new QSpinBox;
|
||||
auto* hard = new QDoubleSpinBox;
|
||||
hard->setRange(0, 100);
|
||||
formLayout->addRow("Hard:", hard);
|
||||
|
||||
auto* medium = new QSpinBox;
|
||||
auto* medium = new QDoubleSpinBox;
|
||||
medium->setRange(0, 100);
|
||||
formLayout->addRow("Medium:", medium);
|
||||
|
||||
auto* easy = new QSpinBox;
|
||||
auto* easy = new QDoubleSpinBox;
|
||||
easy->setRange(0, 100);
|
||||
formLayout->addRow("Easy:", easy);
|
||||
|
||||
auto* btnSaveSettings = new QPushButton("Save");
|
||||
auto* mainLayout = new QVBoxLayout;
|
||||
|
||||
// TODO: make defaults and validate settings values
|
||||
characterWrap->setValue(settings->value("characterWrap").toInt());
|
||||
timezone->setValue(settings->value("timezone").toInt());
|
||||
notRemembered->setValue(settings->value("notRemembered").toInt());
|
||||
hard->setValue(settings->value("hard").toInt());
|
||||
medium->setValue(settings->value("medium").toInt());
|
||||
easy->setValue(settings->value("easy").toInt());
|
||||
// @Improve: validate setting values
|
||||
characterWrap->setValue(settings->value(SETTING_CHARACTER_WRAP).toInt());
|
||||
timezone->setValue(settings->value(SETTING_TIMEZONE).toInt());
|
||||
notRemembered->setValue(settings->value(SETTING_NOT_REMEMBERED).toDouble());
|
||||
hard->setValue(settings->value(SETTING_HARD).toDouble());
|
||||
medium->setValue(settings->value(SETTING_MEDIUM).toDouble());
|
||||
easy->setValue(settings->value(SETTING_EASY).toDouble());
|
||||
|
||||
auto saveSettings = [characterWrap, timezone, notRemembered, hard, medium, easy]() {
|
||||
settings->setValue(SETTING_CHARACTER_WRAP, characterWrap->value());
|
||||
settings->setValue(SETTING_TIMEZONE, timezone->value());
|
||||
settings->setValue(SETTING_NOT_REMEMBERED, notRemembered->value());
|
||||
settings->setValue(SETTING_HARD, hard->value());
|
||||
settings->setValue(SETTING_MEDIUM, medium->value());
|
||||
settings->setValue(SETTING_EASY, easy->value());
|
||||
};
|
||||
|
||||
QObject::connect(
|
||||
btnSaveSettings,
|
||||
&QPushButton::clicked,
|
||||
[characterWrap, timezone, notRemembered, hard, medium, easy]() {
|
||||
settings->setValue("characterWrap", characterWrap->value());
|
||||
settings->setValue("timezone", timezone->value());
|
||||
settings->setValue("notRemembered", notRemembered->value());
|
||||
settings->setValue("hard", hard->value());
|
||||
settings->setValue("medium", medium->value());
|
||||
settings->setValue("easy", easy->value());
|
||||
[saveSettings]() {
|
||||
saveSettings();
|
||||
}
|
||||
);
|
||||
|
||||
QShortcut* shortcutSave = new QShortcut(QKeySequence("Ctrl+S"), settingsWindow);
|
||||
QObject::connect(shortcutSave, &QShortcut::activated, [saveSettings]() {
|
||||
saveSettings();
|
||||
});
|
||||
|
||||
mainLayout->addLayout(formLayout);
|
||||
@@ -947,8 +950,7 @@ int main(int argc, char *argv[]) {
|
||||
&QTreeView::doubleClicked,
|
||||
[](const QModelIndex &index) {
|
||||
auto fileInfo = model->fileInfo(index);
|
||||
currentMdem = fileInfo.filePath().toStdString();
|
||||
reloadMdem();
|
||||
reloadMdem(fileInfo.filePath().toStdString());
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1020,7 +1022,9 @@ int main(int argc, char *argv[]) {
|
||||
editorWindow->show();
|
||||
editor->setText("");
|
||||
});
|
||||
QObject::connect(load, &QToolButton::clicked, &reloadMdem);
|
||||
QObject::connect(load, &QToolButton::clicked, []() {
|
||||
reloadMdem(currentMdem);
|
||||
});
|
||||
QObject::connect(btnSaveFile, &QToolButton::clicked, []() {
|
||||
saveMdem();
|
||||
});
|
||||
@@ -1031,13 +1035,30 @@ int main(int argc, char *argv[]) {
|
||||
trainWindow->show();
|
||||
trainWindow->resize(600, 300);
|
||||
initiatePractice(
|
||||
mdemBuffer,
|
||||
currentMdemBuffer,
|
||||
static_cast<PracticeAlgorithm>(cbAlgorithm->currentData().toInt())
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
{ // Error.
|
||||
errorView = new ErrorView;
|
||||
errorView->box.setObjectName("error-box");
|
||||
errorView->box.setLayout(&errorView->layout);
|
||||
errorView->box.setMinimumHeight(30);
|
||||
errorView->box.setStyleSheet(
|
||||
QString(
|
||||
"QWidget#error-box {"
|
||||
"border: 1px solid red;"
|
||||
"background: %2;"
|
||||
"}"
|
||||
).arg(MDEM_BACKGROUND)
|
||||
);
|
||||
errorView->layout.addWidget(&errorView->label);
|
||||
errorView->label.setWordWrap(true);
|
||||
}
|
||||
|
||||
{ // Mdems
|
||||
QScrollArea *mdemScroll = new QScrollArea();
|
||||
QWidget *mdemContainer = new QWidget();
|
||||
|
||||
@@ -231,7 +231,7 @@ QVBoxLayout *vGroups;
|
||||
std::vector<QStandardItemModel*> groupModels;
|
||||
|
||||
// Questions & State
|
||||
MdemBuffer *currentBuffer;
|
||||
MdemBuffer *practiceBuffer;
|
||||
|
||||
int32_t currentQuestionIndex = -1;
|
||||
std::vector<GroupView*> groupViews;
|
||||
@@ -318,15 +318,19 @@ void setupAnswerQuestion(MultiElementQuestion *question) {
|
||||
if (answerText->isVisible()) {
|
||||
answerText->hide();
|
||||
}
|
||||
QObject::connect(
|
||||
btnTriggerAnswer,
|
||||
&QToolButton::clicked,
|
||||
[](bool checked) {
|
||||
|
||||
auto checkAnswerClicked = []() {
|
||||
answerText->show();
|
||||
btnTriggerAnswer->hide();
|
||||
if (practiceAlgoritm == SPACED) {
|
||||
showFeedBackButtons();
|
||||
}
|
||||
};
|
||||
QObject::connect(
|
||||
btnTriggerAnswer,
|
||||
&QToolButton::clicked,
|
||||
[checkAnswerClicked](bool checked) {
|
||||
checkAnswerClicked();
|
||||
}
|
||||
);
|
||||
btnTriggerAnswer->show();
|
||||
@@ -533,7 +537,7 @@ void setupQuestion(Question *question) {
|
||||
}
|
||||
|
||||
void updatePaginationVisibility() {
|
||||
if (currentQuestionIndex == currentBuffer->questions.size() - 1) {
|
||||
if (currentQuestionIndex == practiceBuffer->questions.size() - 1) {
|
||||
btnNext->hide();
|
||||
} else {
|
||||
btnNext->show();
|
||||
@@ -554,27 +558,27 @@ time_t getTime() {
|
||||
}
|
||||
|
||||
void setupNextQuestion() {
|
||||
if (currentBuffer->questions.size() <= 0) {
|
||||
if (practiceBuffer->questions.size() <= 0) {
|
||||
return;
|
||||
}
|
||||
switch (practiceAlgoritm) {
|
||||
case PRIMARY: {
|
||||
currentQuestionIndex++;
|
||||
if (currentQuestionIndex < currentBuffer->questions.size()) {
|
||||
setupQuestion(currentBuffer->questions[currentQuestionIndex]);
|
||||
if (currentQuestionIndex < practiceBuffer->questions.size()) {
|
||||
setupQuestion(practiceBuffer->questions[currentQuestionIndex]);
|
||||
}
|
||||
updatePaginationVisibility();
|
||||
} break;
|
||||
case RANDOM: {
|
||||
auto questionCandidates = currentBuffer->questions;
|
||||
auto questionCandidates = practiceBuffer->questions;
|
||||
if (currentQuestionIndex > -1) {
|
||||
questionCandidates.erase(questionCandidates.begin() + currentQuestionIndex);
|
||||
}
|
||||
if (questionCandidates.size() > 0) {
|
||||
auto i = randomIndex(&questionCandidates);
|
||||
setupQuestion(questionCandidates[i]);
|
||||
for (int k = 0; k < currentBuffer->questions.size(); ++k) {
|
||||
if (currentBuffer->questions[k] == questionCandidates[i]) {
|
||||
for (int k = 0; k < practiceBuffer->questions.size(); ++k) {
|
||||
if (practiceBuffer->questions[k] == questionCandidates[i]) {
|
||||
currentQuestionIndex = k;
|
||||
break;
|
||||
}
|
||||
@@ -584,27 +588,27 @@ void setupNextQuestion() {
|
||||
case SPACED: {
|
||||
auto questionCandidates = std::vector<Question*>();
|
||||
time_t time = getTime();
|
||||
auto lastTrainedAt = currentBuffer->trainedAt;
|
||||
currentBuffer->trainedAt = time;
|
||||
for (int i = 0; i < currentBuffer->questions.size(); ++i) {
|
||||
auto cooldownSeconds = currentBuffer->questions[i]->Cooldown * 3600;
|
||||
auto lastTrainedAt = practiceBuffer->trainedAt;
|
||||
practiceBuffer->trainedAt = time;
|
||||
for (int i = 0; i < practiceBuffer->questions.size(); ++i) {
|
||||
auto cooldownSeconds = practiceBuffer->questions[i]->Cooldown * 3600;
|
||||
auto cooldownEndsAt = lastTrainedAt + cooldownSeconds;
|
||||
if (i != currentQuestionIndex && cooldownEndsAt <= time) {
|
||||
questionCandidates.push_back(currentBuffer->questions[i]);
|
||||
questionCandidates.push_back(practiceBuffer->questions[i]);
|
||||
}
|
||||
auto newCooldown = cooldownEndsAt - time;
|
||||
if (newCooldown < 0) {
|
||||
newCooldown = 0;
|
||||
}
|
||||
currentBuffer->questions[i]->Cooldown = (double)newCooldown / 3600;
|
||||
practiceBuffer->questions[i]->Cooldown = (double)newCooldown / 3600;
|
||||
}
|
||||
if (questionCandidates.size() > 0) {
|
||||
auto i = randomIndex(&questionCandidates);
|
||||
setupQuestion(
|
||||
questionCandidates[randomIndex(&questionCandidates)]
|
||||
);
|
||||
for (int k = 0; k < currentBuffer->questions.size(); ++k) {
|
||||
if (currentBuffer->questions[k] == questionCandidates[i]) {
|
||||
for (int k = 0; k < practiceBuffer->questions.size(); ++k) {
|
||||
if (practiceBuffer->questions[k] == questionCandidates[i]) {
|
||||
currentQuestionIndex = k;
|
||||
break;
|
||||
}
|
||||
@@ -620,9 +624,9 @@ void initiatePractice(
|
||||
PracticeAlgorithm algorithm
|
||||
) {
|
||||
practiceAlgoritm = algorithm;
|
||||
currentBuffer = mdemBuffer;
|
||||
practiceBuffer = mdemBuffer;
|
||||
|
||||
if (currentBuffer->questions.size() <= 0) {
|
||||
if (practiceBuffer->questions.size() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -632,10 +636,10 @@ void initiatePractice(
|
||||
setupNextQuestion();
|
||||
}
|
||||
|
||||
void setCooldownHours(int cooldown) {
|
||||
void setCooldownHours(double cooldown) {
|
||||
time_t time = getTime();
|
||||
currentBuffer->trainedAt = time;
|
||||
auto question = currentBuffer->questions[currentQuestionIndex];
|
||||
practiceBuffer->trainedAt = time;
|
||||
auto question = practiceBuffer->questions[currentQuestionIndex];
|
||||
question->Cooldown = cooldown;
|
||||
update(true);
|
||||
}
|
||||
@@ -759,19 +763,39 @@ void initTrainWindow() {
|
||||
btnTriggerAnswer = new QToolButton();
|
||||
btnNotRemembered = new QToolButton();
|
||||
QObject::connect(btnNotRemembered, &QToolButton::clicked, []() {
|
||||
QString key = SETTING_NOT_REMEMBERED;
|
||||
if (settings->contains(key)) {
|
||||
setCooldownHours(settings->value(key).toDouble());
|
||||
} else {
|
||||
setCooldownHours(0);
|
||||
}
|
||||
});
|
||||
btnHard = new QToolButton();
|
||||
QObject::connect(btnHard, &QToolButton::clicked, []() {
|
||||
QString key = SETTING_HARD;
|
||||
if (settings->contains(key)) {
|
||||
setCooldownHours(settings->value(key).toDouble());
|
||||
} else {
|
||||
setCooldownHours(12);
|
||||
}
|
||||
});
|
||||
btnMedium = new QToolButton();
|
||||
QObject::connect(btnMedium, &QToolButton::clicked, []() {
|
||||
setCooldownHours(48);
|
||||
QString key = SETTING_MEDIUM;
|
||||
if (settings->contains(key)) {
|
||||
setCooldownHours(settings->value(key).toDouble());
|
||||
} else {
|
||||
setCooldownHours(24);
|
||||
}
|
||||
});
|
||||
btnEasy = new QToolButton();
|
||||
QObject::connect(btnEasy, &QToolButton::clicked, []() {
|
||||
setCooldownHours(72);
|
||||
QString key = SETTING_EASY;
|
||||
if (settings->contains(key)) {
|
||||
setCooldownHours(settings->value(key).toDouble());
|
||||
} else {
|
||||
setCooldownHours(48);
|
||||
}
|
||||
});
|
||||
btnNotRemembered->setText("Not remembered");
|
||||
btnHard->setText("Hard");
|
||||
@@ -780,6 +804,11 @@ void initTrainWindow() {
|
||||
|
||||
btnTriggerAnswer = new QToolButton();
|
||||
|
||||
QShortcut* shortcutShowAnswer = new QShortcut(QKeySequence("Return"), trainWindow);
|
||||
QObject::connect(shortcutShowAnswer, &QShortcut::activated, []() {
|
||||
btnTriggerAnswer->click();
|
||||
});
|
||||
|
||||
rightSpacer = new QSpacerItem(50, 50, QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
btnNext = new QToolButton();
|
||||
|
||||
|
||||
@@ -152,7 +152,8 @@ time_t parseToUTCTime(const std::string datetime, std::string format) {
|
||||
return time;
|
||||
}
|
||||
|
||||
|
||||
// @Fix: Prevent duplicate group names and questions in ordered question (to
|
||||
// simplify checking in practice).
|
||||
Result<ParseInfo> parseQuestions(const std::vector<Token>& tokens) {
|
||||
auto questions = std::vector<Question*>();
|
||||
time_t time = 0;
|
||||
|
||||
Reference in New Issue
Block a user