src/hbtools/hbthemeindexer/main.cpp
changeset 2 06ff229162e9
child 3 11d3954df52a
equal deleted inserted replaced
1:f7ac710697a9 2:06ff229162e9
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (developer.feedback@nokia.com)
       
     6 **
       
     7 ** This file is part of the HbTools module of the UI Extensions for Mobile.
       
     8 **
       
     9 ** GNU Lesser General Public License Usage
       
    10 ** This file may be used under the terms of the GNU Lesser General Public
       
    11 ** License version 2.1 as published by the Free Software Foundation and
       
    12 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
       
    13 ** Please review the following information to ensure the GNU Lesser General
       
    14 ** Public License version 2.1 requirements will be met:
       
    15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    16 **
       
    17 ** In addition, as a special exception, Nokia gives you certain additional
       
    18 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    20 **
       
    21 ** If you have questions regarding the use of this file, please contact
       
    22 ** Nokia at developer.feedback@nokia.com.
       
    23 **
       
    24 ****************************************************************************/
       
    25 
       
    26 #include <hbiconsource_p.h>
       
    27 #include <hbthemeindex_p.h>
       
    28 #include <assert.h>
       
    29 #include <iostream>
       
    30 #include <QApplication>
       
    31 #include <QStringList>
       
    32 #include <QTextStream>
       
    33 #include <QFileInfo>
       
    34 #include <QLibrary>
       
    35 #include <QString>
       
    36 #include <QFile>
       
    37 #include <QMap>
       
    38 #include <QDir>
       
    39 
       
    40 #define RESOURCE_LIB_NAME "HbCore"
       
    41 #define WIN32_DEBUG_SUFFIX "d"
       
    42 #define MAC_DEBUG_SUFFIX "_debug"
       
    43 
       
    44 
       
    45 // Global variables
       
    46 static bool verboseOn = false;
       
    47 static quint32 version = 1; // Current theme index format version
       
    48 
       
    49 QList<HbThemeIndexItemData> IndexItems;
       
    50 QMap<quint32, QString> AddedItems;
       
    51 
       
    52 // Lists that hold icons to be automatically mirrored or icons that are locked
       
    53 QStringList MirroredList;
       
    54 QStringList LockedList;
       
    55 
       
    56 
       
    57 // ------
       
    58 
       
    59 
       
    60 void createMirroredList(const QString &fullThemePath)
       
    61 {
       
    62     std::cout << "Parsing mirrored list for theme " << fullThemePath.toStdString() << "\n";
       
    63     // Find mirrored.txt file
       
    64     QString filename = fullThemePath + "/mirrored.txt";
       
    65     // Try to read file
       
    66     QFile file(filename);
       
    67     if (file.open(QIODevice::ReadOnly)) {
       
    68         QString line;
       
    69 
       
    70         while(!file.atEnd()) {        
       
    71             QByteArray dirtyLine = file.readLine();
       
    72             line = QString(dirtyLine).trimmed();
       
    73             // Skip empty lines and comment lines
       
    74             if (line.isEmpty() || line.at(0) == '#') {
       
    75                 continue; 
       
    76             }
       
    77             MirroredList.append(line);
       
    78         }
       
    79     }
       
    80 }
       
    81 
       
    82 void createLockedList(const QString &fullThemePath)
       
    83 {
       
    84     std::cout << "Parsing locked list for theme " << fullThemePath.toStdString() << "\n";
       
    85     // Find locked.txt file
       
    86     QString filename = fullThemePath + "/locked.txt";
       
    87     // Try to read file
       
    88     QFile file(filename);
       
    89     if (file.open(QIODevice::ReadOnly)) {
       
    90         QString line;
       
    91 
       
    92         while(!file.atEnd()) {        
       
    93             QByteArray dirtyLine = file.readLine();
       
    94             line = QString(dirtyLine).trimmed();
       
    95             // Skip empty lines and comment lines
       
    96             if (line.isEmpty() || line.at(0) == '#') {
       
    97                 continue; 
       
    98             }
       
    99             LockedList.append(line);
       
   100         }
       
   101     }
       
   102 }
       
   103 
       
   104 QSize getDefaultSize(const QString &filename)
       
   105 {
       
   106     HbIconSource source(filename);
       
   107     return source.defaultSize().toSize();
       
   108 }
       
   109 
       
   110 void appendItem(HbThemeIndexItemData &itemData, const QString &itemName)
       
   111 {
       
   112     bool alreadyExists = false;
       
   113 
       
   114     if (AddedItems.contains(itemData.itemNameHash)) {
       
   115         // If there already is item with same hash, it means either:
       
   116         // - same icon is in both scalable and pixmap folder and then we won't append it again
       
   117         // - hashing has failed, it generated same hash for 2 different strings -> TODO: do we care? We could e.g. save hash seed in index...
       
   118         if (AddedItems.value(itemData.itemNameHash) == itemName) {
       
   119             alreadyExists = true;
       
   120         } else {
       
   121             // Two different strings have same hash!!!
       
   122             std::cout << "ERROR: Two different strings (" << AddedItems.value(itemData.itemNameHash).toStdString() << ", " << itemName.toStdString() << ") have same hash\n";
       
   123             alreadyExists = true;
       
   124         }
       
   125     }
       
   126 
       
   127     if (!alreadyExists) {
       
   128         IndexItems.append(itemData);
       
   129         AddedItems.insert(itemData.itemNameHash, itemName);
       
   130     }
       
   131     
       
   132     if (verboseOn) {
       
   133         if (!alreadyExists) {
       
   134             std::cout << "----------------------------------------------------------------\n";
       
   135             std::cout << "Added item" << IndexItems.count() << "\n";
       
   136             std::cout << "Item name: " << itemName.toStdString() << " hash: " << itemData.itemNameHash << "\n";
       
   137             std::cout << "Item type: " << itemData.itemType << "\n";
       
   138             if (itemData.itemType == HbThemeIndexItemData::SvgItem ||
       
   139                 itemData.itemType == HbThemeIndexItemData::PngItem ||
       
   140                 itemData.itemType == HbThemeIndexItemData::MngItem ||
       
   141                 itemData.itemType == HbThemeIndexItemData::GifItem ||
       
   142                 itemData.itemType == HbThemeIndexItemData::XpmItem ||
       
   143                 itemData.itemType == HbThemeIndexItemData::JpgItem ||
       
   144                 itemData.itemType == HbThemeIndexItemData::NvgItem ||
       
   145                 itemData.itemType == HbThemeIndexItemData::SvgzItem ||
       
   146                 itemData.itemType == HbThemeIndexItemData::QpicItem) {
       
   147 
       
   148                 std::cout << "Default size: width: " << itemData.defaultWidth << " height: " << itemData.defaultHeight << "\n";
       
   149             
       
   150                 if (itemData.mirroredItemType != HbThemeIndexItemData::NotDefined) {
       
   151                     std::cout << "Mirrored default size: width:" << itemData.mirroredWidth << " height: " << itemData.mirroredHeight << "\n";
       
   152                 }
       
   153 
       
   154                 if (itemData.flags & HbThemeIndexItemData::Mirrorable) {
       
   155                     std::cout << "Icon is automatically mirrored\n";
       
   156                 }
       
   157             }
       
   158             if (itemData.flags & HbThemeIndexItemData::Locked) {
       
   159                 std::cout << "Item is locked\n";
       
   160             }
       
   161             std::cout << "----------------------------------------------------------------\n\n";
       
   162         } else { // Item already added in index with some other extension, do not add duplicates
       
   163             std::cout << "----------------------------------------------------------------\n";
       
   164             std::cout << "WARNING! Skipped already existing item:" << itemName.toStdString() << "\n";
       
   165         }
       
   166     }
       
   167 }
       
   168 
       
   169 HbThemeIndexItemData::Type getItemType(const QString &itemName)
       
   170 {
       
   171     if (itemName.endsWith(".svg")) {
       
   172         return HbThemeIndexItemData::SvgItem;
       
   173     } else if (itemName.endsWith(".png")) {
       
   174         return HbThemeIndexItemData::PngItem;
       
   175     } else if (itemName.endsWith(".mng")) {
       
   176         return HbThemeIndexItemData::MngItem;
       
   177     } else if (itemName.endsWith(".gif")) {
       
   178         return HbThemeIndexItemData::GifItem;
       
   179     } else if (itemName.endsWith(".xpm")) {
       
   180         return HbThemeIndexItemData::XpmItem;
       
   181     } else if (itemName.endsWith(".jpg")) {
       
   182         return HbThemeIndexItemData::JpgItem;
       
   183     } else if (itemName.endsWith(".nvg")) {
       
   184         return HbThemeIndexItemData::NvgItem;
       
   185     } else if (itemName.endsWith(".svgz")) {
       
   186         return HbThemeIndexItemData::SvgzItem;
       
   187     } else if (itemName.endsWith(".qpic")) {
       
   188         return HbThemeIndexItemData::QpicItem;
       
   189     } else if (itemName.endsWith(".fxml")) {
       
   190         return HbThemeIndexItemData::FxmlItem;
       
   191     } else if (itemName.endsWith(".axml")) {
       
   192         return HbThemeIndexItemData::AxmlItem;
       
   193     }
       
   194 
       
   195     return HbThemeIndexItemData::NotDefined;
       
   196 }
       
   197 
       
   198 void processFile(const QFileInfo &info) //, const QString &themename)
       
   199 {
       
   200     QString fullFilename = info.absoluteFilePath();
       
   201     QString filename = info.fileName();
       
   202 
       
   203     HbThemeIndexItemData itemData;
       
   204 
       
   205     // First get correct item type
       
   206     itemData.itemType = getItemType(filename);
       
   207 
       
   208     switch (itemData.itemType) {
       
   209         case HbThemeIndexItemData::SvgItem: // fallback all icon types
       
   210         case HbThemeIndexItemData::PngItem:
       
   211         case HbThemeIndexItemData::MngItem:
       
   212         case HbThemeIndexItemData::GifItem:
       
   213         case HbThemeIndexItemData::XpmItem:
       
   214         case HbThemeIndexItemData::JpgItem:
       
   215         case HbThemeIndexItemData::NvgItem:
       
   216         case HbThemeIndexItemData::SvgzItem:
       
   217         case HbThemeIndexItemData::QpicItem:
       
   218             {
       
   219             // Define fileName (remove file extension)
       
   220             QString iconname;
       
   221             // If we come here, the filename must end with .* (e.g. .svg)
       
   222             iconname = filename.left(filename.lastIndexOf('.'));
       
   223 
       
   224             itemData.itemNameHash = HbThemeIndex::hash(iconname);
       
   225 
       
   226             // Define default size
       
   227             QSize defaultSize = getDefaultSize(fullFilename);
       
   228             itemData.defaultWidth = static_cast<quint32>(defaultSize.width());
       
   229             itemData.defaultHeight = static_cast<quint32>(defaultSize.height());
       
   230 
       
   231             QString mirroredFilepath = fullFilename;
       
   232 
       
   233             // Define mirrored filename if there is a separate mirrored version of the
       
   234             // icon in 'mirrored' folder and in that case get also its default size
       
   235 
       
   236             int index1 = mirroredFilepath.lastIndexOf('/');
       
   237             int index2 = mirroredFilepath.lastIndexOf('\\');
       
   238 
       
   239             int index = index1 < index2 ? index2 : index1;
       
   240 
       
   241             if (index>0) {
       
   242                 mirroredFilepath = mirroredFilepath.left(index);
       
   243                 mirroredFilepath.append(QString("/mirrored/"));
       
   244 
       
   245                 QStringList extList;
       
   246                 extList << ".svg" << ".png" << ".mng" << ".gif" << ".xpm" << ".jpg" << ".nvg" << ".svgz" << ".qpic";
       
   247 
       
   248                 foreach(QString ext, extList) {
       
   249                     QString mirroredFilenameCandidate = mirroredFilepath + iconname + ext;
       
   250 
       
   251                     if (QFile::exists(mirroredFilenameCandidate)) {
       
   252                         itemData.mirroredItemType = getItemType(mirroredFilenameCandidate);
       
   253                         // Define mirrored icon size
       
   254                         QSize mirroredSize = getDefaultSize(mirroredFilenameCandidate);
       
   255                         itemData.mirroredWidth = static_cast<quint32>(mirroredSize.width());
       
   256                         itemData.mirroredHeight = static_cast<quint32>(mirroredSize.height());
       
   257                         break;
       
   258                     }
       
   259                 }
       
   260             }
       
   261 
       
   262             if (MirroredList.contains(iconname)) {
       
   263                 itemData.flags |= HbThemeIndexItemData::Mirrorable;
       
   264                 // Remove all found items from the list so that in the end we can handle with missing items.
       
   265                 MirroredList.removeOne(iconname);
       
   266             }
       
   267 
       
   268             if (LockedList.contains(iconname)) {
       
   269                 itemData.flags |= HbThemeIndexItemData::Locked;
       
   270                 // Remove all found items from the list so that in the end we can handle with missing items.
       
   271                 LockedList.removeOne(iconname);
       
   272             }
       
   273             appendItem(itemData, iconname);
       
   274             break;
       
   275             }
       
   276 
       
   277         case HbThemeIndexItemData::AxmlItem: // fallback, these are handled same way
       
   278         case HbThemeIndexItemData::FxmlItem:
       
   279             {
       
   280             // Define fileName (file extension not removed)
       
   281             itemData.itemNameHash = HbThemeIndex::hash(filename);
       
   282 
       
   283             if (LockedList.contains(filename)) {
       
   284                 itemData.flags |= HbThemeIndexItemData::Locked;
       
   285                 // Remove all found items from the list so that in the end we can handle with missing items.
       
   286                 LockedList.removeOne(filename);
       
   287             }
       
   288             appendItem(itemData, filename);
       
   289             break;
       
   290             }
       
   291 
       
   292         default:
       
   293             {
       
   294             // Don't append unknown items to the index.
       
   295             break;
       
   296             }
       
   297     } // end switch
       
   298 }
       
   299 
       
   300 bool themeIndexItemDataLessThan(const HbThemeIndexItemData &d1, const HbThemeIndexItemData &d2)
       
   301 {
       
   302     return d1.itemNameHash < d2.itemNameHash;
       
   303 }
       
   304 
       
   305 void processDir(const QDir &dir, const QString &themename, const QString targetName, bool subDir = false)
       
   306 {
       
   307     if (!subDir) {
       
   308         IndexItems.clear();
       
   309         AddedItems.clear();
       
   310         MirroredList.clear();
       
   311         LockedList.clear();
       
   312         createMirroredList(dir.absolutePath()+"/icons/"+themename);
       
   313         createLockedList(dir.absolutePath()+"/icons/"+themename);
       
   314     }
       
   315 
       
   316     QFileInfoList entries = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot);
       
   317     for (int i=0; i<entries.count(); i++) {
       
   318         QFileInfo info = entries.at(i);
       
   319         QString file = info.absoluteFilePath();
       
   320         if (info.isDir()) {
       
   321             // Process subdirs recursively
       
   322             QDir subDir(file);
       
   323             processDir(subDir, themename, targetName, true);
       
   324         }
       
   325         // Process file
       
   326         if (file.contains("/" + themename + "/", Qt::CaseInsensitive)) {
       
   327             processFile(info);
       
   328         }
       
   329     }
       
   330 
       
   331     if (!subDir) {
       
   332         // There might still be items in mirrored list (e.g. frames are not actual items, but they are still mirrored)
       
   333         // So just create empty items for the rest of mirrored items in list
       
   334         foreach (QString mirrored, MirroredList) {
       
   335             HbThemeIndexItemData itemData;
       
   336             itemData.itemNameHash = HbThemeIndex::hash(mirrored);
       
   337             itemData.flags |= HbThemeIndexItemData::Mirrorable;
       
   338             appendItem(itemData, mirrored);
       
   339         }
       
   340         QDir targetDir(targetName);
       
   341         if (!targetDir.exists()) {
       
   342             targetDir.mkpath(targetName);
       
   343         }
       
   344         QString filename = targetName + themename + ".themeindex";
       
   345 
       
   346         QFile::remove(filename);
       
   347         QFile indexFile(filename);
       
   348         if (!indexFile.open(QIODevice::ReadWrite)) {
       
   349             std::cout << "ERROR: could not open index file!\n";
       
   350             return;
       
   351         }
       
   352         
       
   353         // Write the header in the beginning of the file
       
   354         HbThemeIndexHeaderV1 header;
       
   355         header.version = version;
       
   356         header.itemCount = IndexItems.count();
       
   357 
       
   358         if (verboseOn) {
       
   359             std::cout << "============================TOTALS==============================\n";
       
   360             std::cout << "Added " << header.itemCount << " items.\n";
       
   361             std::cout << "================================================================\n";
       
   362         }
       
   363  
       
   364         // Sort the list
       
   365         qStableSort(IndexItems.begin(), IndexItems.end(), themeIndexItemDataLessThan);
       
   366          
       
   367         // Write header info into the file stream
       
   368         qint64 ret = indexFile.write(reinterpret_cast<const char *>(&header), sizeof(HbThemeIndexHeaderV1));
       
   369         assert(ret == sizeof(HbThemeIndexHeaderV1));
       
   370         
       
   371         // Write items into the file stream
       
   372         foreach(const HbThemeIndexItemData &itemData, IndexItems) {
       
   373             ret = indexFile.write(reinterpret_cast<const char *>(&itemData), sizeof(HbThemeIndexItemData));
       
   374             assert(ret == sizeof(HbThemeIndexItemData));
       
   375         }
       
   376         
       
   377         indexFile.close();
       
   378     }
       
   379 }
       
   380 
       
   381 void showHelp() {
       
   382     std::cout << "Themeindexer.exe usage:\n\n";
       
   383     std::cout << "hbthemeindexer [-v] -f filename OR -n themename -s themes source directory -t theme index file target directory\n\n";
       
   384 
       
   385     std::cout << "-n \t\ttheme to be indexed (\"<themename>.themeindex\").\n";
       
   386     std::cout << "-s \t\tthemes source directory is scanned recursively and all the";
       
   387     std::cout << "\t\t\trecognized resource files for given theme are aded in the theme index.\n";
       
   388     std::cout << "-t \t\ttarget directory for the index file.\n";
       
   389 
       
   390     std::cout << "-f <filename>\tfile which contains multiple themes to be indexed. Each in its own row.\n";
       
   391     std::cout << "-v \t\tverbose output\n\n";
       
   392 
       
   393     std::cout << "Example 1:\n";
       
   394     std::cout << "Themeindexer.exe -n mytheme -s c:/mythemes -t c:/mythemes\n";
       
   395     std::cout << "Example 2:\n";
       
   396     std::cout << "Themeindexer.exe -f c:/mythemes/themes.txt\n\n";
       
   397 }
       
   398 
       
   399 void loadHbResource()
       
   400 {
       
   401     bool loadSuccess;
       
   402     // To load resources embedded in hb library
       
   403     QString resourceLibName(RESOURCE_LIB_NAME);
       
   404     QLibrary hbLib(resourceLibName);
       
   405     loadSuccess = hbLib.load();
       
   406     
       
   407     if ( !loadSuccess ) {
       
   408         // Library may not be loaded, if it was built in debug mode and the name in debug mode is
       
   409         // different, change the name to debug version in that scenario
       
   410 #ifdef Q_OS_WIN32
       
   411         resourceLibName += WIN32_DEBUG_SUFFIX;
       
   412 #elif defined(Q_OS_MAC)
       
   413         resourceLibName += MAC_DEBUG_SUFFIX;
       
   414 #endif
       
   415         // On symbian library name in debug mode is same as that in release mode,
       
   416         // so no need to do anything for that
       
   417         hbLib.setFileName(resourceLibName);
       
   418         loadSuccess = hbLib.load();
       
   419     }
       
   420 }
       
   421 
       
   422 int main(int argc, char *argv[])
       
   423 {
       
   424     QApplication app(argc, argv, false); // GUIenabled=false
       
   425 
       
   426     if (argc <= 2) {
       
   427         showHelp();
       
   428     } else {
       
   429         // Load HbCore resource to be able to index hbdefault theme
       
   430         loadHbResource();
       
   431 
       
   432         QString filename;
       
   433         QString themename;
       
   434         QDir basedir;
       
   435         QString targetname;
       
   436         QStringList args(app.arguments());
       
   437 
       
   438         for (int n = 0; n < args.count(); n++) {
       
   439             if (args[n].toLower() == "-n") {
       
   440                 themename = args[n+1];
       
   441                 n++;
       
   442             } else if (args[n].toLower() == "-s") {
       
   443                 basedir = QDir(args[n+1]);
       
   444                 n++;
       
   445             } else if (args[n].toLower() == "-t") {
       
   446                 targetname = args[n+1];
       
   447                 n++;
       
   448             } else if (args[n].toLower() == "-v") {
       
   449                 verboseOn = true;
       
   450             } else if (args[n].toLower() == "-f") {
       
   451                 filename = args[n+1];
       
   452             }
       
   453         }
       
   454 
       
   455         if (filename.length() > 0) {
       
   456             if (!QFile::exists(filename)) {
       
   457                 std::cout << "Error: file " << filename.toStdString() << " does not exist.\n";
       
   458             } else {
       
   459                 // Open file and parse lines. Each line should have three value separated with:
       
   460                 QFile themesToBeIndexed(filename);
       
   461                 if (themesToBeIndexed.open(QIODevice::ReadOnly | QIODevice::Text)) {
       
   462                     QTextStream in(&themesToBeIndexed);
       
   463 
       
   464                     while(!in.atEnd()) {
       
   465                         QString line = in.readLine();
       
   466 
       
   467                         QStringList values = line.split(' ');
       
   468                         if (values.count() == 3) {
       
   469                             themename = values[0];
       
   470                             basedir = values[1];
       
   471                             targetname = values[2];
       
   472 
       
   473                             targetname.replace('\\', '/');
       
   474                             // Check that targetname has / at the end
       
   475                             if (!targetname.endsWith('/')) {
       
   476                                 targetname.append('/');
       
   477                             }
       
   478                             processDir(basedir, themename, targetname);
       
   479                         }
       
   480                     }
       
   481 
       
   482                     themesToBeIndexed.close();
       
   483 
       
   484                     // Loop through themes string list and call processDir
       
   485                 } else {
       
   486                     std::cout << "Error: file " << filename.toStdString() << " could not be opened.\n";
       
   487                 }
       
   488             }
       
   489         } else {
       
   490             // Index only given theme
       
   491 
       
   492             targetname.replace('\\', '/');
       
   493             // Check that targetname has / at the end
       
   494             if (!targetname.endsWith('/')) {
       
   495                 targetname.append('/');
       
   496             }
       
   497 
       
   498             processDir(basedir, themename, targetname);
       
   499 
       
   500         }
       
   501     }
       
   502 
       
   503     IndexItems.clear();
       
   504     return 0;
       
   505 }
       
   506