Files
mdemory/src/cpp/qtapp/main.cpp
2024-10-06 12:54:44 +03:00

653 lines
17 KiB
C++

#include <cstdio>
#include <format>
#include <fstream>
#include <iostream>
#include <qabstractbutton.h>
#include <qboxlayout.h>
#include <qlabel.h>
#include <qlayoutitem.h>
#include <qmainwindow.h>
#include <qnamespace.h>
#include <qobjectdefs.h>
#include <qsizepolicy.h>
#include <qtoolbutton.h>
#include <qwidget.h>
#include <qwindow.h>
#include <qwindowdefs.h>
#include <regex>
#include <string>
#include <QApplication>
#include <QMainWindow>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QToolButton>
#include <QFileSystemModel>
#include <QTreeView>
#include <QSplitter>
#include <QVariant>
#include <QMessageBox>
#include <QScrollArea>
#include <QSpacerItem>
#include <QFile>
#include <QTime>
#include <QRegularExpression>
#include <QStringList>
#include <QListView>
#include <QWindow>
#include <qabstractitemmodel.h>
#include "api.h"
#include "parser.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;
QVector<QLabel*> backLabels;
QToolButton showButton;
int labelCount;
};
struct ErrorView {
QWidget box;
QVBoxLayout layout;
QLabel label;
};
struct Page {
int start;
int end;
};
QString workingPath = "/home/jorenchik/Code/mdemory/memorybase";
std::string currentPath = "";
// Mdem list
QLabel *deckListLabel;
QVBoxLayout *hMdemScroll;
QList<Mdem*> mdems = QList<Mdem*>();
std::vector<Question*> questions = std::vector<Question*>();
QSpacerItem *mdemSpacer;
std::vector<ErrorView*> errorPool;
std::vector<ErrorView*> errorViews;
// Pagination
int currentPage = -1;
std::vector<Page> pages;
QList<QToolButton*> paginationButtons;
QToolButton* prevButton;
QToolButton* firstButton;
QToolButton* lastButton;
QToolButton* nextButton;
QLabel* paginationLabel;
// Mdem actions
QToolButton *load;
QToolButton *practice;
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();
}
}
}
}
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->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) {
// @Fix: 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<Question*>& 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) {
if (MultiElementQuestion* mw = dynamic_cast<MultiElementQuestion*>(questions[i])) {
mdems[i]->wFrontText.setText(
QString::fromStdString(mw->QuestionText)
);
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 < mdems[i]->backLabels.size()) {
mdems[i]->backLabels[k]->setText(QString::fromStdString(answer));
} else {
auto label = new QLabel();
label->setText(QString::fromStdString(answer));
mdems[i]->backLabels.push_back(label);
mdems[i]->wBack.layout()->addWidget(label);
}
}
mdems[i]->labelCount = choices.size();
} else if (GroupQuestion* mw = dynamic_cast<GroupQuestion*>(questions[i])) {
mdems[i]->wFrontText.setText(
QString::fromStdString(mw->QuestionText)
);
auto groups = mw->Groups;
std::vector<std::string> 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 < mdems[i]->backLabels.size()) {
mdems[i]->backLabels[k]->setText(QString::fromStdString(elements[k]));
} else {
auto label = new QLabel();
label->setText(QString::fromStdString(elements[k]));
mdems[i]->backLabels.push_back(label);
mdems[i]->wBack.layout()->addWidget(label);
}
}
mdems[i]->labelCount = elements.size();
}
if (!mdems[i]->wMdem.isVisible()) {
mdems[i]->wMdem.show();
}
}
hMdemScroll->addItem(mdemSpacer);
}
void makePages() {
pages.clear();
auto len = questions.size();
for (int i = 0; i < (len / PER_PAGE) + 1; i++) {
auto startingIndex = PER_PAGE * i ;
auto amount = PER_PAGE;
if (i == questions.size() / PER_PAGE) {
amount = questions.size() % PER_PAGE;
}
pages.push_back(Page{startingIndex, startingIndex + amount});
}
}
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();
}
// Handle page slice
const Page& page = pages[pageIdx];
std::vector<Question*> pageSlice(questions.begin() + page.start, questions.begin() + page.end);
CreateMdems(pageSlice);
}
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;
};
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);
errorView->box.hide();
}
}
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 loadMdem() {
auto file = std::ifstream(currentPath);
std::string content;
if (file) {
std::stringstream buffer;
buffer << file.rdbuf();
content = buffer.str();
auto res = transpile(content, true);
for (auto question: questions) {
delete question;
}
questions.clear();
std::smatch matches;
auto filenameMatched = std::regex_search(currentPath, matches, lastPathElementExp);
auto filename = matches[2];
deckListLabel->setText(
QString::fromStdString(std::format("mdem: {}", filename.str()))
);
while (errorViews.size() > 0) {
auto errorView = errorViews.back();
errorViews.pop_back();
errorView->box.hide();
releaseError(&errorView);
}
if (res.error == "") {
time_t trainedAt;
if (res.value.lastTrainedAt == 0) {
trainedAt = 0;
} else {
trainedAt = res.value.lastTrainedAt + 3600 * 2;
}
std::cout << std::format("Last trained at: {}", trainedAt) << std::endl;
questions = res.value.questions;
makePages();
SwitchPage(0);
} else {
std::cout << std::format("Compilation error: {}", res.error) << std::endl;
// Show errors.
hMdemScroll->removeItem(mdemSpacer);
auto errorView = acquireError();
errorView->label.setText(
QString::fromStdString(
std::format(
"Error while transpiling {}: {} ({}:{})",
filename.str(),
res.error,
res.row,
res.column
)
)
);
errorView->box.show();
hMdemScroll->addWidget(&errorView->box);
hMdemScroll->addItem(mdemSpacer);
for (auto mdem: mdems) {
if (mdem->wMdem.isVisible()) {
mdem->wMdem.hide();
}
}
}
} else {
std::cout << std::format("Could not open the file: {}", currentPath) << std::endl;
}
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
QMainWindow window;
QSplitter *hSplitter = new QSplitter();
// LeftSide
QWidget *leftWidget = new QWidget();
QVBoxLayout *leftLayout = new QVBoxLayout();
QWidget *leftTop = new QWidget();
QVBoxLayout *vLeftTop = new QVBoxLayout();
QLabel *mdemLabel = new QLabel("mdems");
QFileSystemModel *model = new QFileSystemModel();
QTreeView *mdemList = new QTreeView();
mdemLabel->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(mdemLabel);
model->setRootPath(workingPath);
// Hide all columns except the first one
mdemList->setModel(model);
QObject::connect(
mdemList,
&QTreeView::doubleClicked,
[model](const QModelIndex &index) {
auto fileInfo = model->fileInfo(index);
currentPath = fileInfo.filePath().toStdString();
loadMdem();
}
);
QModelIndex rootIndex = model->index(
"/home/jorenchik/Code/mdemory/memorybase"
);
mdemList->setRootIndex(rootIndex);
for (int col = 1; col < model->columnCount(); ++col) {
mdemList->hideColumn(col);
}
model->setHeaderData(0, Qt::Horizontal, QObject::tr("Custom Name"));
leftLayout->addWidget(mdemList);
// DeckList
QLabel *deckLabel = new QLabel("decks");
QWidget *deckLabelBox = new QWidget();
QVBoxLayout *vDeckLabelBox = new QVBoxLayout();
QListView *deckList = new QListView();
deckLabel->setStyleSheet(
"font-size: 17px;"
"font-weight: 400;"
);
deckLabelBox->setLayout(vDeckLabelBox);
vDeckLabelBox->addWidget(deckLabel);
deckLabelBox->setMinimumSize(0, 40);
leftLayout->addWidget(deckLabelBox);
leftLayout->addWidget(deckList);
// RightSide
QWidget *rightWidget = new QWidget();
QVBoxLayout *rightLayout = new QVBoxLayout();
rightWidget->setLayout(rightLayout);
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);
load = new QToolButton();
practice = new QToolButton();
hTop->addWidget(load);
hTop->addWidget(practice);
// Buttons
load->setText("Load");
practice->setText("Practice");
QObject::connect(load, &QToolButton::clicked, &loadMdem);
// 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();
QObject::connect(
practice,
&QToolButton::clicked,
[](bool checked) {
trainWindow->show();
trainWindow->resize(600, 300);
setQuestions(questions);
}
);
window.setCentralWidget(hSplitter);
window.show();
return app.exec();
}