qt 6.5.1 original

This commit is contained in:
kleuter
2023-10-29 23:33:08 +01:00
parent 71d22ab6b0
commit 85d238dfda
21202 changed files with 5499099 additions and 0 deletions

View File

@ -0,0 +1,456 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "baselineprotocol.h"
#include <QLibraryInfo>
#include <QImage>
#include <QBuffer>
#include <QHostInfo>
#include <QSysInfo>
#if QT_CONFIG(process)
# include <QProcess>
#endif
#include <QFileInfo>
#include <QDir>
#include <QThread>
#include <QTime>
#include <QPointer>
#include <QRegularExpression>
const QString PI_Project(QLS("Project"));
const QString PI_ProjectImageKeys(QLS("ProjectImageKeys"));
const QString PI_TestCase(QLS("TestCase"));
const QString PI_HostName(QLS("HostName"));
const QString PI_HostAddress(QLS("HostAddress"));
const QString PI_OSName(QLS("OSName"));
const QString PI_OSVersion(QLS("OSVersion"));
const QString PI_QtVersion(QLS("QtVersion"));
const QString PI_QtBuildMode(QLS("QtBuildMode"));
const QString PI_GitCommit(QLS("GitCommit"));
const QString PI_GitBranch(QLS("GitBranch"));
PlatformInfo PlatformInfo::localHostInfo()
{
PlatformInfo pi;
pi.insert(PI_HostName, QHostInfo::localHostName());
pi.insert(PI_QtVersion, QLS(qVersion()));
pi.insert(PI_QtBuildMode, QLibraryInfo::isDebugBuild() ? QLS("QtDebug") : QLS("QtRelease"));
#if defined(Q_OS_LINUX) && QT_CONFIG(process)
pi.insert(PI_OSName, QLS("Linux"));
#elif defined(Q_OS_WIN)
pi.insert(PI_OSName, QLS("Windows"));
#elif defined(Q_OS_DARWIN)
pi.insert(PI_OSName, QLS("Darwin"));
#else
pi.insert(PI_OSName, QLS("Other"));
#endif
pi.insert(PI_OSVersion, QSysInfo::kernelVersion());
QString gc = qEnvironmentVariable("BASELINE_GIT_COMMIT");
#if QT_CONFIG(process)
if (gc.isEmpty()) {
QProcess git;
QString cmd;
QStringList args;
#if defined(Q_OS_WIN)
cmd = QLS("cmd.exe");
args << QLS("/c") << QLS("git");
#else
cmd = QLS("git");
#endif
args << QLS("log") << QLS("--max-count=1") << QLS("--pretty=%H [%an] [%ad] %s");
git.start(cmd, args);
git.waitForFinished(3000);
if (!git.exitCode())
gc = QString::fromLocal8Bit(git.readAllStandardOutput().constData()).simplified();
}
#endif // QT_CONFIG(process)
pi.insert(PI_GitCommit, gc.isEmpty() ? QLS("Unknown") : gc);
if (qEnvironmentVariableIsSet("JENKINS_HOME"))
pi.setAdHocRun(false);
QString gb = qEnvironmentVariable("GIT_BRANCH");
if (!gb.isEmpty())
pi.insert(PI_GitBranch, gb);
return pi;
}
void PlatformInfo::addOverride(const QString& key, const QString& value)
{
orides.append(key);
orides.append(value);
}
QStringList PlatformInfo::overrides() const
{
return orides;
}
void PlatformInfo::setAdHocRun(bool isAdHoc)
{
adHoc = isAdHoc;
}
bool PlatformInfo::isAdHocRun() const
{
return adHoc;
}
QDataStream & operator<< (QDataStream &stream, const PlatformInfo &pi)
{
stream << static_cast<const QMap<QString, QString>&>(pi);
stream << pi.orides << pi.adHoc;
return stream;
}
QDataStream & operator>> (QDataStream &stream, PlatformInfo &pi)
{
stream >> static_cast<QMap<QString, QString>&>(pi);
stream >> pi.orides >> pi.adHoc;
return stream;
}
// Defined in lookup3.c:
void hashword2 (
const quint32 *k, /* the key, an array of quint32 values */
size_t length, /* the length of the key, in quint32s */
quint32 *pc, /* IN: seed OUT: primary hash value */
quint32 *pb); /* IN: more seed OUT: secondary hash value */
quint64 ImageItem::computeChecksum(const QImage &image)
{
QImage img(image);
const qsizetype bpl = img.bytesPerLine();
const int padBytes = bpl - (qsizetype(img.width()) * img.depth() / 8);
if (padBytes) {
uchar *p = img.bits() + bpl - padBytes;
const int h = img.height();
for (int y = 0; y < h; ++y) {
memset(p, 0, padBytes);
p += bpl;
}
}
quint32 h1 = 0xfeedbacc;
quint32 h2 = 0x21604894;
hashword2((const quint32 *)img.constBits(), img.sizeInBytes()/4, &h1, &h2);
return (quint64(h1) << 32) | h2;
}
#if 0
QString ImageItem::engineAsString() const
{
switch (engine) {
case Raster:
return QLS("Raster");
break;
case OpenGL:
return QLS("OpenGL");
break;
default:
break;
}
return QLS("Unknown");
}
QString ImageItem::formatAsString() const
{
static const int numFormats = 16;
static const char *formatNames[numFormats] = {
"Invalid",
"Mono",
"MonoLSB",
"Indexed8",
"RGB32",
"ARGB32",
"ARGB32-Premult",
"RGB16",
"ARGB8565-Premult",
"RGB666",
"ARGB6666-Premult",
"RGB555",
"ARGB8555-Premult",
"RGB888",
"RGB444",
"ARGB4444-Premult"
};
if (renderFormat < 0 || renderFormat >= numFormats)
return QLS("UnknownFormat");
return QLS(formatNames[renderFormat]);
}
#endif
void ImageItem::writeImageToStream(QDataStream &out) const
{
if (image.isNull() || image.format() == QImage::Format_Invalid) {
out << quint8(0);
return;
}
out << quint8('Q') << quint8(image.format());
out << quint8(QSysInfo::ByteOrder) << quint8(0); // pad to multiple of 4 bytes
out << quint32(image.width()) << quint32(image.height()) << quint32(image.bytesPerLine());
out << qCompress(reinterpret_cast<const uchar *>(image.constBits()), image.sizeInBytes());
//# can be followed by colormap for formats that use it
}
void ImageItem::readImageFromStream(QDataStream &in)
{
quint8 hdr, fmt, endian, pad;
quint32 width, height, bpl;
QByteArray data;
in >> hdr;
if (hdr != 'Q') {
image = QImage();
return;
}
in >> fmt >> endian >> pad;
if (!fmt || fmt >= QImage::NImageFormats) {
image = QImage();
return;
}
if (endian != QSysInfo::ByteOrder) {
qWarning("ImageItem cannot read streamed image with different endianness");
image = QImage();
return;
}
in >> width >> height >> bpl;
in >> data;
data = qUncompress(data);
QImage res((const uchar *)data.constData(), width, height, bpl, QImage::Format(fmt));
image = res.copy(); //# yuck, seems there is currently no way to avoid data copy
}
QDataStream & operator<< (QDataStream &stream, const ImageItem &ii)
{
stream << ii.testFunction << ii.itemName << ii.itemChecksum << quint8(ii.status) << ii.imageChecksums << ii.misc;
ii.writeImageToStream(stream);
return stream;
}
QDataStream & operator>> (QDataStream &stream, ImageItem &ii)
{
quint8 encStatus;
stream >> ii.testFunction >> ii.itemName >> ii.itemChecksum >> encStatus >> ii.imageChecksums >> ii.misc;
ii.status = ImageItem::ItemStatus(encStatus);
ii.readImageFromStream(stream);
return stream;
}
BaselineProtocol::BaselineProtocol()
{
}
BaselineProtocol::~BaselineProtocol()
{
disconnect();
}
bool BaselineProtocol::disconnect()
{
socket.close();
return (socket.state() == QTcpSocket::UnconnectedState) ? true : socket.waitForDisconnected(Timeout);
}
bool BaselineProtocol::connect(const QString &testCase, bool *dryrun, const PlatformInfo& clientInfo)
{
errMsg.clear();
QByteArray serverName(qgetenv("QT_LANCELOT_SERVER"));
if (serverName.isNull())
serverName = "lancelot.test.qt-project.org";
socket.connectToHost(serverName, ServerPort);
if (!socket.waitForConnected(Timeout)) {
QThread::msleep(3000); // Wait a bit and try again, the server might just be restarting
if (!socket.waitForConnected(Timeout)) {
errMsg += QLS("TCP connectToHost failed. Host:") + QLS(serverName) + QLS(" port:") + QString::number(ServerPort);
return false;
}
}
PlatformInfo pi = clientInfo.isEmpty() ? PlatformInfo::localHostInfo() : clientInfo;
pi.insert(PI_TestCase, testCase);
QByteArray block;
QDataStream ds(&block, QIODevice::ReadWrite);
ds << pi;
if (!sendBlock(AcceptPlatformInfo, block)) {
errMsg += QLS("Failed to send data to server.");
return false;
}
Command cmd = UnknownError;
if (!receiveBlock(&cmd, &block)) {
errMsg.prepend(QLS("Failed to get response from server. "));
return false;
}
if (cmd == Abort) {
errMsg += QLS("Server rejected connection. Reason: ") + QString::fromLatin1(block);
return false;
}
if (dryrun)
*dryrun = (cmd == DoDryRun);
if (cmd != Ack && cmd != DoDryRun) {
errMsg += QLS("Unexpected response from server.");
return false;
}
return true;
}
bool BaselineProtocol::acceptConnection(PlatformInfo *pi)
{
errMsg.clear();
QByteArray block;
Command cmd = AcceptPlatformInfo;
if (!receiveBlock(&cmd, &block) || cmd != AcceptPlatformInfo)
return false;
if (pi) {
QDataStream ds(block);
ds >> *pi;
pi->insert(PI_HostAddress, socket.peerAddress().toString());
}
return true;
}
bool BaselineProtocol::requestBaselineChecksums(const QString &testFunction, ImageItemList *itemList)
{
errMsg.clear();
if (!itemList)
return false;
for (ImageItemList::iterator it = itemList->begin(); it != itemList->end(); it++)
it->testFunction = testFunction;
QByteArray block;
QDataStream ds(&block, QIODevice::WriteOnly);
ds << *itemList;
if (!sendBlock(RequestBaselineChecksums, block))
return false;
Command cmd;
QByteArray rcvBlock;
if (!receiveBlock(&cmd, &rcvBlock) || cmd != BaselineProtocol::Ack)
return false;
QDataStream rds(&rcvBlock, QIODevice::ReadOnly);
rds >> *itemList;
return true;
}
bool BaselineProtocol::submitMatch(const ImageItem &item, QByteArray *serverMsg)
{
Command cmd;
ImageItem smallItem = item;
smallItem.image = QImage(); // No need to waste bandwidth sending image (identical to baseline) to server
return (sendItem(AcceptMatch, smallItem) && receiveBlock(&cmd, serverMsg) && cmd == Ack);
}
bool BaselineProtocol::submitNewBaseline(const ImageItem &item, QByteArray *serverMsg)
{
Command cmd;
return (sendItem(AcceptNewBaseline, item) && receiveBlock(&cmd, serverMsg) && cmd == Ack);
}
bool BaselineProtocol::submitMismatch(const ImageItem &item, QByteArray *serverMsg, bool *fuzzyMatch)
{
Command cmd;
if (sendItem(AcceptMismatch, item) && receiveBlock(&cmd, serverMsg) && (cmd == Ack || cmd == FuzzyMatch)) {
if (fuzzyMatch)
*fuzzyMatch = (cmd == FuzzyMatch);
return true;
}
return false;
}
bool BaselineProtocol::sendItem(Command cmd, const ImageItem &item)
{
errMsg.clear();
QBuffer buf;
buf.open(QIODevice::WriteOnly);
QDataStream ds(&buf);
ds << item;
if (!sendBlock(cmd, buf.data())) {
errMsg.prepend(QLS("Failed to submit image to server. "));
return false;
}
return true;
}
bool BaselineProtocol::sendBlock(Command cmd, const QByteArray &block)
{
QDataStream s(&socket);
// TBD: set qds version as a constant
s << quint16(ProtocolVersion) << quint16(cmd);
s.writeBytes(block.constData(), block.size());
return true;
}
bool BaselineProtocol::receiveBlock(Command *cmd, QByteArray *block)
{
while (socket.bytesAvailable() < int(2*sizeof(quint16) + sizeof(quint32))) {
if (!socket.waitForReadyRead(Timeout))
return false;
}
QDataStream ds(&socket);
quint16 rcvProtocolVersion, rcvCmd;
ds >> rcvProtocolVersion >> rcvCmd;
if (rcvProtocolVersion != ProtocolVersion) {
errMsg = QLS("Baseline protocol version mismatch, received:") + QString::number(rcvProtocolVersion)
+ QLS(" expected:") + QString::number(ProtocolVersion);
return false;
}
if (cmd)
*cmd = Command(rcvCmd);
QByteArray uMsg;
quint32 remaining;
ds >> remaining;
uMsg.resize(remaining);
int got = 0;
char* uMsgBuf = uMsg.data();
do {
got = ds.readRawData(uMsgBuf, remaining);
remaining -= got;
uMsgBuf += got;
} while (remaining && got >= 0 && socket.waitForReadyRead(Timeout));
if (got < 0)
return false;
if (block)
*block = uMsg;
return true;
}
QString BaselineProtocol::errorMessage()
{
QString ret = errMsg;
if (socket.error() >= 0)
ret += QLS(" Socket state: ") + socket.errorString();
return ret;
}

