/****************************************************************************
**
** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (developer.feedback@nokia.com)
**
** This file is part of the HbServers module of the UI Extensions for Mobile.
**
** 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.
**
** If you have questions regarding the use of this file, please contact
** Nokia at developer.feedback@nokia.com.
**
****************************************************************************/
#include <hbiconsource_p.h>
#include <hbthemeindex_p.h>
#include <assert.h>
#include <iostream>
#include <QApplication>
#include <QStringList>
#include <QTextStream>
#include <QFileInfo>
#include <QLibrary>
#include <QString>
#include <QFile>
#include <QMap>
#include <QDir>
#define RESOURCE_LIB_NAME "HbCore"
#define WIN32_DEBUG_SUFFIX "d"
#define MAC_DEBUG_SUFFIX "_debug"
// For being able to sort the index items based on the iconname
class IndexItemInfo
{
public:
QString iconname;
HbThemeIndexItem item;
};
bool operator<(const IndexItemInfo &left, const IndexItemInfo &right)
{
return left.iconname < right.iconname;
}
// Global variables
static int counter = 0;
static bool verboseOn = false;
static int version = 1; // Current theme index format version
QList<IndexItemInfo> IndexItems;
QMap<QString, int> Strings;
QByteArray StringBuffer;
// ------
QSize getDefaultSize(const QString &filename)
{
HbIconSource source(filename);
return source.defaultSize().toSize();
}
int getStringOffset(const QString &string)
{
int offset = Strings.value(string, -1);
if (offset < 0) {
// Allocate new string in the string buffer
offset = StringBuffer.size();
StringBuffer.append(string.toLatin1());
StringBuffer.append('\0');
// Add offset to the target paths list
Strings.insert(string, offset);
}
return offset;
}
void processFile(const QFileInfo &info, const QString &themename)
{
QString fullFilename = info.absoluteFilePath();
QString filename = info.fileName();
if (filename.endsWith(".svg") ||
filename.endsWith(".png") ||
filename.endsWith(".mng") ||
filename.endsWith(".gif") ||
filename.endsWith(".xpm") ||
filename.endsWith(".jpg") ||
filename.endsWith(".nvg") ||
filename.endsWith(".svgz") ||
filename.endsWith(".qpic")) {
IndexItemInfo itemInfo;
QString targetPath;
// If not "hbdefault", which is in resource file, resolve target path for the icon
if (!fullFilename.startsWith(':') &&
!fullFilename.contains("icons/hbdefault") &&
!fullFilename.contains("icons\\hbdefault")) {
if (fullFilename.contains("scalable")) {
targetPath = "/resource/hb/themes/icons/" + themename + "/scalable/";
} else {
targetPath = "/resource/hb/themes/icons/" + themename + "/pixmap/";
}
} else {
// Resource file target path, used with "hbdefault" theme
if (fullFilename.contains("scalable")) {
targetPath = ":/themes/icons/hbdefault/scalable/";
} else {
targetPath = ":/themes/icons/hbdefault/pixmap/";
}
}
itemInfo.item.folderOffset = getStringOffset(targetPath);
itemInfo.item.extOffset = getStringOffset(filename.mid(filename.lastIndexOf('.')));
// Define iconname (remove file extension)
QString iconname;
int extIndex = filename.lastIndexOf('.');
if (extIndex > 0) {
iconname = filename.left(extIndex);
} else {
iconname = filename;
}
itemInfo.item.iconnameOffset = getStringOffset(iconname);
itemInfo.iconname = iconname;
// Define default size
itemInfo.item.defaultSize = getDefaultSize(fullFilename);
QString mirroredFilepath = fullFilename;
// Define mirrored filename if there is a separate mirrored version of the
// icon in 'mirrored' folder and in that case get also its default size
int index1 = mirroredFilepath.lastIndexOf('/');
int index2 = mirroredFilepath.lastIndexOf('\\');
int index = index1 < index2 ? index2 : index1;
if (index>0) {
mirroredFilepath = mirroredFilepath.left(index);
mirroredFilepath.append(QString("/mirrored/"));
QStringList extList;
extList << ".svg" << ".png" << ".mng" << ".gif" << ".xpm" << ".jpg" << ".nvg" << ".svgz" << ".qpic";
foreach(QString ext, extList) {
QString mirroredFilenameCandidate = mirroredFilepath + iconname + ext;
if (QFile::exists(mirroredFilenameCandidate)) {
itemInfo.item.mirroredExtOffset = getStringOffset(ext);
itemInfo.item.mirroredDefaultSize = getDefaultSize(mirroredFilenameCandidate);
break;
}
}
}
bool alreadyExists = false;
// Check if there is already an item with the same iconname in the index
foreach(const IndexItemInfo &info, IndexItems) {
if (info.iconname == itemInfo.iconname) {
alreadyExists = true;
break;
}
}
if (!alreadyExists) {
IndexItems.append(itemInfo);
if (verboseOn) {
std::cout << "----------------------------------------------------------------\n";
std::cout << "Added item" << counter << "\n";
std::cout << "Iconname:" << &StringBuffer.data()[itemInfo.item.iconnameOffset] << "\n";
std::cout << "Folder:" << &StringBuffer.data()[itemInfo.item.folderOffset] << "\n";
std::cout << "Extension:" << &StringBuffer.data()[itemInfo.item.extOffset] << "\n";
std::cout << "Default size: width: " << itemInfo.item.defaultSize.width() << " height: " << itemInfo.item.defaultSize.height() << "\n";
if (itemInfo.item.mirroredExtOffset >= 0) {
std::cout << "Mirrored extension:" << &StringBuffer.data()[itemInfo.item.mirroredExtOffset] << "\n";
} else {
std::cout << "Mirrored extension: <empty>\n";
}
std::cout << "Mirrored default size: width:" << itemInfo.item.mirroredDefaultSize.width() << " height: " << itemInfo.item.mirroredDefaultSize.height() << "\n";
}
counter++;
} else { // Icon already added in index with some other extension, do not add duplicates
if (verboseOn) {
std::cout << "----------------------------------------------------------------\n";
std::cout << "WARNING! Skipped already existing icon:" << fullFilename.toStdString() << "\n";
}
}
}
}
void adjustOffsets() {
int adjustment = sizeof(HbThemeIndexHeaderV1) + IndexItems.count() * sizeof(HbThemeIndexItem);
for (int i = 0; i<IndexItems.count(); ++i) {
IndexItems[i].item.iconnameOffset += adjustment;
IndexItems[i].item.folderOffset += adjustment;
IndexItems[i].item.extOffset += adjustment;
if (IndexItems[i].item.mirroredExtOffset >= 0) {
IndexItems[i].item.mirroredExtOffset += adjustment;
}
}
}
void processDir(const QDir &dir, const QString &themename, const QString targetName, bool subDir = false)
{
if (!subDir) {
IndexItems.clear();
Strings.clear();
StringBuffer.clear();
}
QFileInfoList entries = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot);
for (int i=0; i<entries.count(); i++) {
QFileInfo info = entries.at(i);
QString file = info.absoluteFilePath();
if (info.isDir()) {
// Process subdirs recursively
QDir subDir(file);
processDir(subDir, themename, targetName, true);
}
// Process file
processFile(info, themename);
}
if (!subDir) {
QDir targetDir(targetName);
if (!targetDir.exists()) {
targetDir.mkpath(targetName);
}
QString filename = targetName + themename + ".themeindex";
QFile::remove(filename);
QFile indexFile(filename);
if (!indexFile.open(QIODevice::ReadWrite)) {
std::cout << "ERROR: could not open index file!\n";
return;
}
// Write the header in the beginning of the file
HbThemeIndexHeaderV1 header;
header.version = version;
header.count = IndexItems.count();
qint64 ret = indexFile.write(reinterpret_cast<const char *>(&header), sizeof(HbThemeIndexHeaderV1));
assert(ret == sizeof(HbThemeIndexHeaderV1));
// Sort the list
qSort(IndexItems);
// Fix offsets in the items to be based on the beginning of the theme index instead of
// the beginning of the string buffer area.
adjustOffsets();
// Write the items in the file stream
foreach(const IndexItemInfo &itemInfo, IndexItems) {
ret = indexFile.write(reinterpret_cast<const char *>(&itemInfo.item), sizeof(HbThemeIndexItem));
assert(ret == sizeof(HbThemeIndexItem));
}
// Write the string buffer in the stream
ret = indexFile.write(StringBuffer.constData(), StringBuffer.size());
assert(ret == StringBuffer.size());
indexFile.close();
}
}
void showHelp() {
std::cout << "Themeindexer.exe usage:\n\n";
std::cout << "themeindexer [-v] -f filename OR -n themename -s theme icons source directory -t theme index file target directory\n\n";
std::cout << "-n \t\tname of index file (\"<themename>.themeindex\").\n";
std::cout << "-s \t\ticons source directory is scanned recursively and all the";
std::cout << "\t\t\trecognized icon files are aded in the theme index.\n";
std::cout << "-t \t\ttarget directory for the index file.\n";
std::cout << "-f <filename>\tfile which contains multiple themes to be indexed. Each in its own row.\n";
std::cout << "-v \t\tverbose output\n\n";
std::cout << "Example 1:\n";
std::cout << "Themeindexer.exe -n theme1 -s c:/themes/icons/theme1/ -t c:/temp/\n\n";
std::cout << "Example 2:\n";
std::cout << "Themeindexer.exe -f c:/mythemes/themes.txt\n\n";
}
void loadHbResource()
{
bool loadSuccess;
// To load resources embedded in hb library
QString resourceLibName(RESOURCE_LIB_NAME);
QLibrary hbLib(resourceLibName);
loadSuccess = hbLib.load();
if ( !loadSuccess ) {
// Library may not be loaded, if it was built in debug mode and the name in debug mode is
// different, change the name to debug version in that scenario
#ifdef Q_OS_WIN32
resourceLibName += WIN32_DEBUG_SUFFIX;
#elif defined(Q_OS_MAC)
resourceLibName += MAC_DEBUG_SUFFIX;
#endif
// On symbian library name in debug mode is same as that in release mode,
// so no need to do anything for that
hbLib.setFileName(resourceLibName);
loadSuccess = hbLib.load();
}
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv, false); // GUIenabled=false
if (argc <= 2) {
showHelp();
} else {
// Load HbCore resource to be able to index hbdefault theme
loadHbResource();
QString filename;
QString themename;
QDir basedir;
QString targetname;
QStringList args(app.arguments());
for (int n = 0; n < args.count(); n++) {
if (args[n].toLower() == "-n") {
themename = args[n+1];
n++;
} else if (args[n].toLower() == "-s") {
basedir = QDir(args[n+1]);
n++;
} else if (args[n].toLower() == "-t") {
targetname = args[n+1];
n++;
} else if (args[n].toLower() == "-v") {
verboseOn = true;
} else if (args[n].toLower() == "-f") {
filename = args[n+1];
}
}
if (filename.length() > 0) {
if (!QFile::exists(filename)) {
std::cout << "Error: file " << filename.toStdString() << " does not exist.\n";
} else {
// Open file and parse lines. Each line should have three value separated with:
QFile themesToBeIndexed(filename);
if (themesToBeIndexed.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&themesToBeIndexed);
while(!in.atEnd()) {
QString line = in.readLine();
QStringList values = line.split(' ');
if (values.count() == 3) {
themename = values[0];
basedir = values[1];
targetname = values[2];
targetname.replace('\\', '/');
// Check that targetname has / at the end
if (!targetname.endsWith('/')) {
targetname.append('/');
}
processDir(basedir, themename, targetname);
}
}
themesToBeIndexed.close();
// Loop through themes string list and call processDir
} else {
std::cout << "Error: file " << filename.toStdString() << " could not be opened.\n";
}
}
} else {
// Index only given theme
targetname.replace('\\', '/');
// Check that targetname has / at the end
if (!targetname.endsWith('/')) {
targetname.append('/');
}
processDir(basedir, themename, targetname);
}
}
return 0;
}