/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the tools applications of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "rcc.h" | |
#include <QtCore/QByteArray> | |
#include <QtCore/QDateTime> | |
#include <QtCore/QDebug> | |
#include <QtCore/QDir> | |
#include <QtCore/QDirIterator> | |
#include <QtCore/QFile> | |
#include <QtCore/QIODevice> | |
#include <QtCore/QLocale> | |
#include <QtCore/QStack> | |
#include <QtXml/QDomDocument> | |
QT_BEGIN_NAMESPACE | |
enum { | |
CONSTANT_USENAMESPACE = 1, | |
CONSTANT_COMPRESSLEVEL_DEFAULT = -1, | |
CONSTANT_COMPRESSTHRESHOLD_DEFAULT = 70 | |
}; | |
#define writeString(s) write(s, sizeof(s)) | |
void RCCResourceLibrary::write(const char *str, int len) | |
{ | |
--len; // trailing \0 on string literals... | |
int n = m_out.size(); | |
m_out.resize(n + len); | |
memcpy(m_out.data() + n, str, len); | |
} | |
void RCCResourceLibrary::writeByteArray(const QByteArray &other) | |
{ | |
m_out.append(other); | |
} | |
static inline QString msgOpenReadFailed(const QString &fname, const QString &why) | |
{ | |
return QString::fromUtf8("Unable to open %1 for reading: %2\n").arg(fname).arg(why); | |
} | |
/////////////////////////////////////////////////////////// | |
// | |
// RCCFileInfo | |
// | |
/////////////////////////////////////////////////////////// | |
class RCCFileInfo | |
{ | |
public: | |
enum Flags | |
{ | |
NoFlags = 0x00, | |
Compressed = 0x01, | |
Directory = 0x02 | |
}; | |
RCCFileInfo(const QString &name = QString(), const QFileInfo &fileInfo = QFileInfo(), | |
QLocale::Language language = QLocale::C, | |
QLocale::Country country = QLocale::AnyCountry, | |
uint flags = NoFlags, | |
int compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT, | |
int compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT); | |
~RCCFileInfo(); | |
QString resourceName() const; | |
public: | |
qint64 writeDataBlob(RCCResourceLibrary &lib, qint64 offset, QString *errorMessage); | |
qint64 writeDataName(RCCResourceLibrary &, qint64 offset); | |
void writeDataInfo(RCCResourceLibrary &lib); | |
int m_flags; | |
QString m_name; | |
QLocale::Language m_language; | |
QLocale::Country m_country; | |
QFileInfo m_fileInfo; | |
RCCFileInfo *m_parent; | |
QHash<QString, RCCFileInfo*> m_children; | |
int m_compressLevel; | |
int m_compressThreshold; | |
qint64 m_nameOffset; | |
qint64 m_dataOffset; | |
qint64 m_childOffset; | |
}; | |
RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, | |
QLocale::Language language, QLocale::Country country, uint flags, | |
int compressLevel, int compressThreshold) | |
{ | |
m_name = name; | |
m_fileInfo = fileInfo; | |
m_language = language; | |
m_country = country; | |
m_flags = flags; | |
m_parent = 0; | |
m_nameOffset = 0; | |
m_dataOffset = 0; | |
m_childOffset = 0; | |
m_compressLevel = compressLevel; | |
m_compressThreshold = compressThreshold; | |
} | |
RCCFileInfo::~RCCFileInfo() | |
{ | |
qDeleteAll(m_children); | |
} | |
QString RCCFileInfo::resourceName() const | |
{ | |
QString resource = m_name; | |
for (RCCFileInfo *p = m_parent; p; p = p->m_parent) | |
resource = resource.prepend(p->m_name + QLatin1Char('/')); | |
return QLatin1Char(':') + resource; | |
} | |
void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib) | |
{ | |
const bool text = (lib.m_format == RCCResourceLibrary::C_Code); | |
//some info | |
if (text) { | |
if (m_language != QLocale::C) { | |
lib.writeString(" // "); | |
lib.writeByteArray(resourceName().toLocal8Bit()); | |
lib.writeString(" ["); | |
lib.writeByteArray(QByteArray::number(m_country)); | |
lib.writeString("::"); | |
lib.writeByteArray(QByteArray::number(m_language)); | |
lib.writeString("[\n "); | |
} else { | |
lib.writeString(" // "); | |
lib.writeByteArray(resourceName().toLocal8Bit()); | |
lib.writeString("\n "); | |
} | |
} | |
//pointer data | |
if (m_flags & RCCFileInfo::Directory) { | |
// name offset | |
lib.writeNumber4(m_nameOffset); | |
// flags | |
lib.writeNumber2(m_flags); | |
// child count | |
lib.writeNumber4(m_children.size()); | |
// first child offset | |
lib.writeNumber4(m_childOffset); | |
} else { | |
// name offset | |
lib.writeNumber4(m_nameOffset); | |
// flags | |
lib.writeNumber2(m_flags); | |
// locale | |
lib.writeNumber2(m_country); | |
lib.writeNumber2(m_language); | |
//data offset | |
lib.writeNumber4(m_dataOffset); | |
} | |
if (text) | |
lib.writeChar('\n'); | |
} | |
qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, | |
QString *errorMessage) | |
{ | |
const bool text = (lib.m_format == RCCResourceLibrary::C_Code); | |
//capture the offset | |
m_dataOffset = offset; | |
//find the data to be written | |
QFile file(m_fileInfo.absoluteFilePath()); | |
if (!file.open(QFile::ReadOnly)) { | |
*errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString()); | |
return 0; | |
} | |
QByteArray data = file.readAll(); | |
#ifndef QT_NO_COMPRESS | |
// Check if compression is useful for this file | |
if (m_compressLevel != 0 && data.size() != 0) { | |
QByteArray compressed = | |
qCompress(reinterpret_cast<uchar *>(data.data()), data.size(), m_compressLevel); | |
int compressRatio = int(100.0 * (data.size() - compressed.size()) / data.size()); | |
if (compressRatio >= m_compressThreshold) { | |
data = compressed; | |
m_flags |= Compressed; | |
} | |
} | |
#endif // QT_NO_COMPRESS | |
// some info | |
if (text) { | |
lib.writeString(" // "); | |
lib.writeByteArray(m_fileInfo.absoluteFilePath().toLocal8Bit()); | |
lib.writeString("\n "); | |
} | |
// write the length | |
lib.writeNumber4(data.size()); | |
if (text) | |
lib.writeString("\n "); | |
offset += 4; | |
// write the payload | |
const char *p = data.constData(); | |
if (text) { | |
for (int i = data.size(), j = 0; --i >= 0; --j) { | |
lib.writeHex(*p++); | |
if (j == 0) { | |
lib.writeString("\n "); | |
j = 16; | |
} | |
} | |
} else { | |
for (int i = data.size(); --i >= 0; ) | |
lib.writeChar(*p++); | |
} | |
offset += data.size(); | |
// done | |
if (text) | |
lib.writeString("\n "); | |
return offset; | |
} | |
qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset) | |
{ | |
const bool text = (lib.m_format == RCCResourceLibrary::C_Code); | |
// capture the offset | |
m_nameOffset = offset; | |
// some info | |
if (text) { | |
lib.writeString(" // "); | |
lib.writeByteArray(m_name.toLocal8Bit()); | |
lib.writeString("\n "); | |
} | |
// write the length | |
lib.writeNumber2(m_name.length()); | |
if (text) | |
lib.writeString("\n "); | |
offset += 2; | |
// write the hash | |
lib.writeNumber4(qHash(m_name)); | |
if (text) | |
lib.writeString("\n "); | |
offset += 4; | |
// write the m_name | |
const QChar *unicode = m_name.unicode(); | |
for (int i = 0; i < m_name.length(); ++i) { | |
lib.writeNumber2(unicode[i].unicode()); | |
if (text && i % 16 == 0) | |
lib.writeString("\n "); | |
} | |
offset += m_name.length()*2; | |
// done | |
if (text) | |
lib.writeString("\n "); | |
return offset; | |
} | |
/////////////////////////////////////////////////////////// | |
// | |
// RCCResourceLibrary | |
// | |
/////////////////////////////////////////////////////////// | |
RCCResourceLibrary::Strings::Strings() : | |
TAG_RCC(QLatin1String("RCC")), | |
TAG_RESOURCE(QLatin1String("qresource")), | |
TAG_FILE(QLatin1String("file")), | |
ATTRIBUTE_LANG(QLatin1String("lang")), | |
ATTRIBUTE_PREFIX(QLatin1String("prefix")), | |
ATTRIBUTE_ALIAS(QLatin1String("alias")), | |
ATTRIBUTE_THRESHOLD(QLatin1String("threshold")), | |
ATTRIBUTE_COMPRESS(QLatin1String("compress")) | |
{ | |
} | |
RCCResourceLibrary::RCCResourceLibrary() | |
: m_root(0), | |
m_format(C_Code), | |
m_verbose(false), | |
m_compressLevel(CONSTANT_COMPRESSLEVEL_DEFAULT), | |
m_compressThreshold(CONSTANT_COMPRESSTHRESHOLD_DEFAULT), | |
m_treeOffset(0), | |
m_namesOffset(0), | |
m_dataOffset(0), | |
m_useNameSpace(CONSTANT_USENAMESPACE), | |
m_errorDevice(0) | |
{ | |
m_out.reserve(30 * 1000 * 1000); | |
} | |
RCCResourceLibrary::~RCCResourceLibrary() | |
{ | |
delete m_root; | |
} | |
bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, | |
const QString &fname, QString currentPath, bool ignoreErrors) | |
{ | |
Q_ASSERT(m_errorDevice); | |
const QChar slash = QLatin1Char('/'); | |
if (!currentPath.isEmpty() && !currentPath.endsWith(slash)) | |
currentPath += slash; | |
QDomDocument document; | |
{ | |
QString errorMsg; | |
int errorLine = 0; | |
int errorColumn = 0; | |
if (!document.setContent(inputDevice, &errorMsg, &errorLine, &errorColumn)) { | |
if (ignoreErrors) | |
return true; | |
const QString msg = QString::fromUtf8("RCC Parse Error: '%1' Line: %2 Column: %3 [%4]\n").arg(fname).arg(errorLine).arg(errorColumn).arg(errorMsg); | |
m_errorDevice->write(msg.toUtf8()); | |
return false; | |
} | |
} | |
QDomElement domRoot = document.firstChildElement(m_strings.TAG_RCC).toElement(); | |
if (!domRoot.isNull() && domRoot.tagName() == m_strings.TAG_RCC) { | |
for (QDomNode node = domRoot.firstChild(); !node.isNull(); node = node.nextSibling()) { | |
if (!node.isElement()) | |
continue; | |
QDomElement child = node.toElement(); | |
if (!child.isNull() && child.tagName() == m_strings.TAG_RESOURCE) { | |
QLocale::Language language = QLocale::c().language(); | |
QLocale::Country country = QLocale::c().country(); | |
if (child.hasAttribute(m_strings.ATTRIBUTE_LANG)) { | |
QString attribute = child.attribute(m_strings.ATTRIBUTE_LANG); | |
QLocale lang = QLocale(attribute); | |
language = lang.language(); | |
if (2 == attribute.length()) { | |
// Language only | |
country = QLocale::AnyCountry; | |
} else { | |
country = lang.country(); | |
} | |
} | |
QString prefix; | |
if (child.hasAttribute(m_strings.ATTRIBUTE_PREFIX)) | |
prefix = child.attribute(m_strings.ATTRIBUTE_PREFIX); | |
if (!prefix.startsWith(slash)) | |
prefix.prepend(slash); | |
if (!prefix.endsWith(slash)) | |
prefix += slash; | |
for (QDomNode res = child.firstChild(); !res.isNull(); res = res.nextSibling()) { | |
if (res.isElement() && res.toElement().tagName() == m_strings.TAG_FILE) { | |
QString fileName(res.firstChild().toText().data()); | |
if (fileName.isEmpty()) { | |
const QString msg = QString::fromUtf8("RCC: Warning: Null node in XML of '%1'\n").arg(fname); | |
m_errorDevice->write(msg.toUtf8()); | |
} | |
QString alias; | |
if (res.toElement().hasAttribute(m_strings.ATTRIBUTE_ALIAS)) | |
alias = res.toElement().attribute(m_strings.ATTRIBUTE_ALIAS); | |
else | |
alias = fileName; | |
int compressLevel = m_compressLevel; | |
if (res.toElement().hasAttribute(m_strings.ATTRIBUTE_COMPRESS)) | |
compressLevel = res.toElement().attribute(m_strings.ATTRIBUTE_COMPRESS).toInt(); | |
int compressThreshold = m_compressThreshold; | |
if (res.toElement().hasAttribute(m_strings.ATTRIBUTE_THRESHOLD)) | |
compressThreshold = res.toElement().attribute(m_strings.ATTRIBUTE_THRESHOLD).toInt(); | |
// Special case for -no-compress. Overrides all other settings. | |
if (m_compressLevel == -2) | |
compressLevel = 0; | |
alias = QDir::cleanPath(alias); | |
while (alias.startsWith(QLatin1String("../"))) | |
alias.remove(0, 3); | |
alias = QDir::cleanPath(m_resourceRoot) + prefix + alias; | |
QString absFileName = fileName; | |
if (QDir::isRelativePath(absFileName)) | |
absFileName.prepend(currentPath); | |
QFileInfo file(absFileName); | |
if (!file.exists()) { | |
m_failedResources.push_back(absFileName); | |
const QString msg = QString::fromUtf8("RCC: Error in '%1': Cannot find file '%2'\n").arg(fname).arg(fileName); | |
m_errorDevice->write(msg.toUtf8()); | |
if (ignoreErrors) | |
continue; | |
else | |
return false; | |
} else if (file.isFile()) { | |
const bool arc = | |
addFile(alias, | |
RCCFileInfo(alias.section(slash, -1), | |
file, | |
language, | |
country, | |
RCCFileInfo::NoFlags, | |
compressLevel, | |
compressThreshold) | |
); | |
if (!arc) | |
m_failedResources.push_back(absFileName); | |
} else { | |
QDir dir; | |
if (file.isDir()) { | |
dir.setPath(file.filePath()); | |
} else { | |
dir.setPath(file.path()); | |
dir.setNameFilters(QStringList(file.fileName())); | |
if (alias.endsWith(file.fileName())) | |
alias = alias.left(alias.length()-file.fileName().length()); | |
} | |
if (!alias.endsWith(slash)) | |
alias += slash; | |
QDirIterator it(dir, QDirIterator::FollowSymlinks|QDirIterator::Subdirectories); | |
while (it.hasNext()) { | |
it.next(); | |
QFileInfo child(it.fileInfo()); | |
if (child.fileName() != QLatin1String(".") && child.fileName() != QLatin1String("..")) { | |
const bool arc = | |
addFile(alias + child.fileName(), | |
RCCFileInfo(child.fileName(), | |
child, | |
language, | |
country, | |
RCCFileInfo::NoFlags, | |
compressLevel, | |
compressThreshold) | |
); | |
if (!arc) | |
m_failedResources.push_back(child.fileName()); | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
if (m_root == 0) { | |
const QString msg = QString::fromUtf8("RCC: Warning: No resources in '%1'.\n").arg(fname); | |
m_errorDevice->write(msg.toUtf8()); | |
if (!ignoreErrors && m_format == Binary) { | |
// create dummy entry, otherwise loading qith QResource will crash | |
m_root = new RCCFileInfo(QString(), QFileInfo(), | |
QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); | |
} | |
} | |
return true; | |
} | |
bool RCCResourceLibrary::addFile(const QString &alias, const RCCFileInfo &file) | |
{ | |
Q_ASSERT(m_errorDevice); | |
if (file.m_fileInfo.size() > 0xffffffff) { | |
const QString msg = QString::fromUtf8("File too big: %1\n").arg(file.m_fileInfo.absoluteFilePath()); | |
m_errorDevice->write(msg.toUtf8()); | |
return false; | |
} | |
if (!m_root) | |
m_root = new RCCFileInfo(QString(), QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); | |
RCCFileInfo *parent = m_root; | |
const QStringList nodes = alias.split(QLatin1Char('/')); | |
for (int i = 1; i < nodes.size()-1; ++i) { | |
const QString node = nodes.at(i); | |
if (node.isEmpty()) | |
continue; | |
if (!parent->m_children.contains(node)) { | |
RCCFileInfo *s = new RCCFileInfo(node, QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory); | |
s->m_parent = parent; | |
parent->m_children.insert(node, s); | |
parent = s; | |
} else { | |
parent = parent->m_children[node]; | |
} | |
} | |
const QString filename = nodes.at(nodes.size()-1); | |
RCCFileInfo *s = new RCCFileInfo(file); | |
s->m_parent = parent; | |
if (parent->m_children.contains(filename)) { | |
foreach (const QString &fileName, m_fileNames) | |
qWarning("%s: Warning: potential duplicate alias detected: '%s'", | |
qPrintable(fileName), qPrintable(filename)); | |
} | |
parent->m_children.insertMulti(filename, s); | |
return true; | |
} | |
void RCCResourceLibrary::reset() | |
{ | |
if (m_root) { | |
delete m_root; | |
m_root = 0; | |
} | |
m_errorDevice = 0; | |
m_failedResources.clear(); | |
} | |
bool RCCResourceLibrary::readFiles(bool ignoreErrors, QIODevice &errorDevice) | |
{ | |
reset(); | |
m_errorDevice = &errorDevice; | |
//read in data | |
if (m_verbose) { | |
const QString msg = QString::fromUtf8("Processing %1 files [%2]\n") | |
.arg(m_fileNames.size()).arg(static_cast<int>(ignoreErrors)); | |
m_errorDevice->write(msg.toUtf8()); | |
} | |
for (int i = 0; i < m_fileNames.size(); ++i) { | |
QFile fileIn; | |
QString fname = m_fileNames.at(i); | |
QString pwd; | |
if (fname == QLatin1String("-")) { | |
fname = QLatin1String("(stdin)"); | |
pwd = QDir::currentPath(); | |
fileIn.setFileName(fname); | |
if (!fileIn.open(stdin, QIODevice::ReadOnly)) { | |
m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8()); | |
return false; | |
} | |
} else { | |
pwd = QFileInfo(fname).path(); | |
fileIn.setFileName(fname); | |
if (!fileIn.open(QIODevice::ReadOnly)) { | |
m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8()); | |
return false; | |
} | |
} | |
if (m_verbose) { | |
const QString msg = QString::fromUtf8("Interpreting %1\n").arg(fname); | |
m_errorDevice->write(msg.toUtf8()); | |
} | |
if (!interpretResourceFile(&fileIn, fname, pwd, ignoreErrors)) | |
return false; | |
} | |
return true; | |
} | |
QStringList RCCResourceLibrary::dataFiles() const | |
{ | |
QStringList ret; | |
QStack<RCCFileInfo*> pending; | |
if (!m_root) | |
return ret; | |
pending.push(m_root); | |
while (!pending.isEmpty()) { | |
RCCFileInfo *file = pending.pop(); | |
for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin(); | |
it != file->m_children.end(); ++it) { | |
RCCFileInfo *child = it.value(); | |
if (child->m_flags & RCCFileInfo::Directory) | |
pending.push(child); | |
ret.append(child->m_fileInfo.filePath()); | |
} | |
} | |
return ret; | |
} | |
// Determine map of resource identifier (':/newPrefix/images/p1.png') to file via recursion | |
static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m) | |
{ | |
typedef QHash<QString, RCCFileInfo*>::const_iterator ChildConstIterator; | |
const QChar slash = QLatin1Char('/'); | |
const ChildConstIterator cend = m_root->m_children.constEnd(); | |
for (ChildConstIterator it = m_root->m_children.constBegin(); it != cend; ++it) { | |
const RCCFileInfo *child = it.value(); | |
QString childName = path; | |
childName += slash; | |
childName += child->m_name; | |
if (child->m_flags & RCCFileInfo::Directory) { | |
resourceDataFileMapRecursion(child, childName, m); | |
} else { | |
m.insert(childName, child->m_fileInfo.filePath()); | |
} | |
} | |
} | |
RCCResourceLibrary::ResourceDataFileMap RCCResourceLibrary::resourceDataFileMap() const | |
{ | |
ResourceDataFileMap rc; | |
if (m_root) | |
resourceDataFileMapRecursion(m_root, QString(QLatin1Char(':')), rc); | |
return rc; | |
} | |
bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &errorDevice) | |
{ | |
m_errorDevice = &errorDevice; | |
//write out | |
if (m_verbose) | |
m_errorDevice->write("Outputting code\n"); | |
if (!writeHeader()) { | |
m_errorDevice->write("Could not write header\n"); | |
return false; | |
} | |
if (m_root) { | |
if (!writeDataBlobs()) { | |
m_errorDevice->write("Could not write data blobs.\n"); | |
return false; | |
} | |
if (!writeDataNames()) { | |
m_errorDevice->write("Could not write file names\n"); | |
return false; | |
} | |
if (!writeDataStructure()) { | |
m_errorDevice->write("Could not write data tree\n"); | |
return false; | |
} | |
} | |
if (!writeInitializer()) { | |
m_errorDevice->write("Could not write footer\n"); | |
return false; | |
} | |
outDevice.write(m_out, m_out.size()); | |
return true; | |
} | |
void RCCResourceLibrary::writeHex(quint8 tmp) | |
{ | |
const char * const digits = "0123456789abcdef"; | |
writeChar('0'); | |
writeChar('x'); | |
if (tmp < 16) { | |
writeChar(digits[tmp]); | |
} else { | |
writeChar(digits[tmp >> 4]); | |
writeChar(digits[tmp & 0xf]); | |
} | |
writeChar(','); | |
} | |
void RCCResourceLibrary::writeNumber2(quint16 number) | |
{ | |
if (m_format == RCCResourceLibrary::Binary) { | |
writeChar(number >> 8); | |
writeChar(number); | |
} else { | |
writeHex(number >> 8); | |
writeHex(number); | |
} | |
} | |
void RCCResourceLibrary::writeNumber4(quint32 number) | |
{ | |
if (m_format == RCCResourceLibrary::Binary) { | |
writeChar(number >> 24); | |
writeChar(number >> 16); | |
writeChar(number >> 8); | |
writeChar(number); | |
} else { | |
writeHex(number >> 24); | |
writeHex(number >> 16); | |
writeHex(number >> 8); | |
writeHex(number); | |
} | |
} | |
bool RCCResourceLibrary::writeHeader() | |
{ | |
if (m_format == C_Code) { | |
writeString("/****************************************************************************\n"); | |
writeString("** Resource object code\n"); | |
writeString("**\n"); | |
writeString("** Created: "); | |
writeByteArray(QDateTime::currentDateTime().toString().toLatin1()); | |
writeString("\n** by: The Resource Compiler for Qt version "); | |
writeByteArray(QT_VERSION_STR); | |
writeString("\n**\n"); | |
writeString("** WARNING! All changes made in this file will be lost!\n"); | |
writeString( "*****************************************************************************/\n\n"); | |
writeString("#include <QtCore/qglobal.h>\n\n"); | |
} else if (m_format == Binary) { | |
writeString("qres"); | |
writeNumber4(0); | |
writeNumber4(0); | |
writeNumber4(0); | |
writeNumber4(0); | |
} | |
return true; | |
} | |
bool RCCResourceLibrary::writeDataBlobs() | |
{ | |
Q_ASSERT(m_errorDevice); | |
if (m_format == C_Code) | |
writeString("static const unsigned char qt_resource_data[] = {\n"); | |
else if (m_format == Binary) | |
m_dataOffset = m_out.size(); | |
QStack<RCCFileInfo*> pending; | |
if (!m_root) | |
return false; | |
pending.push(m_root); | |
qint64 offset = 0; | |
QString errorMessage; | |
while (!pending.isEmpty()) { | |
RCCFileInfo *file = pending.pop(); | |
for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin(); | |
it != file->m_children.end(); ++it) { | |
RCCFileInfo *child = it.value(); | |
if (child->m_flags & RCCFileInfo::Directory) | |
pending.push(child); | |
else { | |
offset = child->writeDataBlob(*this, offset, &errorMessage); | |
if (offset == 0) | |
m_errorDevice->write(errorMessage.toUtf8()); | |
} | |
} | |
} | |
if (m_format == C_Code) | |
writeString("\n};\n\n"); | |
return true; | |
} | |
bool RCCResourceLibrary::writeDataNames() | |
{ | |
if (m_format == C_Code) | |
writeString("static const unsigned char qt_resource_name[] = {\n"); | |
else if (m_format == Binary) | |
m_namesOffset = m_out.size(); | |
QHash<QString, int> names; | |
QStack<RCCFileInfo*> pending; | |
if (!m_root) | |
return false; | |
pending.push(m_root); | |
qint64 offset = 0; | |
while (!pending.isEmpty()) { | |
RCCFileInfo *file = pending.pop(); | |
for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin(); | |
it != file->m_children.end(); ++it) { | |
RCCFileInfo *child = it.value(); | |
if (child->m_flags & RCCFileInfo::Directory) | |
pending.push(child); | |
if (names.contains(child->m_name)) { | |
child->m_nameOffset = names.value(child->m_name); | |
} else { | |
names.insert(child->m_name, offset); | |
offset = child->writeDataName(*this, offset); | |
} | |
} | |
} | |
if (m_format == C_Code) | |
writeString("\n};\n\n"); | |
return true; | |
} | |
static bool qt_rcc_compare_hash(const RCCFileInfo *left, const RCCFileInfo *right) | |
{ | |
return qHash(left->m_name) < qHash(right->m_name); | |
} | |
bool RCCResourceLibrary::writeDataStructure() | |
{ | |
if (m_format == C_Code) | |
writeString("static const unsigned char qt_resource_struct[] = {\n"); | |
else if (m_format == Binary) | |
m_treeOffset = m_out.size(); | |
QStack<RCCFileInfo*> pending; | |
if (!m_root) | |
return false; | |
//calculate the child offsets (flat) | |
pending.push(m_root); | |
int offset = 1; | |
while (!pending.isEmpty()) { | |
RCCFileInfo *file = pending.pop(); | |
file->m_childOffset = offset; | |
//sort by hash value for binary lookup | |
QList<RCCFileInfo*> m_children = file->m_children.values(); | |
qSort(m_children.begin(), m_children.end(), qt_rcc_compare_hash); | |
//write out the actual data now | |
for (int i = 0; i < m_children.size(); ++i) { | |
RCCFileInfo *child = m_children.at(i); | |
++offset; | |
if (child->m_flags & RCCFileInfo::Directory) | |
pending.push(child); | |
} | |
} | |
//write out the structure (ie iterate again!) | |
pending.push(m_root); | |
m_root->writeDataInfo(*this); | |
while (!pending.isEmpty()) { | |
RCCFileInfo *file = pending.pop(); | |
//sort by hash value for binary lookup | |
QList<RCCFileInfo*> m_children = file->m_children.values(); | |
qSort(m_children.begin(), m_children.end(), qt_rcc_compare_hash); | |
//write out the actual data now | |
for (int i = 0; i < m_children.size(); ++i) { | |
RCCFileInfo *child = m_children.at(i); | |
child->writeDataInfo(*this); | |
if (child->m_flags & RCCFileInfo::Directory) | |
pending.push(child); | |
} | |
} | |
if (m_format == C_Code) | |
writeString("\n};\n\n"); | |
return true; | |
} | |
void RCCResourceLibrary::writeMangleNamespaceFunction(const QByteArray &name) | |
{ | |
if (m_useNameSpace) { | |
writeString("QT_MANGLE_NAMESPACE("); | |
writeByteArray(name); | |
writeChar(')'); | |
} else { | |
writeByteArray(name); | |
} | |
} | |
void RCCResourceLibrary::writeAddNamespaceFunction(const QByteArray &name) | |
{ | |
if (m_useNameSpace) { | |
writeString("QT_PREPEND_NAMESPACE("); | |
writeByteArray(name); | |
writeChar(')'); | |
} else { | |
writeByteArray(name); | |
} | |
} | |
bool RCCResourceLibrary::writeInitializer() | |
{ | |
if (m_format == C_Code) { | |
//write("\nQT_BEGIN_NAMESPACE\n"); | |
QString initName = m_initName; | |
if (!initName.isEmpty()) { | |
initName.prepend(QLatin1Char('_')); | |
initName.replace(QRegExp(QLatin1String("[^a-zA-Z0-9_]")), QLatin1String("_")); | |
} | |
//init | |
if (m_useNameSpace) | |
writeString("QT_BEGIN_NAMESPACE\n\n"); | |
if (m_root) { | |
writeString("extern Q_CORE_EXPORT bool qRegisterResourceData\n " | |
"(int, const unsigned char *, " | |
"const unsigned char *, const unsigned char *);\n\n"); | |
writeString("extern Q_CORE_EXPORT bool qUnregisterResourceData\n " | |
"(int, const unsigned char *, " | |
"const unsigned char *, const unsigned char *);\n\n"); | |
} | |
if (m_useNameSpace) | |
writeString("QT_END_NAMESPACE\n\n\n"); | |
QString initResources = QLatin1String("qInitResources"); | |
initResources += initName; | |
writeString("int "); | |
writeMangleNamespaceFunction(initResources.toLatin1()); | |
writeString("()\n{\n"); | |
if (m_root) { | |
writeString(" "); | |
writeAddNamespaceFunction("qRegisterResourceData"); | |
writeString("\n (0x01, qt_resource_struct, " | |
"qt_resource_name, qt_resource_data);\n"); | |
} | |
writeString(" return 1;\n"); | |
writeString("}\n\n"); | |
writeString("Q_CONSTRUCTOR_FUNCTION("); | |
writeMangleNamespaceFunction(initResources.toLatin1()); | |
writeString(")\n\n"); | |
//cleanup | |
QString cleanResources = QLatin1String("qCleanupResources"); | |
cleanResources += initName; | |
writeString("int "); | |
writeMangleNamespaceFunction(cleanResources.toLatin1()); | |
writeString("()\n{\n"); | |
if (m_root) { | |
writeString(" "); | |
writeAddNamespaceFunction("qUnregisterResourceData"); | |
writeString("\n (0x01, qt_resource_struct, " | |
"qt_resource_name, qt_resource_data);\n"); | |
} | |
writeString(" return 1;\n"); | |
writeString("}\n\n"); | |
writeString("Q_DESTRUCTOR_FUNCTION("); | |
writeMangleNamespaceFunction(cleanResources.toLatin1()); | |
writeString(")\n\n"); | |
} else if (m_format == Binary) { | |
int i = 4; | |
char *p = m_out.data(); | |
p[i++] = 0; // 0x01 | |
p[i++] = 0; | |
p[i++] = 0; | |
p[i++] = 1; | |
p[i++] = (m_treeOffset >> 24) & 0xff; | |
p[i++] = (m_treeOffset >> 16) & 0xff; | |
p[i++] = (m_treeOffset >> 8) & 0xff; | |
p[i++] = (m_treeOffset >> 0) & 0xff; | |
p[i++] = (m_dataOffset >> 24) & 0xff; | |
p[i++] = (m_dataOffset >> 16) & 0xff; | |
p[i++] = (m_dataOffset >> 8) & 0xff; | |
p[i++] = (m_dataOffset >> 0) & 0xff; | |
p[i++] = (m_namesOffset >> 24) & 0xff; | |
p[i++] = (m_namesOffset >> 16) & 0xff; | |
p[i++] = (m_namesOffset >> 8) & 0xff; | |
p[i++] = (m_namesOffset >> 0) & 0xff; | |
} | |
return true; | |
} | |
QT_END_NAMESPACE |