We will consider slots that are directly related to the server in conjunction with the client. For now, let's focus on those slots that relate to project and task management and general application slots.
Let's start with connections. I moved them into a separate method 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); }
To simplify working with project list items, we define an enumeration for Qt::DisplayRole and additional roles 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(); }
Creating a project through the tracker is limited only to projects without VCS control. This is done to simplify the functionality of the tracker itself. Projects under SLE control are tracked by the tracker using the following code.
// 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.
Понял. Да, спасибо.
Согласен.
Понял.