/
waterstone
/
json_inspector
Обзор
Документация
Войти
/
waterstone
/
json_inspector
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
form.cpp
345 строк
10 KB
Peter Sakhno
Switch from DOM to SAX. Use QPromise for thread control and progrees indication.
18 ноя 2025, 23:54
18 ноя 2025, 23:54
aae5d62
Код
Авторство
О чём код?
#include "form.h" #include <fstream> #include <QFile> #include <QHeaderView> #include <QMessageBox> #include <QApplication> #include <QtConcurrentRun> #include <rapidjson/error/en.h> #include <rapidjson/istreamwrapper.h> #include "sax_reader.h" #include "tree_model.h" #include "values_model.h" #include "ui_form.h" namespace { const std::string kTagRoot; const std::string kTagArrayItem; const int kProgressHeight = 2; const QString kProgressStyle = QStringLiteral( "QProgressBar {min-height: %1; max-height: %1; border: none; background: transparent}" "QProgressBar::chunk{ background-color: %2; }"); } Form::Form(QWidget *parent) : QWidget(parent), m_ui(std::make_unique<Ui::Form>()) { m_ui->setupUi(this); m_ui->stackedWidget->setCurrentIndex(0); const auto palette = QApplication::palette(); auto color = palette.color(QPalette::ColorGroup::Active, QPalette::ColorRole::Highlight); if (!color.isValid()) color = Qt::darkBlue; m_ui->progressBar->setStyleSheet(kProgressStyle.arg(kProgressHeight).arg(color.name())); auto margins = m_ui->formMainLayout->contentsMargins(); const int top = margins.top() - kProgressHeight; margins.setTop(std::max(top, kProgressHeight)); m_ui->formMainLayout->setContentsMargins(margins); QPointer<QHeaderView> hv = m_ui->tableView->horizontalHeader(); if (hv) { QFont font = hv->font(); font.setBold(true); hv->setFont(font); hv->setSectionsClickable(false); hv->setStretchLastSection(true); } hv = m_ui->tableView->verticalHeader(); if (hv) { hv->setSectionsClickable(false); hv->setSectionResizeMode(QHeaderView::ResizeToContents); } hv = m_ui->treeView->header(); if (hv) { QFont font = hv->font(); font.setBold(true); hv->setFont(font); } QObject::connect(&m_future_watcher, &JsonLoadWatcher::started, this, &Form::OnJsonLoadStarted); QObject::connect(&m_future_watcher, &JsonLoadWatcher::finished, this, &Form::OnJsonLoadFinished); QObject::connect(&m_future_watcher, &JsonLoadWatcher::progressRangeChanged, this, &Form::OnJsonLoadRange); QObject::connect(&m_future_watcher, &JsonLoadWatcher::progressValueChanged, this, &Form::OnJsonLoadProgress); } template <typename T, bool Connect_Selection> void Form::SetModel(QAbstractItemView* view) { if (view == nullptr) return; auto prev_model = view->model(); auto prev_sel_model = view->selectionModel(); view->setModel(new T(view)); if (prev_model != nullptr) prev_model->deleteLater(); if (prev_sel_model != nullptr) prev_sel_model->deleteLater(); if (Connect_Selection) { QPointer<QItemSelectionModel> sel_model = view->selectionModel(); if (sel_model) { QObject::connect(sel_model, &QItemSelectionModel::currentChanged, this, &Form::OnTreeItemActivated); } } } template <typename T, bool Connect_Selection> QPointer<T> Form::GetModel(QAbstractItemView* view) { if (view == nullptr) return {}; QPointer<T> model = qobject_cast<T*>(view->model()); if (model) return model; SetModel<T, Connect_Selection>(view); return qobject_cast<T*>(view->model()); } void Form::OpenJSON(const QString& file_name) { if (m_future_watcher.isRunning()) { QApplication::setOverrideCursor(Qt::WaitCursor); m_future_watcher.cancel(); m_future_watcher.waitForFinished(); QApplication::restoreOverrideCursor(); } m_future_watcher.setFuture(QtConcurrent::run(&Form::LoadJson, this, file_name)); } void Form::OnTreeItemActivated(const QModelIndex& index, const QModelIndex&) { if (!index.isValid()) return; auto values_model = GetModel<ValuesModel>(m_ui->tableView); if (!values_model) return; auto tree_model = GetModel<TreeModel>(m_ui->treeView); if (!tree_model) return; const auto id = static_cast<unsigned int>(index.internalId()); values_model->SetData(tree_model->GetDataById(id)); if (QPointer<QHeaderView> hv = m_ui->tableView->horizontalHeader()) { if (QHeaderView::ResizeToContents != hv->sectionResizeMode(0)) hv->setSectionResizeMode(0, QHeaderView::ResizeToContents); } } void Form::OnJsonLoadStarted() { m_ui->stackedWidget->setCurrentIndex(1); emit JsonLoadingStarted(); } void Form::OnJsonLoadFinished() { m_ui->stackedWidget->setCurrentIndex(0); if (!m_future_watcher.isCanceled()) { auto result = m_future_watcher.result(); if (result.root_node) { auto tree_model = GetModel<TreeModel, true>(m_ui->treeView); if (tree_model) { tree_model->SetData(result.root_node); if (result.root_node) m_ui->treeView->expandAll(); auto values_model = GetModel<ValuesModel>(m_ui->tableView); if (values_model) values_model->SetData({}); } } else { QString message = tr("Failed to load JSON file"); if (result.error_message && !result.error_message->isEmpty()) message = *result.error_message; QMessageBox::critical(this, tr("Open JSON file"), message); } } emit JsonLoadingFinished(); } void Form::OnJsonLoadRange(int min, int max) { m_ui->progressBar->setRange(min, max); } void Form::OnJsonLoadProgress(int value) { m_ui->progressBar->setValue(value); } void Form::OnCloseApp() { if (m_future_watcher.isRunning()) { QApplication::setOverrideCursor(Qt::WaitCursor); m_future_watcher.cancel(); m_future_watcher.waitForFinished(); QApplication::restoreOverrideCursor(); } } bool Form::CanSave() const { QPointer<TreeModel> model = qobject_cast<TreeModel*>(m_ui->treeView->model()); if (!model) return false; return model->CanSave(); } void Form::SaveJSONTags(const QString& file_name) { auto model = GetModel<TreeModel>(m_ui->treeView); if (!model) return; auto root_node = model->GetData().lock(); if (!root_node) return; QFile save_file(file_name); if (!save_file.open(QIODevice::WriteOnly | QFile::Truncate)) { QMessageBox::critical(this, tr("Export JSON tags"), tr("Couldn't open file to save JSON tags")); return; } using StringList = std::set<std::string_view>; StringList nodes, arrays, values, other; std::function<void(std::optional<JSONNodeWP> node)> save_json_tag; save_json_tag = [&nodes, &arrays, &values, &other, &save_json_tag] ( std::optional<JSONNodeWP> json_node_ptr) { if (!json_node_ptr) return; auto json_node = json_node_ptr->lock(); if (!json_node) return; const auto& json_tag = json_node->GetTag(); if (!json_tag.empty() && kTagRoot != json_tag && kTagArrayItem != json_tag) { StringList* target_list = nullptr; JSONNode::Type t = json_node->GetType(); switch (t) { case JSONNode::Type::Object: target_list = &nodes; break; case JSONNode::Type::Array: target_list = &arrays; break; case JSONNode::Type::Value: target_list = &values; break; case JSONNode::Type::Undefined: target_list = &other; break; } if (target_list != nullptr) target_list->insert(json_tag); } for (int i = 0; i < json_node->ChildrenCount(); i++) save_json_tag(json_node->GetChildAt(i)); }; save_json_tag(root_node); QTextStream out(&save_file); auto write_to_file = [&out](const StringList& list) { if (list.empty()) return; for (const auto& str : list) { if (!str.empty()) out << QString::fromStdString(str.data()) << Qt::endl; } out << Qt::endl; }; write_to_file(nodes); write_to_file(arrays); write_to_file(values); write_to_file(other); out.flush(); save_file.close(); } void Form::LoadJson(JsonLoadResultPromise& promise, const QString& file_name) { if (promise.isCanceled()) return; std::ifstream in(file_name.toStdString()); if (!in.is_open()) { promise.addResult({ nullptr, tr("Couldn't open JSON file") }); return; } if (promise.isCanceled()) return; struct ProgressController : public IProgressController { unsigned long stream_size{ 0 }; JsonLoadResultPromise& promise; std::ifstream& stream; ProgressController(JsonLoadResultPromise& promise_, std::ifstream& stream_) : promise(promise_), stream(stream_) { stream.seekg(0, std::ios_base::end); stream_size = static_cast<decltype(stream_size)>(stream.tellg()); stream.seekg(0, std::ios_base::beg); if (stream_size) promise.setProgressRange(0, 100); else promise.setProgressRange(0, 0); } bool IsStopped() const override { return promise.isCanceled(); } void UpdateProgress() override { if (stream_size) { promise.setProgressValue( static_cast<int>(stream.tellg() * 100 / stream_size)); } } } progress_controller(promise, in); SAXReaderHandler json_handler(progress_controller); rapidjson::IStreamWrapper stream(in); if (rapidjson::Reader json_reader; !json_reader.Parse(stream, json_handler)) { const QString error_str = QStringLiteral("Error(%1): %2") .arg(static_cast<unsigned>(json_reader.GetErrorOffset())) .arg(rapidjson::GetParseError_En(json_reader.GetParseErrorCode())); promise.addResult({ nullptr, error_str }); return; } if (json_handler.HasResult() && !promise.isCanceled()) promise.addResult({ json_handler.TakeResult() }); }