|
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 |