|
|
|
@@ -0,0 +1,529 @@
|
|
|
|
|
#include <KSvg/FrameSvg>
|
|
|
|
|
|
|
|
|
|
#include <QFile>
|
|
|
|
|
#include <QFileInfo>
|
|
|
|
|
#include <QGuiApplication>
|
|
|
|
|
#include <QHash>
|
|
|
|
|
#include <QIODevice>
|
|
|
|
|
#include <QSet>
|
|
|
|
|
#include <QSizeF>
|
|
|
|
|
#include <QString>
|
|
|
|
|
#include <QXmlStreamReader>
|
|
|
|
|
#include <QtTypes>
|
|
|
|
|
|
|
|
|
|
#include <cmath>
|
|
|
|
|
#include <cstddef>
|
|
|
|
|
#include <initializer_list>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
struct ElementInfo {
|
|
|
|
|
QString name;
|
|
|
|
|
QHash<QString, QString> attributes;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct Orientation {
|
|
|
|
|
const char* name;
|
|
|
|
|
qreal originX;
|
|
|
|
|
qreal left;
|
|
|
|
|
qreal top;
|
|
|
|
|
qreal right;
|
|
|
|
|
qreal bottom;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct State {
|
|
|
|
|
const char* name;
|
|
|
|
|
qreal originY;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct Geometry {
|
|
|
|
|
qreal x;
|
|
|
|
|
qreal y;
|
|
|
|
|
qreal width;
|
|
|
|
|
qreal height;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
constexpr qreal comparisonTolerance = 0.001;
|
|
|
|
|
constexpr qreal markerOffset = 20;
|
|
|
|
|
constexpr qreal markerSize = 4;
|
|
|
|
|
constexpr qreal testFrameHeight = 32;
|
|
|
|
|
constexpr qreal testFrameWidth = 36;
|
|
|
|
|
constexpr qreal tileSize = 44;
|
|
|
|
|
|
|
|
|
|
constexpr std::initializer_list<Orientation> orientations = {
|
|
|
|
|
{"", 0, 6, 8, 6, 12},
|
|
|
|
|
{"north", 44, 6, 12, 6, 8 },
|
|
|
|
|
{"west", 88, 12, 6, 8, 6 },
|
|
|
|
|
{"east", 132, 8, 6, 12, 6 },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
constexpr std::initializer_list<State> frameStates = {
|
|
|
|
|
{"normal", 0 },
|
|
|
|
|
{"minimized", 44 },
|
|
|
|
|
{"focus", 88 },
|
|
|
|
|
{"hover", 132},
|
|
|
|
|
{"progress", 0 },
|
|
|
|
|
{"attention", 0 },
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
constexpr std::initializer_list<State> marginStates = {
|
|
|
|
|
{"normal", 0 },
|
|
|
|
|
{"minimized", 44 },
|
|
|
|
|
{"focus", 88 },
|
|
|
|
|
{"hover", 132},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
constexpr std::initializer_list<const char*> frameParts = {
|
|
|
|
|
"center",
|
|
|
|
|
"top",
|
|
|
|
|
"bottom",
|
|
|
|
|
"left",
|
|
|
|
|
"right",
|
|
|
|
|
"topleft",
|
|
|
|
|
"topright",
|
|
|
|
|
"bottomleft",
|
|
|
|
|
"bottomright",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
constexpr std::initializer_list<const char*> marginSides = {
|
|
|
|
|
"top",
|
|
|
|
|
"bottom",
|
|
|
|
|
"left",
|
|
|
|
|
"right",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
QString framePrefix(const Orientation& orientation, const State& state) {
|
|
|
|
|
if (QString::fromLatin1(orientation.name).isEmpty()) {
|
|
|
|
|
return QString::fromLatin1(state.name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return QStringLiteral("%1-%2").arg(
|
|
|
|
|
QString::fromLatin1(orientation.name), QString::fromLatin1(state.name)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString marginHintId(
|
|
|
|
|
const Orientation& orientation, const State& state, const char* side
|
|
|
|
|
) {
|
|
|
|
|
return QStringLiteral("%1-hint-%2-margin")
|
|
|
|
|
.arg(framePrefix(orientation, state), QString::fromLatin1(side));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool closeEnough(qreal actual, qreal expected) {
|
|
|
|
|
return std::abs(actual - expected) < comparisonTolerance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool checkValue(
|
|
|
|
|
const QString& context, const char* name, qreal actual, qreal expected
|
|
|
|
|
) {
|
|
|
|
|
if (closeEnough(actual, expected)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::cerr << context.toStdString() << ": expected " << name << "=" << expected
|
|
|
|
|
<< ", got " << actual << "\n";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool readElements(const QString& path, QHash<QString, ElementInfo>& elements) {
|
|
|
|
|
QFile file(path);
|
|
|
|
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
|
|
|
std::cerr << path.toStdString() << ": cannot open file\n";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QXmlStreamReader reader(&file);
|
|
|
|
|
bool success = true;
|
|
|
|
|
|
|
|
|
|
while (!reader.atEnd()) {
|
|
|
|
|
reader.readNext();
|
|
|
|
|
|
|
|
|
|
if (!reader.isStartElement()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QString elementId =
|
|
|
|
|
reader.attributes().value(QStringLiteral("id")).toString();
|
|
|
|
|
if (elementId.isEmpty()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (elements.contains(elementId)) {
|
|
|
|
|
std::cerr << path.toStdString() << ": " << elementId.toStdString()
|
|
|
|
|
<< ": duplicate element id\n";
|
|
|
|
|
success = false;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ElementInfo element;
|
|
|
|
|
element.name = reader.name().toString();
|
|
|
|
|
|
|
|
|
|
for (const QXmlStreamAttribute& attribute : reader.attributes()) {
|
|
|
|
|
element.attributes.insert(
|
|
|
|
|
attribute.name().toString(), attribute.value().toString()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
elements.insert(elementId, element);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (reader.hasError()) {
|
|
|
|
|
std::cerr << path.toStdString()
|
|
|
|
|
<< ": invalid XML: " << reader.errorString().toStdString()
|
|
|
|
|
<< "\n";
|
|
|
|
|
success = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return success;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool parseNumber(
|
|
|
|
|
const QString& path,
|
|
|
|
|
const QString& elementId,
|
|
|
|
|
const ElementInfo& element,
|
|
|
|
|
const char* attributeName,
|
|
|
|
|
qreal& value
|
|
|
|
|
) {
|
|
|
|
|
const QString attribute = QString::fromLatin1(attributeName);
|
|
|
|
|
|
|
|
|
|
if (!element.attributes.contains(attribute)) {
|
|
|
|
|
std::cerr << path.toStdString() << ": " << elementId.toStdString()
|
|
|
|
|
<< ": missing " << attributeName << "\n";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool parsed = false;
|
|
|
|
|
value = element.attributes.value(attribute).toDouble(&parsed);
|
|
|
|
|
|
|
|
|
|
if (!parsed) {
|
|
|
|
|
std::cerr << path.toStdString() << ": " << elementId.toStdString()
|
|
|
|
|
<< ": invalid " << attributeName << " value "
|
|
|
|
|
<< element.attributes.value(attribute).toStdString() << "\n";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Geometry expectedMarginGeometry(
|
|
|
|
|
const Orientation& orientation, const State& state, const QString& side
|
|
|
|
|
) {
|
|
|
|
|
if (side == QStringLiteral("top")) {
|
|
|
|
|
return {
|
|
|
|
|
orientation.originX + markerOffset,
|
|
|
|
|
state.originY,
|
|
|
|
|
markerSize,
|
|
|
|
|
orientation.top
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (side == QStringLiteral("bottom")) {
|
|
|
|
|
return {
|
|
|
|
|
orientation.originX + markerOffset,
|
|
|
|
|
state.originY + tileSize - orientation.bottom,
|
|
|
|
|
markerSize,
|
|
|
|
|
orientation.bottom,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (side == QStringLiteral("left")) {
|
|
|
|
|
return {
|
|
|
|
|
orientation.originX,
|
|
|
|
|
state.originY + markerOffset,
|
|
|
|
|
orientation.left,
|
|
|
|
|
markerSize
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
orientation.originX + tileSize - orientation.right,
|
|
|
|
|
state.originY + markerOffset,
|
|
|
|
|
orientation.right,
|
|
|
|
|
markerSize,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool checkFrameContract(
|
|
|
|
|
const QString& path, const QHash<QString, ElementInfo>& elements
|
|
|
|
|
) {
|
|
|
|
|
bool success = true;
|
|
|
|
|
|
|
|
|
|
for (const Orientation& orientation : orientations) {
|
|
|
|
|
for (const State& state : frameStates) {
|
|
|
|
|
const QString prefix = framePrefix(orientation, state);
|
|
|
|
|
|
|
|
|
|
if (!elements.contains(prefix)) {
|
|
|
|
|
std::cerr << path.toStdString() << ": " << prefix.toStdString()
|
|
|
|
|
<< ": missing frame group\n";
|
|
|
|
|
success = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const char* part : frameParts) {
|
|
|
|
|
const QString elementId = QStringLiteral("%1-%2").arg(
|
|
|
|
|
prefix, QString::fromLatin1(part)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!elements.contains(elementId)) {
|
|
|
|
|
std::cerr << path.toStdString() << ": " << elementId.toStdString()
|
|
|
|
|
<< ": missing frame part\n";
|
|
|
|
|
success = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return success;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool checkMarginHint(
|
|
|
|
|
const QString& path,
|
|
|
|
|
const QHash<QString, ElementInfo>& elements,
|
|
|
|
|
const QString& elementId,
|
|
|
|
|
const Geometry& expected
|
|
|
|
|
) {
|
|
|
|
|
const ElementInfo element = elements.value(elementId);
|
|
|
|
|
|
|
|
|
|
if (element.name != QStringLiteral("rect")) {
|
|
|
|
|
std::cerr << path.toStdString() << ": " << elementId.toStdString()
|
|
|
|
|
<< ": expected rect element\n";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool success = true;
|
|
|
|
|
qreal actual = 0;
|
|
|
|
|
|
|
|
|
|
if (parseNumber(path, elementId, element, "x", actual)) {
|
|
|
|
|
success = checkValue(
|
|
|
|
|
QStringLiteral("%1: %2").arg(path, elementId),
|
|
|
|
|
"x",
|
|
|
|
|
actual,
|
|
|
|
|
expected.x
|
|
|
|
|
) &&
|
|
|
|
|
success;
|
|
|
|
|
} else {
|
|
|
|
|
success = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (parseNumber(path, elementId, element, "y", actual)) {
|
|
|
|
|
success = checkValue(
|
|
|
|
|
QStringLiteral("%1: %2").arg(path, elementId),
|
|
|
|
|
"y",
|
|
|
|
|
actual,
|
|
|
|
|
expected.y
|
|
|
|
|
) &&
|
|
|
|
|
success;
|
|
|
|
|
} else {
|
|
|
|
|
success = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (parseNumber(path, elementId, element, "width", actual)) {
|
|
|
|
|
success = checkValue(
|
|
|
|
|
QStringLiteral("%1: %2").arg(path, elementId),
|
|
|
|
|
"width",
|
|
|
|
|
actual,
|
|
|
|
|
expected.width
|
|
|
|
|
) &&
|
|
|
|
|
success;
|
|
|
|
|
} else {
|
|
|
|
|
success = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (parseNumber(path, elementId, element, "height", actual)) {
|
|
|
|
|
success = checkValue(
|
|
|
|
|
QStringLiteral("%1: %2").arg(path, elementId),
|
|
|
|
|
"height",
|
|
|
|
|
actual,
|
|
|
|
|
expected.height
|
|
|
|
|
) &&
|
|
|
|
|
success;
|
|
|
|
|
} else {
|
|
|
|
|
success = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return success;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool checkMarginContract(
|
|
|
|
|
const QString& path, const QHash<QString, ElementInfo>& elements
|
|
|
|
|
) {
|
|
|
|
|
bool success = true;
|
|
|
|
|
QSet<QString> expectedIds;
|
|
|
|
|
|
|
|
|
|
for (const Orientation& orientation : orientations) {
|
|
|
|
|
for (const State& state : marginStates) {
|
|
|
|
|
for (const char* sideName : marginSides) {
|
|
|
|
|
const QString side = QString::fromLatin1(sideName);
|
|
|
|
|
const QString elementId = marginHintId(orientation, state, sideName);
|
|
|
|
|
expectedIds.insert(elementId);
|
|
|
|
|
|
|
|
|
|
if (!elements.contains(elementId)) {
|
|
|
|
|
std::cerr << path.toStdString() << ": " << elementId.toStdString()
|
|
|
|
|
<< ": missing margin hint\n";
|
|
|
|
|
success = false;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Geometry expected = expectedMarginGeometry(
|
|
|
|
|
orientation, state, side
|
|
|
|
|
);
|
|
|
|
|
success = checkMarginHint(path, elements, elementId, expected) &&
|
|
|
|
|
success;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto iterator = elements.constBegin(); iterator != elements.constEnd();
|
|
|
|
|
++iterator) {
|
|
|
|
|
const QString& elementId = iterator.key();
|
|
|
|
|
bool isMarginHint = false;
|
|
|
|
|
|
|
|
|
|
for (const char* side : marginSides) {
|
|
|
|
|
isMarginHint = isMarginHint ||
|
|
|
|
|
elementId.endsWith(QStringLiteral("-hint-%1-margin")
|
|
|
|
|
.arg(QString::fromLatin1(side)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isMarginHint && !expectedIds.contains(elementId)) {
|
|
|
|
|
std::cerr << path.toStdString() << ": " << elementId.toStdString()
|
|
|
|
|
<< ": unexpected margin hint\n";
|
|
|
|
|
success = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return success;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool checkStretchHint(
|
|
|
|
|
const QString& path, const QHash<QString, ElementInfo>& elements
|
|
|
|
|
) {
|
|
|
|
|
const QString elementId = QStringLiteral("hint-stretch-borders");
|
|
|
|
|
|
|
|
|
|
if (!elements.contains(elementId)) {
|
|
|
|
|
std::cerr << path.toStdString()
|
|
|
|
|
<< ": hint-stretch-borders: missing stretch hint\n";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QString name = elements.value(elementId).name;
|
|
|
|
|
if (name == QStringLiteral("path") || name == QStringLiteral("rect")) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::cerr << path.toStdString()
|
|
|
|
|
<< ": hint-stretch-borders: expected path or rect element\n";
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool checkSvgContract(const QString& path) {
|
|
|
|
|
QHash<QString, ElementInfo> elements;
|
|
|
|
|
|
|
|
|
|
if (!readElements(path, elements)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool success = true;
|
|
|
|
|
success = checkFrameContract(path, elements) && success;
|
|
|
|
|
success = checkMarginContract(path, elements) && success;
|
|
|
|
|
success = checkStretchHint(path, elements) && success;
|
|
|
|
|
return success;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool checkKsvgPrefixes(const QString& path) {
|
|
|
|
|
KSvg::FrameSvg frame;
|
|
|
|
|
frame.setUsingRenderingCache(false);
|
|
|
|
|
frame.setImagePath(path);
|
|
|
|
|
|
|
|
|
|
bool success = true;
|
|
|
|
|
|
|
|
|
|
for (const Orientation& orientation : orientations) {
|
|
|
|
|
for (const State& state : frameStates) {
|
|
|
|
|
const QString prefix = framePrefix(orientation, state);
|
|
|
|
|
|
|
|
|
|
if (!frame.hasElementPrefix(prefix)) {
|
|
|
|
|
std::cerr << prefix.toStdString() << ": missing KSVG frame prefix\n";
|
|
|
|
|
success = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return success;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool checkKsvgMargins(const QString& path) {
|
|
|
|
|
bool success = true;
|
|
|
|
|
|
|
|
|
|
for (const Orientation& orientation : orientations) {
|
|
|
|
|
for (const State& state : marginStates) {
|
|
|
|
|
const QString prefix = framePrefix(orientation, state);
|
|
|
|
|
|
|
|
|
|
KSvg::FrameSvg frame;
|
|
|
|
|
frame.setUsingRenderingCache(false);
|
|
|
|
|
frame.setImagePath(path);
|
|
|
|
|
frame.setElementPrefix(prefix);
|
|
|
|
|
frame.resizeFrame(QSizeF(testFrameWidth, testFrameHeight));
|
|
|
|
|
|
|
|
|
|
qreal left = 0;
|
|
|
|
|
qreal top = 0;
|
|
|
|
|
qreal right = 0;
|
|
|
|
|
qreal bottom = 0;
|
|
|
|
|
frame.getFixedMargins(left, top, right, bottom);
|
|
|
|
|
|
|
|
|
|
success = checkValue(prefix, "left", left, orientation.left) && success;
|
|
|
|
|
success = checkValue(prefix, "top", top, orientation.top) && success;
|
|
|
|
|
success = checkValue(prefix, "right", right, orientation.right) &&
|
|
|
|
|
success;
|
|
|
|
|
success = checkValue(prefix, "bottom", bottom, orientation.bottom) &&
|
|
|
|
|
success;
|
|
|
|
|
success = checkValue(
|
|
|
|
|
prefix,
|
|
|
|
|
"minimumWidth",
|
|
|
|
|
frame.minimumDrawingWidth(),
|
|
|
|
|
orientation.left + orientation.right
|
|
|
|
|
) &&
|
|
|
|
|
success;
|
|
|
|
|
success = checkValue(
|
|
|
|
|
prefix,
|
|
|
|
|
"minimumHeight",
|
|
|
|
|
frame.minimumDrawingHeight(),
|
|
|
|
|
orientation.top + orientation.bottom
|
|
|
|
|
) &&
|
|
|
|
|
success;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return success;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
int main(int argc, char** argv) {
|
|
|
|
|
const QGuiApplication app(argc, argv);
|
|
|
|
|
|
|
|
|
|
if (argc != 3) {
|
|
|
|
|
std::cerr << "usage: " << argv[0]
|
|
|
|
|
<< " widgets/tasks.svg widgets/tasks.svgz\n";
|
|
|
|
|
return 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const QString svgPath =
|
|
|
|
|
QFileInfo(QString::fromLocal8Bit(argv[1])).absoluteFilePath();
|
|
|
|
|
const QString svgzPath =
|
|
|
|
|
QFileInfo(QString::fromLocal8Bit(argv[2])).absoluteFilePath();
|
|
|
|
|
|
|
|
|
|
bool success = true;
|
|
|
|
|
success = checkSvgContract(svgPath) && success;
|
|
|
|
|
success = checkKsvgPrefixes(svgzPath) && success;
|
|
|
|
|
success = checkKsvgMargins(svgzPath) && success;
|
|
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const size_t frameCount = orientations.size() * frameStates.size();
|
|
|
|
|
const size_t hintCount = orientations.size() * marginStates.size() *
|
|
|
|
|
marginSides.size();
|
|
|
|
|
std::cout << "ok: " << frameCount << " task frames, " << hintCount
|
|
|
|
|
<< " margin hints, and KSVG task margins\n";
|
|
|
|
|
return 0;
|
|
|
|
|
}
|