/
waterstone
/
TaskList_Demo
Обзор
Документация
Войти
/
waterstone
/
TaskList_Demo
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/tasklist_table_model.cpp
651 строка
17 KB
Peter Sakhno
TaskListTableModel - small revie. TaskList_Test - fix build
23 янв 2026, 14:57
23 янв 2026, 14:57
38d9bd3
Код
Авторство
О чём код?
#include "tasklist_table_model.h" #include <set> #include <random> #include <QDateTime> #include <QPromise> #include <QtConcurrentRun> #include <QFutureSynchronizer> namespace { void TaskSimulation(QPromise<void>& promise, int duration, qulonglong /*id*/) { if (!DurationRange::valid(duration)) { promise.setProgressRange(ProgressRange::bad(), ProgressRange::bad()); promise.setProgressValue(ProgressRange::bad()); for (;;) { QThread::msleep(500); promise.suspendIfRequested(); if (promise.isCanceled()) { break; } } } else { promise.setProgressRange(ProgressRange::min(), ProgressRange::max()); const int stop = duration * 1000 / 250; for (int i = 0; i <= stop; i++) { QThread::msleep(250); promise.setProgressValue( ProgressRange::adjust(i * ProgressRange::max() / stop)); promise.suspendIfRequested(); if (promise.isCanceled()) { break; } } } } class DurationGenerator { public: DurationGenerator() : m_generator(std::random_device{}()), // Start distribution from 0 to simulate indeterminated processes m_distribution(static_cast<DurationRange::value_type>(0), DurationRange::max()) { } auto operator()() { return m_distribution(m_generator); } private: std::mt19937 m_generator; std::uniform_int_distribution<DurationRange::value_type> m_distribution; }; } TaskListTableModel::TaskListTableModel(QObject* parent) : QAbstractTableModel (parent) { } QVariant TaskListTableModel::headerData(int section, Qt::Orientation /*orientation*/, int role) const { if (section < TaskListTableModel::ColumnBegin or TaskListTableModel::ColumnEnd <= section) { return {}; } if (Qt::DisplayRole == role) { switch (section) { case TaskListTableModel::TimestampColumn: return tr("Date"); case TaskListTableModel::CaptionColumn: return tr("Caption"); case TaskListTableModel::ProgressColumn: return tr("Progress"); case TaskListTableModel::ActionColumn: return tr("Start/Stop"); } } return {}; } int TaskListTableModel::rowCount(const QModelIndex& parent) const { if (parent.isValid ()) return 0; return static_cast<int>(m_task_list.size()); } int TaskListTableModel::columnCount(const QModelIndex& parent) const { if (parent.isValid()) return 0; return static_cast<int>(TaskListTableModel::ColumnEnd); } QVariant TaskListTableModel::data(const QModelIndex& index, int role) const { if (!index.isValid ()) return {}; if (Qt::DisplayRole == role) return GetDisplayRole(index); else if (Qt::UserRole == role) return GetUserRole(index); return {}; } bool TaskListTableModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.isValid()) return false; auto task = std::next(m_task_list.begin(), index.row()); if (task == m_task_list.end()) return false; if (Qt::EditRole != role) return false; bool data_set = false; if (TaskListTableModel::CaptionColumn == index.column()) { if (value.userType() != QMetaType::QString) return false; if (task->caption != value.toString()) { task->caption = value.toString(); data_set = true; } } else if (TaskListTableModel::ActionColumn == index.column()) { if (value.userType() != QMetaType::Bool) return false; data_set = SwitchTaskState(*task, value.toBool()); } if (data_set) emit dataChanged(index, index); return data_set; } // Rows manipulation: bool TaskListTableModel::insertRows(int row, int count, const QModelIndex& parent) { if (count <= 0) return false; if (row < 0) row = 0; if (row > static_cast<decltype(row)>(m_task_list.size())) row = static_cast<decltype(row)>(m_task_list.size()); beginInsertRows(parent, row, row + count - 1); DurationGenerator generate_duration; for (int i = row; i < row + count; i++) { m_task_list.insert(std::next(m_task_list.cbegin(), i), { m_task_id_counter(), QDateTime::currentMSecsSinceEpoch(), generate_duration() } ); } endInsertRows(); return true; } bool TaskListTableModel::removeRows(int row, int count, const QModelIndex& parent) { if (count <= 0) return false; if (row < 0 or static_cast<decltype(row)>(m_task_list.size()) <= row) return false; beginRemoveRows(parent, row, row + count - 1); for (int i = row; i < row + count; i++) { auto it = std::next(m_task_list.begin(), i); if (it != m_task_list.end()) DeleteTaskWatcher(it->id); } auto cit_beg = std::next(m_task_list.cbegin(), row); auto cit_end = std::next(cit_beg, count); m_task_list.erase(cit_beg, cit_end); endRemoveRows(); return true; } bool TaskListTableModel::HasActiveRows() const { if (m_task_list.empty()) return false; for (const auto& task : m_task_list) { if (task.active) return true; } return false; } bool TaskListTableModel::HasActiveRows(const std::set<int>& selected_rows) const { if (m_task_list.empty()) return false; if (selected_rows.empty()) return false; for (int row : selected_rows) { auto cit = std::next(m_task_list.cbegin(), row); if (cit != m_task_list.cend() and cit->active) return true; } return false; } QString TaskListTableModel::GenerateTaskName() const { return tr("New task #%1").arg(m_task_id_counter); } void TaskListTableModel::AddTask(QString caption) { const int pos = static_cast<decltype(pos)>(m_task_list.size()); beginInsertRows({}, pos, pos); m_task_list.push_back( { m_task_id_counter(), QDateTime::currentMSecsSinceEpoch(), DurationGenerator{}(), caption, } ); endInsertRows(); } bool TaskListTableModel::RemoveAll() { if (m_task_list.empty()) return false; beginResetModel(); for (const auto& t : m_task_list) DeleteTaskWatcher(t.id); m_task_list.clear(); endResetModel(); return true; } bool TaskListTableModel::RemoveSelected(const std::set<int>& selected_rows) { if (m_task_list.empty()) return false; if (selected_rows.empty()) return false; if (selected_rows.size() == m_task_list.size()) return RemoveAll(); // First, get all rows sorted backward std::set<int, std::greater<int>> rows_to_delete; rows_to_delete.insert(selected_rows.cbegin(), selected_rows.cend()); // Second, go through sorted rows and find gaps, selection ranges may not // be continuous. // Go from bottom to top, that way deleting of lower rows will not affect // indexes of upper rows. auto r_cit = rows_to_delete.cbegin(); auto l_cit = r_cit; bool rows_removed = false; do { // Get next auto next_cit = std::next(l_cit, 1); // Check next if (next_cit != rows_to_delete.cend() and *next_cit == *l_cit - 1) { l_cit++; continue; } // Remove rows between [l; r] if (removeRows(*l_cit, *r_cit - *l_cit + 1)) rows_removed = true; // Shift forward l and r r_cit = ++l_cit; } while (l_cit != rows_to_delete.cend()); return rows_removed; } void TaskListTableModel::ActivateSelected(const std::set<int>& selected_rows, bool active) { if (m_task_list.empty()) return; if (selected_rows.empty()) return; for (int row : selected_rows) { auto it = std::next(m_task_list.begin(), row); if (it != m_task_list.end() and it->active != active) SwitchTaskState(*it, active); } } void TaskListTableModel::StopAllTasks() { if (m_task_watchers.empty()) return; m_indeterminate_task_list_animation.clear(); m_indeterminate_task_list_animation_timer.stop(); QFutureSynchronizer<void> synchronizer; synchronizer.setCancelOnWait(true); for (const auto& [_, watcher] : m_task_watchers) { if (watcher && watcher->isRunning()) synchronizer.addFuture(watcher->future()); } } QVariant TaskListTableModel::GetDisplayRole(const QModelIndex& index) const { if (!index.isValid()) return {}; auto task_cit = std::next(m_task_list.cbegin(), index.row()); if (task_cit == m_task_list.cend()) return {}; switch (index.column()) { case TaskListTableModel::TimestampColumn: { auto dt = QDateTime::fromMSecsSinceEpoch(task_cit->timestamp); if (dt.isNull() or !dt.isValid()) return tr("<Unknown>"); return dt.date().toString( QLocale::system().dateFormat(QLocale::ShortFormat)) + QStringLiteral(" ") + dt.time().toString(QLocale::system().timeFormat(QLocale::LongFormat)); } case TaskListTableModel::CaptionColumn: { if (!task_cit->caption.isEmpty()) return task_cit->caption; return tr("<Unnamed task #%1>").arg(task_cit->id); } case TaskListTableModel::ActionColumn: { if (task_cit->active) return tr("Stop (%1)").arg(task_cit->duration); else return tr("Start (%1)").arg(task_cit->duration); } } return {}; } QVariant TaskListTableModel::GetUserRole(const QModelIndex& index) const { if (!index.isValid()) return {}; auto task_cit = std::next(m_task_list.cbegin(), index.row()); if (task_cit == m_task_list.cend()) return {}; switch (index.column()) { case TaskListTableModel::TimestampColumn: { if (task_cit->timestamp != 0) return task_cit->timestamp; } break; case TaskListTableModel::CaptionColumn: { if (!task_cit->caption.isEmpty()) return task_cit->caption; } break; case TaskListTableModel::ProgressColumn: { if (ProgressRange::valid(task_cit->progress) or AnimationRange::valid(task_cit->progress)) { return task_cit->progress; } } break; case TaskListTableModel::ActionColumn: return task_cit->active; } return {}; } void TaskListTableModel::OnTaskStarted() { // Get watcher instance TaskWatcherQP watcher = qobject_cast<TaskWatcher*>(sender()); if (!watcher) return; // Get task id, it should be stored as 'result' auto task_id = watcher->taskId(); // Update task UpdateTask(task_id, true, ProgressRange::bad()); // Start animation for indeterminated task AnimateTask(task_id, true); } void TaskListTableModel::OnTaskProgress() { // Get watcher instance TaskWatcherQP watcher = qobject_cast<TaskWatcher*>(sender()); if (!watcher) return; // Get task id, it should be stored as 'result' auto task_id = watcher->taskId(); // Get progress value, it should be [0; 100] const int progress = watcher->progressValue(); // Update task UpdateTask(task_id, true, progress); } void TaskListTableModel::OnTaskFinished() { // Get watcher instance TaskWatcherQP watcher = qobject_cast<TaskWatcher*>(sender()); if (!watcher) return; // Get task id, it should be stored as 'result' auto task_id = watcher->taskId(); // Check if watcher is still in the list, // if not - task is removed auto watcher_it = m_task_watchers.find(task_id); if (watcher_it == m_task_watchers.end()) { // Schedule watcher for self destroy when ready watcher->deleteLater(); // No need to update task return; } // Update task UpdateTask(task_id, false, ProgressRange::bad()); // Stop animation for indeterminated task AnimateTask(task_id, false); } void TaskListTableModel::UpdateTask(Task::id_type task_id, bool active, int progress) { // Get task by id auto task_it = std::find_if(m_task_list.begin(), m_task_list.end(), [task_id](const auto& t) { return t.id == task_id; }); if (task_it == m_task_list.end()) return; // it's ok, task may be removed from the list // Calc task row const int task_row = static_cast<decltype(task_row)>( std::distance(m_task_list.begin(), task_it)); // Update task progress bool progress_updated = false; if (task_it->progress != progress) { task_it->progress = progress; progress_updated = true; } // Update task run state bool state_updated = false; if (task_it->active != active) { task_it->active = active; state_updated = true; } // Signal, if task was updated if (progress_updated or state_updated) { const int col_beg = progress_updated ? TaskListTableModel::ProgressColumn : TaskListTableModel::ActionColumn; const int col_end = state_updated ? TaskListTableModel::ActionColumn : TaskListTableModel::ProgressColumn; emit dataChanged(index(task_row, col_beg), index(task_row, col_end)); } } void TaskListTableModel::DeleteTaskWatcher(Task::id_type task_id) { auto watcher_it = m_task_watchers.find(task_id); if (watcher_it != m_task_watchers.end()) { if (watcher_it->second) { if (watcher_it->second->isRunning()) watcher_it->second->cancel(); else watcher_it->second->deleteLater(); } m_task_watchers.erase(watcher_it); } } bool TaskListTableModel::SwitchTaskState(Task& task, bool new_state) { if (task.active == new_state) return false; // Get task watcher auto watcher_it = m_task_watchers.find(task.id); if (new_state) { if (watcher_it == m_task_watchers.end()) { // Create new task watcher for the task TaskWatcherQP watcher = new TaskWatcher(task.id, this); // Listen for task watcher signals QObject::connect(watcher, &TaskWatcher::progressValueChanged, this, &TaskListTableModel::OnTaskProgress); QObject::connect(watcher, &TaskWatcher::started, this, &TaskListTableModel::OnTaskStarted); QObject::connect(watcher, &TaskWatcher::finished, this, &TaskListTableModel::OnTaskFinished); // Store task watcher watcher_it = m_task_watchers.insert({ task.id, watcher }).first; } // Lauhch task simulation watcher_it->second->setFuture( QtConcurrent::run(TaskSimulation, task.duration, static_cast<qulonglong>(task.id))); } else if (watcher_it != m_task_watchers.end() and watcher_it->second->isRunning()) { // Cancel task if task it is still running watcher_it->second->cancel(); } // Update task run state task.active = new_state; // task.progress should be updated by task watcher signals return true; } void TaskListTableModel::AnimateTask(Task::id_type task_id, bool animate) { if (animate) { // Get task by id auto task_it = std::find_if(m_task_list.begin(), m_task_list.end(), [task_id](const auto& t) { return t.id == task_id; }); if (task_it == m_task_list.end()) return; // Check task duration, if it is ok, animation is not needed if (DurationRange::valid(task_it->duration)) return; // Start animation of indeterminated progress for the task m_indeterminate_task_list_animation.insert(task_it->id); // Start animation timer if (!m_indeterminate_task_list_animation_timer.isActive()) m_indeterminate_task_list_animation_timer.start(kAnimationTime, this); } else { // Stop animation for the task m_indeterminate_task_list_animation.erase(task_id); // Stop animation timer if (m_indeterminate_task_list_animation.empty() and m_indeterminate_task_list_animation_timer.isActive()) { m_indeterminate_task_list_animation_timer.stop(); } } } void TaskListTableModel::timerEvent(QTimerEvent* event) { if (nullptr == event) return; if (!m_indeterminate_task_list_animation_timer.isActive() or m_indeterminate_task_list_animation_timer.id() != event->id()) { return; } if (m_indeterminate_task_list_animation.empty()) { m_indeterminate_task_list_animation_timer.stop(); return; } for (auto task_id : m_indeterminate_task_list_animation) { // Get task by id auto task_it = std::find_if(m_task_list.begin(), m_task_list.end(), [task_id](const auto& t) { return t.id == task_id; }); if (task_it == m_task_list.end()) continue; if (!task_it->active) { // Data in model was changed, but process not yet finished if (!AnimationRange::valid(task_it->progress)) continue; task_it->progress = AnimationRange::bad(); } else { // Advance animation AnimationRange::increase(task_it->progress, kAnimationStep); } // Calc task row const int task_row = static_cast<decltype(task_row)>( std::distance(m_task_list.begin(), task_it)); // Get model index for the task const QModelIndex index = this->index(task_row, TaskListTableModel::ProgressColumn); // Update view emit dataChanged(index, index, {Qt::DisplayRole, Qt::UserRole}); } }