Files
z-bar-qt/Plugins/ZShell/alignaudit.cpp
T

207 lines
5.2 KiB
C++

#include "alignaudit.hpp"
#include <QDateTime>
#include <QFile>
#include <QGuiApplication>
#include <QQmlContext>
#include <QRegularExpression>
#include <QScreen>
#include <QTextStream>
#include <QTimer>
#include <QQuickItem>
#include <QQuickWindow>
#include <cmath>
#include <functional>
#include <qobject.h>
#ifdef ALIGN_AUDIT_USE_QML_PRIVATE
#include <private/qqmldata_p.h>
#endif
namespace ZShell {
namespace {
bool isOff(double v, double tol) {
return std::abs(v - std::round(v)) > tol;
}
QString num(double v, bool off) {
return QString::number(v, 'f', 2) + (off ? QStringLiteral("*") : QString());
}
QString typeName(const QQuickItem* item) {
static const QRegularExpression suffix(QStringLiteral("_QML(TYPE)?_\\d+$"));
QString name = QString::fromUtf8(item->metaObject()->className());
name.remove(suffix);
if (name.startsWith(QLatin1String("QQuick"))) {
name.remove(0, 6);
}
return name;
}
QString label(const QQuickItem* item) {
QString s = typeName(item);
if (!item->objectName().isEmpty()) {
s += QLatin1Char('#') + item->objectName();
}
return s;
}
QString ancestry(const QQuickItem* item, int maxDepth = 8) {
QStringList parts;
for (const QQuickItem* i = item; i && parts.size() < maxDepth;
i = i->parentItem()) {
parts.prepend(label(i));
}
return parts.join(QLatin1String(" > "));
}
struct Source {
QString file;
int line = 0;
int column = 0;
};
Source sourceOf(QQuickItem* item) {
Source s;
if (const QQmlContext* ctx = qmlContext(item)) {
const QUrl url = ctx->baseUrl();
s.file = url.isLocalFile() ? url.toLocalFile() : url.toString();
}
#ifdef ALIGN_AUDIT_USE_QML_PRIVATE
if (const QQmlData* d = QQmlData::get(item, false)) {
s.line = int(d->lineNumber);
s.column = int(d->columnNumber);
}
#endif
return s;
}
void visit(QQuickItem* item, const std::function<void(QQuickItem*)>& fn) {
if (!item->isVisible() || item->opacity() <= 0.0) {
return; // whole subtree is invisible
}
fn(item);
for (QQuickItem* child : item->childItems()) {
visit(child, fn);
}
}
int auditWindow(QQuickWindow* window, const Options& o, QTextStream& out) {
const double dpr = window->effectiveDevicePixelRatio();
const double tol = o.tolerance;
int count = 0;
visit(window->contentItem(), [&](QQuickItem* item) {
if (item->width() <= 0 || item->height() <= 0) {
return;
}
if (o.paintedOnly &&
!item->flags().testFlag(QQuickItem::ItemHasContents) &&
!item->clip()) {
return;
}
const bool isText = item->inherits("QQuickText");
const QRectF r =
item->mapRectToScene(QRectF(0, 0, item->width(), item->height()));
const double l = r.left() * dpr, t = r.top() * dpr;
const double rt = r.right() * dpr, b = r.bottom() * dpr;
const bool offL = isOff(l, tol), offT = isOff(t, tol);
const bool offR = !isText && isOff(rt, tol);
const bool offB = !isText && isOff(b, tol);
if (!(offL || offT || offR || offB)) {
return;
}
const bool own = isOff(item->x() * dpr, tol) ||
isOff(item->y() * dpr, tol) ||
(!isText && (isOff(item->width() * dpr, tol) ||
isOff(item->height() * dpr, tol)));
if (!own && !o.includeInherited) {
return;
}
const Source src = sourceOf(item);
out << src.file;
if (src.line > 0) {
out << ':' << src.line << ':' << src.column;
}
out << ": " << label(item) << " unaligned ("
<< (own ? "own" : "inherited") << ")\n"
<< " device px: left=" << num(l, offL) << " top=" << num(t, offT)
<< " right=" << num(rt, offR) << " bottom=" << num(b, offB) << "\n"
<< " local: x=" << item->x() << " y=" << item->y()
<< " w=" << item->width() << " h=" << item->height() << "\n"
<< " path: " << ancestry(item) << "\n";
++count;
});
return count;
}
} // namespace
struct ScreenReport {
double dpr = 1.0;
int windows = 0;
int count = 0;
QString body;
};
int run(const Options& options) {
QMap<QString, ScreenReport> reports;
for (QWindow* w : QGuiApplication::allWindows()) {
auto* qw = qobject_cast<QQuickWindow*>(w);
if (!qw || !qw->isVisible() || !qw->contentItem()) {
continue;
}
const QString name = qw->screen() ? qw->screen()->name()
: QStringLiteral("?");
ScreenReport& report = reports[name];
report.dpr = qw->effectiveDevicePixelRatio();
++report.windows;
QString windowBody;
QTextStream stream(&windowBody);
report.count += auditWindow(qw, options, stream);
stream.flush();
report.body += windowBody;
}
QFile file(options.outputPath);
if (!file.open(
QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
qWarning() << "AlignAudit: cannot open" << options.outputPath;
return -1;
}
QTextStream out(&file);
int total = 0;
for (const ScreenReport& r : std::as_const(reports)) {
total += r.count;
}
out << "# align audit "
<< QDateTime::currentDateTime().toString(Qt::ISODate)
<< " ('*' marks the off edges, values are device pixels)\n"
<< "# total: " << total << "\n\n";
for (auto it = reports.cbegin(); it != reports.cend(); ++it) {
out << "# screen " << it.key() << " dpr=" << it.value().dpr
<< " windows=" << it.value().windows
<< " offenders=" << it.value().count << "\n"
<< it.value().body << "\n";
}
return total;
}
void runLater(int delayMs, const Options& options) {
QTimer::singleShot(delayMs, qApp, [options] { run(options); });
}
} // namespace ZShell