Слоты, имеющие отношение непосредственно к серверу, мы рассмотрим в связке с клиентом. Пока же сосредоточимся на тех слотах, которые относятся к управлению проектами и задачами и общих слотах приложения.
Начнём с соединений. Их я вынес в отдельный метод 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.
Понял. Да, спасибо.
Согласен.
Понял.