View File

@ -0,0 +1,144 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#ifndef BASELINEPROTOCOL_H
#define BASELINEPROTOCOL_H
#include <QDataStream>
#include <QTcpSocket>
#include <QImage>
#include <QList>
#include <QMap>
#include <QPointer>
#include <QStringList>
#define QLS QLatin1String
#define QLC QLatin1Char
#define FileFormat "png"
extern const QString PI_Project;
extern const QString PI_ProjectImageKeys;
extern const QString PI_TestCase;
extern const QString PI_HostName;
extern const QString PI_HostAddress;
extern const QString PI_OSName;
extern const QString PI_OSVersion;
extern const QString PI_QtVersion;
extern const QString PI_QtBuildMode;
extern const QString PI_GitCommit;
extern const QString PI_GitBranch;
class PlatformInfo : public QMap<QString, QString>
{
public:
static PlatformInfo localHostInfo();
void addOverride(const QString& key, const QString& value);
QStringList overrides() const;
bool isAdHocRun() const;
void setAdHocRun(bool isAdHoc);
private:
QStringList orides;
bool adHoc = true;
friend QDataStream & operator<< (QDataStream &stream, const PlatformInfo &pi);
friend QDataStream & operator>> (QDataStream &stream, PlatformInfo& pi);
};
QDataStream & operator<< (QDataStream &stream, const PlatformInfo &pi);
QDataStream & operator>> (QDataStream &stream, PlatformInfo& pi);
struct ImageItem
{
static quint64 computeChecksum(const QImage& image);
enum ItemStatus {
Ok = 0,
BaselineNotFound = 1,
IgnoreItem = 2,
Mismatch = 3,
FuzzyMatch = 4,
Error = 5
};
QString testFunction;
QString itemName;
ItemStatus status = Ok;
QImage image;
QList<quint64> imageChecksums;
quint16 itemChecksum = 0;
QByteArray misc;
void writeImageToStream(QDataStream &stream) const;
void readImageFromStream(QDataStream &stream);
};
QDataStream & operator<< (QDataStream &stream, const ImageItem &ii);
QDataStream & operator>> (QDataStream &stream, ImageItem& ii);
Q_DECLARE_METATYPE(ImageItem);
typedef QList<ImageItem> ImageItemList;
class BaselineProtocol
{
public:
BaselineProtocol();
~BaselineProtocol();
static BaselineProtocol *instance(QObject *parent = nullptr);
// ****************************************************
// Important constants here
// ****************************************************
enum Constant {
ProtocolVersion = 5,
ServerPort = 54129,
Timeout = 15000
};
enum Command {
UnknownError = 0,
// Queries
AcceptPlatformInfo = 1,
RequestBaselineChecksums = 2,
AcceptMatch = 3,
AcceptNewBaseline = 4,
AcceptMismatch = 5,
// Responses
Ack = 128,
Abort = 129,
DoDryRun = 130,
FuzzyMatch = 131
};
// For client:
// For advanced client:
bool connect(const QString &testCase, bool *dryrun = nullptr, const PlatformInfo& clientInfo = PlatformInfo());
bool disconnect();
bool requestBaselineChecksums(const QString &testFunction, ImageItemList *itemList);
bool submitMatch(const ImageItem &item, QByteArray *serverMsg);
bool submitNewBaseline(const ImageItem &item, QByteArray *serverMsg);
bool submitMismatch(const ImageItem &item, QByteArray *serverMsg, bool *fuzzyMatch = nullptr);
// For server:
bool acceptConnection(PlatformInfo *pi);
QString errorMessage();
private:
bool sendItem(Command cmd, const ImageItem &item);
bool sendBlock(Command cmd, const QByteArray &block);
bool receiveBlock(Command *cmd, QByteArray *block);
QString errMsg;
QTcpSocket socket;
friend class BaselineThread;
friend class BaselineHandler;
};
#endif // BASELINEPROTOCOL_H

View File

@ -0,0 +1,10 @@
INCLUDEPATH += $$PWD
QT *= network
SOURCES += \
$$PWD/baselineprotocol.cpp \
$$PWD/lookup3.cpp
HEADERS += \
$$PWD/baselineprotocol.h

View File

