#include "mainwindow.h" #include "ui_mainwindow.h" #define PRIORITIES_KEY "priorities" #define STATUS_KEY "status" #define BOARDS_KEY "boards" #define FILTERS_KEY "filters" #include #include #include #include #include #include "prefdialog.h" #include "aboutdialog.h" #include "namedialog.h" #include "taskdialog.h" #include "filterdialog.h" #include "boardconfigdialog.h" #include "../tools.h" #include "../services/taskstateservice.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); ui->boardStatus->setVisible(false); this->menuSelectedBoardItem = nullptr; this->menuSelectedFilterItem = nullptr; this->menuSelectedTaskItem = nullptr; init(); this->selectedBoardIndex = -1; // Change "name" column size ui->taskList->header()->resizeSection(0, 520); // Change "status" column size ui->taskList->header()->resizeSection(1, 150); connect(ui->actionPreferences, &QAction::triggered, this, &MainWindow::openPreferences); connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::openAbout); connect(ui->actionNew, &QAction::triggered, this, &MainWindow::onNewBoardClick); connect(ui->actionNew_task, &QAction::triggered, this, &MainWindow::onNewTaskClick); connect(ui->actionNew_filter, &QAction::triggered, this, &MainWindow::onNewFilterClick); connect(ui->boardList, &QListWidget::currentRowChanged, this, &MainWindow::onBoardSelected); connect(ui->boardList, &QListWidget::customContextMenuRequested, this, &MainWindow::prepareBoardMenu); connect(ui->filterListWidget, &QListWidget::currentRowChanged, this, &MainWindow::onFilterSelected); connect(ui->filterListWidget, &QListWidget::customContextMenuRequested, this, &MainWindow::prepareFilterMenu); connect(ui->taskList, &QTreeWidget::itemDoubleClicked, this, &MainWindow::onEditTask); connect(ui->taskList, &QTreeWidget::customContextMenuRequested, this, &MainWindow::prepareTaskMenu); ui->boardList->setContextMenuPolicy(Qt::CustomContextMenu); ui->taskList->setContextMenuPolicy(Qt::CustomContextMenu); ui->filterListWidget->setContextMenuPolicy(Qt::CustomContextMenu); } MainWindow::~MainWindow() { for (uint16_t i = 0; i < boards.count(); i++) { Board *b = boards.takeAt(i); delete b; } delete ui; } void MainWindow::openPreferences() { PrefDialog dialog(this); if (dialog.exec() == QDialog::DialogCode::Accepted) { TaskStateService::getInstance()->updatePriorities(dialog.getPriorities()); TaskStateService::getInstance()->updateStatuses(dialog.getStatus()); save(); redrawTaskTree(); } } void MainWindow::openAbout() { AboutDialog dialog(this); dialog.exec(); } void MainWindow::prepareBoardMenu(const QPoint &pos) { QMenu menu(this); this->menuSelectedBoardItem = ui->boardList->itemAt(pos); if (this->menuSelectedBoardItem != nullptr) { QAction *renameAction = new QAction(tr("Edit board"), this); connect(renameAction, &QAction::triggered, this, &MainWindow::onEditNameBoardMenu); menu.addAction(renameAction); QAction *deleteAction = new QAction(tr("Delete the board and all the tasks"), this); connect(deleteAction, &QAction::triggered, this, &MainWindow::onRemoveBoardMenu); menu.addAction(deleteAction); menu.addSeparator(); } QAction *addAction = new QAction(tr("New board"), this); connect(addAction, &QAction::triggered, this, &MainWindow::onNewBoardClick); menu.addAction(addAction); menu.exec(ui->boardList->mapToGlobal(pos)); } void MainWindow::prepareTaskMenu(const QPoint &pos) { bool show = false; QMenu menu(this); this->menuSelectedTaskItem = ui->taskList->itemAt(pos); if (menuSelectedTaskItem != nullptr) { show = true; QAction *renameAction = new QAction(tr("Edit the task"), this); connect(renameAction, &QAction::triggered, this, &MainWindow::onEditNameTaskMenu); menu.addAction(renameAction); if (selectedBoardIndex > -1) { QAction *deleteAction = new QAction(tr("Delete from the board"), this); connect(deleteAction, &QAction::triggered, this, &MainWindow::onRemoveTaskMenu); menu.addAction(deleteAction); } menu.addSeparator(); } if (selectedBoardIndex > -1) { show = true; QAction *addAction = new QAction(tr("New task"), this); connect(addAction, &QAction::triggered, this, &MainWindow::onNewTaskClick); menu.addAction(addAction); } if (show) { menu.exec(ui->taskList->mapToGlobal(pos)); } } void MainWindow::onNewBoardClick() { NameDialog dialog("Create a board", "New empty board", "", this); if (dialog.exec() == QDialog::DialogCode::Accepted) { QString name = dialog.getChoosenName(); QString desc = dialog.getDescription(); Board *b = new Board(name, desc); boards.append(b); QListWidgetItem *item = new QListWidgetItem(name); item->setStatusTip(desc); ui->boardList->addItem(item); save(); } } void MainWindow::onNewTaskClick() { if (selectedBoardIndex > -1) { TaskDialog dialog(this); if (dialog.exec() == QDialog::DialogCode::Accepted) { Task t = dialog.getTask(); Board *b = boards[selectedBoardIndex]; b->add(t); QTreeWidgetItem *item = new QTreeWidgetItem(); updateTaskRow(item, t); ui->taskList->addTopLevelItem(item); save(); } } } void MainWindow::onNewFilterClick() { FilterDialog dialog("New filter", boards, this); if (dialog.exec() == QDialog::DialogCode::Accepted) { Filter f = dialog.getFilter(); filters.append(f); QListWidgetItem *item = new QListWidgetItem(f.getName()); item->setToolTip(f.getDescription()); ui->filterListWidget->addItem(item); save(); } } void MainWindow::onBoardSelected(int i) { selectedBoardIndex = i; selectedFilterIndex = -1; ui->filterListWidget->blockSignals(true); ui->filterListWidget->setCurrentIndex(ui->filterListWidget->currentIndex().sibling(-1, -1)); ui->filterListWidget->blockSignals(false); filterResult.clear(); if (selectedBoardIndex > -1) { Board *b = boards[selectedBoardIndex]; ui->label->setText(b->getName()); if (b->getDescription().length() > 0) { ui->boardDescription->setText(b->getDescription()); ui->boardDescription->setEnabled(true); } else { ui->boardDescription->setText("No description"); ui->boardDescription->setEnabled(false); } ui->actionNew_task->setDisabled(false); } else { ui->label->setText("No board selected"); ui->boardDescription->clear(); ui->actionNew_task->setDisabled(true); } redrawTaskTree(); } void MainWindow::onFilterSelected(int i) { selectedFilterIndex = i; selectedBoardIndex = -1; ui->boardList->blockSignals(true); ui->boardList->setCurrentIndex(ui->boardList->currentIndex().sibling(-1, -1)); ui->boardList->blockSignals(false); filterResult.clear(); ui->actionNew_task->setDisabled(true); if (selectedFilterIndex > -1) { Filter f = filters[selectedFilterIndex]; ui->label->setText(f.getName()); if (f.getDescription().length() > 0) { ui->boardDescription->setText(f.getDescription()); ui->boardDescription->setEnabled(true); } else { ui->boardDescription->setText("No description"); ui->boardDescription->setEnabled(false); } } else { ui->label->setText("No board selected"); ui->boardDescription->clear(); } redrawTaskTree(); } void MainWindow::onEditTask(QTreeWidgetItem *item) { Task *t = getSelectedTask(); if (t != nullptr) { TaskDialog dialog(t, this); if (dialog.exec() == QDialog::DialogCode::Accepted) { Task editedTask = dialog.getTask(); t->update(editedTask); updateTaskRow(item, editedTask); save(); redrawBoardStatus(); if (selectedFilterIndex > -1) { redrawTaskTree(); } } } } void MainWindow::onRemoveBoardMenu() { if (menuSelectedBoardItem != nullptr) { int i = ui->boardList->indexFromItem(menuSelectedBoardItem).row(); if (i == -1) { return; } QMessageBox::StandardButton result = QMessageBox::question(this, "Delete a board", "Do you want to delete this board?"); if (result == QMessageBox::Yes) { boards.removeAt(i); delete ui->boardList->takeItem(i); if (selectedBoardIndex == i) { selectedBoardIndex = -1; } redrawTaskTree(); save(); } } } void MainWindow::onRemoveTaskMenu() { if (selectedBoardIndex > -1 && ui->taskList->selectedItems().length() == 1) { QMessageBox::StandardButton result = QMessageBox::question(this, "Delete a task", "Do you want to delete this task?"); if (result == QMessageBox::Yes) { int16_t i = ui->taskList->indexOfTopLevelItem(ui->taskList->currentItem()); Board *b = boards[selectedBoardIndex]; b->remove(i); redrawTaskTree(); save(); } } } void MainWindow::onRemoveFilterMenu() { if (menuSelectedFilterItem != nullptr) { int i = ui->filterListWidget->indexFromItem(menuSelectedFilterItem).row(); if (i == -1) { return; } QMessageBox::StandardButton result = QMessageBox::question(this, "Delete a filter", "Do you want to delete this filter?"); if (result == QMessageBox::Yes) { filters.removeAt(i); delete ui->filterListWidget->takeItem(i); if (selectedFilterIndex == i) { selectedFilterIndex = -1; } redrawTaskTree(); save(); } } } void MainWindow::onEditNameBoardMenu() { if (menuSelectedBoardItem != nullptr) { int i = ui->boardList->indexFromItem(menuSelectedBoardItem).row(); if (i == -1) { return; } Board *b = boards.at(i); BoardConfigDialog dialog(b, this); if (dialog.exec() == QDialog::DialogCode::Accepted) { QString newName = dialog.getName(); QString newDesc = dialog.getDescription(); b->setName(newName); b->setDescription(newDesc); b->setShowingStatus(dialog.isShowingStatus()); if (!dialog.isAutoStatus()) { std::optional status = TaskStateService::getInstance()->getStatusByUUID(dialog.getStatus()); if (status.has_value()) { b->setDirtyStatus(status.value()); } else { b->removeDirtyStatus(); } } else { b->removeDirtyStatus(); } QListWidgetItem *item = ui->boardList->item(i); item->setText(newName); item->setToolTip(newDesc); ui->label->setText(newName); if (newDesc.length() > 0) { ui->boardDescription->setText(newDesc); ui->boardDescription->setEnabled(true); } else { ui->boardDescription->setText("No description"); ui->boardDescription->setEnabled(false); } redrawBoardStatus(); save(); } } } void MainWindow::onEditNameTaskMenu() { Task *t = getSelectedTask(); if (t != nullptr) { onEditTask(ui->taskList->currentItem()); } } void MainWindow::onEditFilterMenu() { if (menuSelectedFilterItem != nullptr) { int i = ui->filterListWidget->indexFromItem(menuSelectedFilterItem).row(); Filter f = filters[i]; FilterDialog dialog("Edit the filter", f, boards, this); if (dialog.exec() == QDialog::DialogCode::Accepted) { Filter f = dialog.getFilter(); filters[selectedFilterIndex] = f; QListWidgetItem *item = ui->filterListWidget->item(selectedFilterIndex); item->setText(f.getName()); item->setToolTip(f.getDescription()); ui->label->setText(f.getName()); if (f.getDescription().length() > 0) { ui->boardDescription->setText(f.getDescription()); ui->boardDescription->setEnabled(true); } else { ui->boardDescription->setText("No description"); ui->boardDescription->setEnabled(false); } redrawTaskTree(); save(); } } } void MainWindow::prepareFilterMenu(const QPoint &pos) { QMenu menu(this); this->menuSelectedFilterItem = ui->filterListWidget->itemAt(pos); if (this->menuSelectedFilterItem != nullptr) { QAction *renameAction = new QAction(tr("Edit the filter"), this); connect(renameAction, &QAction::triggered, this, &MainWindow::onEditFilterMenu); menu.addAction(renameAction); QAction *deleteAction = new QAction(tr("Delete"), this); connect(deleteAction, &QAction::triggered, this, &MainWindow::onRemoveFilterMenu); menu.addAction(deleteAction); menu.addSeparator(); } QAction *addAction = new QAction(tr("New filter"), this); connect(addAction, &QAction::triggered, this, &MainWindow::onNewFilterClick); menu.addAction(addAction); menu.exec(ui->filterListWidget->mapToGlobal(pos)); } void MainWindow::init() { if (Tools::isSaveFileExist()) { QJsonDocument doc; if (Tools::readSaveFile(doc)) { QJsonObject save = doc.object(); QJsonArray jsonPriorities = save[PRIORITIES_KEY].toArray(); QJsonArray jsonStatus = save[STATUS_KEY].toArray(); QJsonArray jsonBoards = save[BOARDS_KEY].toArray(); QJsonArray jsonFilters = save[FILTERS_KEY].toArray(); QVector priorities; QVector statuses; for (QJsonValueRef value : jsonPriorities) { priorities.append(Priority(value.toObject())); } for (QJsonValueRef value : jsonStatus) { statuses.append(Status(value.toObject())); } for (QJsonValueRef value : jsonBoards) { boards.append(new Board(value.toObject())); } for (QJsonValueRef value : jsonFilters) { filters.append(Filter(value.toObject())); } TaskStateService::getInstance()->updatePriorities(priorities); TaskStateService::getInstance()->updateStatuses(statuses); redrawFilterList(); redrawBoardList(); return; } } save(); } QVector MainWindow::defaultFilters() { QVector res; return res; } const QString MainWindow::getPriorityLabel(QString uuid) { std::optional p = TaskStateService::getInstance()->getPriorityByUUID(uuid); if (p.has_value()) { return p.value().getName(); } return ""; } const QString MainWindow::getStatusLabel(QString uuid) { std::optional s = TaskStateService::getInstance()->getStatusByUUID(uuid); if (s.has_value()) { return s.value().getName(); } return ""; } const QColor MainWindow::getPriorityColor(QString uuid, QColor defaultColor) { std::optional p = TaskStateService::getInstance()->getPriorityByUUID(uuid); if (p.has_value()) { return p.value().getColor(); } return defaultColor; } const QColor MainWindow::getStatusColor(QString uuid, QColor defaultColor) { std::optional s = TaskStateService::getInstance()->getStatusByUUID(uuid); if (s.has_value()) { return s.value().getColor(); } return defaultColor; } const QJsonDocument MainWindow::getJsonSave() { QJsonDocument doc; QJsonObject obj; QJsonArray jsonPriorities; QJsonArray jsonStatus; QJsonArray jsonBoards; QJsonArray jsonFilters; QVector priorities = TaskStateService::getInstance()->getPriorities(); foreach (Priority p, priorities) { jsonPriorities.append(p.toJson()); } QVector statuses = TaskStateService::getInstance()->getStatuses(); foreach (Status s, statuses) { jsonStatus.append(s.toJson()); } foreach (Board *b, this->boards) { jsonBoards.append(b->toJson()); } foreach (Filter f, this->filters) { jsonFilters.append(f.toJson()); } obj[PRIORITIES_KEY] = jsonPriorities; obj[STATUS_KEY] = jsonStatus; obj[BOARDS_KEY] = jsonBoards; obj[FILTERS_KEY] = jsonFilters; doc.setObject(obj); return doc; } Task *MainWindow::getSelectedTask() { QList items = ui->taskList->selectedItems(); if (items.count() != 1) { return nullptr; } if (selectedBoardIndex > -1) { Board *b = boards[selectedBoardIndex]; int16_t i = ui->taskList->indexOfTopLevelItem(items[0]); return b->taskAt(i); } else if (selectedFilterIndex > -1) { if (!filterResult.empty()) { int16_t i = ui->taskList->indexOfTopLevelItem(items[0]); return filterResult[i]; } } return nullptr; } void MainWindow::updateTaskRow(QTreeWidgetItem *item, Task t) { item->setText(0, t.getTitle()); item->setToolTip(0, t.getDescription()); item->setText(3, t.getExpectedFor().toString()); if (t.getExpectedFor() < QDate::currentDate()) { QBrush fgColor = item->foreground(3); fgColor.setColor(QColor(176, 67, 48)); fgColor.setStyle(Qt::BrushStyle::SolidPattern); item->setForeground(3, fgColor); } else if (t.getExpectedFor() == QDate::currentDate()) { QBrush fgColor = item->foreground(3); fgColor.setColor(QColor(176, 142, 48)); fgColor.setStyle(Qt::BrushStyle::SolidPattern); item->setForeground(3, fgColor); } else { QBrush fgColor = item->foreground(3); fgColor.setStyle(Qt::BrushStyle::NoBrush); item->setForeground(3, fgColor); } if (!t.getStatusUUID().isEmpty()) { item->setText(1, getStatusLabel(t.getStatusUUID())); QBrush bgColor = item->background(1); QBrush fgColor = item->foreground(1); bgColor.setColor(getStatusColor(t.getStatusUUID(), bgColor.color())); bgColor.setStyle(Qt::BrushStyle::SolidPattern); fgColor.setColor(Tools::getForegroundColor(bgColor.color())); fgColor.setStyle(Qt::BrushStyle::SolidPattern); item->setBackground(1, bgColor); item->setForeground(1, fgColor); } if (!t.getPriorityUUID().isEmpty()) { item->setText(2, getPriorityLabel(t.getPriorityUUID())); QBrush bgColor = item->background(2); QBrush fgColor = item->foreground(2); bgColor.setColor(getPriorityColor(t.getPriorityUUID(), bgColor.color())); bgColor.setStyle(Qt::BrushStyle::SolidPattern); fgColor.setColor(Tools::getForegroundColor(bgColor.color())); fgColor.setStyle(Qt::BrushStyle::SolidPattern); item->setBackground(2, bgColor); item->setForeground(2, fgColor); } } void MainWindow::redrawBoardList() { QListWidget *l = ui->boardList; uint16_t itemCount = l->count(); for (int16_t i = itemCount; i >= 0; i--) { delete l->takeItem(i); } foreach (Board *b, boards) { QListWidgetItem *item = new QListWidgetItem(b->getName()); item->setToolTip(b->getDescription()); l->addItem(item); } } void MainWindow::redrawFilterList() { QListWidget *l = ui->filterListWidget; uint16_t itemCount = l->count(); for (int16_t i = itemCount; i >= 0; i--) { delete l->takeItem(i); } foreach (Filter f, filters) { QListWidgetItem *item = new QListWidgetItem(f.getName()); item->setToolTip(f.getDescription()); l->addItem(item); } } void MainWindow::redrawTaskTree() { QTreeWidget *l = ui->taskList; uint16_t itemCount = l->topLevelItemCount(); for (int16_t i = itemCount; i >= 0; i--) { delete l->takeTopLevelItem(i); } if (selectedBoardIndex > -1) { Board *b = boards[selectedBoardIndex]; std::optional boardStatus = TaskStateService::getInstance()->getStatusByUUID(b->getStatus()); if (boardStatus.has_value()) { ui->boardStatus->setStyleSheet(Tools::getStatusLabelStylesheet(boardStatus.value())); ui->boardStatus->setText(boardStatus.value().getName()); ui->boardStatus->setVisible(true); } foreach (Task *t, b->getTasks()) { QTreeWidgetItem *item = new QTreeWidgetItem(); updateTaskRow(item, *t); ui->taskList->addTopLevelItem(item); } } else if (selectedFilterIndex > -1) { Filter f = filters[selectedFilterIndex]; filterResult = f.filter(boards); foreach (Task *t, filterResult) { QTreeWidgetItem *item = new QTreeWidgetItem(); updateTaskRow(item, *t); ui->taskList->addTopLevelItem(item); } } redrawBoardStatus(); } void MainWindow::redrawBoardStatus() { ui->boardStatus->setVisible(false); if (selectedBoardIndex > -1) { Board *b = boards[selectedBoardIndex]; if (b->isShowingStatus()) { std::optional boardStatus = TaskStateService::getInstance()->getStatusByUUID(b->getStatus()); if (boardStatus.has_value()) { ui->boardStatus->setStyleSheet(Tools::getStatusLabelStylesheet(boardStatus.value())); ui->boardStatus->setText(boardStatus.value().getName()); ui->boardStatus->setVisible(true); } } } } void MainWindow::save() { QJsonDocument doc = getJsonSave(); if (!Tools::writeSaveToFile(doc)) { QMessageBox::critical(this, "Failed to save", "Failed to write the save to the file", QMessageBox::StandardButton::Ok); } }