Слоты, имеющие отношение непосредственно к серверу, мы рассмотрим в связке с клиентом. Пока же сосредоточимся на тех слотах, которые относятся к управлению проектами и задачами и общих слотах приложения.
Начнём с соединений. Их я вынес в отдельный метод void TrackerServer::createConnections() .
// trackerserver.cpp // Метод создания подключений void TrackerServer::createConnections() { // Установка текущего проекта connect(projectView, &QListView::clicked, this, &TrackerServer::slotSetCurrentProject); connect(projectView, &QListView::activated, this, &TrackerServer::slotSetCurrentProject); // Подключение сигнала о смене статуса к обработчику connect(statusDelegate, &ComboBoxDelegate::commitData, this, &TrackerServer::slotChangeTaskStatus); // Создание новой задачи connect(newTaskButton, &QPushButton::clicked, this, &TrackerServer::slotNewTask); // Редактирование существующей задачи connect(editTaskButton, &QPushButton::clicked, this, &TrackerServer::slotEditTask); connect(taskView, &QTableView::doubleClicked, this, &TrackerServer::slotEditTask); // Удаление существующей задачи connect(deleteTaskButton, &QPushButton::clicked, this, &TrackerServer::slotDeleteTask); // Создание нового проекта connect(newProjectButton, &QPushButton::clicked, this, &TrackerServer::slotNewProject); // Архивация существующего проекта connect(archiveProjectButton, &QPushButton::clicked, this, &TrackerServer::slotArchiveProject); // Открытие архива проектов connect(openArchiveButton, &QPushButton::clicked, this, &TrackerServer::slotOpenArchive); // Открытие окна трекера connect(actionOpenWindow, &QAction::triggered, this, &TrackerServer::slotOpenTrackerWindow); // Открытие окна трекера по двойному клику на иконке в трее connect(trayIcon, &QSystemTrayIcon::activated, this, &TrackerServer::slotDoubleClickOpenTrackerWindow); // Завершение работы трекера connect(actionQuit, &QAction::triggered, qApp, &QApplication::quit); // Открытие окна настроек приложения connect(actionSettings, &QAction::triggered, this, &TrackerServer::slotOpenSettingsWindow); }
Для упрощения работы с элементами списка проектов определяем перечисление для Qt::DisplayRole и дополнительных ролей Qt::UserRole .
// trackerserver.h enum ProjectProperty { VisibleName = Qt::DisplayRole, Name = Qt::UserRole + 1, Id = Qt::UserRole + 2, Manual = Qt::UserRole + 3, Arch = Qt::UserRole + 4 };
// trackerserver.cpp // Слот установки текущего проекта void TrackerServer::slotSetCurrentProject(QModelIndex index) { // Получаем элемент модели, соответствующий полученному индексу QStandardItem* item = projectListModel->itemFromIndex(index); // Получаем id текущего проекта projectCurrentId = item->data(ProjectProperty::Id).toInt(); // Устанавливаем условие фильтрации таблицы задач по текущему id проекта taskFilterModel->setFilterFixedString(QString("%1").arg(projectCurrentId)); // Обновляем представление таблицы задач updateTaskView(); // Считаем количество строк в модели задач int rowNumber = taskFilterModel->rowCount(); // Если количество строк больше нуля if(rowNumber > 0) { // Получаем индекс нулевого элемента модели задач int row = 0; QModelIndex index = taskFilterModel->index(row, CustomSortFilterProxyModel::E_COLUMN_NUMBER); // Устанавливаем в представлении текущий элемент с этим индексом taskView->setCurrentIndex(index); // Делаем доступными кнопки редактирования и удаления задач editTaskButton->setEnabled(true); deleteTaskButton->setEnabled(true); } else { // Иначе блокируем кнопки редактирования и удаления задач editTaskButton->setDisabled(true); deleteTaskButton->setDisabled(true); } // Делаем доступной кнопку создания новой задачи newTaskButton->setEnabled(true); // Проверяем, закрыты ли все задачи текущего проекта if(database->isClosedAllTasks(projectCurrentId)) // Если да, то делаем доступной кнопку архивирования проекта archiveProjectButton->setEnabled(true); else // Если нет, то блокируем кнопку архивирования проекта archiveProjectButton->setDisabled(true); // Устанавливаем в виде текущим элемент с полученным индексом projectView->setCurrentIndex(index); } // Слот создания нового проекта void TrackerServer::slotNewProject() { // Объявляем диалог создания нового проекта QSharedPointer<NewProjectDialog> dialog(new NewProjectDialog()); // Созадаём диалоговое окно if(dialog->exec() == QDialog::Accepted) { // Если в диалоге нажата кнопка "ОК", // получаем из диалога наименование проекта QString projectName = dialog->getProjectName(); // Если наименование проекта не пустое if(!projectName.isEmpty()) { // Добавляем проект со свойство "без контроля СКВ" database->insertNewProject(projectName, ProjectVCS::WithoutVCS); // Обновляем модель проектов projectTableModel->select(); // Устанавливаем свойства элемента QString visibleProjectName = projectName; int row = projectTableModel->rowCount() - 1; int projectId = projectTableModel->record(row).value(idProjectCol).toInt(); int projectManual = ProjectVCS::WithoutVCS; int projectArch = ProjectArchive::Active; visibleProjectName = "(M) " + projectName; QStandardItem* item = new QStandardItem(); // Загружаем данные в элемент item->setData(visibleProjectName, ProjectProperty::VisibleName); item->setData(projectName, ProjectProperty::Name); item->setData(projectId, ProjectProperty::Id); item->setData(projectManual, ProjectProperty::Manual); item->setData(projectArch, ProjectProperty::Arch); // Загружаем элемент в модель представления проектов projectListModel->appendRow(item); // Устанавливаем текущий id проекта равный id нового проекта projectCurrentId = projectId; // Получаем индекс элемента нового проекта в модели представления int column = 0; QModelIndex currentProjectIndex = projectListModel->index(row, column); // Устанавливаем условие фильтрации таблицы задач по текущему id проекта taskFilterModel->setFilterFixedString(QString("%1").arg(projectCurrentId)); // Устанвливаем текущий проект по индексу slotSetCurrentProject(currentProjectIndex); // Поскольку новый проект пустой, блокируем кнопки удаления и редактирования задач, // и делаем доступной кнопку создания новой задачи editTaskButton->setDisabled(true); deleteTaskButton->setDisabled(true); newTaskButton->setEnabled(true); } } } // Слот архивации проекта void TrackerServer::slotArchiveProject() { // Определяем индекс текущего проекта QModelIndex currentProjectIndex = projectView->currentIndex(); int row = currentProjectIndex.row(); // Скрываем проект в представлении projectView->setRowHidden(row, true); // Устанавливаем для элемента модели пресдтавления свойство архивации проекта QStandardItem* item = projectListModel->item(row); item->setData(ProjectArchive::Archive, ProjectProperty::Arch); projectListModel->setItem(row, item); // Получаем из элемента модели представления id проекта int projectId = item->data(ProjectProperty::Id).toInt(); // Архивируем проект database->archiveProject(projectId); // Блокируем кнопку архивации проектов archiveProjectButton->setDisabled(true); // Определяем, остались ли видимые элементы в списке проектов int numberRows = projectListModel->rowCount(); int numberRowsNotHidden = 0; int firstRowNotHidden = 0; for(int row = 0; row < numberRows; ++row) { if(!projectView->isRowHidden(row)) { ++numberRowsNotHidden; firstRowNotHidden = row; break; } } if(numberRowsNotHidden > 0) { // Если видимые элементы остались, определем индекс первого QModelIndex index = projectListModel->index(firstRowNotHidden, 0); // Устанавливаем этот элемент текущим проектом slotSetCurrentProject(index); } else { // Если видимых элементов нет, обнуляем текущий id проекта projectCurrentId = 0; // Устанавливаем условие фильтрации таблицы задач по текущему id проекта taskFilterModel->setFilterFixedString(QString("%1").arg(projectCurrentId)); // Обновляем представление таблицы задач updateTaskView(); } } // Слот открытия архива void TrackerServer::slotOpenArchive() { // Объявляем диалог открытия архива QSharedPointer<OpenArchiveDialog> dialog(new OpenArchiveDialog(projectListModel)); // Если подтверждено извлечение из архива проекта/проектов if(dialog->exec() == QDialog::Accepted) { // Получаем список id извлекаемых из архива проектов QList<int> projectIdList = dialog->getListProjectId(); int lastId = projectIdList.last(); // Извлекаем проекты из архива foreach(int id, projectIdList) database->extractProject(id); // Обновляем модель списка проектов updateProjectListModel(); // Обновляем виджет списка проектов updateProjectView(); int numberRows = projectListModel->rowCount(); // Определяем индес последнего извлечённого проекта QModelIndex lastIndex; for(int row = 0; row < numberRows; ++row) { QStandardItem* item = projectListModel->item(row); int projectId = item->data(ProjectProperty::Id).toInt(); if(projectId == lastId) lastIndex = projectListModel->indexFromItem(item); } // Устанавливаем текущим последний извлечённый проект slotSetCurrentProject(lastIndex); } } // Слот изменения состояния задачи void TrackerServer::slotChangeTaskStatus(QWidget* /* editor */) { // Обновляем модель SQL-таблицы задач taskSqlTableModel->select(); if(database->isClosedAllTasks(projectCurrentId)) // Если все задачи закрыты, делаем доступной кнопку архивации проектов archiveProjectButton->setEnabled(true); else // Если нет, блокируем кнопку архивации проектов archiveProjectButton->setDisabled(true); } // Слот создания новой задачи void TrackerServer::slotNewTask() { // Объявляем диалог создания / редактирования задачи QSharedPointer<NewEditTaskDialog> dialog(new NewEditTaskDialog()); // Если создание новой задачи подтверждено if(dialog->exec() == QDialog::Accepted) { // Получаем из диалога тип и описание задачи QString type = dialog->getType(); QString description = dialog->getDescription(); // Если тип задачи и описание не пустые if(!type.isEmpty() && !description.isEmpty()) { // Добавляем новую задачу database->insertNewTask(projectCurrentId, type, description); // Обновляем модель SQL-таблицы задач taskSqlTableModel->select(); // Обновляем представление задач updateTaskView(); // Определяем номер строки созданной задачи int row = taskFilterModel->rowCount() - 1; // Определяем индекс новой задачи в модели представления задач QModelIndex index = taskFilterModel->index(row, CustomSortFilterProxyModel::E_COLUMN_NUMBER); // Устанавливаем эту задачу текущей в представлении taskView->setCurrentIndex(index); // Делаем доступными кнопки редактирования и удаления задач editTaskButton->setEnabled(true); deleteTaskButton->setEnabled(true); } } } // Слот редактирования задачи void TrackerServer::slotEditTask() { // Объявляем диалог создания / редактирования задачи QSharedPointer<NewEditTaskDialog> dialog(new NewEditTaskDialog()); // Извлекаем из текущего индекса задачи id, тип и описание задачи QModelIndex currentIndex = taskView->currentIndex(); QModelIndex idIndex = taskFilterModel->index(currentIndex.row(), CustomSortFilterProxyModel::E_COLUMN_NUMBER); QModelIndex typeIndex = taskFilterModel->index(currentIndex.row(), CustomSortFilterProxyModel::E_COLUMN_TYPE); QModelIndex descriptionIndex = taskFilterModel->index(currentIndex.row(), CustomSortFilterProxyModel::E_COLUMN_DESCRIPTION); QString type = taskFilterModel->data(typeIndex, Qt::DisplayRole).toString(); QString description = taskFilterModel->data(descriptionIndex, Qt::DisplayRole).toString(); int id = taskFilterModel->data(idIndex, Qt::DisplayRole).toInt(); // Передаём в диалог тип и описание задачи dialog->setType(type); dialog->setDescription(description); // Если редактирование задачи подтверждено if(dialog->exec() == QDialog::Accepted) { // Если тип или описание задачи отредактированы if(type != dialog->getType() || description != dialog->getDescription()) { // Редактируем задачу в базе database->editTask(id, dialog->getType(), dialog->getDescription()); // Обновляем модель SQL-таблицы задач taskSqlTableModel->select(); // Обновляем представление задач updateTaskView(); // Устанавливаем отредактированную задачу текущей в представлении taskView->setCurrentIndex(currentIndex); } } } // Слот удаления задачи void TrackerServer::slotDeleteTask() { // Объявляем диалог удаления задачи QSharedPointer<DeleteTaskDialog> dialog(new DeleteTaskDialog()); // Извлекаем из индекса id задачи QModelIndex currentIndex = taskView->currentIndex(); QModelIndex idIndex = taskFilterModel->index(currentIndex.row(), CustomSortFilterProxyModel::E_COLUMN_NUMBER); int id = taskFilterModel->data(idIndex, Qt::DisplayRole).toInt(); // Если удаление задачи подтверждено if(dialog->exec() == QDialog::Accepted) { // Удаляем задачу из базы database->deleteTask(id); // Обновляем модель SQL-таблицы задач taskSqlTableModel->select(); // Обновляем представление задач updateTaskView(); // Определяем, остались ли видимые задачи int numberRows = taskFilterModel->rowCount(); if(numberRows > 0) { // Если остались, делаем текущей первую int row = 0; QModelIndex index = taskFilterModel->index(row, CustomSortFilterProxyModel::E_COLUMN_NUMBER); taskView->setCurrentIndex(index); } else if(numberRows == 0) { // Если нет, блокируем кнопки редактирования и удаления задач editTaskButton->setDisabled(true); deleteTaskButton->setDisabled(true); } } } // Слот открытия окна трекера void TrackerServer::slotOpenTrackerWindow() { setVisible(true); } // Слот открытия окна трекера по двойному клику на иконке в трее void TrackerServer::slotDoubleClickOpenTrackerWindow(QSystemTrayIcon::ActivationReason reason) { if(reason == QSystemTrayIcon::DoubleClick) setVisible(true); } // Слот открытия окна настроек программы void TrackerServer::slotOpenSettingsWindow() { QSharedPointer<SettingsDialog> dialog(new SettingsDialog()); dialog->exec(); }
Создание проекта через трекер ограничено только проектами без контроля СКВ. Это сделано для упрощения функционала самого трекера. Проекты под контролем СКВ отслеживаются трекером с помощью следующего кода.
// trackerserver.cpp // Метод инициализации проектов под СКВ void TrackerServer::initProjectsWithVCS() { // В переменную записываем путь к репозиториям из объекта settings класса QSettings QString pathToProjects = settings.value("PathToProjects", "").toString(); if(!pathToProjects.isEmpty()) { // Если путь не пустой, формируем из списка репозиториев список проектов под СКВ QDir dir(pathToProjects); QStringList projectFileList; foreach(QFileInfo item, dir.entryInfoList()) { if(item.isDir()) { QString dirPath = item.absoluteFilePath(); QString dirName = item.fileName(); // Критерием проекта под СКВ является наличие в репозитории в папке hooks // файла log.txt, куда хуки pre-commit и post-commit вносят информацию // о номере коммита и текст коммита с номером закрываемой задачи QString fileNamePath = dirPath + "/hooks/log.txt"; QFile file(fileNamePath); if(file.exists()) projectFileList << dirName; } } if(!projectFileList.isEmpty()) { // Если список проектов под СКВ не пустой, извлекаем из базы данных // список проектов и соответствующих им id проектов QStringList projectNameList; QList<int> projectIdList; projectTableModel->select(); int numberRows = projectTableModel->rowCount(); for(int row = 0; row < numberRows; ++row) { int projectId = projectTableModel->record(row).value(idProjectCol).toInt(); QString projectName = projectTableModel->record(row).value(visibleNameProjectCol).toString(); int manualFlag = projectTableModel->record(row).value(manualPropertyCol).toInt(); if(manualFlag == ProjectVCS::WithVCS) { projectIdList << projectId; projectNameList << projectName; } } // Формируем список новых проектов, отсутствующих в базе QStringList newProjectList; foreach(QString file, projectFileList) { int count = 0; foreach(QString project, projectNameList) { if(file == project) ++count; } if(count == 0) newProjectList << file; } // Формируем список проектов, подлежащих автоматической архивации, // если репозитории этих проектов были удалены QStringList archiveProjectList; foreach(QString project, projectNameList) { int count = 1; foreach(QString file, projectFileList) { if(project == file) --count; } if(count > 0) archiveProjectList << project; } // Вносим новые проекты по списку foreach(QString project, newProjectList) database->insertNewProject(project, ProjectVCS::WithVCS); // Архивируем проекты по списку foreach(QString project, archiveProjectList) { for(int i = 0; i < projectNameList.size(); ++i) { if(project == projectNameList.at(i)) { database->archiveProject(projectIdList.at(i)); // Получае перечень задач для данного проекта QList<int> taskIdList = database->getIdListForProject(projectIdList.at(i)); foreach(int taskId, taskIdList) { if(!database->isClosed(taskId)) // Если задача не закрыта, закрываем её с пометкой "forced" database->closeTask(taskId, "forced"); } } } } // Обновляем SQL-модель таблицы задач taskSqlTableModel->select(); } } }
В общем-то достаточно написать так
Все enum после Name будут и так увеличиваться на единицу.
Объявление переменной row не нужно
Можно переписать так, читабельность от этого не страдает. 0 здесь не будет магической цифрой, поскольку здесь очевидно, что выбирается первая строка.
foreach выполняет глубокое копирование , лучше переходите полноценно на современные стандарты C++. То есть вместо этого
Можно написать так
К тому же foreach - это макрос, а значит получаем работу препроцессора, которае в случае конструкций языка, например for, не происходит. Конечно есть случае, где foreach ещё оправдан, но их мало на самом деле.
Там где у вас QModelIndex берутся, также можно переписать в одну строку, как в предыдущем варианте. В конечном счёте и так ясно, что за это индексы по enum CustomSortFilterProxyModel::E_COLUMN_TYPE например.
Думаю, что вот это
Можно переписать так
Вот это лишнее переусложнение
Просто создайте на стеке и тогда по окончанию работы диалог сам уничтожиться, не говоря уже о том, что в предыдущем варианте лишнее создание умного указателя с созданием объекта диалога и его копированием.
Вот это
Можно ак переписать
P/S/ Вы можете в статье добавлять рекомендуемые статьи, например, список всех статей по Simple Tracker. Тогда пользователи смогу лучше ориентироваться по всем вашим статьям.
Это мне самому нужно было, чтобы при переписываниии с Qt::UserRole + n на enum не запутаться. Сам себе проблему придумал, поскольку сразу не ввёл enum.
Тоже пишу так для себя, поскольку ещё не до окнца освоился с QModelIndex.
Понял. Да, спасибо.
Согласен.
Понял.