Skip to content

Commit 7e3070c

Browse files
committed
Add DspxInspectorDialog
1 parent 8dd5798 commit 7e3070c

5 files changed

Lines changed: 443 additions & 0 deletions

File tree

Lines changed: 361 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,361 @@
1+
#include "DspxInspectorDialog.h"
2+
#include "DspxInspectorDialog_p.h"
3+
4+
#include <QApplication>
5+
#include <QDir>
6+
#include <QVBoxLayout>
7+
#include <QHBoxLayout>
8+
#include <QLineEdit>
9+
#include <QPushButton>
10+
#include <QTreeView>
11+
#include <QStandardItemModel>
12+
#include <QTabWidget>
13+
14+
#include <opendspx/model.h>
15+
#include <opendspxserializer/serializer.h>
16+
17+
#include <coreplugin/OpenSaveProjectFileScenario.h>
18+
19+
namespace Core {
20+
21+
DspxInspectorDialog::DspxInspectorDialog(QWidget *parent) : QDialog(parent), d_ptr(new DspxInspectorDialogPrivate) {
22+
Q_D(DspxInspectorDialog);
23+
d->q_ptr = this;
24+
auto layout = new QVBoxLayout;
25+
setLayout(layout);
26+
auto browseLayout = new QHBoxLayout;
27+
layout->addLayout(browseLayout);
28+
auto pathEdit = new QLineEdit;
29+
browseLayout->addWidget(pathEdit);
30+
auto browseButton = new QPushButton(tr("Browse"));
31+
browseLayout->addWidget(browseButton);
32+
auto runCheckButton = new QPushButton(tr("Run Check"));
33+
runCheckButton->setEnabled(false);
34+
layout->addWidget(runCheckButton);
35+
auto tabWidget = new QTabWidget;
36+
layout->addWidget(tabWidget);
37+
auto fileStructureTreeView = new QTreeView;
38+
fileStructureTreeView->setHeaderHidden(true);
39+
tabWidget->addTab(fileStructureTreeView, tr("File Structure"));
40+
auto problemTreeView = new QTreeView;
41+
problemTreeView->setHeaderHidden(true);
42+
tabWidget->addTab(problemTreeView, tr("Problems"));
43+
44+
d->tabWidget = tabWidget;
45+
d->problemTreeView = problemTreeView;
46+
d->fileStructureTreeView = fileStructureTreeView;
47+
resize(640, 480);
48+
setWindowTitle(tr("DSPX Inspector"));
49+
50+
connect(pathEdit, &QLineEdit::textChanged, this, &DspxInspectorDialog::setPath);
51+
connect(this, &DspxInspectorDialog::pathChanged, pathEdit, &QLineEdit::setText);
52+
connect(this, &DspxInspectorDialog::pathChanged, runCheckButton, [runCheckButton](const QString &path) {
53+
runCheckButton->setEnabled(!path.isEmpty());
54+
});
55+
56+
57+
auto scenario = new OpenSaveProjectFileScenario(this);
58+
connect(browseButton, &QPushButton::clicked, [scenario, this] {
59+
auto path = scenario->openProjectFile();
60+
if (!path.isEmpty())
61+
setPath(path);
62+
});
63+
64+
connect(runCheckButton, &QPushButton::clicked, this, &DspxInspectorDialog::runCheck);
65+
}
66+
67+
DspxInspectorDialog::~DspxInspectorDialog() = default;
68+
69+
QString DspxInspectorDialog::path() const {
70+
Q_D(const DspxInspectorDialog);
71+
return d->path;
72+
}
73+
74+
void DspxInspectorDialog::setPath(const QString &path) {
75+
Q_D(DspxInspectorDialog);
76+
if (d->path != path) {
77+
d->path = QDir::toNativeSeparators(path);
78+
Q_EMIT pathChanged(d->path);
79+
}
80+
}
81+
82+
static void addErrorItem(QStandardItemModel *model, const QString &message, const QString &jsonPath, const QIcon &icon, const QList<QPair<QString, QVariant>> &info, const QString &description = {}) {
83+
auto titleItem = new QStandardItem;
84+
titleItem->setText(message);
85+
titleItem->setIcon(icon);
86+
titleItem->setToolTip(description);
87+
titleItem->setEditable(false);
88+
auto pathItem = new QStandardItem;
89+
pathItem->setText(jsonPath);
90+
pathItem->setEditable(false);
91+
if (!jsonPath.isEmpty()) {
92+
pathItem->setToolTip("JSONPath");
93+
}
94+
model->appendRow({titleItem, pathItem});
95+
for (const auto &[key, value] : info) {
96+
auto keyItem = new QStandardItem;
97+
keyItem->setText(key);
98+
keyItem->setEditable(false);
99+
auto valueItem = new QStandardItem;
100+
valueItem->setData(value, Qt::DisplayRole);
101+
valueItem->setEditable(false);
102+
titleItem->appendRow({keyItem, valueItem});
103+
}
104+
}
105+
106+
void DspxInspectorDialog::runCheck() {
107+
static const char *jsonDataTypeNames[] = {
108+
QT_TR_NOOP("Null"),
109+
QT_TR_NOOP("Boolean"),
110+
QT_TR_NOOP("Integer"),
111+
QT_TR_NOOP("Number"),
112+
QT_TR_NOOP("String"),
113+
QT_TR_NOOP("Array"),
114+
QT_TR_NOOP("Object"),
115+
};
116+
Q_D(DspxInspectorDialog);
117+
if (d->path.isEmpty())
118+
return;
119+
if (d->problemTreeView->model()) {
120+
d->problemTreeView->model()->deleteLater();
121+
d->problemTreeView->setModel(nullptr);
122+
}
123+
auto problemModel = new QStandardItemModel(this);
124+
d->problemTreeView->setModel(problemModel);
125+
QFile f(d->path);
126+
if (!f.open(QIODevice::ReadOnly)) {
127+
addErrorItem(
128+
problemModel,
129+
tr("Failed to open file"),
130+
{},
131+
style()->standardIcon(QStyle::SP_MessageBoxCritical),
132+
{
133+
{tr("Path"), d->path},
134+
{tr("Error code"), f.error()},
135+
{tr("Error text"), f.errorString()}
136+
}
137+
);
138+
} else {
139+
auto data = f.readAll();
140+
QDspx::SerializationErrorList errors;
141+
auto dspxModel = QDspx::Serializer::deserialize(data, errors, QDspx::Serializer::CheckError | QDspx::Serializer::CheckWarning);
142+
for (const auto &error : errors) {
143+
switch (error->type()) {
144+
case QDspx::SerializationError::JsonParseFailure: {
145+
auto e = error.staticCast<QDspx::JsonParseFailureError>();
146+
addErrorItem(
147+
problemModel,
148+
tr("Fatal: Failed to parse JSON") + QStringLiteral(" (0x%1)").arg(QString::number(error->type(), 16), 4, '0'),
149+
{},
150+
style()->standardIcon(QStyle::SP_MessageBoxCritical),
151+
{
152+
{tr("Offset in file"), e->error().offset},
153+
{tr("Error code"), e->error().error},
154+
{tr("Error text"), e->error().errorString()},
155+
},
156+
tr("The file is not a valid JSON document.")
157+
);
158+
break;
159+
}
160+
case QDspx::SerializationError::JsonRootIsNotObject: {
161+
addErrorItem(
162+
problemModel,
163+
tr("Fatal: Root is not an object") + QStringLiteral(" (0x%1)").arg(QString::number(error->type(), 16), 4, '0'),
164+
QStringLiteral("$"),
165+
style()->standardIcon(QStyle::SP_MessageBoxCritical),
166+
{},
167+
tr("The root of JSON document is not an object.")
168+
);
169+
break;
170+
}
171+
case QDspx::SerializationError::UnrecognizedVersion: {
172+
auto e = error.staticCast<QDspx::UnrecognizedVersionError>();
173+
addErrorItem(
174+
problemModel,
175+
tr("Fatal: Unrecognized version") + QStringLiteral(" (0x%1)").arg(QString::number(error->type(), 16), 4, '0'),
176+
QStringLiteral("$.version"),
177+
style()->standardIcon(QStyle::SP_MessageBoxCritical),
178+
{
179+
{tr("Actual version"), e->actualVersion()},
180+
},
181+
tr("This project file may have been created with a newer version of %1 or another application, and its version is not recognized by %1. Please try exporting the project from the application with which it was created as a version compatible with your current %1.").arg(QApplication::applicationDisplayName())
182+
);
183+
break;
184+
}
185+
case QDspx::SerializationError::InvalidDataType: {
186+
auto e = error.staticCast<QDspx::InvalidDataTypeError>();
187+
QStringList expectedTypeStrings;
188+
for (auto expectedType : e->expectedTypes()) {
189+
expectedTypeStrings.append(tr(jsonDataTypeNames[expectedType]));
190+
}
191+
addErrorItem(
192+
problemModel,
193+
tr("Error: Invalid data type") + QStringLiteral(" (0x%1)").arg(QString::number(error->type(), 16), 4, '0'),
194+
e->path(),
195+
style()->standardIcon(QStyle::SP_MessageBoxCritical),
196+
{
197+
{tr("Expected data type"), expectedTypeStrings.join(tr(", "))},
198+
{tr("Actual data type"), tr(jsonDataTypeNames[e->actualType()])},
199+
},
200+
tr("The value at the specific path is not of the expected data type.")
201+
);
202+
break;
203+
}
204+
case QDspx::SerializationError::InvalidObjectType: {
205+
auto e = error.staticCast<QDspx::InvalidObjectTypeError>();
206+
addErrorItem(
207+
problemModel,
208+
tr("Error: Invalid object type") + QStringLiteral(" (0x%1)").arg(QString::number(error->type(), 16), 4, '0'),
209+
e->path(),
210+
style()->standardIcon(QStyle::SP_MessageBoxCritical),
211+
{
212+
{tr("Expected object type"), e->expectedTypes().join(QStringLiteral(", "))},
213+
{tr("Actual object type"), e->actualType()},
214+
},
215+
tr("The object at the specific path is not of the expected object type.")
216+
);
217+
break;
218+
}
219+
case QDspx::SerializationError::RangeConstraintViolation: {
220+
auto e = error.staticCast<QDspx::RangeConstraintViolationError>();
221+
addErrorItem(
222+
problemModel,
223+
tr("Error: Range constraint violation") + QStringLiteral(" (0x%1)").arg(QString::number(error->type(), 16), 4, '0'),
224+
e->path(),
225+
style()->standardIcon(QStyle::SP_MessageBoxCritical),
226+
{
227+
{tr("Expected maximum value"), e->expectedMaximum().isNull() ? tr("None") : e->expectedMaximum()},
228+
{tr("Expected minimum value"), e->expectedMinimum().isNull() ? tr("None") : e->expectedMinimum()},
229+
{tr("Actual value"), e->actualValue().toString()},
230+
},
231+
tr("The value of the property at the specific path is outside the allowed range. The value must be between the expected minimum and maximum value (inclusive).")
232+
);
233+
break;
234+
}
235+
case QDspx::SerializationError::EnumConstraintViolation: {
236+
auto e = error.staticCast<QDspx::EnumConstraintViolationError>();
237+
QList<QPair<QString, QVariant>> info;
238+
for (const auto &expectedEnumValue : e->expectedEnumValues()) {
239+
info.emplace_back(tr("Expected enum value"), expectedEnumValue.toString());
240+
}
241+
info.emplace_back(tr("Actual enum value"), e->actualEnumValue().toString());
242+
addErrorItem(
243+
problemModel,
244+
tr("Error: Enum constraint violation") + QStringLiteral(" (0x%1)").arg(QString::number(error->type(), 16), 4, '0'),
245+
e->path(),
246+
style()->standardIcon(QStyle::SP_MessageBoxCritical),
247+
info,
248+
tr("The value of the property at the specific path is not one of the allowed enum values.")
249+
);
250+
break;
251+
}
252+
case QDspx::SerializationError::MissingProperty: {
253+
auto e = error.staticCast<QDspx::MissingPropertyError>();
254+
QList<QPair<QString, QVariant>> info;
255+
for (const auto &missingProperty : e->missingProperties()) {
256+
info.emplace_back(tr("Missing property"), missingProperty);
257+
}
258+
addErrorItem(
259+
problemModel,
260+
tr("Error: Missing properties") + QStringLiteral(" (0x%1)").arg(QString::number(error->type(), 16), 4, '0'),
261+
e->path(),
262+
style()->standardIcon(QStyle::SP_MessageBoxCritical),
263+
info,
264+
tr("One or more properties are missing in the object at the specific path.")
265+
);
266+
break;
267+
}
268+
case QDspx::SerializationError::RedundantProperty: {
269+
auto e = error.staticCast<QDspx::MissingPropertyError>();
270+
QList<QPair<QString, QVariant>> info;
271+
for (const auto &redundantProperty : e->missingProperties()) {
272+
info.emplace_back(tr("Redundant property"), redundantProperty);
273+
}
274+
addErrorItem(
275+
problemModel,
276+
tr("Error: Redundant properties") + QStringLiteral(" (0x%1)").arg(QString::number(error->type(), 16), 4, '0'),
277+
e->path(),
278+
style()->standardIcon(QStyle::SP_MessageBoxCritical),
279+
info,
280+
tr("One or more properties are redundant in the object at the specific path.")
281+
);
282+
break;
283+
}
284+
case QDspx::SerializationError::OverlappingItem: {
285+
auto e = error.staticCast<QDspx::OverlappingItemError>();
286+
QList<QPair<QString, QVariant>> info;
287+
for (auto overlappedItemIndex : e->overlappedItemIndexes()) {
288+
info.emplace_back(tr("Index"), overlappedItemIndex);
289+
}
290+
addErrorItem(
291+
problemModel,
292+
tr("Warning: Overlapping items") + QStringLiteral(" (0x%1)").arg(QString::number(error->type(), 16), 4, '0'),
293+
e->path(),
294+
style()->standardIcon(QStyle::SP_MessageBoxWarning),
295+
info,
296+
tr("Items at specific indexes in the array at the specific path overlap.")
297+
);
298+
break;
299+
}
300+
case QDspx::SerializationError::ZeroLengthRange: {
301+
auto e = error.staticCast<QDspx::ZeroLengthRangeError>();
302+
addErrorItem(
303+
problemModel,
304+
tr("Warning: Zero-length range") + QStringLiteral(" (0x%1)").arg(QString::number(error->type(), 16), 4, '0'),
305+
e->path(),
306+
style()->standardIcon(QStyle::SP_MessageBoxWarning),
307+
{},
308+
tr("The range length of the entity object at the specific path is zero.")
309+
);
310+
break;
311+
}
312+
case QDspx::SerializationError::ErroneousClipRange: {
313+
auto e = error.staticCast<QDspx::ErroneousClipRangeError>();
314+
addErrorItem(
315+
problemModel,
316+
tr("Warning: Erroneous clip range") + QStringLiteral(" (0x%1)").arg(QString::number(error->type(), 16), 4, '0'),
317+
e->path(),
318+
style()->standardIcon(QStyle::SP_MessageBoxWarning),
319+
{},
320+
tr("The clipping range of the clip entity object at the specific path exceeds its range limit.")
321+
);
322+
break;
323+
}
324+
case QDspx::SerializationError::ErroneousClipPosition: {
325+
auto e = error.staticCast<QDspx::ErroneousClipPositionError>();
326+
addErrorItem(
327+
problemModel,
328+
tr("Warning: Erroneous clip position") + QStringLiteral(" (0x%1)").arg(QString::number(error->type(), 16), 4, '0'),
329+
e->path(),
330+
style()->standardIcon(QStyle::SP_MessageBoxWarning),
331+
{},
332+
tr("The position of the clip entity object at the specific path exceeds the view range limit. It might be not visible in the viewport.")
333+
);
334+
break;
335+
}
336+
case QDspx::SerializationError::SafeRangeLimitExceeded: {
337+
auto e = error.staticCast<QDspx::SafeRangeLimitExceededError>();
338+
addErrorItem(
339+
problemModel,
340+
tr("Warning: Safe range limit exceeded") + QStringLiteral(" (0x%1)").arg(QString::number(error->type(), 16), 4, '0'),
341+
e->path(),
342+
style()->standardIcon(QStyle::SP_MessageBoxWarning),
343+
{},
344+
tr("The position of the entity object at the specific path exceeds the safe project length limit (4,000,000 ticks).")
345+
);
346+
break;
347+
}
348+
}
349+
}
350+
351+
// TODO show file structure
352+
}
353+
354+
355+
d->problemTreeView->resizeColumnToContents(0);
356+
if (problemModel->rowCount() != 0) {
357+
d->tabWidget->setCurrentIndex(1);
358+
}
359+
}
360+
361+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#ifndef DIFFSCOPE_COREPLUGIN_DSPXINSPECTORDIALOG_H
2+
#define DIFFSCOPE_COREPLUGIN_DSPXINSPECTORDIALOG_H
3+
4+
#include <QDialog>
5+
#include <qqmlintegration.h>
6+
7+
namespace Core {
8+
9+
class DspxInspectorDialogPrivate;
10+
11+
class DspxInspectorDialog : public QDialog {
12+
Q_OBJECT
13+
QML_ELEMENT
14+
Q_DECLARE_PRIVATE(DspxInspectorDialog)
15+
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
16+
public:
17+
explicit DspxInspectorDialog(QWidget *parent = nullptr);
18+
~DspxInspectorDialog() override;
19+
20+
QString path() const;
21+
void setPath(const QString &path);
22+
23+
public slots:
24+
void runCheck();
25+
26+
Q_SIGNALS:
27+
void pathChanged(const QString &path);
28+
29+
private:
30+
QScopedPointer<DspxInspectorDialogPrivate> d_ptr;
31+
32+
};
33+
34+
}
35+
36+
#endif //DIFFSCOPE_COREPLUGIN_DSPXINSPECTORDIALOG_H

0 commit comments

Comments
 (0)