@ -0,0 +1,821 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
/*
These functions are based on:
-------------------------------------------------------------------------------
lookup3.c, by Bob Jenkins, May 2006, Public Domain.
These are functions for producing 32-bit hashes for hash table lookup.
hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final()
are externally useful functions. Routines to test the hash are included
if SELF_TEST is defined. You can use this free for any purpose. It's in
the public domain. It has no warranty.
You probably want to use hashlittle(). hashlittle() and hashbig()
hash byte arrays. hashlittle() is is faster than hashbig() on
little-endian machines. Intel and AMD are little-endian machines.
On second thought, you probably want hashlittle2(), which is identical to
hashlittle() except it returns two 32-bit hashes for the price of one.
You could implement hashbig2() if you wanted but I haven't bothered here.
If you want to find a hash of, say, exactly 7 integers, do
a = i1; b = i2; c = i3;
mix(a,b,c);
a += i4; b += i5; c += i6;
mix(a,b,c);
a += i7;
final(a,b,c);
then use c as the hash value. If you have a variable length array of
4-byte integers to hash, use hashword(). If you have a byte array (like
a character string), use hashlittle(). If you have several byte arrays, or
a mix of things, see the comments above hashlittle().
Why is this so big? I read 12 bytes at a time into 3 4-byte integers,
then mix those integers. This is fast (you can do a lot more thorough
mixing with 12*3 instructions on 3 integers than you can with 3 instructions
on 1 byte), but shoehorning those bytes into integers efficiently is messy.
-------------------------------------------------------------------------------
*/
#include <QtGlobal>
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
# define HASH_LITTLE_ENDIAN 0
# define HASH_BIG_ENDIAN 1
#else
# define HASH_LITTLE_ENDIAN 1
# define HASH_BIG_ENDIAN 0
#endif
#define hashsize(n) ((quint32)1<<(n))
#define hashmask(n) (hashsize(n)-1)
#define rot(x,k) (((x)<<(k)) | ((x)>>(32-(k))))
/*
-------------------------------------------------------------------------------
mix -- mix 3 32-bit values reversibly.
This is reversible, so any information in (a,b,c) before mix() is
still in (a,b,c) after mix().
If four pairs of (a,b,c) inputs are run through mix(), or through
mix() in reverse, there are at least 32 bits of the output that
are sometimes the same for one pair and different for another pair.
This was tested for:
* pairs that differed by one bit, by two bits, in any combination
of top bits of (a,b,c), or in any combination of bottom bits of
(a,b,c).
* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
is commonly produced by subtraction) look like a single 1-bit
difference.
* the base values were pseudorandom, all zero but one bit set, or
all zero plus a counter that starts at zero.
Some k values for my "a-=c; a^=rot(c,k); c+=b;" arrangement that
satisfy this are
4 6 8 16 19 4
9 15 3 18 27 15
14 9 3 7 17 3
Well, "9 15 3 18 27 15" didn't quite get 32 bits diffing
for "differ" defined as + with a one-bit base and a two-bit delta. I
used http://burtleburtle.net/bob/hash/avalanche.html to choose
the operations, constants, and arrangements of the variables.
This does not achieve avalanche. There are input bits of (a,b,c)
that fail to affect some output bits of (a,b,c), especially of a. The
most thoroughly mixed value is c, but it doesn't really even achieve
avalanche in c.
This allows some parallelism. Read-after-writes are good at doubling
the number of bits affected, so the goal of mixing pulls in the opposite
direction as the goal of parallelism. I did what I could. Rotates
seem to cost as much as shifts on every machine I could lay my hands
on, and rotates are much kinder to the top and bottom bits, so I used
rotates.
-------------------------------------------------------------------------------
*/
#define mix(a,b,c) \
{ \
a -= c; a ^= rot(c, 4); c += b; \
b -= a; b ^= rot(a, 6); a += c; \
c -= b; c ^= rot(b, 8); b += a; \
a -= c; a ^= rot(c,16); c += b; \
b -= a; b ^= rot(a,19); a += c; \
c -= b; c ^= rot(b, 4); b += a; \
}
/*
-------------------------------------------------------------------------------
final -- final mixing of 3 32-bit values (a,b,c) into c
Pairs of (a,b,c) values differing in only a few bits will usually
produce values of c that look totally different. This was tested for
* pairs that differed by one bit, by two bits, in any combination
of top bits of (a,b,c), or in any combination of bottom bits of
(a,b,c).
* "differ" is defined as +, -, ^, or ~^. For + and -, I transformed
the output delta to a Gray code (a^(a>>1)) so a string of 1's (as
is commonly produced by subtraction) look like a single 1-bit
difference.
* the base values were pseudorandom, all zero but one bit set, or
all zero plus a counter that starts at zero.
These constants passed:
14 11 25 16 4 14 24
12 14 25 16 4 14 24
and these came close:
4 8 15 26 3 22 24
10 8 15 26 3 22 24
11 8 15 26 3 22 24
-------------------------------------------------------------------------------
*/
#define final(a,b,c) \
{ \
c ^= b; c -= rot(b,14); \
a ^= c; a -= rot(c,11); \
b ^= a; b -= rot(a,25); \
c ^= b; c -= rot(b,16); \
a ^= c; a -= rot(c,4); \
b ^= a; b -= rot(a,14); \
c ^= b; c -= rot(b,24); \
}
/*
--------------------------------------------------------------------
This works on all machines. To be useful, it requires
-- that the key be an array of quint32's, and
-- that the length be the number of quint32's in the key
The function hashword() is identical to hashlittle() on little-endian
machines, and identical to hashbig() on big-endian machines,
except that the length has to be measured in quint32s rather than in
bytes. hashlittle() is more complicated than hashword() only because
hashlittle() has to dance around fitting the key bytes into registers.
--------------------------------------------------------------------
*/
quint32 hashword(
const quint32 *k, /* the key, an array of quint32 values */
size_t length, /* the length of the key, in quint32s */
quint32 initval) /* the previous hash, or an arbitrary value */
{
quint32 a,b,c;
/* Set up the internal state */
a = b = c = 0xdeadbeef + (((quint32)length)<<2) + initval;
/*------------------------------------------------- handle most of the key */
while (length > 3)
{
a += k[0];
b += k[1];
c += k[2];
mix(a,b,c);
length -= 3;
k += 3;
}
/*------------------------------------------- handle the last 3 quint32's */
switch (length) /* all the case statements fall through */
{
case 3 : c+=k[2];
Q_FALLTHROUGH();
case 2 : b+=k[1];
Q_FALLTHROUGH();
case 1 : a+=k[0];
final(a,b,c);
Q_FALLTHROUGH();
case 0: /* case 0: nothing left to add */
break;
}
/*------------------------------------------------------ report the result */
return c;
}
/*
--------------------------------------------------------------------
hashword2() -- same as hashword(), but take two seeds and return two
32-bit values. pc and pb must both be nonnull, and *pc and *pb must
both be initialized with seeds. If you pass in (*pb)==0, the output
(*pc) will be the same as the return value from hashword().
--------------------------------------------------------------------
*/
void hashword2 (
const quint32 *k, /* the key, an array of quint32 values */
size_t length, /* the length of the key, in quint32s */
quint32 *pc, /* IN: seed OUT: primary hash value */
quint32 *pb) /* IN: more seed OUT: secondary hash value */
{
quint32 a,b,c;
/* Set up the internal state */
a = b = c = 0xdeadbeef + ((quint32)(length<<2)) + *pc;
c += *pb;
/*------------------------------------------------- handle most of the key */
while (length > 3)
{
a += k[0];
b += k[1];
c += k[2];
mix(a,b,c);
length -= 3;
k += 3;
}
/*------------------------------------------- handle the last 3 quint32's */
switch (length) /* all the case statements fall through */
{
case 3 : c+=k[2];
Q_FALLTHROUGH();
case 2 : b+=k[1];
Q_FALLTHROUGH();
case 1 : a+=k[0];
final(a,b,c);
Q_FALLTHROUGH();
case 0: /* case 0: nothing left to add */
break;
}
/*------------------------------------------------------ report the result */
*pc=c; *pb=b;
}
/*
-------------------------------------------------------------------------------
hashlittle() -- hash a variable-length key into a 32-bit value
k : the key (the unaligned variable-length array of bytes)
length : the length of the key, counting by bytes
initval : can be any 4-byte value
Returns a 32-bit value. Every bit of the key affects every bit of
the return value. Two keys differing by one or two bits will have
totally different hash values.
The best hash table sizes are powers of 2. There is no need to do
mod a prime (mod is sooo slow!). If you need less than 32 bits,
use a bitmask. For example, if you need only 10 bits, do
h = (h & hashmask(10));
In which case, the hash table should have hashsize(10) elements.
If you are hashing n strings (quint8 **)k, do it like this:
for (i=0, h=0; i<n; ++i) h = hashlittle( k[i], len[i], h);
By Bob Jenkins, 2006. bob_jenkins@burtleburtle.net. You may use this
code any way you wish, private, educational, or commercial. It's free.
Use for hash table lookup, or anything where one collision in 2^^32 is
acceptable. Do NOT use for cryptographic purposes.
-------------------------------------------------------------------------------
*/
quint32 hashlittle( const void *key, size_t length, quint32 initval)
{
quint32 a,b,c; /* internal state */
union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */
/* Set up the internal state */
a = b = c = 0xdeadbeef + ((quint32)length) + initval;
u.ptr = key;
if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
const quint32 *k = (const quint32 *)key; /* read 32-bit chunks */
/*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
while (length > 12)
{
a += k[0];
b += k[1];
c += k[2];
mix(a,b,c);
length -= 12;
k += 3;
}
/*----------------------------- handle the last (probably partial) block */
/*
* "k[2]&0xffffff" actually reads beyond the end of the string, but
* then masks off the part it's not allowed to read. Because the
* string is aligned, the masked-off tail is in the same word as the
* rest of the string. Every machine with memory protection I've seen
* does it on word boundaries, so is OK with this. But VALGRIND will
* still catch it and complain. The masking trick does make the hash
* noticeably faster for short strings (like English words).
*/
#ifndef VALGRIND
switch (length)
{
case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
case 8 : b+=k[1]; a+=k[0]; break;
case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
case 6 : b+=k[1]&0xffff; a+=k[0]; break;
case 5 : b+=k[1]&0xff; a+=k[0]; break;
case 4 : a+=k[0]; break;
case 3 : a+=k[0]&0xffffff; break;
case 2 : a+=k[0]&0xffff; break;
case 1 : a+=k[0]&0xff; break;
case 0 : return c; /* zero length strings require no mixing */
}
#else /* make valgrind happy */
const quint8 *k8 = (const quint8 *)k;
switch (length)
{
case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
case 11: c+=((quint32)k8[10])<<16;
Q_FALLTHROUGH();
case 10: c+=((quint32)k8[9])<<8;
Q_FALLTHROUGH();
case 9 : c+=k8[8];
Q_FALLTHROUGH();
case 8 : b+=k[1]; a+=k[0]; break;
case 7 : b+=((quint32)k8[6])<<16;
Q_FALLTHROUGH();
case 6 : b+=((quint32)k8[5])<<8;
Q_FALLTHROUGH();
case 5 : b+=k8[4];
Q_FALLTHROUGH();
case 4 : a+=k[0]; break;
case 3 : a+=((quint32)k8[2])<<16;
Q_FALLTHROUGH();
case 2 : a+=((quint32)k8[1])<<8;
Q_FALLTHROUGH();
case 1 : a+=k8[0]; break;
case 0 : return c;
}
#endif /* !valgrind */
} else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
const quint16 *k = (const quint16 *)key; /* read 16-bit chunks */
const quint8 *k8;
/*--------------- all but last block: aligned reads and different mixing */
while (length > 12)
{
a += k[0] + (((quint32)k[1])<<16);
b += k[2] + (((quint32)k[3])<<16);
c += k[4] + (((quint32)k[5])<<16);
mix(a,b,c);
length -= 12;
k += 6;
}
/*----------------------------- handle the last (probably partial) block */
k8 = (const quint8 *)k;
switch (length)
{
case 12: c+=k[4]+(((quint32)k[5])<<16);
b+=k[2]+(((quint32)k[3])<<16);
a+=k[0]+(((quint32)k[1])<<16);
break;
case 11: c+=((quint32)k8[10])<<16;
Q_FALLTHROUGH();
case 10: c+=k[4];
b+=k[2]+(((quint32)k[3])<<16);
a+=k[0]+(((quint32)k[1])<<16);
break;
case 9 : c+=k8[8];
Q_FALLTHROUGH();
case 8 : b+=k[2]+(((quint32)k[3])<<16);
a+=k[0]+(((quint32)k[1])<<16);
break;
case 7 : b+=((quint32)k8[6])<<16;
Q_FALLTHROUGH();
case 6 : b+=k[2];
a+=k[0]+(((quint32)k[1])<<16);
break;
case 5 : b+=k8[4];
Q_FALLTHROUGH();
case 4 : a+=k[0]+(((quint32)k[1])<<16);
break;
case 3 : a+=((quint32)k8[2])<<16;
Q_FALLTHROUGH();
case 2 : a+=k[0];
break;
case 1 : a+=k8[0];
break;
case 0 : return c; /* zero length requires no mixing */
}
} else { /* need to read the key one byte at a time */
const quint8 *k = (const quint8 *)key;
/*--------------- all but the last block: affect some 32 bits of (a,b,c) */
while (length > 12)
{
a += k[0];
a += ((quint32)k[1])<<8;
a += ((quint32)k[2])<<16;
a += ((quint32)k[3])<<24;
b += k[4];
b += ((quint32)k[5])<<8;
b += ((quint32)k[6])<<16;
b += ((quint32)k[7])<<24;
c += k[8];
c += ((quint32)k[9])<<8;
c += ((quint32)k[10])<<16;
c += ((quint32)k[11])<<24;
mix(a,b,c);
length -= 12;
k += 12;
}
/*-------------------------------- last block: affect all 32 bits of (c) */
switch (length) /* all the case statements fall through */
{
case 12: c+=((quint32)k[11])<<24;
Q_FALLTHROUGH();
case 11: c+=((quint32)k[10])<<16;
Q_FALLTHROUGH();
case 10: c+=((quint32)k[9])<<8;
Q_FALLTHROUGH();
case 9 : c+=k[8];
Q_FALLTHROUGH();
case 8 : b+=((quint32)k[7])<<24;
Q_FALLTHROUGH();
case 7 : b+=((quint32)k[6])<<16;
Q_FALLTHROUGH();
case 6 : b+=((quint32)k[5])<<8;
Q_FALLTHROUGH();
case 5 : b+=k[4];
Q_FALLTHROUGH();
case 4 : a+=((quint32)k[3])<<24;
Q_FALLTHROUGH();
case 3 : a+=((quint32)k[2])<<16;
Q_FALLTHROUGH();
case 2 : a+=((quint32)k[1])<<8;
Q_FALLTHROUGH();
case 1 : a+=k[0];
break;
case 0 : return c;
}
}
final(a,b,c);
return c;
}
/*
* hashlittle2: return 2 32-bit hash values
*
* This is identical to hashlittle(), except it returns two 32-bit hash
* values instead of just one. This is good enough for hash table
* lookup with 2^^64 buckets, or if you want a second hash if you're not
* happy with the first, or if you want a probably-unique 64-bit ID for
* the key. *pc is better mixed than *pb, so use *pc first. If you want
* a 64-bit value do something like "*pc + (((uint64_t)*pb)<<32)".
*/
void hashlittle2(
const void *key, /* the key to hash */
size_t length, /* length of the key */
quint32 *pc, /* IN: primary initval, OUT: primary hash */
quint32 *pb) /* IN: secondary initval, OUT: secondary hash */
{
quint32 a,b,c; /* internal state */
union { const void *ptr; size_t i; } u; /* needed for Mac Powerbook G4 */
/* Set up the internal state */
a = b = c = 0xdeadbeef + ((quint32)length) + *pc;
c += *pb;
u.ptr = key;
if (HASH_LITTLE_ENDIAN && ((u.i & 0x3) == 0)) {
const quint32 *k = (const quint32 *)key; /* read 32-bit chunks */
/*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
while (length > 12)
{
a += k[0];
b += k[1];
c += k[2];
mix(a,b,c);
length -= 12;
k += 3;
}
/*----------------------------- handle the last (probably partial) block */
/*
* "k[2]&0xffffff" actually reads beyond the end of the string, but
* then masks off the part it's not allowed to read. Because the
* string is aligned, the masked-off tail is in the same word as the
* rest of the string. Every machine with memory protection I've seen
* does it on word boundaries, so is OK with this. But VALGRIND will
* still catch it and complain. The masking trick does make the hash
* noticeably faster for short strings (like English words).
*/
#ifndef VALGRIND
switch (length)
{
case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
case 11: c+=k[2]&0xffffff; b+=k[1]; a+=k[0]; break;
case 10: c+=k[2]&0xffff; b+=k[1]; a+=k[0]; break;
case 9 : c+=k[2]&0xff; b+=k[1]; a+=k[0]; break;
case 8 : b+=k[1]; a+=k[0]; break;
case 7 : b+=k[1]&0xffffff; a+=k[0]; break;
case 6 : b+=k[1]&0xffff; a+=k[0]; break;
case 5 : b+=k[1]&0xff; a+=k[0]; break;
case 4 : a+=k[0]; break;
case 3 : a+=k[0]&0xffffff; break;
case 2 : a+=k[0]&0xffff; break;
case 1 : a+=k[0]&0xff; break;
case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
}
#else /* make valgrind happy */
const quint8 *k8 = (const quint8 *)k;
switch (length)
{
case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
case 11: c+=((quint32)k8[10])<<16;
Q_FALLTHROUGH();
case 10: c+=((quint32)k8[9])<<8;
Q_FALLTHROUGH();
case 9 : c+=k8[8];
Q_FALLTHROUGH();
case 8 : b+=k[1]; a+=k[0]; break;
case 7 : b+=((quint32)k8[6])<<16;
Q_FALLTHROUGH();
case 6 : b+=((quint32)k8[5])<<8;
Q_FALLTHROUGH();
case 5 : b+=k8[4];
Q_FALLTHROUGH();
case 4 : a+=k[0]; break;
case 3 : a+=((quint32)k8[2])<<16;
Q_FALLTHROUGH();
case 2 : a+=((quint32)k8[1])<<8;
Q_FALLTHROUGH();
case 1 : a+=k8[0]; break;
case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
}
#endif /* !valgrind */
} else if (HASH_LITTLE_ENDIAN && ((u.i & 0x1) == 0)) {
const quint16 *k = (const quint16 *)key; /* read 16-bit chunks */
const quint8 *k8;
/*--------------- all but last block: aligned reads and different mixing */
while (length > 12)
{
a += k[0] + (((quint32)k[1])<<16);
b += k[2] + (((quint32)k[3])<<16);
c += k[4] + (((quint32)k[5])<<16);
mix(a,b,c);
length -= 12;
k += 6;
}
/*----------------------------- handle the last (probably partial) block */
k8 = (const quint8 *)k;
switch (length)
{
case 12: c+=k[4]+(((quint32)k[5])<<16);
b+=k[2]+(((quint32)k[3])<<16);
a+=k[0]+(((quint32)k[1])<<16);
break;
case 11: c+=((quint32)k8[10])<<16;
Q_FALLTHROUGH();
case 10: c+=k[4];
b+=k[2]+(((quint32)k[3])<<16);
a+=k[0]+(((quint32)k[1])<<16);
break;
case 9 : c+=k8[8];
Q_FALLTHROUGH();
case 8 : b+=k[2]+(((quint32)k[3])<<16);
a+=k[0]+(((quint32)k[1])<<16);
break;
case 7 : b+=((quint32)k8[6])<<16;
Q_FALLTHROUGH();
case 6 : b+=k[2];
a+=k[0]+(((quint32)k[1])<<16);
break;
case 5 : b+=k8[4];
Q_FALLTHROUGH();
case 4 : a+=k[0]+(((quint32)k[1])<<16);
break;
case 3 : a+=((quint32)k8[2])<<16;
Q_FALLTHROUGH();
case 2 : a+=k[0];
break;
case 1 : a+=k8[0];
break;
case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
}
} else { /* need to read the key one byte at a time */
const quint8 *k = (const quint8 *)key;
/*--------------- all but the last block: affect some 32 bits of (a,b,c) */
while (length > 12)
{
a += k[0];
a += ((quint32)k[1])<<8;
a += ((quint32)k[2])<<16;
a += ((quint32)k[3])<<24;
b += k[4];
b += ((quint32)k[5])<<8;
b += ((quint32)k[6])<<16;
b += ((quint32)k[7])<<24;
c += k[8];
c += ((quint32)k[9])<<8;
c += ((quint32)k[10])<<16;
c += ((quint32)k[11])<<24;
mix(a,b,c);
length -= 12;
k += 12;
}
/*-------------------------------- last block: affect all 32 bits of (c) */
switch (length) /* all the case statements fall through */
{
case 12: c+=((quint32)k[11])<<24;
Q_FALLTHROUGH();
case 11: c+=((quint32)k[10])<<16;
Q_FALLTHROUGH();
case 10: c+=((quint32)k[9])<<8;
Q_FALLTHROUGH();
case 9 : c+=k[8];
Q_FALLTHROUGH();
case 8 : b+=((quint32)k[7])<<24;
Q_FALLTHROUGH();
case 7 : b+=((quint32)k[6])<<16;
Q_FALLTHROUGH();
case 6 : b+=((quint32)k[5])<<8;
Q_FALLTHROUGH();
case 5 : b+=k[4];
Q_FALLTHROUGH();
case 4 : a+=((quint32)k[3])<<24;
Q_FALLTHROUGH();
case 3 : a+=((quint32)k[2])<<16;
Q_FALLTHROUGH();
case 2 : a+=((quint32)k[1])<<8;
Q_FALLTHROUGH();
case 1 : a+=k[0];
break;
case 0 : *pc=c; *pb=b; return; /* zero length strings require no mixing */
}
}
final(a,b,c);
*pc=c; *pb=b;
}
/*
* hashbig():
* This is the same as hashword() on big-endian machines. It is different
* from hashlittle() on all machines. hashbig() takes advantage of
* big-endian byte ordering.
*/
quint32 hashbig( const void *key, size_t length, quint32 initval)
{
quint32 a,b,c;
union { const void *ptr; size_t i; } u; /* to cast key to (size_t) happily */
/* Set up the internal state */
a = b = c = 0xdeadbeef + ((quint32)length) + initval;
u.ptr = key;
if (HASH_BIG_ENDIAN && ((u.i & 0x3) == 0)) {
const quint32 *k = (const quint32 *)key; /* read 32-bit chunks */
/*------ all but last block: aligned reads and affect 32 bits of (a,b,c) */
while (length > 12)
{
a += k[0];
b += k[1];
c += k[2];
mix(a,b,c);
length -= 12;
k += 3;
}
/*----------------------------- handle the last (probably partial) block */
/*
* "k[2]<<8" actually reads beyond the end of the string, but
* then shifts out the part it's not allowed to read. Because the
* string is aligned, the illegal read is in the same word as the
* rest of the string. Every machine with memory protection I've seen
* does it on word boundaries, so is OK with this. But VALGRIND will
* still catch it and complain. The masking trick does make the hash
* noticeably faster for short strings (like English words).
*/
#ifndef VALGRIND
switch (length)
{
case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
case 11: c+=k[2]&0xffffff00; b+=k[1]; a+=k[0]; break;
case 10: c+=k[2]&0xffff0000; b+=k[1]; a+=k[0]; break;
case 9 : c+=k[2]&0xff000000; b+=k[1]; a+=k[0]; break;
case 8 : b+=k[1]; a+=k[0]; break;
case 7 : b+=k[1]&0xffffff00; a+=k[0]; break;
case 6 : b+=k[1]&0xffff0000; a+=k[0]; break;
case 5 : b+=k[1]&0xff000000; a+=k[0]; break;
case 4 : a+=k[0]; break;
case 3 : a+=k[0]&0xffffff00; break;
case 2 : a+=k[0]&0xffff0000; break;
case 1 : a+=k[0]&0xff000000; break;
case 0 : return c; /* zero length strings require no mixing */
}
#else /* make valgrind happy */
const quint8 *k8 = (const quint8 *)k;
switch (length) /* all the case statements fall through */
{
case 12: c+=k[2]; b+=k[1]; a+=k[0]; break;
case 11: c+=((quint32)k8[10])<<8;
Q_FALLTHROUGH();
case 10: c+=((quint32)k8[9])<<16;
Q_FALLTHROUGH();
case 9 : c+=((quint32)k8[8])<<24;
Q_FALLTHROUGH();
case 8 : b+=k[1]; a+=k[0]; break;
case 7 : b+=((quint32)k8[6])<<8;
Q_FALLTHROUGH();
case 6 : b+=((quint32)k8[5])<<16;
Q_FALLTHROUGH();
case 5 : b+=((quint32)k8[4])<<24;
Q_FALLTHROUGH();
case 4 : a+=k[0]; break;
case 3 : a+=((quint32)k8[2])<<8;
Q_FALLTHROUGH();
case 2 : a+=((quint32)k8[1])<<16;
Q_FALLTHROUGH();
case 1 : a+=((quint32)k8[0])<<24; break;
case 0 : return c;
}
#endif /* !VALGRIND */
} else { /* need to read the key one byte at a time */
const quint8 *k = (const quint8 *)key;
/*--------------- all but the last block: affect some 32 bits of (a,b,c) */
while (length > 12)
{
a += ((quint32)k[0])<<24;
a += ((quint32)k[1])<<16;
a += ((quint32)k[2])<<8;
a += ((quint32)k[3]);
b += ((quint32)k[4])<<24;
b += ((quint32)k[5])<<16;
b += ((quint32)k[6])<<8;
b += ((quint32)k[7]);
c += ((quint32)k[8])<<24;
c += ((quint32)k[9])<<16;
c += ((quint32)k[10])<<8;
c += ((quint32)k[11]);
mix(a,b,c);
length -= 12;
k += 12;
}
/*-------------------------------- last block: affect all 32 bits of (c) */
switch (length) /* all the case statements fall through */
{
case 12: c+=k[11];
Q_FALLTHROUGH();
case 11: c+=((quint32)k[10])<<8;
Q_FALLTHROUGH();
case 10: c+=((quint32)k[9])<<16;
Q_FALLTHROUGH();
case 9 : c+=((quint32)k[8])<<24;
Q_FALLTHROUGH();
case 8 : b+=k[7];
Q_FALLTHROUGH();
case 7 : b+=((quint32)k[6])<<8;
Q_FALLTHROUGH();
case 6 : b+=((quint32)k[5])<<16;
Q_FALLTHROUGH();
case 5 : b+=((quint32)k[4])<<24;
Q_FALLTHROUGH();
case 4 : a+=k[3];
Q_FALLTHROUGH();
case 3 : a+=((quint32)k[2])<<8;
Q_FALLTHROUGH();
case 2 : a+=((quint32)k[1])<<16;
Q_FALLTHROUGH();
case 1 : a+=((quint32)k[0])<<24;
break;
case 0 : return c;
}
}
final(a,b,c);
return c;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,323 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#ifndef PAINTCOMMANDS_H
#define PAINTCOMMANDS_H
#include <qcolor.h>
#include <qmap.h>
#include <qpainterpath.h>
#include <qregion.h>
#include <qregularexpression.h>
#include <qstringlist.h>
#include <qpixmap.h>
#include <qbrush.h>
#include <qhash.h>
QT_FORWARD_DECLARE_CLASS(QPainter)
#ifndef QT_NO_OPENGL
QT_FORWARD_DECLARE_CLASS(QOpenGLFramebufferObject)
QT_FORWARD_DECLARE_CLASS(QOpenGLPaintDevice)
QT_FORWARD_DECLARE_CLASS(QOpenGLContext)
#endif
enum DeviceType {
WidgetType,
BitmapType,
PixmapType,
ImageType,
ImageMonoType,
OpenGLType,
OpenGLBufferType,
PictureType,
PrinterType,
PdfType,
PsType,
GrabType,
CustomDeviceType,
CustomWidgetType,
ImageWidgetType
};
/************************************************************************/
class PaintCommands
{
public:
// construction / initialization
PaintCommands(const QStringList &cmds, int /*w*/, int /*h*/, QImage::Format format)
: m_painter(0)
, m_surface_painter(0)
, m_format(format)
, m_commands(cmds)
, m_gradientSpread(QGradient::PadSpread)
, m_gradientCoordinate(QGradient::LogicalMode)
, m_verboseMode(false)
, m_type(WidgetType)
, m_checkers_background(true)
, m_shouldDrawText(true)
#ifndef QT_NO_OPENGL
, m_default_glcontext(0)
, m_surface_glcontext(0)
, m_surface_glbuffer(0)
, m_surface_glpaintdevice(0)
#endif
{
staticInit();
}
public:
void setCheckersBackground(bool b) { staticInit(); m_checkers_background = b; }
void setContents(const QStringList &cmds) {
staticInit();
m_blockMap.clear();
m_pathMap.clear();
m_pixmapMap.clear();
m_imageMap.clear();
m_regionMap.clear();
m_gradientStops.clear();
m_controlPoints.clear();
m_gradientSpread = QGradient::PadSpread;
m_gradientCoordinate = QGradient::LogicalMode;
m_commands = cmds;
}
void setPainter(QPainter *pt) { staticInit(); m_painter = pt; }
void setType(DeviceType t) { staticInit(); m_type = t; }
void setFilePath(const QString &path) { staticInit(); m_filepath = path; }
void setControlPoints(const QList<QPointF> &points)
{
staticInit();
m_controlPoints = points;
}
void setVerboseMode(bool v) { staticInit(); m_verboseMode = v; }
void insertAt(int commandIndex, const QStringList &newCommands);
void setShouldDrawText(bool drawText) { m_shouldDrawText = drawText; }
// run
void runCommands();
private:
// run
void runCommand(const QString &scriptLine);
// conversion methods
int convertToInt(const QString &str);
double convertToDouble(const QString &str);
float convertToFloat(const QString &str);
QColor convertToColor(const QString &str);
// commands: comments
void command_comment(QRegularExpressionMatch re);
// commands: importer
void command_import(QRegularExpressionMatch re);
// commands: blocks
void command_begin_block(QRegularExpressionMatch re);
void command_end_block(QRegularExpressionMatch re);
void command_repeat_block(QRegularExpressionMatch re);
// commands: misc
void command_textlayout_draw(QRegularExpressionMatch re);
void command_abort(QRegularExpressionMatch re);
// commands: noops
void command_noop(QRegularExpressionMatch re);
// commands: setters
void command_setBgMode(QRegularExpressionMatch re);
void command_setBackground(QRegularExpressionMatch re);
void command_setOpacity(QRegularExpressionMatch re);
void command_path_setFillRule(QRegularExpressionMatch re);
void command_setBrush(QRegularExpressionMatch re);
void command_setBrushOrigin(QRegularExpressionMatch re);
void command_brushTranslate(QRegularExpressionMatch re);
void command_brushRotate(QRegularExpressionMatch re);
void command_brushScale(QRegularExpressionMatch re);
void command_brushShear(QRegularExpressionMatch re);
void command_setClipPath(QRegularExpressionMatch re);
void command_setClipRect(QRegularExpressionMatch re);
void command_setClipRectF(QRegularExpressionMatch re);
void command_setClipRegion(QRegularExpressionMatch re);
void command_setClipping(QRegularExpressionMatch re);
void command_setCompositionMode(QRegularExpressionMatch re);
void command_setFont(QRegularExpressionMatch re);
void command_setPen(QRegularExpressionMatch re);
void command_setPen2(QRegularExpressionMatch re);
void command_pen_setDashOffset(QRegularExpressionMatch re);
void command_pen_setDashPattern(QRegularExpressionMatch re);
void command_pen_setCosmetic(QRegularExpressionMatch re);
void command_setRenderHint(QRegularExpressionMatch re);
void command_clearRenderHint(QRegularExpressionMatch re);
void command_gradient_appendStop(QRegularExpressionMatch re);
void command_gradient_clearStops(QRegularExpressionMatch re);
void command_gradient_setConical(QRegularExpressionMatch re);
void command_gradient_setLinear(QRegularExpressionMatch re);
void command_gradient_setRadial(QRegularExpressionMatch re);
void command_gradient_setRadialExtended(QRegularExpressionMatch re);
void command_gradient_setLinearPen(QRegularExpressionMatch re);
void command_gradient_setSpread(QRegularExpressionMatch re);
void command_gradient_setCoordinateMode(QRegularExpressionMatch re);
// commands: drawing ops
void command_drawArc(QRegularExpressionMatch re);
void command_drawChord(QRegularExpressionMatch re);
void command_drawConvexPolygon(QRegularExpressionMatch re);
void command_drawEllipse(QRegularExpressionMatch re);
void command_drawImage(QRegularExpressionMatch re);
void command_drawLine(QRegularExpressionMatch re);
void command_drawLines(QRegularExpressionMatch re);
void command_drawPath(QRegularExpressionMatch re);
void command_drawPie(QRegularExpressionMatch re);
void command_drawPixmap(QRegularExpressionMatch re);
void command_drawPoint(QRegularExpressionMatch re);
void command_drawPolygon(QRegularExpressionMatch re);
void command_drawPolyline(QRegularExpressionMatch re);
void command_drawRect(QRegularExpressionMatch re);
void command_drawRoundedRect(QRegularExpressionMatch re);
void command_drawRoundRect(QRegularExpressionMatch re);
void command_drawText(QRegularExpressionMatch re);
void command_drawStaticText(QRegularExpressionMatch re);
void command_drawGlyphRun(QRegularExpressionMatch re);
void command_drawTextDocument(QRegularExpressionMatch re);
void command_drawTiledPixmap(QRegularExpressionMatch re);
void command_fillRect(QRegularExpressionMatch re);
void command_fillRectF(QRegularExpressionMatch re);
void command_drawPixmapFragments(QRegularExpressionMatch re);
// paths
void command_path_addEllipse(QRegularExpressionMatch re);
void command_path_addPolygon(QRegularExpressionMatch re);
void command_path_addRect(QRegularExpressionMatch re);
void command_path_addText(QRegularExpressionMatch re);
void command_path_arcTo(QRegularExpressionMatch re);
void command_path_closeSubpath(QRegularExpressionMatch re);
void command_path_createOutline(QRegularExpressionMatch re);
void command_path_cubicTo(QRegularExpressionMatch re);
void command_path_debugPrint(QRegularExpressionMatch re);
void command_path_lineTo(QRegularExpressionMatch re);
void command_path_moveTo(QRegularExpressionMatch re);
void command_region_addEllipse(QRegularExpressionMatch re);
void command_region_addRect(QRegularExpressionMatch re);
// getters
void command_region_getClipRegion(QRegularExpressionMatch re);
void command_path_getClipPath(QRegularExpressionMatch re);
// commands: surface begin/end
void command_surface_begin(QRegularExpressionMatch re);
void command_surface_end(QRegularExpressionMatch re);
// commands: save/restore painter state
void command_restore(QRegularExpressionMatch re);
void command_save(QRegularExpressionMatch re);
// commands: pixmap/image
void command_pixmap_load(QRegularExpressionMatch re);
void command_pixmap_setMask(QRegularExpressionMatch re);
void command_bitmap_load(QRegularExpressionMatch re);
void command_pixmap_setDevicePixelRatio(QRegularExpressionMatch re);
void command_image_convertToFormat(QRegularExpressionMatch re);
void command_image_load(QRegularExpressionMatch re);
void command_image_setColor(QRegularExpressionMatch re);
void command_image_setColorCount(QRegularExpressionMatch re);
void command_image_setDevicePixelRatio(QRegularExpressionMatch re);
// commands: transformation
void command_resetMatrix(QRegularExpressionMatch re);
void command_translate(QRegularExpressionMatch re);
void command_rotate(QRegularExpressionMatch re);
void command_rotate_x(QRegularExpressionMatch re);
void command_rotate_y(QRegularExpressionMatch re);
void command_scale(QRegularExpressionMatch re);
void command_mapQuadToQuad(QRegularExpressionMatch re);
void command_setMatrix(QRegularExpressionMatch re);
// attributes
QPainter *m_painter;
QPainter *m_surface_painter;
QImage::Format m_format;
QImage m_surface_image;
QRectF m_surface_rect;
QStringList m_commands;
QString m_currentCommand;
int m_currentCommandIndex;
QString m_filepath;
QMap<QString, QStringList> m_blockMap;
QMap<QString, QPainterPath> m_pathMap;
QMap<QString, QPixmap> m_pixmapMap;
QMap<QString, QImage> m_imageMap;
QMap<QString, QRegion> m_regionMap;
QGradientStops m_gradientStops;
QGradient::Spread m_gradientSpread;
QGradient::CoordinateMode m_gradientCoordinate;
bool m_abort;
bool m_verboseMode;
DeviceType m_type;
bool m_checkers_background;
bool m_shouldDrawText;
QList<QPointF> m_controlPoints;
#ifndef QT_NO_OPENGL
QOpenGLContext *m_default_glcontext;
QOpenGLContext *m_surface_glcontext;
QOpenGLFramebufferObject *m_surface_glbuffer;
QOpenGLPaintDevice *m_surface_glpaintdevice;
#endif
// painter functionalities string tables
static const char *brushStyleTable[];
static const char *penStyleTable[];
static const char *fontWeightTable[];
static const char *fontHintingTable[];
static const char *fontCapitalizationTable[];
static const char *clipOperationTable[];
static const char *spreadMethodTable[];
static const char *coordinateMethodTable[];
static const char *compositionModeTable[];
static const char *imageFormatTable[];
static const char *sizeModeTable[];
static const char *renderHintTable[];
static int translateEnum(const char *table[], const QString &pattern, int limit);
// utility
template <typename T> T image_load(const QString &filepath);
// commands dictionary management
static void staticInit();
public:
struct PaintCommandInfos
{
PaintCommandInfos(QString id, void (PaintCommands::*p)(QRegularExpressionMatch), QRegularExpression r, QString sy, QString sa)
: identifier(id)
, regExp(r)
, syntax(sy)
, sample(sa)
, paintMethod(p)
{}
PaintCommandInfos(QString title)
: identifier(title), paintMethod(0) {}
bool isSectionHeader() const { return paintMethod == 0; }
QString identifier;
QRegularExpression regExp;
QString syntax;
QString sample;
void (PaintCommands::*paintMethod)(QRegularExpressionMatch);
};
static PaintCommandInfos *findCommandById(const QString &identifier) {
for (int i=0; i<s_commandInfoTable.size(); i++)
if (s_commandInfoTable[i].identifier == identifier)
return &s_commandInfoTable[i];
return 0;
}
static QList<PaintCommandInfos> s_commandInfoTable;
static QList<QPair<QString,QStringList> > s_enumsTable;
static QMultiHash<QString, int> s_commandHash;
};
#endif // PAINTCOMMANDS_H

View File

@ -0,0 +1,406 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qbaselinetest.h"
#include "baselineprotocol.h"
#include <QtCore/QDir>
#include <QFile>
#define MAXCMDLINEARGS 128
namespace QBaselineTest {
static char *fargv[MAXCMDLINEARGS];
static bool simfail = false;
static PlatformInfo customInfo;
static bool customAutoModeSet = false;
static BaselineProtocol proto;
static bool connected = false;
static bool triedConnecting = false;
static bool dryRunMode = false;
static enum { UploadMissing, UploadAll, UploadNone } baselinePolicy = UploadMissing;
static bool abortIfUnstable = true;
static QByteArray curFunction;
static ImageItemList itemList;
static bool gotBaselines;
void handleCmdLineArgs(int *argcp, char ***argvp)
{
if (!argcp || !argvp)
return;
bool showHelp = false;
int fargc = 0;
int numArgs = *argcp;
for (int i = 0; i < numArgs; i++) {
QByteArray arg = (*argvp)[i];
QByteArray nextArg = (i+1 < numArgs) ? (*argvp)[i+1] : nullptr;
if (arg == "-simfail") {
simfail = true;
} else if (arg == "-fuzzlevel") {
i++;
bool ok = false;
(void)nextArg.toInt(&ok);
if (!ok) {
qWarning() << "-fuzzlevel requires integer parameter";
showHelp = true;
break;
}
customInfo.insert("FuzzLevel", QString::fromLatin1(nextArg));
} else if (arg == "-auto") {
customAutoModeSet = true;
customInfo.setAdHocRun(false);
} else if (arg == "-adhoc") {
customAutoModeSet = true;
customInfo.setAdHocRun(true);
} else if (arg == "-setbaselines") {
baselinePolicy = UploadAll;
} else if (arg == "-keeprunning") {
abortIfUnstable = false;
} else if (arg == "-nosetbaselines") {
baselinePolicy = UploadNone;
} else if (arg == "-compareto") {
i++;
int split = qMax(0, nextArg.indexOf('='));
QByteArray key = nextArg.left(split).trimmed();
QByteArray value = nextArg.mid(split+1).trimmed();
if (key.isEmpty() || value.isEmpty()) {
qWarning() << "-compareto requires parameter of the form <key>=<value>";
showHelp = true;
break;
}
customInfo.addOverride(key, value);
} else {
if ( (arg == "-help") || (arg == "--help") )
showHelp = true;
if (fargc >= MAXCMDLINEARGS) {
qWarning() << "Too many command line arguments!";
break;
}
fargv[fargc++] = (*argvp)[i];
}
}
*argcp = fargc;
*argvp = fargv;
if (showHelp) {
// TBD: arrange for this to be printed *after* QTest's help
QTextStream out(stdout);
out << "\n Baseline testing (lancelot) options:\n";
out << " -simfail : Force an image comparison mismatch. For testing purposes.\n";
out << " -fuzzlevel <int> : Specify the percentage of fuzziness in comparison. Overrides server default. 0 means exact match.\n";
out << " -auto : Inform server that this run is done by a daemon, CI system or similar.\n";
out << " -adhoc (default) : The inverse of -auto; this run is done by human, e.g. for testing.\n";
out << " -keeprunning : Run all tests even if the system is unstable \n";
out << " -setbaselines : Store ALL rendered images as new baselines. Forces replacement of previous baselines.\n";
out << " -nosetbaselines : Do not store rendered images as new baselines when previous baselines are missing.\n";
out << " -compareto KEY=VAL : Force comparison to baselines from a different client,\n";
out << " for example: -compareto QtVersion=4.8.0\n";
out << " Multiple -compareto client specifications may be given.\n";
out << "\n";
}
}
bool shouldAbortIfUnstable()
{
return abortIfUnstable;
}
void addClientProperty(const QString& key, const QString& value)
{
customInfo.insert(key, value);
}
/*
If a client property script is present, run it and accept its output
in the form of one 'key: value' property per line
*/
void fetchCustomClientProperties()
{
QFile file("hostinfo.txt");
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return;
QTextStream in(&file);
while (!in.atEnd()) {
QString line = in.readLine().trimmed(); // ###local8bit? utf8?
if (line.startsWith(QLatin1Char('#'))) // Ignore comments in file
continue;
QString key, val;
int colonPos = line.indexOf(':');
if (colonPos > 0) {
key = line.left(colonPos).simplified().replace(' ', '_');
val = line.mid(colonPos+1).trimmed();
}
if (!key.isEmpty() && key.size() < 64 && val.size() < 256) // ###TBD: maximum 256 chars in value?
addClientProperty(key, val);
else
qDebug() << "Unparseable script output ignored:" << line;
}
}
bool connect(QByteArray *msg, bool *error)
{
if (connected) {
return true;
}
else if (triedConnecting) {
// Avoid repeated connection attempts, to avoid the program using Timeout * #testItems seconds before giving up
*msg = "Not connected to baseline server.";
*error = true;
return false;
}
triedConnecting = true;
fetchCustomClientProperties();
// Merge the platform info set by the program with the protocols default info
PlatformInfo clientInfo = customInfo;
PlatformInfo defaultInfo = PlatformInfo::localHostInfo();
const auto &defaultInfoKeys = defaultInfo.keys();
for (const QString &key : defaultInfoKeys) {
if (!clientInfo.contains(key))
clientInfo.insert(key, defaultInfo.value(key));
}
if (!customAutoModeSet)
clientInfo.setAdHocRun(defaultInfo.isAdHocRun());
QString testCase = clientInfo.value(PI_TestCase);
if (testCase.isEmpty() && QTest::testObject() && QTest::testObject()->metaObject()) {
//qDebug() << "Trying to Read TestCaseName from Testlib!";
testCase = QTest::testObject()->metaObject()->className();
}
if (testCase.isEmpty()) {
qWarning("QBaselineTest::connect: No test case name specified, cannot connect.");
return false;
}
if (!proto.connect(testCase, &dryRunMode, clientInfo)) {
*msg += "Failed to connect to baseline server: " + proto.errorMessage().toLatin1();
*error = true;
return false;
}
connected = true;
return true;
}
bool disconnectFromBaselineServer()
{
if (proto.disconnect()) {
connected = false;
triedConnecting = false;
return true;
}
return false;
}
bool connectToBaselineServer(QByteArray *msg)
{
bool dummy;
QByteArray dummyMsg;
return connect(msg ? msg : &dummyMsg, &dummy);
}
void setAutoMode(bool mode)
{
customInfo.setAdHocRun(!mode);
customAutoModeSet = true;
}
void setSimFail(bool fail)
{
simfail = fail;
}
void setProject(const QString &projectName)
{
addClientProperty(PI_Project, projectName);
}
void setProjectImageKeys(const QStringList &keys)
{
QString keyList = keys.join(QLC(','));
addClientProperty(PI_ProjectImageKeys, keyList);
}
void modifyImage(QImage *img)
{
uint c0 = 0x0000ff00;
uint c1 = 0x0080ff00;
img->setPixel(1,1,c0);
img->setPixel(2,1,c1);
img->setPixel(3,1,c0);
img->setPixel(1,2,c1);
img->setPixel(1,3,c0);
img->setPixel(2,3,c1);
img->setPixel(3,3,c0);
img->setPixel(1,4,c1);
img->setPixel(1,5,c0);
}
bool compareItem(const ImageItem &baseline, const QImage &img, QByteArray *msg, bool *error)
{
ImageItem item = baseline;
if (simfail) {
// Simulate test failure by forcing image mismatch; for testing purposes
QImage misImg = img;
modifyImage(&misImg);
item.image = misImg;
simfail = false; // One failure is typically enough
} else {
item.image = img;
}
item.imageChecksums.clear();
item.imageChecksums.prepend(ImageItem::computeChecksum(item.image));
QByteArray srvMsg;
switch (baseline.status) {
case ImageItem::Ok:
break;
case ImageItem::IgnoreItem :
qDebug() << msg->constData() << "Ignored, blacklisted on server.";
return true;
break;
case ImageItem::BaselineNotFound:
if (!customInfo.overrides().isEmpty() || baselinePolicy == UploadNone) {
qWarning() << "Cannot compare to baseline: No such baseline found on server.";
return true;
}
if (proto.submitNewBaseline(item, &srvMsg))
qDebug() << msg->constData() << "Baseline not found on server. New baseline uploaded.";
else
qDebug() << msg->constData() << "Baseline not found on server. Uploading of new baseline failed:" << srvMsg;
return true;
break;
default:
qWarning() << "Unexpected reply from baseline server.";
return true;
break;
}
*error = false;
// The actual comparison of the given image with the baseline:
if (baseline.imageChecksums.contains(item.imageChecksums.at(0))) {
if (!proto.submitMatch(item, &srvMsg))
qWarning() << "Failed to report image match to server:" << srvMsg;
return true;
}
// At this point, we have established a legitimate mismatch
if (baselinePolicy == UploadAll) {
if (proto.submitNewBaseline(item, &srvMsg))
qDebug() << msg->constData() << "Forcing new baseline; uploaded ok.";
else
qDebug() << msg->constData() << "Forcing new baseline; uploading failed:" << srvMsg;
return true;
}
bool fuzzyMatch = false;
bool res = proto.submitMismatch(item, &srvMsg, &fuzzyMatch);
if (res && fuzzyMatch) {
qInfo() << "Baseline server reports:" << srvMsg;
return true; // The server decides: a fuzzy match means no mismatch
}
*msg += "Mismatch. See report:\n " + srvMsg;
if (dryRunMode) {
qDebug() << "Dryrun, so ignoring" << *msg;
return true;
}
return false;
}
bool checkImage(const QImage &img, const char *name, quint16 checksum, QByteArray *msg, bool *error, int manualdatatag)
{
if (!connected && !connect(msg, error))
return true;
QByteArray itemName;
bool hasName = qstrlen(name);
const char *tag = QTest::currentDataTag();
if (qstrlen(tag)) {
itemName = tag;
if (hasName)
itemName.append('_').append(name);
} else {
itemName = hasName ? name : "default_name";
}
if (manualdatatag > 0)
{
itemName.prepend("_");
itemName.prepend(QByteArray::number(manualdatatag));
}
*msg = "Baseline check of image '" + itemName + "': ";
ImageItem item;
item.itemName = QString::fromLatin1(itemName);
item.itemChecksum = checksum;
item.testFunction = QString::fromLatin1(QTest::currentTestFunction());
ImageItemList list;
list.append(item);
if (!proto.requestBaselineChecksums(QLatin1String(QTest::currentTestFunction()), &list) || list.isEmpty()) {
*msg = "Communication with baseline server failed: " + proto.errorMessage().toLatin1();
*error = true;
return true;
}
return compareItem(list.at(0), img, msg, error);
}
QTestData &newRow(const char *dataTag, quint16 checksum)
{
if (QTest::currentTestFunction() != curFunction) {
curFunction = QTest::currentTestFunction();
itemList.clear();
gotBaselines = false;
}
ImageItem item;
item.itemName = QString::fromLatin1(dataTag);
item.itemChecksum = checksum;
item.testFunction = QString::fromLatin1(QTest::currentTestFunction());
itemList.append(item);
return QTest::newRow(dataTag);
}
bool testImage(const QImage& img, QByteArray *msg, bool *error)
{
if (!connected && !connect(msg, error))
return true;
if (QTest::currentTestFunction() != curFunction || itemList.isEmpty()) {
qWarning() << "Usage error: QBASELINE_TEST used without corresponding QBaselineTest::newRow()";
return true;
}
if (!gotBaselines) {
if (!proto.requestBaselineChecksums(QString::fromLatin1(QTest::currentTestFunction()), &itemList) || itemList.isEmpty()) {
*msg = "Communication with baseline server failed: " + proto.errorMessage().toLatin1();
*error = true;
return true;
}
gotBaselines = true;
}
QString curTag = QString::fromLatin1(QTest::currentDataTag());
ImageItemList::const_iterator it = itemList.constBegin();
while (it != itemList.constEnd() && it->itemName != curTag)
++it;
if (it == itemList.constEnd()) {
qWarning() << "Usage error: QBASELINE_TEST used without corresponding QBaselineTest::newRow() for row" << curTag;
return true;
}
return compareItem(*it, img, msg, error);
}
}

View File

@ -0,0 +1,62 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#ifndef BASELINETEST_H
#define BASELINETEST_H
#include <QTest>
#include <QString>
namespace QBaselineTest {
void setAutoMode(bool mode);
void setSimFail(bool fail);
void handleCmdLineArgs(int *argcp, char ***argvp);
void setProject(const QString &projectName); // Selects server config settings and top level dir
void setProjectImageKeys(const QStringList &keys); // Overrides the ItemPathKeys config setting
void addClientProperty(const QString& key, const QString& value);
bool connectToBaselineServer(QByteArray *msg = nullptr);
bool checkImage(const QImage& img, const char *name, quint16 checksum, QByteArray *msg, bool *error, int manualdatatag = 0);
bool testImage(const QImage& img, QByteArray *msg, bool *error);
QTestData &newRow(const char *dataTag, quint16 checksum = 0);
bool disconnectFromBaselineServer();
bool shouldAbortIfUnstable();
}
#define QBASELINE_CHECK_SUM(image, name, checksum)\
do {\
QByteArray _msg;\
bool _err = false;\
if (!QBaselineTest::checkImage((image), (name), (checksum), &_msg, &_err)) {\
QFAIL(_msg.constData());\
} else if (_err) {\
QSKIP(_msg.constData());\
}\
} while (0)
#define QBASELINE_CHECK_SUM_DEFERRED(image, name, checksum)\
do {\
QByteArray _msg;\
bool _err = false;\
if (!QBaselineTest::checkImage((image), (name), (checksum), &_msg, &_err)) {\
QTest::qFail(_msg.constData(), __FILE__, __LINE__);\
} else if (_err) {\
QSKIP(_msg.constData());\
}\
} while (0)
#define QBASELINE_CHECK(image, name) QBASELINE_CHECK_SUM(image, name, 0)
#define QBASELINE_CHECK_DEFERRED(image, name) QBASELINE_CHECK_SUM_DEFERRED(image, name, 0)
#define QBASELINE_TEST(image)\
do {\
QByteArray _msg;\
bool _err = false;\
if (!QBaselineTest::testImage((image), &_msg, &_err)) {\
QFAIL(_msg.constData());\
} else if (_err) {\
QSKIP(_msg.constData());\
}\
} while (0)
#endif // BASELINETEST_H

View File

@ -0,0 +1,14 @@
QT *= testlib
SOURCES += \
$$PWD/qbaselinetest.cpp
HEADERS += \
$$PWD/qbaselinetest.h
qtHaveModule(widgets) {
SOURCES += $$PWD/qwidgetbaselinetest.cpp
HEADERS += $$PWD/qwidgetbaselinetest.h
}
include($$PWD/baselineprotocol.pri)

View File

@ -0,0 +1,170 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qwidgetbaselinetest.h"
#include <qbaselinetest.h>
#include <QApplication>
#include <QStyle>
#include <QStyleHints>
#include <QScreen>
#include <QtWidgets/private/qapplication_p.h>
QT_BEGIN_NAMESPACE
QWidgetBaselineTest::QWidgetBaselineTest()
{
QBaselineTest::setProject("Widgets");
// Set key platform properties that are relevant for the appearance of widgets
const QString platformName = QGuiApplication::platformName() + "-" + QSysInfo::productType();
QBaselineTest::addClientProperty("PlatformName", platformName);
QBaselineTest::addClientProperty("OSVersion", QSysInfo::productVersion());
// Encode a number of parameters that impact the UI
QPalette palette;
QFont font;
const QString styleName =
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QApplication::style()->metaObject()->className();
#else
QApplication::style()->name();
#endif
// turn off animations and make the cursor flash time really long to avoid blinking
QApplication::style()->setProperty("_qt_animation_time", QTime());
QGuiApplication::styleHints()->setCursorFlashTime(50000);
QByteArray appearanceBytes;
{
QDataStream appearanceStream(&appearanceBytes, QIODevice::WriteOnly);
appearanceStream << palette << font;
const qreal screenDpr = QApplication::primaryScreen()->devicePixelRatio();
if (screenDpr != 1.0) {
qWarning() << "DPR is" << screenDpr << "- images will not be compared to 1.0 baseline!";
appearanceStream << screenDpr;
}
}
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
const quint16 appearanceId = qChecksum(appearanceBytes, appearanceBytes.size());
#else
const quint16 appearanceId = qChecksum(appearanceBytes);
#endif
// Assume that text that's darker than the background means we run in light mode
// This results in a more meaningful appearance ID between different runs than
// just the checksum of the various attributes.
const QColor windowColor = palette.window().color();
const QColor textColor = palette.text().color();
const QString appearanceIdString = (windowColor.value() > textColor.value()
? QString("light-%1-%2") : QString("dark-%1-%2"))
.arg(styleName).arg(appearanceId, 0, 16);
QBaselineTest::addClientProperty("AppearanceID", appearanceIdString);
// let users know where they can find the results
qDebug() << "PlatformName computed to be:" << platformName;
qDebug() << "Appearance ID computed as:" << appearanceIdString;
}
void QWidgetBaselineTest::initTestCase()
{
// Check and setup the environment. Failure to do so skips the test.
QByteArray msg;
if (!QBaselineTest::connectToBaselineServer(&msg))
QSKIP(msg);
}
void QWidgetBaselineTest::init()
{
QVERIFY(!window);
window = new QWidget;
window->setWindowTitle(QTest::currentDataTag());
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
window->setScreen(QGuiApplication::primaryScreen());
#endif
window->move(QGuiApplication::primaryScreen()->availableGeometry().topLeft());
doInit();
}
void QWidgetBaselineTest::cleanup()
{
doCleanup();
delete window;
window = nullptr;
}
void QWidgetBaselineTest::makeVisible()
{
Q_ASSERT(window);
window->show();
QApplicationPrivate::setActiveWindow(window);
QVERIFY(QTest::qWaitForWindowActive(window));
// explicitly unset focus, the test needs to control when focus is shown
if (window->focusWidget())
window->focusWidget()->clearFocus();
}
/*
Grabs the test window and returns the resulting QImage, without
compensating for DPR differences.
*/
QImage QWidgetBaselineTest::takeSnapshot()
{
// make sure all effects are done
QTest::qWait(250);
return window->grab().toImage();
}
/*!
Sets standard widget properties on the test window and its children,
and uploads snapshots. The widgets are returned in the same state
that they had before.
Call this helper after setting up the test window.
*/
void QWidgetBaselineTest::takeStandardSnapshots()
{
makeVisible();
struct PublicWidget : QWidget {
bool focusNextPrevChild(bool next) override { return QWidget::focusNextPrevChild(next); }
};
QBASELINE_CHECK_DEFERRED(takeSnapshot(), "default");
// try hard to set focus
static_cast<PublicWidget*>(window)->focusNextPrevChild(true);
if (!window->focusWidget()) {
QWidget *firstChild = window->findChild<QWidget*>();
if (firstChild)
firstChild->setFocus();
}
if (testWindow()->focusWidget()) {
QBASELINE_CHECK_DEFERRED(takeSnapshot(), "focused");
testWindow()->focusWidget()->clearFocus();
}
// this disables all children
window->setEnabled(false);
QBASELINE_CHECK_DEFERRED(takeSnapshot(), "disabled");
window->setEnabled(true);
// show and activate another window so that our test window becomes inactive
QWidget otherWindow;
otherWindow.move(window->geometry().bottomRight() + QPoint(10, 10));
otherWindow.resize(50, 50);
otherWindow.setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint);
otherWindow.show();
otherWindow.windowHandle()->requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&otherWindow));
QBASELINE_CHECK_DEFERRED(takeSnapshot(), "inactive");
window->windowHandle()->requestActivate();
QVERIFY(QTest::qWaitForWindowActive(window));
if (window->focusWidget())
window->focusWidget()->clearFocus();
}
QT_END_NAMESPACE

View File

@ -0,0 +1,40 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
#include <QObject>
#include <QImage>
QT_BEGIN_NAMESPACE
class QWidget;
class QWidgetBaselineTest : public QObject
{
Q_OBJECT
public:
QWidgetBaselineTest();
void takeStandardSnapshots();
QWidget *testWindow() const { return window; }
protected:
virtual void doInit() {}
virtual void doCleanup() {}
private slots:
void initTestCase();
void init();
void cleanup();
protected:
void makeVisible();
QImage takeSnapshot();
private:
QWidget *window = nullptr;
};
QT_END_NAMESPACE