diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index af3e68b..2a798ea 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,5 +4,10 @@ project(Mdemory LANGUAGES CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED True) +set( + CMAKE_CXX_FLAGS_DEBUG + "${CMAKE_CXX_FLAGS_DEBUG} -g3 -O0 -ggdb -fvar-tracking-assignments -fno-inline" +) + add_subdirectory(transpiler) add_subdirectory(qtapp) diff --git a/src/include/main.h b/src/include/main.h index a065515..e69de29 100644 --- a/src/include/main.h +++ b/src/include/main.h @@ -1,28 +0,0 @@ -#pragma once - -#include -#include - -#include "parser.h" - -void update(bool isChanged = false); - -void saveMdem(); - -struct MdemBuffer { - std::vector questions = std::vector(); - 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" diff --git a/src/include/mdemList.h b/src/include/mdemList.h new file mode 100644 index 0000000..783d98a --- /dev/null +++ b/src/include/mdemList.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "parser.h" + +struct MdemBuffer { + std::vector questions = std::vector(); + time_t trainedAt = 0; + bool error = false; + bool isModified = false; +}; + +struct Mdem { + QWidget wMdem; + QVBoxLayout vMdem; + QLabel wFrontText; + QWidget wFront; + QHBoxLayout hFront; + QWidget wBack; + QVBoxLayout hBack; + QToolButton editButton; + QToolButton deleteButton; + QToolButton showButton; + int labelCount; + QVector backLabels; + Question *question; +}; + +struct ErrorView { + QWidget box; + QVBoxLayout layout; + QLabel label; +}; + +struct Page { + size_t start; + size_t end; +}; + +void update(bool isChanged = false); +void saveMdem(); +void updateMdemInfo(std::string filename = "", bool isChanged = true); +QMainWindow *initMdemListWindow(); + +#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" diff --git a/src/include/settings.h b/src/include/settings.h new file mode 100644 index 0000000..df78e94 --- /dev/null +++ b/src/include/settings.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include + +extern QSettings *settings; +QWidget *initSettings(); diff --git a/src/include/trainWindow.h b/src/include/trainWindow.h index 08e1b0a..49eb08d 100644 --- a/src/include/trainWindow.h +++ b/src/include/trainWindow.h @@ -3,7 +3,7 @@ #include #include "main.h" -#include "parser.h" +#include "mdemList.h" extern QMainWindow *trainWindow; diff --git a/src/qtapp/CMakeLists.txt b/src/qtapp/CMakeLists.txt index ecdbcc7..4132a6b 100644 --- a/src/qtapp/CMakeLists.txt +++ b/src/qtapp/CMakeLists.txt @@ -19,7 +19,9 @@ include_directories(/usr/include/qt/Qsci) add_executable( MdemoryApp main.cpp + mdemList.cpp trainWindow.cpp + settings.cpp ) target_link_libraries(MdemoryApp Qt5::Widgets /usr/lib/libqscintilla2_qt5.so api) diff --git a/src/qtapp/main.cpp b/src/qtapp/main.cpp index f721efd..d1e8dd9 100644 --- a/src/qtapp/main.cpp +++ b/src/qtapp/main.cpp @@ -1,1157 +1,10 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "main.h" -#include "api.h" -#include "parser.h" -#include "qscilexer.h" -#include "trainWindow.h" - -#define TEXT_LG = 20 -#define ERROR_POOL_CHUNK 50 -#define DISTANCE 2 -#define PER_PAGE 8 -#define MDEM_BACKGROUND "#F7F7F7" - -struct Mdem { - QWidget wMdem; - QVBoxLayout vMdem; - QLabel wFrontText; - QWidget wFront; - QHBoxLayout hFront; - QWidget wBack; - QVBoxLayout hBack; - QToolButton editButton; - QToolButton deleteButton; - QToolButton showButton; - int labelCount; - QVector backLabels; - Question *question; -}; - -struct ErrorView { - QWidget box; - QVBoxLayout layout; - QLabel label; -}; - -struct Page { - int start; - int end; -}; - -// Memorybase info. -// @Improve: set the default directory for convenience. It should be configured by user -// or set to "". -std::string currentPath = "/home/jorenchik/Code/mdemory/memorybase/"; -std::string currentMdem = ""; -QFileSystemModel *model; -QTreeView *mdemList; -QLabel *membaseLabel; - -std::map buffers; -MdemBuffer *currentMdemBuffer; - -// Mdem scroll list. -QList mdems = QList(); -QVBoxLayout *hMdemScroll; -QSpacerItem *mdemSpacer; -ErrorView* errorView; - -// Editor -Mdem* editMdem; - -// Pagination -int currentPage = -1; -std::vector pages; -QList paginationButtons; -QToolButton* prevButton; -QToolButton* firstButton; -QToolButton* lastButton; -QToolButton* nextButton; -QLabel* paginationLabel; - -// Top layout. -QLabel *deckListLabel; -QToolButton *add; -QToolButton *btnSaveFile; -QToolButton *load; -QToolButton *practice; - -QsciScintilla *editor; -QMainWindow* editorWindow; - -QSettings *settings; - -const std::regex lastPathElementExp = std::regex("(.+\\/)*(.+)"); - -void showBacklabels(Mdem *mdem) { - for (int i = 0; i < mdem->backLabels.size(); ++i) { - if (i < mdem->labelCount) { - if (!mdem->backLabels[i]->isVisible()) { - mdem->backLabels[i]->show(); - } - } else { - if (mdem->backLabels[i]->isVisible()) { - mdem->backLabels[i]->hide(); - } - } - } -} - -std::string outputMdem(std::vector 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(SETTING_TIMEZONE).toInt(); - /*time = time + 3600 * timezoneOffset;*/ - std::tm* tm = std::localtime(&time); - char buffer[100]; - std::strftime(buffer, sizeof(buffer), "%d.%m.%Y %H\\:%M", tm); - auto time = std::string(buffer); - ss << time << std::endl; - } - - for (auto question: questions) { - ss << std::endl; - std::string cooldownPart; - if (question->Cooldown != 0) { - cooldownPart = std::format(" [{:.2f}]", question->Cooldown); - } - ss << wrapText( - std::format("-{}{} >\n", - cooldownPart, - " " + escapeText(question->QuestionText)), - wrap_width - ); - if (MultiElementQuestion* mw = dynamic_cast(question)) { - for (auto choice: mw->Choices) { - char opener; - if (choice.IsCorrect) { - opener = '+'; - } else { - opener = '-'; - } - std::string orderModifier; - if (mw->type == MultiElementType::Order) { - orderModifier = "^"; - } - ss << - wrapText( - std::format( - "\t{}{} {}\n", - opener, - orderModifier, - escapeText(choice.Answer) - ) - , wrap_width); - } - } else if (GroupQuestion* gq = dynamic_cast(question)) { - for (auto group: gq->Groups) { - ss << wrapText( - std::format( - "\t- {}:\n", - escapeText(group.name) - ) - , wrap_width); - for (auto element: group.elements) { - ss << wrapText( - std::format( - "\t\t- {}\n", - escapeText(element) - ) - , wrap_width); - } - } - } - } - return ss.str(); -} - -void makePages() { - pages.clear(); - auto len = currentMdemBuffer->questions.size(); - auto pageAmount = len / PER_PAGE; - if (len % PER_PAGE != 0) { - pageAmount += 1; - } - for (int i = 0; i < pageAmount; i++) { - auto startingIndex = PER_PAGE * i ; - auto amount = PER_PAGE; - if (i == currentMdemBuffer->questions.size() / PER_PAGE) { - amount = currentMdemBuffer->questions.size() % PER_PAGE; - } - pages.push_back(Page{startingIndex, startingIndex + amount}); - } -} - -void setupMdem(Mdem *mdem, Question *question) { - std::stringstream ss; - if (question->Cooldown > 0) { - ss << std::format("[{:.2f}] ", question->Cooldown); - } - ss << question->QuestionText; - mdem->wFrontText.setText( - QString::fromStdString(ss.str()) - ); - if (MultiElementQuestion* mw = dynamic_cast(question)) { - auto choices = mw->Choices; - for (size_t k = 0; k < choices.size(); ++k) { - auto answer = choices[k].Answer; - switch (mw->type) { - case MultiElementType::Order: - answer = std::format("{}. {}", k + 1, answer); - break; - case MultiElementType::MultiChoice: - if (choices[k].IsCorrect) { - answer = std::format("+ {}", answer); - } else { - answer = std::format("- {}", answer); - } - break; - case MultiElementType::Regular: - answer = std::format("- {}", answer); - break; - } - if (k < mdem->backLabels.size()) { - mdem->backLabels[k]->setText(QString::fromStdString(answer)); - } else { - auto label = new QLabel(); - label->setText(QString::fromStdString(answer)); - mdem->backLabels.push_back(label); - mdem->wBack.layout()->addWidget(label); - } - } - mdem->labelCount = choices.size(); - } else if (GroupQuestion* mw = dynamic_cast(question)) { - auto groups = mw->Groups; - std::vector elements; - for (size_t k = 0; k < groups.size(); ++k) { - auto answer = groups[k].name; - elements.push_back(std::format("- {}:", answer)); - for (size_t l = 0; l < groups[k].elements.size(); ++l) { - elements.push_back(std::format(" - {}", groups[k].elements[l])); - } - } - for (size_t k = 0; k < elements.size(); ++k) { - if (k < mdem->backLabels.size()) { - mdem->backLabels[k]->setText(QString::fromStdString(elements[k])); - } else { - auto label = new QLabel(); - label->setText(QString::fromStdString(elements[k])); - mdem->backLabels.push_back(label); - mdem->wBack.layout()->addWidget(label); - } - } - mdem->labelCount = elements.size(); - } -} - -void SwitchPage(int pageIdx); - -std::string getFilename(std::string path) { - std::smatch matches; - auto filenameMatched = std::regex_search(path, matches, lastPathElementExp); - return matches[2].str(); -} - -void updateMdemInfo(std::string filename, bool isChanged) { - currentMdemBuffer->isModified = isChanged; - if (filename.length() > 0) { - std::stringstream ss; - ss << std::format("mdem: {}", filename); - if (isChanged) { - ss << "*"; - } - 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); - } - deckListLabel->setText(QString::fromStdString(ss.str())); - } else { - deckListLabel->setText(""); - } -} - -Mdem* makeMdem() { - auto mdem = new Mdem; - mdem->wMdem.setLayout(&mdem->vMdem); - - QString id = QString("mdem_%1").arg(1); - mdem->wMdem.setObjectName(id); - - // Front - mdem->wFront.setMinimumHeight(60); - mdem->wFront.setLayout(&mdem->hFront); - mdem->wFront.setProperty("first", "true"); - mdem->wMdem.setStyleSheet(QString( - "QWidget#%1 > QWidget {" - "border-right: 1px solid gray;" - "border-bottom: 1px solid gray;" - "border-left: 1px solid gray;" - "background: %2;" - "} " - "QWidget#%1 > QWidget[first=\"true\"] {" - "border-top: 1px solid gray;" - "}" - ).arg(id, MDEM_BACKGROUND)); - - // Add Front Content - mdem->hFront.addWidget(&mdem->wFrontText); - mdem->hFront.addStretch(1); - - mdem->editButton.setText("Edit"); - QObject::connect( - &mdem->editButton, - &QToolButton::clicked, - [mdem](bool checked) { - editMdem = mdem; - if (mdem->question) { - editor->setText( - QString::fromStdString( - outputMdem(std::vector{mdem->question}) - ) - ); - editorWindow->show(); - editor->setCursorPosition(1, 2); - } - } - ); - mdem->hFront.addWidget(&mdem->editButton); - - mdem->deleteButton.setText("Delete"); - QObject::connect( - &mdem->deleteButton, - &QToolButton::clicked, - [mdem](bool checked) { - if (mdem->question) { - Question* deleted = nullptr; - 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); - break; - } - } - makePages(); - SwitchPage(currentPage); - } - if (editMdem == mdem) { - editorWindow->hide(); - } - } - ); - mdem->hFront.addWidget(&mdem->deleteButton); - - mdem->showButton.setText("Show"); - mdem->hFront.addWidget(&mdem->showButton); - - // Back - QVBoxLayout *hBack = new QVBoxLayout(); - mdem->wBack.setLayout(&mdem->hBack); - mdem->vMdem.addWidget(&mdem->wBack); - - // Add Back Content - for (int i = 0; i < 20; ++i) { - // @Improve: back label pooling - QLabel *elBackText = new QLabel(); - mdem->hBack.addWidget(elBackText); - mdem->backLabels.append(elBackText); - } - - mdem->vMdem.addWidget(&mdem->wFront); - mdem->vMdem.addWidget(&mdem->wBack); - mdem->vMdem.setContentsMargins(0, 0, 0, 0); - mdem->vMdem.setSpacing(0); - - mdem->wBack.hide(); - mdem->wMdem.hide(); - - // Connect button to toggle view - QObject::connect(&mdem->showButton, &QToolButton::clicked, [mdem]() { - if (mdem->wBack.isVisible()) { - mdem->wBack.hide(); - mdem->showButton.setText("Show"); - } else { - mdem->wBack.show(); - mdem->showButton.setText("Hide"); - } - showBacklabels(mdem); - }); - return mdem; -} - - -void CreateMdems(std::vector& questions) { - hMdemScroll->removeItem(mdemSpacer); - - for (Mdem *mdem : mdems) { - if (mdem->wMdem.isVisible()) { - mdem->wMdem.hide(); - } - } - - if (questions.size() > mdems.size()) { - auto amount = questions.size() - mdems.size(); - for (size_t i = 0; i < amount; ++i) { - auto mdem = makeMdem(); - mdems.append(mdem); - hMdemScroll->addWidget(&mdem->wMdem); - } - } - - for (size_t i = 0; i < questions.size(); ++i) { - mdems[i]->question = questions[i]; - setupMdem(mdems[i], questions[i]); - if (!mdems[i]->wMdem.isVisible()) { - mdems[i]->wMdem.show(); - } - } - - hMdemScroll->addItem(mdemSpacer); -} - -void update(bool isChanged) { - if (currentPage > -1) { - SwitchPage(currentPage); - } - if (currentMdem.length() > 0) { - updateMdemInfo(getFilename(currentMdem), isChanged); - } -} - -void SwitchPage(int pageIdx) { - currentPage = pageIdx; - - // Hide all pagination buttons - for (auto& button : paginationButtons) { - button->hide(); - } - - int l = 0; - char buffer[50]; - snprintf(buffer, sizeof(buffer), "Page: %d", pageIdx + 1); - paginationLabel->setText(buffer); - - // Hide widgets in mdems - for (auto& mdem : mdems) { - if (mdem->wBack.isVisible()) { - mdem->wBack.hide(); - mdem->showButton.setText("Show"); - } - } - - // Update pagination buttons - for (int k = -DISTANCE; k <= DISTANCE; ++k) { - if (pageIdx + k >= 0 && pageIdx + k < pages.size()) { - auto button = paginationButtons[l]; - snprintf(buffer, sizeof(buffer), "%d", pageIdx + k + 1); - button->setText(buffer); - - if (pageIdx + k != pageIdx) { - button->show(); - } else { - button->hide(); - } - ++l; - } - } - - // Handle first and last buttons - if (pageIdx > 0 && pages.size() > 1) { - firstButton->show(); - } else { - firstButton->hide(); - } - - if (pageIdx < pages.size() - 1 && pages.size() > 1) { - lastButton->show(); - } else { - lastButton->hide(); - } - - // Handle next and previous buttons - if (!pages.empty() && currentPage < pages.size() - 1) { - nextButton->show(); - } else { - nextButton->hide(); - } - - if (!pages.empty() && currentPage >= 1) { - prevButton->show(); - } else { - prevButton->hide(); - } - - Page page; - if (pages.size() <= 0) { - page = Page(); - } else if (pageIdx < pages.size()){ - page = pages[pageIdx]; - } else { - if (pageIdx - 1 < pages.size()) { - page = pages[pageIdx -1]; - } else { - page = pages[0]; - } - } - - std::vector pageSlice( - currentMdemBuffer->questions.begin() + page.start, - currentMdemBuffer->questions.begin() + page.end - ); - CreateMdems(pageSlice); -} - -void reloadMdem(std::string path) { - if (path == "") { - return; - } - - auto toRemove = std::vector(); - 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); - } - - 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 file = std::ifstream(path); - std::string content; - - // Reset the mdem list. - for (auto mdem: mdems) { - mdem->wMdem.hide(); - } - for (auto question: currentMdemBuffer->questions) { - delete question; - } - currentMdemBuffer->questions.clear(); - - if (file) { - std::stringstream buffer; - buffer << file.rdbuf(); - content = buffer.str(); - auto res = transpile(content, true); - - currentMdemBuffer->error = res.error.length() > 0; - if (res.error == "") { - if (res.value.lastTrainedAt == 0) { - currentMdemBuffer->trainedAt = 0; - } else { - auto timezoneOffset = settings->value("timezone").toInt(); - currentMdemBuffer->trainedAt = res.value.lastTrainedAt - 3600 * timezoneOffset; - } - std::cout << std::format("Last trained at: {}", currentMdemBuffer->trainedAt) << std::endl; - - currentMdemBuffer->questions = res.value.questions; - errorView->box.hide(); - } else { - currentMdemBuffer->trainedAt = 0; - std::cout << std::format("Compilation error: {}", res.error) << std::endl; - - for (auto question: res.value.questions) { - delete question; - } - - // Show errors. - hMdemScroll->removeItem(mdemSpacer); - errorView->label.setText( - QString::fromStdString( - std::format( - "Error while transpiling {}: {} ({}:{})", - filename, - res.error, - res.row, - res.column - ) - ) - ); - 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; - } - - hideQuestionElements(); - trainWindow->close(); -} - -void pickDirectory(QString directory) { - currentPath = directory.toStdString(); - - // Update tree view. - if (directory.length() <= 0) { - membaseLabel->setText(directory); - return; - } - mdemList->setRootIndex(model->setRootPath(directory)); - std::smatch matches; - - // Update label. - membaseLabel->setText(QString::fromStdString( - std::format( - "memorybase: {}", - getFilename(currentPath) - ) - )); - - reloadMdem(""); -} - -void setupEditorSave() { - auto res = transpile(editor->text().toStdString(), true); - if (res.error.length() > 0) { - currentMdemBuffer->trainedAt = 0; - for (auto question: res.value.questions) { - delete question; - } - QMessageBox::information( - nullptr, - "Editing error", - QString::fromStdString(res.error) - ); - } else { - if (editMdem) { - if (res.value.questions.size() <= 0) { - QMessageBox::information( - nullptr, - "Editing error", - "There are no questions in your input." - ); - } else if (res.value.questions.size() == 1) { - auto oldQuestion = editMdem->question; - editMdem->question = res.value.questions[0]; - for (int i = 0; i < currentMdemBuffer->questions.size(); ++i) { - if (currentMdemBuffer->questions[i] == oldQuestion) { - currentMdemBuffer->questions.erase(currentMdemBuffer->questions.begin() + i); - delete oldQuestion; - currentMdemBuffer->questions.insert(currentMdemBuffer->questions.begin() + i, res.value.questions[0]); - break; - } - } - setupMdem(editMdem, res.value.questions[0]); - showBacklabels(editMdem); - editorWindow->hide(); - updateMdemInfo(getFilename(currentMdem), true); - } else { - QMessageBox::information( - nullptr, - "Editing error", - "There are more than question in your input. Please enter one." - ); - } - } else { - if (res.value.questions.size() <= 0) { - QMessageBox::information( - nullptr, - "Editing error", - "There are no questions in your input." - ); - } else { - currentMdemBuffer->questions.insert( - currentMdemBuffer->questions.begin(), - res.value.questions.begin(), - res.value.questions.end() - ); - makePages(); - SwitchPage(0); - editorWindow->hide(); - updateMdemInfo(getFilename(currentMdem), true); - } - } - } -}; - -void saveMdem() { - auto filename = getFilename(currentMdem); - std::ofstream out(currentMdem); - out << outputMdem(currentMdemBuffer->questions, currentMdemBuffer->trainedAt); - updateMdemInfo(getFilename(currentMdem), false); -} +#include "mdemList.h" int main(int argc, char *argv[]) { - - QApplication app(argc, argv); - QMainWindow window; - - auto* settingsWindow = new QWidget; - - { // Menu bar. - QMenuBar *menuBar = new QMenuBar; - QFileDialog *fileDialog = new QFileDialog; - - QMenu *menu = new QMenu("File"); - QAction *actionOpen = menu->addAction("Open memorybase"); - QObject::connect(actionOpen, &QAction::triggered, [fileDialog]() { - fileDialog->setDirectory(QDir::homePath()); - fileDialog->setFileMode(QFileDialog::FileMode::Directory); - fileDialog->open(); - QObject::disconnect(fileDialog, 0, 0, 0); - fileDialog->connect( - fileDialog, - &QFileDialog::fileSelected, - pickDirectory - ); - }); - - QAction *openSettings = menu->addAction("Settings"); - QObject::connect( - openSettings, - &QAction::triggered, - [settingsWindow]() { - settingsWindow->show(); - }); - - menuBar->addMenu(menu); - window.setMenuBar(menuBar); - } - - { // Settings window. - QString configDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); - QDir().mkpath(configDir); - QString settingsFile = configDir + "/mdem.ini"; - settings = new QSettings(settingsFile, QSettings::IniFormat); - settingsWindow->setWindowTitle("Settings"); - - auto formLayout = new QFormLayout; - - auto characterWrap = new QSpinBox; - 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 QDoubleSpinBox; - notRemembered->setRange(0, 100); - formLayout->addRow("Not remembered:", notRemembered); - - auto* hard = new QDoubleSpinBox; - hard->setRange(0, 100); - formLayout->addRow("Hard:", hard); - - auto* medium = new QDoubleSpinBox; - medium->setRange(0, 100); - formLayout->addRow("Medium:", medium); - - auto* easy = new QDoubleSpinBox; - easy->setRange(0, 100); - formLayout->addRow("Easy:", easy); - - auto* btnSaveSettings = new QPushButton("Save"); - auto* mainLayout = new QVBoxLayout; - - // @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, - [saveSettings]() { - saveSettings(); - } - ); - - QShortcut* shortcutSave = new QShortcut(QKeySequence("Ctrl+S"), settingsWindow); - QObject::connect(shortcutSave, &QShortcut::activated, [saveSettings]() { - saveSettings(); - }); - - mainLayout->addLayout(formLayout); - mainLayout->addWidget(btnSaveSettings); - - settingsWindow->setLayout(mainLayout); - } - - { // Setup editor. - editorWindow = new QMainWindow; - - editorWindow->setWindowTitle("QScintilla Simple Editor"); - editorWindow->resize(800, 600); - QWidget *wEditor = new QWidget; - QVBoxLayout *vlEditor = new QVBoxLayout; - wEditor->setLayout(vlEditor); - - QsciLexerCPP *lexer = new QsciLexerCPP; - editor = new QsciScintilla; - editor->setLexer(lexer); - editor->setUtf8(true); - editor->setMarginWidth(0, 15); - editor->zoomIn(2); - - QHBoxLayout *buttonLayout = new QHBoxLayout; - QWidget *editorButtons = new QWidget; - auto btnSaveEditor = new QPushButton; - - editorButtons->setLayout(buttonLayout); - btnSaveEditor->setText(QString::fromStdString("Save")); - QObject::connect( - btnSaveEditor, - &QToolButton::clicked, - setupEditorSave - ); - buttonLayout->addWidget(btnSaveEditor); - - vlEditor->addWidget(editor); - vlEditor->addWidget(editorButtons); - - QShortcut* shortcutSave = new QShortcut(QKeySequence("Ctrl+S"), editorWindow); - QObject::connect(shortcutSave, &QShortcut::activated, []() { - if (editor->isVisible()) { - setupEditorSave(); - } - }); - - editorWindow->setCentralWidget(wEditor); - } - - QSplitter *hSplitter = new QSplitter(); - QWidget *leftWidget = new QWidget(); - { // Left side. - QVBoxLayout *leftLayout = new QVBoxLayout(); - QWidget *leftTop = new QWidget(); - QVBoxLayout *vLeftTop = new QVBoxLayout(); - mdemList = new QTreeView(); - membaseLabel = new QLabel(); - model = new QFileSystemModel(); - membaseLabel->setStyleSheet( - "font-size: 17px;" - "font-weight: 400;" - ); - mdemSpacer = new QSpacerItem(50, 50, QSizePolicy::Minimum, QSizePolicy::Expanding); - - leftWidget->setLayout(leftLayout); - leftLayout->addWidget(leftTop); - leftTop->setLayout(vLeftTop); - leftTop->setMinimumSize(0, 40); - vLeftTop->addWidget(membaseLabel); - // Hide all columns except the first one - mdemList->setModel(model); - - QObject::connect( - mdemList, - &QTreeView::doubleClicked, - [](const QModelIndex &index) { - auto fileInfo = model->fileInfo(index); - reloadMdem(fileInfo.filePath().toStdString()); - } - ); - - for (int col = 1; col < model->columnCount(); ++col) { - mdemList->hideColumn(col); - } - model->setHeaderData(0, Qt::Horizontal, QObject::tr("Custom Name")); - leftLayout->addWidget(mdemList); - } - - - // RightSide - QWidget *rightWidget = new QWidget(); - QVBoxLayout *rightLayout = new QVBoxLayout(); - rightWidget->setLayout(rightLayout); - - { // Mdem list top. - QWidget *top = new QWidget(); - QHBoxLayout *hTop = new QHBoxLayout(); - deckListLabel = new QLabel(); - deckListLabel->setStyleSheet( - "font-size: 17px;" - "font-weight: 400;" - ); - top->setMinimumSize(0, 40); - top->setLayout(hTop); - rightLayout->addWidget(top); - - hTop->addWidget(deckListLabel); - hTop->addStretch(1); - - add = new QToolButton; - btnSaveFile = new QToolButton; - load = new QToolButton; - auto cbAlgorithm = new QComboBox; - practice = new QToolButton; - - // Buttons - auto buttons = new QWidget(); - auto vlButtons = new QVBoxLayout(); - buttons->setLayout(vlButtons); - auto buttonsTop = new QWidget(); - auto hlButtonsTop = new QHBoxLayout(); - buttonsTop->setLayout(hlButtonsTop); - auto buttonsBottom = new QWidget(); - auto hlButtonsBottom = new QHBoxLayout(); - buttonsBottom->setLayout(hlButtonsBottom); - add->setText("Add"); - btnSaveFile->setText("Save"); - load->setText("Load"); - hlButtonsTop->addWidget(add); - hlButtonsTop->addWidget(btnSaveFile); - hlButtonsTop->addWidget(load); - cbAlgorithm->addItem("Spaced", SPACED); - cbAlgorithm->addItem("Random", RANDOM); - cbAlgorithm->addItem("Primary", PRIMARY); - practice->setText("Practice"); - hlButtonsBottom->addWidget(cbAlgorithm); - hlButtonsBottom->addWidget(practice); - vlButtons->addWidget(buttonsTop); - vlButtons->addWidget(buttonsBottom); - hlButtonsTop->setContentsMargins(0, 0, 0, 0); - hlButtonsBottom->setContentsMargins(0, 5, 0, 0); - - hTop->addWidget(buttons); - - QObject::connect(add, &QToolButton::clicked, []() { - editMdem = nullptr; - editorWindow->show(); - editor->setText(""); - }); - QObject::connect(load, &QToolButton::clicked, []() { - reloadMdem(currentMdem); - }); - QObject::connect(btnSaveFile, &QToolButton::clicked, []() { - saveMdem(); - }); - QObject::connect( - practice, - &QToolButton::clicked, - [cbAlgorithm](bool checked) { - trainWindow->show(); - trainWindow->resize(600, 300); - initiatePractice( - currentMdemBuffer, - static_cast(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(); - hMdemScroll = new QVBoxLayout(); - mdemScroll->setWidget(mdemContainer); - mdemScroll->setWidgetResizable(true); - mdemContainer->setLayout(hMdemScroll); - rightLayout->addWidget(mdemScroll); - hMdemScroll->addItem(mdemSpacer); - } - - { // Pagination - hSplitter->addWidget(leftWidget); - hSplitter->addWidget(rightWidget); - hSplitter->setStretchFactor(0, 1); - hSplitter->setStretchFactor(1, 3); - - auto pagination = new QWidget(); - auto hPagination = new QHBoxLayout(); - pagination->setLayout(hPagination); - - firstButton = new QToolButton(); - firstButton->setText(QString::fromStdString("<<")); - hPagination->addWidget(firstButton); - firstButton->hide(); - QObject::connect(firstButton, &QToolButton::clicked, [](bool checked) { - if (pages.size() > 0) { - SwitchPage(0); - } - }); - - prevButton = new QToolButton(); - prevButton->setText(QString::fromStdString("<")); - hPagination->addWidget(prevButton); - prevButton->hide(); - QObject::connect(prevButton, &QToolButton::clicked, [](bool checked) { - if (pages.size() > 0) { - SwitchPage(currentPage - 1); - } - }); - - for (int i = 0; i < PER_PAGE; i++) { - auto elButton = new QToolButton(); - elButton->setText(QString("%1").arg(i+1)); - hPagination->addWidget(elButton); - elButton->hide(); - QObject::connect(elButton, &QToolButton::clicked, [elButton](bool checked) { - auto pageNum = std::stoi(elButton->text().toStdString().c_str()); - auto pageIdx = pageNum - 1; - if (pageIdx < pages.size()) { - SwitchPage(pageIdx); - } - }); - paginationButtons.push_back(elButton); - } - - nextButton = new QToolButton(); - nextButton->setText(QString::fromStdString(">")); - hPagination->addWidget(nextButton); - nextButton->hide(); - QObject::connect(nextButton, &QToolButton::clicked, [](bool checked) { - if (pages.size() > 0) { - SwitchPage(currentPage + 1); - } - }); - - lastButton = new QToolButton(); - lastButton->setText(QString::fromStdString(">>")); - hPagination->addWidget(lastButton); - lastButton->hide(); - QObject::connect(lastButton, &QToolButton::clicked, [](bool checked) { - if (pages.size() > 0) { - SwitchPage(pages.size() - 1); - } - }); - hPagination->addStretch(1); - paginationLabel = new QLabel(); - hPagination->addWidget(paginationLabel); - rightLayout->addWidget(pagination); - } - - initTrainWindow(); - - window.setCentralWidget(hSplitter); - window.show(); - - if (currentPath.length() > 0) { - pickDirectory(QString::fromStdString(currentPath)); - } - - QShortcut* shortcutSaveFile = new QShortcut(QKeySequence("Ctrl+S"), &window); - QObject::connect(shortcutSaveFile, &QShortcut::activated, []() { - saveMdem(); - }); - return app.exec(); + auto *app = new QApplication(argc, argv); + initMdemListWindow(); + app->exec(); + return 0; } diff --git a/src/qtapp/mdemList.cpp b/src/qtapp/mdemList.cpp new file mode 100644 index 0000000..d6b7d07 --- /dev/null +++ b/src/qtapp/mdemList.cpp @@ -0,0 +1,1059 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "settings.h" +#include "mdemList.h" +#include "trainWindow.h" +#include "api.h" +#include "parser.h" +#include "qscilexer.h" + +#define TEXT_LG = 20 +#define ERROR_POOL_CHUNK 50 +#define DISTANCE 2 +#define PER_PAGE 8 +#define MDEM_BACKGROUND "#F7F7F7" + +// Memorybase info. +// @Improve: set the default directory for convenience. It should be configured by user +// or set to "". +std::string currentPath = "/home/jorenchik/Code/mdemory/memorybase/"; +std::string currentMdem = ""; +QFileSystemModel *model; +QTreeView *mdemList; +QLabel *membaseLabel; + +std::map buffers; +MdemBuffer *currentMdemBuffer; + +// Mdem scroll list. +std::vector mdems = std::vector(); +QVBoxLayout *hMdemScroll; +QSpacerItem *mdemSpacer; +ErrorView* errorView; + +// Editor +Mdem* editMdem; + +// Pagination +int currentPage = -1; +std::vector pages; +QList paginationButtons; +QToolButton* prevButton; +QToolButton* firstButton; +QToolButton* lastButton; +QToolButton* nextButton; +QLabel* paginationLabel; + +// Top layout. +QLabel *deckListLabel; +QToolButton *add; +QToolButton *btnSaveFile; +QToolButton *load; +QToolButton *practice; + +QsciScintilla *editor; +QMainWindow* editorWindow; + +const std::regex lastPathElementExp = std::regex("(.+\\/)*(.+)"); + +void showBacklabels(Mdem *mdem) { + for (size_t i = 0; i < mdem->backLabels.size(); ++i) { + if (i < mdem->labelCount) { + if (!mdem->backLabels[i]->isVisible()) { + mdem->backLabels[i]->show(); + } + } else { + if (mdem->backLabels[i]->isVisible()) { + mdem->backLabels[i]->hide(); + } + } + } +} + +std::string outputMdem(std::vector 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(SETTING_TIMEZONE).toInt(); + /*time = time + 3600 * timezoneOffset;*/ + std::tm* tm = std::localtime(&time); + char buffer[100]; + std::strftime(buffer, sizeof(buffer), "%d.%m.%Y %H\\:%M", tm); + auto time = std::string(buffer); + ss << time << std::endl; + } + + for (auto question: questions) { + ss << std::endl; + std::string cooldownPart; + if (question->Cooldown != 0) { + cooldownPart = std::format(" [{:.2f}]", question->Cooldown); + } + ss << wrapText( + std::format("-{}{} >\n", + cooldownPart, + " " + escapeText(question->QuestionText)), + wrap_width + ); + if (MultiElementQuestion* mw = dynamic_cast(question)) { + for (auto choice: mw->Choices) { + char opener; + if (choice.IsCorrect) { + opener = '+'; + } else { + opener = '-'; + } + std::string orderModifier; + if (mw->type == MultiElementType::Order) { + orderModifier = "^"; + } + ss << + wrapText( + std::format( + "\t{}{} {}\n", + opener, + orderModifier, + escapeText(choice.Answer) + ) + , wrap_width); + } + } else if (GroupQuestion* gq = dynamic_cast(question)) { + for (auto group: gq->Groups) { + ss << wrapText( + std::format( + "\t- {}:\n", + escapeText(group.name) + ) + , wrap_width); + for (auto element: group.elements) { + ss << wrapText( + std::format( + "\t\t- {}\n", + escapeText(element) + ) + , wrap_width); + } + } + } + } + return ss.str(); +} + +void makePages() { + pages.clear(); + auto len = currentMdemBuffer->questions.size(); + auto pageAmount = len / PER_PAGE; + if (len % PER_PAGE != 0) { + pageAmount += 1; + } + for (size_t i = 0; i < pageAmount; i++) { + size_t startingIndex = PER_PAGE * i ; + size_t amount = PER_PAGE; + if (i == currentMdemBuffer->questions.size() / PER_PAGE) { + amount = currentMdemBuffer->questions.size() % PER_PAGE; + } + pages.push_back(Page{startingIndex, startingIndex + amount}); + } +} + +void setupMdem(Mdem *mdem, Question *question) { + std::stringstream ss; + if (question->Cooldown > 0) { + ss << std::format("[{:.2f}] ", question->Cooldown); + } + ss << question->QuestionText; + mdem->wFrontText.setText( + QString::fromStdString(ss.str()) + ); + if (MultiElementQuestion* mw = dynamic_cast(question)) { + auto choices = mw->Choices; + for (size_t k = 0; k < choices.size(); ++k) { + auto answer = choices[k].Answer; + switch (mw->type) { + case MultiElementType::Order: + answer = std::format("{}. {}", k + 1, answer); + break; + case MultiElementType::MultiChoice: + if (choices[k].IsCorrect) { + answer = std::format("+ {}", answer); + } else { + answer = std::format("- {}", answer); + } + break; + case MultiElementType::Regular: + answer = std::format("- {}", answer); + break; + } + if (k < mdem->backLabels.size()) { + mdem->backLabels[k]->setText(QString::fromStdString(answer)); + } else { + auto label = new QLabel(); + label->setText(QString::fromStdString(answer)); + mdem->backLabels.push_back(label); + mdem->wBack.layout()->addWidget(label); + } + } + mdem->labelCount = choices.size(); + } else if (GroupQuestion* mw = dynamic_cast(question)) { + auto groups = mw->Groups; + std::vector elements; + for (size_t k = 0; k < groups.size(); ++k) { + auto answer = groups[k].name; + elements.push_back(std::format("- {}:", answer)); + for (size_t l = 0; l < groups[k].elements.size(); ++l) { + elements.push_back(std::format(" - {}", groups[k].elements[l])); + } + } + for (size_t k = 0; k < elements.size(); ++k) { + if (k < mdem->backLabels.size()) { + mdem->backLabels[k]->setText(QString::fromStdString(elements[k])); + } else { + auto label = new QLabel(); + label->setText(QString::fromStdString(elements[k])); + mdem->backLabels.push_back(label); + mdem->wBack.layout()->addWidget(label); + } + } + mdem->labelCount = elements.size(); + } +} + +void SwitchPage(int pageIdx); + +std::string getFilename(std::string path) { + std::smatch matches; + auto filenameMatched = std::regex_search(path, matches, lastPathElementExp); + return matches[2].str(); +} + +void updateMdemInfo(std::string filename, bool isChanged) { + currentMdemBuffer->isModified = isChanged; + if (filename.length() > 0) { + std::stringstream ss; + ss << std::format("mdem: {}", filename); + if (isChanged) { + ss << "*"; + } + 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); + } + deckListLabel->setText(QString::fromStdString(ss.str())); + } else { + deckListLabel->setText(""); + } +} + +Mdem* makeMdem() { + auto mdem = new Mdem; + mdem->wMdem.setLayout(&mdem->vMdem); + + QString id = QString("mdem_%1").arg(1); + mdem->wMdem.setObjectName(id); + + // Front + mdem->wFront.setMinimumHeight(60); + mdem->wFront.setLayout(&mdem->hFront); + mdem->wFront.setProperty("first", "true"); + mdem->wMdem.setStyleSheet(QString( + "QWidget#%1 > QWidget {" + "border-right: 1px solid gray;" + "border-bottom: 1px solid gray;" + "border-left: 1px solid gray;" + "background: %2;" + "} " + "QWidget#%1 > QWidget[first=\"true\"] {" + "border-top: 1px solid gray;" + "}" + ).arg(id, MDEM_BACKGROUND)); + + // Add Front Content + mdem->hFront.addWidget(&mdem->wFrontText); + mdem->hFront.addStretch(1); + + mdem->editButton.setText("Edit"); + QObject::connect( + &mdem->editButton, + &QToolButton::clicked, + [mdem]() { + editMdem = mdem; + if (mdem->question) { + editor->setText( + QString::fromStdString( + outputMdem(std::vector{mdem->question}) + ) + ); + editorWindow->show(); + editor->setCursorPosition(1, 2); + } + } + ); + mdem->hFront.addWidget(&mdem->editButton); + + mdem->deleteButton.setText("Delete"); + QObject::connect( + &mdem->deleteButton, + &QToolButton::clicked, + [mdem]() { + if (mdem->question) { + Question* deleted = nullptr; + for (size_t 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); + break; + } + } + makePages(); + SwitchPage(currentPage); + } + if (editMdem == mdem) { + editorWindow->hide(); + } + } + ); + mdem->hFront.addWidget(&mdem->deleteButton); + + mdem->showButton.setText("Show"); + mdem->hFront.addWidget(&mdem->showButton); + + // Back + QVBoxLayout *hBack = new QVBoxLayout(); + mdem->wBack.setLayout(&mdem->hBack); + mdem->vMdem.addWidget(&mdem->wBack); + + // Add Back Content + for (size_t i = 0; i < 20; ++i) { + // @Improve: back label pooling + QLabel *elBackText = new QLabel(); + mdem->hBack.addWidget(elBackText); + mdem->backLabels.push_back(elBackText); + } + + mdem->vMdem.addWidget(&mdem->wFront); + mdem->vMdem.addWidget(&mdem->wBack); + mdem->vMdem.setContentsMargins(0, 0, 0, 0); + mdem->vMdem.setSpacing(0); + + mdem->wBack.hide(); + mdem->wMdem.hide(); + + // Connect button to toggle view + QObject::connect(&mdem->showButton, &QToolButton::clicked, [mdem]() { + if (mdem->wBack.isVisible()) { + mdem->wBack.hide(); + mdem->showButton.setText("Show"); + } else { + mdem->wBack.show(); + mdem->showButton.setText("Hide"); + } + showBacklabels(mdem); + }); + return mdem; +} + + +void CreateMdems(std::vector& questions) { + hMdemScroll->removeItem(mdemSpacer); + + for (Mdem *mdem : mdems) { + if (mdem->wMdem.isVisible()) { + mdem->wMdem.hide(); + } + } + + if (questions.size() > mdems.size()) { + auto amount = questions.size() - mdems.size(); + for (size_t i = 0; i < amount; ++i) { + auto mdem = makeMdem(); + mdems.push_back(mdem); + hMdemScroll->addWidget(&mdem->wMdem); + } + } + + for (size_t i = 0; i < questions.size(); ++i) { + mdems[i]->question = questions[i]; + setupMdem(mdems[i], questions[i]); + if (!mdems[i]->wMdem.isVisible()) { + mdems[i]->wMdem.show(); + } + } + + hMdemScroll->addItem(mdemSpacer); +} + +void update(bool isChanged) { + if (currentPage > -1) { + SwitchPage(currentPage); + } + if (currentMdem.length() > 0) { + updateMdemInfo(getFilename(currentMdem), isChanged); + } +} + +void SwitchPage(int pageIdx) { + currentPage = pageIdx; + + // Hide all pagination buttons + for (auto& button : paginationButtons) { + button->hide(); + } + + int l = 0; + char buffer[50]; + snprintf(buffer, sizeof(buffer), "Page: %d", pageIdx + 1); + paginationLabel->setText(buffer); + + // Hide widgets in mdems + for (auto& mdem : mdems) { + if (mdem->wBack.isVisible()) { + mdem->wBack.hide(); + mdem->showButton.setText("Show"); + } + } + + // Update pagination buttons + for (int k = -DISTANCE; k <= DISTANCE; ++k) { + if (pageIdx + k >= 0 && pageIdx + k < pages.size()) { + auto button = paginationButtons[l]; + snprintf(buffer, sizeof(buffer), "%d", pageIdx + k + 1); + button->setText(buffer); + + if (pageIdx + k != pageIdx) { + button->show(); + } else { + button->hide(); + } + ++l; + } + } + + // Handle first and last buttons + if (pageIdx > 0 && pages.size() > 1) { + firstButton->show(); + } else { + firstButton->hide(); + } + + if (pageIdx < pages.size() - 1 && pages.size() > 1) { + lastButton->show(); + } else { + lastButton->hide(); + } + + // Handle next and previous buttons + if (!pages.empty() && currentPage < pages.size() - 1) { + nextButton->show(); + } else { + nextButton->hide(); + } + + if (!pages.empty() && currentPage >= 1) { + prevButton->show(); + } else { + prevButton->hide(); + } + + Page page; + if (pages.size() <= 0) { + page = Page(); + } else if (pageIdx < pages.size()){ + page = pages[pageIdx]; + } else { + if (pageIdx - 1 < pages.size()) { + page = pages[pageIdx -1]; + } else { + page = pages[0]; + } + } + + std::vector pageSlice( + currentMdemBuffer->questions.begin() + page.start, + currentMdemBuffer->questions.begin() + page.end + ); + CreateMdems(pageSlice); +} + +void reloadMdem(std::string path) { + if (path == "") { + return; + } + + auto toRemove = std::vector(); + 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); + } + + 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 file = std::ifstream(path); + std::string content; + + // Reset the mdem list. + for (auto mdem: mdems) { + mdem->wMdem.hide(); + } + for (auto question: currentMdemBuffer->questions) { + delete question; + } + currentMdemBuffer->questions.clear(); + + if (file) { + std::stringstream buffer; + buffer << file.rdbuf(); + content = buffer.str(); + auto res = transpile(content, true); + + currentMdemBuffer->error = res.error.length() > 0; + if (res.error == "") { + if (res.value.lastTrainedAt == 0) { + currentMdemBuffer->trainedAt = 0; + } else { + auto timezoneOffset = settings->value("timezone").toInt(); + currentMdemBuffer->trainedAt = res.value.lastTrainedAt - 3600 * timezoneOffset; + } + std::cout << std::format("Last trained at: {}", currentMdemBuffer->trainedAt) << std::endl; + + currentMdemBuffer->questions = res.value.questions; + errorView->box.hide(); + } else { + currentMdemBuffer->trainedAt = 0; + std::cout << std::format("Compilation error: {}", res.error) << std::endl; + + for (auto question: res.value.questions) { + delete question; + } + + // Show errors. + hMdemScroll->removeItem(mdemSpacer); + errorView->label.setText( + QString::fromStdString( + std::format( + "Error while transpiling {}: {} ({}:{})", + filename, + res.error, + res.row, + res.column + ) + ) + ); + 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; + } + + hideQuestionElements(); + trainWindow->close(); +} + +void pickDirectory(QString directory) { + currentPath = directory.toStdString(); + + // Update tree view. + if (directory.length() <= 0) { + membaseLabel->setText(directory); + return; + } + mdemList->setRootIndex(model->setRootPath(directory)); + std::smatch matches; + + // Update label. + membaseLabel->setText(QString::fromStdString( + std::format( + "memorybase: {}", + getFilename(currentPath) + ) + )); + + reloadMdem(""); +} + +void setupEditorSave() { + auto res = transpile(editor->text().toStdString(), true); + if (res.error.length() > 0) { + currentMdemBuffer->trainedAt = 0; + for (auto question: res.value.questions) { + delete question; + } + QMessageBox::information( + nullptr, + "Editing error", + QString::fromStdString(res.error) + ); + } else { + if (editMdem) { + if (res.value.questions.size() <= 0) { + QMessageBox::information( + nullptr, + "Editing error", + "There are no questions in your input." + ); + } else if (res.value.questions.size() == 1) { + auto oldQuestion = editMdem->question; + editMdem->question = res.value.questions[0]; + for (size_t i = 0; i < currentMdemBuffer->questions.size(); ++i) { + if (currentMdemBuffer->questions[i] == oldQuestion) { + currentMdemBuffer->questions.erase(currentMdemBuffer->questions.begin() + i); + delete oldQuestion; + currentMdemBuffer->questions.insert(currentMdemBuffer->questions.begin() + i, res.value.questions[0]); + break; + } + } + setupMdem(editMdem, res.value.questions[0]); + showBacklabels(editMdem); + editorWindow->hide(); + updateMdemInfo(getFilename(currentMdem), true); + } else { + QMessageBox::information( + nullptr, + "Editing error", + "There are more than question in your input. Please enter one." + ); + } + } else { + if (res.value.questions.size() <= 0) { + QMessageBox::information( + nullptr, + "Editing error", + "There are no questions in your input." + ); + } else { + currentMdemBuffer->questions.insert( + currentMdemBuffer->questions.begin(), + res.value.questions.begin(), + res.value.questions.end() + ); + makePages(); + SwitchPage(0); + editorWindow->hide(); + updateMdemInfo(getFilename(currentMdem), true); + } + } + } +}; + +void saveMdem() { + auto filename = getFilename(currentMdem); + std::ofstream out(currentMdem); + out << outputMdem(currentMdemBuffer->questions, currentMdemBuffer->trainedAt); + updateMdemInfo(getFilename(currentMdem), false); +} + + +QMainWindow *initMdemListWindow() { + QMainWindow* window = new QMainWindow; + auto *settingsWindow = initSettings(); + + { // Menu bar. + QMenuBar *menuBar = new QMenuBar; + QFileDialog *fileDialog = new QFileDialog; + + QMenu *menu = new QMenu("File"); + QAction *actionOpen = menu->addAction("Open memorybase"); + QObject::connect(actionOpen, &QAction::triggered, [fileDialog]() { + fileDialog->setDirectory(QDir::homePath()); + fileDialog->setFileMode(QFileDialog::FileMode::Directory); + fileDialog->open(); + QObject::disconnect(fileDialog, 0, 0, 0); + fileDialog->connect( + fileDialog, + &QFileDialog::fileSelected, + pickDirectory + ); + }); + + QAction *openSettings = menu->addAction("Settings"); + QObject::connect( + openSettings, + &QAction::triggered, + [settingsWindow]() { + settingsWindow->show(); + }); + + menuBar->addMenu(menu); + window->setMenuBar(menuBar); + } + + { // Setup editor. + editorWindow = new QMainWindow; + + editorWindow->setWindowTitle("QScintilla Simple Editor"); + editorWindow->resize(800, 600); + QWidget *wEditor = new QWidget; + QVBoxLayout *vlEditor = new QVBoxLayout; + wEditor->setLayout(vlEditor); + + QsciLexerCPP *lexer = new QsciLexerCPP; + editor = new QsciScintilla; + editor->setLexer(lexer); + editor->setUtf8(true); + editor->setMarginWidth(0, 15); + editor->zoomIn(2); + + QHBoxLayout *buttonLayout = new QHBoxLayout; + QWidget *editorButtons = new QWidget; + auto btnSaveEditor = new QPushButton; + + editorButtons->setLayout(buttonLayout); + btnSaveEditor->setText(QString::fromStdString("Save")); + QObject::connect( + btnSaveEditor, + &QToolButton::clicked, + setupEditorSave + ); + buttonLayout->addWidget(btnSaveEditor); + + vlEditor->addWidget(editor); + vlEditor->addWidget(editorButtons); + + QShortcut* shortcutSave = new QShortcut(QKeySequence("Ctrl+S"), editorWindow); + QObject::connect(shortcutSave, &QShortcut::activated, []() { + if (editor->isVisible()) { + setupEditorSave(); + } + }); + + editorWindow->setCentralWidget(wEditor); + } + + QSplitter *hSplitter = new QSplitter(); + QWidget *leftWidget = new QWidget(); + { // Left side. + QVBoxLayout *leftLayout = new QVBoxLayout(); + QWidget *leftTop = new QWidget(); + QVBoxLayout *vLeftTop = new QVBoxLayout(); + mdemList = new QTreeView(); + membaseLabel = new QLabel(); + model = new QFileSystemModel(); + membaseLabel->setStyleSheet( + "font-size: 17px;" + "font-weight: 400;" + ); + mdemSpacer = new QSpacerItem(50, 50, QSizePolicy::Minimum, QSizePolicy::Expanding); + + leftWidget->setLayout(leftLayout); + leftLayout->addWidget(leftTop); + leftTop->setLayout(vLeftTop); + leftTop->setMinimumSize(0, 40); + vLeftTop->addWidget(membaseLabel); + // Hide all columns except the first one + mdemList->setModel(model); + + QObject::connect( + mdemList, + &QTreeView::doubleClicked, + [](const QModelIndex &index) { + auto fileInfo = model->fileInfo(index); + reloadMdem(fileInfo.filePath().toStdString()); + } + ); + + for (int col = 1; col < model->columnCount(); ++col) { + mdemList->hideColumn(col); + } + model->setHeaderData(0, Qt::Horizontal, QObject::tr("Custom Name")); + leftLayout->addWidget(mdemList); + } + + + // RightSide + QWidget *rightWidget = new QWidget(); + QVBoxLayout *rightLayout = new QVBoxLayout(); + rightWidget->setLayout(rightLayout); + + { // Mdem list top. + QWidget *top = new QWidget(); + QHBoxLayout *hTop = new QHBoxLayout(); + deckListLabel = new QLabel(); + deckListLabel->setStyleSheet( + "font-size: 17px;" + "font-weight: 400;" + ); + top->setMinimumSize(0, 40); + top->setLayout(hTop); + rightLayout->addWidget(top); + + hTop->addWidget(deckListLabel); + hTop->addStretch(1); + + add = new QToolButton; + btnSaveFile = new QToolButton; + load = new QToolButton; + auto cbAlgorithm = new QComboBox; + practice = new QToolButton; + + // Buttons + auto buttons = new QWidget(); + auto vlButtons = new QVBoxLayout(); + buttons->setLayout(vlButtons); + auto buttonsTop = new QWidget(); + auto hlButtonsTop = new QHBoxLayout(); + buttonsTop->setLayout(hlButtonsTop); + auto buttonsBottom = new QWidget(); + auto hlButtonsBottom = new QHBoxLayout(); + buttonsBottom->setLayout(hlButtonsBottom); + add->setText("Add"); + btnSaveFile->setText("Save"); + load->setText("Load"); + hlButtonsTop->addWidget(add); + hlButtonsTop->addWidget(btnSaveFile); + hlButtonsTop->addWidget(load); + cbAlgorithm->addItem("Spaced", SPACED); + cbAlgorithm->addItem("Random", RANDOM); + cbAlgorithm->addItem("Primary", PRIMARY); + practice->setText("Practice"); + hlButtonsBottom->addWidget(cbAlgorithm); + hlButtonsBottom->addWidget(practice); + vlButtons->addWidget(buttonsTop); + vlButtons->addWidget(buttonsBottom); + hlButtonsTop->setContentsMargins(0, 0, 0, 0); + hlButtonsBottom->setContentsMargins(0, 5, 0, 0); + + hTop->addWidget(buttons); + + QObject::connect(add, &QToolButton::clicked, []() { + editMdem = nullptr; + editorWindow->show(); + editor->setText(""); + }); + QObject::connect(load, &QToolButton::clicked, []() { + reloadMdem(currentMdem); + }); + QObject::connect(btnSaveFile, &QToolButton::clicked, []() { + saveMdem(); + }); + QObject::connect( + practice, + &QToolButton::clicked, + [cbAlgorithm]() { + trainWindow->show(); + trainWindow->resize(600, 300); + initiatePractice( + currentMdemBuffer, + static_cast(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(); + hMdemScroll = new QVBoxLayout(); + mdemScroll->setWidget(mdemContainer); + mdemScroll->setWidgetResizable(true); + mdemContainer->setLayout(hMdemScroll); + rightLayout->addWidget(mdemScroll); + hMdemScroll->addItem(mdemSpacer); + } + + { // Pagination + hSplitter->addWidget(leftWidget); + hSplitter->addWidget(rightWidget); + hSplitter->setStretchFactor(0, 1); + hSplitter->setStretchFactor(1, 3); + + auto pagination = new QWidget(); + auto hPagination = new QHBoxLayout(); + pagination->setLayout(hPagination); + + firstButton = new QToolButton(); + firstButton->setText(QString::fromStdString("<<")); + hPagination->addWidget(firstButton); + firstButton->hide(); + QObject::connect(firstButton, &QToolButton::clicked, []() { + if (pages.size() > 0) { + SwitchPage(0); + } + }); + + prevButton = new QToolButton(); + prevButton->setText(QString::fromStdString("<")); + hPagination->addWidget(prevButton); + prevButton->hide(); + QObject::connect(prevButton, &QToolButton::clicked, []() { + if (pages.size() > 0) { + SwitchPage(currentPage - 1); + } + }); + + for (size_t i = 0; i < PER_PAGE; i++) { + auto elButton = new QToolButton(); + elButton->setText(QString("%1").arg(i+1)); + hPagination->addWidget(elButton); + elButton->hide(); + QObject::connect(elButton, &QToolButton::clicked, [elButton]() { + auto pageNum = std::stoi(elButton->text().toStdString().c_str()); + auto pageIdx = pageNum - 1; + if (pageIdx < pages.size()) { + SwitchPage(pageIdx); + } + }); + paginationButtons.push_back(elButton); + } + + nextButton = new QToolButton(); + nextButton->setText(QString::fromStdString(">")); + hPagination->addWidget(nextButton); + nextButton->hide(); + QObject::connect(nextButton, &QToolButton::clicked, []() { + if (pages.size() > 0) { + SwitchPage(currentPage + 1); + } + }); + + lastButton = new QToolButton(); + lastButton->setText(QString::fromStdString(">>")); + hPagination->addWidget(lastButton); + lastButton->hide(); + QObject::connect(lastButton, &QToolButton::clicked, []() { + if (pages.size() > 0) { + SwitchPage(pages.size() - 1); + } + }); + hPagination->addStretch(1); + paginationLabel = new QLabel(); + hPagination->addWidget(paginationLabel); + rightLayout->addWidget(pagination); + } + + initTrainWindow(); + + window->setCentralWidget(hSplitter); + window->show(); + + if (currentPath.length() > 0) { + pickDirectory(QString::fromStdString(currentPath)); + } + + QShortcut* shortcutSaveFile = new QShortcut(QKeySequence("Ctrl+S"), window); + QObject::connect(shortcutSaveFile, &QShortcut::activated, []() { + saveMdem(); + }); + + return window; +} diff --git a/src/qtapp/settings.cpp b/src/qtapp/settings.cpp new file mode 100644 index 0000000..2b1e8e1 --- /dev/null +++ b/src/qtapp/settings.cpp @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdemList.h" +#include "api.h" +#include "qscilexer.h" + +QSettings *settings; + +QWidget *initSettings () { + QString configDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); + QDir().mkpath(configDir); + QString settingsFile = configDir + "/mdem.ini"; + settings = new QSettings(settingsFile, QSettings::IniFormat); + auto* settingsWindow = new QWidget; + settingsWindow->setWindowTitle("Settings"); + + auto formLayout = new QFormLayout; + + auto characterWrap = new QSpinBox; + 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 QDoubleSpinBox; + notRemembered->setRange(0, 100); + formLayout->addRow("Not remembered:", notRemembered); + + auto* hard = new QDoubleSpinBox; + hard->setRange(0, 100); + formLayout->addRow("Hard:", hard); + + auto* medium = new QDoubleSpinBox; + medium->setRange(0, 100); + formLayout->addRow("Medium:", medium); + + auto* easy = new QDoubleSpinBox; + easy->setRange(0, 100); + formLayout->addRow("Easy:", easy); + + auto* btnSaveSettings = new QPushButton("Save"); + auto* mainLayout = new QVBoxLayout; + + // @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, + [saveSettings]() { + saveSettings(); + } + ); + + QShortcut* shortcutSave = new QShortcut(QKeySequence("Ctrl+S"), settingsWindow); + QObject::connect(shortcutSave, &QShortcut::activated, [saveSettings]() { + saveSettings(); + }); + + mainLayout->addLayout(formLayout); + mainLayout->addWidget(btnSaveSettings); + + settingsWindow->setLayout(mainLayout); + return settingsWindow; +} + + diff --git a/src/qtapp/trainWindow.cpp b/src/qtapp/trainWindow.cpp index 40b5a75..ecfd4dc 100644 --- a/src/qtapp/trainWindow.cpp +++ b/src/qtapp/trainWindow.cpp @@ -31,7 +31,8 @@ #include #include -#include "main.h" +#include "settings.h" +#include "mdemList.h" #include "trainWindow.h" #include "parser.h" @@ -75,15 +76,15 @@ public: Qt::GlobalColor color; auto colorIdx = index.data(Qt::UserRole + 1).toInt() ; switch (colorIdx) { - case NEUTRAL: + case NEUTRAL: { color = Qt::lightGray; - break; - case INCORRECT: + } break; + case INCORRECT: { color = Qt::red; - break; - case CORRECT: + } break; + case CORRECT: { color = Qt::green; - break; + } break; } painter->setOpacity(0.5); if (color) { @@ -248,7 +249,7 @@ QToolButton *btnEasy; QStandardItem* acquireItem() { if (itemPool.size() <= 0) { - for (int i = 0; i < ITEM_POOL_CHUNK; ++i) { + for (size_t i = 0; i < ITEM_POOL_CHUNK; ++i) { itemPool.push_back(new QStandardItem); } } @@ -268,7 +269,7 @@ void releaseAllItems() { std::endl; auto releaseAllInModel = [](QStandardItemModel *model) { auto itemCount = model->rowCount(); - for (int i = 0; i < itemCount; ++i) { + for (size_t i = 0; i < itemCount; ++i) { auto item = model->item(0); model->takeRow(item->row()); releaseItem(&item); @@ -357,7 +358,7 @@ void setupOrderQuestion(MultiElementQuestion *question) { btnTriggerAnswer, &QToolButton::clicked, [question](bool checked) { - for (int i = 0; i < orderModel->rowCount(); ++i) { + for (size_t 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; @@ -399,7 +400,7 @@ void setupMultiChoiceQuestion(MultiElementQuestion *question) { btnTriggerAnswer, &QToolButton::clicked, [question](bool checked) { - for (int i = 0; i < multiChoiceModel->rowCount(); ++i) { + for (size_t i = 0; i < multiChoiceModel->rowCount(); ++i) { auto item = multiChoiceModel->item(i, 0); auto isCorrect = question->Choices[i].IsCorrect == (item->checkState() == Qt::Checked); if (isCorrect) { @@ -459,7 +460,7 @@ void setupGroupQuestion(GroupQuestion *question) { for (int k = 0; k < groupViews.size(); k++) { groupViews[k]->widget.hide(); } - for (int i = 0; i < question->Groups.size(); i++) { + for (size_t i = 0; i < question->Groups.size(); i++) { GroupView *groupView; if (i < groupViews.size()) { groupView = groupViews[i]; @@ -479,11 +480,11 @@ void setupGroupQuestion(GroupQuestion *question) { btnTriggerAnswer, &QToolButton::clicked, [question](bool checked) { - for (int i = 0; i < groupItemModel->rowCount(); ++i) { + for (size_t 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++) { + for (size_t i = 0; i < groupViews.size(); i++) { auto groupView = groupViews[i]; auto group = question->Groups[i]; for (int j = 0; j < groupView->itemModel.rowCount(); ++j) { @@ -568,7 +569,7 @@ void setupNextQuestion() { setupQuestion(practiceBuffer->questions[currentQuestionIndex]); } updatePaginationVisibility(); - } break; + } break; case RANDOM: { auto questionCandidates = practiceBuffer->questions; if (currentQuestionIndex > -1) { @@ -584,13 +585,13 @@ void setupNextQuestion() { } } } - } break; + } break; case SPACED: { auto questionCandidates = std::vector(); time_t time = getTime(); auto lastTrainedAt = practiceBuffer->trainedAt; practiceBuffer->trainedAt = time; - for (int i = 0; i < practiceBuffer->questions.size(); ++i) { + for (size_t i = 0; i < practiceBuffer->questions.size(); ++i) { auto cooldownSeconds = practiceBuffer->questions[i]->Cooldown * 3600; auto cooldownEndsAt = lastTrainedAt + cooldownSeconds; if (i != currentQuestionIndex && cooldownEndsAt <= time) { @@ -615,7 +616,7 @@ void setupNextQuestion() { } } update(true); - } break; + } break; } } diff --git a/src/transpiler/lexer.cpp b/src/transpiler/lexer.cpp index 05d85fe..2df6326 100644 --- a/src/transpiler/lexer.cpp +++ b/src/transpiler/lexer.cpp @@ -113,6 +113,7 @@ Result> tokenizeMdem(const std::string& fileRunes) { column = 0; } + // Add escape char if (c == '\\') { i += 1; if (i < fileRunes.size()) { @@ -139,7 +140,7 @@ Result> tokenizeMdem(const std::string& fileRunes) { // EmitTokens switch (c) { - case '[': + case '[': { makeTokenWithTokenBuffer( TokenType::CooldownStart, 1, @@ -149,8 +150,8 @@ Result> tokenizeMdem(const std::string& fileRunes) { previousColumn = column; textStarted = false; identifierStarted = true; - break; - case ']': + } break; + case ']': { if (!identifierStarted) { return { tokens, @@ -168,8 +169,8 @@ Result> tokenizeMdem(const std::string& fileRunes) { previousColumn = column; textStarted = false; identifierStarted = false; - break; - case '-': + } break; + case '-': { makeTokenWithTokenBuffer( TokenType::ElementDashStart, 1, @@ -178,8 +179,8 @@ Result> tokenizeMdem(const std::string& fileRunes) { previousRow = row; previousColumn = column; textStarted = false; - break; - case '^': + } break; + case '^': { makeTokenWithTokenBuffer( TokenType::ElementOrderModifier, 1, @@ -188,8 +189,8 @@ Result> tokenizeMdem(const std::string& fileRunes) { previousRow = row; previousColumn = column; textStarted = false; - break; - case ':': + } break; + case ':': { makeTokenWithTokenBuffer( TokenType::MatchGroupEnd, 1, @@ -198,8 +199,8 @@ Result> tokenizeMdem(const std::string& fileRunes) { previousRow = row; previousColumn = column; textStarted = false; - break; - case '>': + } break; + case '>': { makeTokenWithTokenBuffer( TokenType::QuestionEnd, 1, @@ -207,8 +208,9 @@ Result> tokenizeMdem(const std::string& fileRunes) { ); previousRow = row; previousColumn = column; - break; - case '+': + textStarted = false; + } break; + case '+': { makeTokenWithTokenBuffer( TokenType::ElementPlusStart, 1, @@ -217,7 +219,7 @@ Result> tokenizeMdem(const std::string& fileRunes) { previousRow = row; previousColumn = column; textStarted = false; - break; + } break; } column += 1; @@ -262,7 +264,7 @@ std::string Token::ToString(const TokenType* ttype) { case TokenType::Cooldown: return "cooldown"; case TokenType::CooldownStart: return "start of cooldown"; case TokenType::CooldownEnd: return "end of cooldown"; - case TokenType::StartOfFile: return "start of the file"; + case TokenType::StartOfFile: return "start of the file"; case TokenType::EndOfFile: return "end of file"; default: return "unrecognized token"; }