|
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 HbServers 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 <QtGui> |
|
29 #include <assert.h> |
|
30 #include <iostream> |
|
31 |
|
32 #define RESOURCE_LIB_NAME "HbCore" |
|
33 #define WIN32_DEBUG_SUFFIX "d" |
|
34 #define MAC_DEBUG_SUFFIX "_debug" |
|
35 |
|
36 // For being able to sort the index items based on the iconname |
|
37 class IndexItemInfo |
|
38 { |
|
39 public: |
|
40 QString iconname; |
|
41 HbThemeIndexItem item; |
|
42 }; |
|
43 |
|
44 bool operator<(const IndexItemInfo &left, const IndexItemInfo &right) |
|
45 { |
|
46 return left.iconname < right.iconname; |
|
47 } |
|
48 |
|
49 // Global variables |
|
50 |
|
51 static int counter = 0; |
|
52 static bool verboseOn = false; |
|
53 |
|
54 static int version = 1; // Current theme index format version |
|
55 |
|
56 QList<IndexItemInfo> IndexItems; |
|
57 |
|
58 QMap<QString, int> Strings; |
|
59 QByteArray StringBuffer; |
|
60 |
|
61 // ------ |
|
62 |
|
63 QSize getDefaultSize(const QString &filename) |
|
64 { |
|
65 HbIconSource source(filename); |
|
66 return source.defaultSize().toSize(); |
|
67 } |
|
68 |
|
69 int getStringOffset(const QString &string) |
|
70 { |
|
71 int offset = Strings.value(string, -1); |
|
72 if (offset < 0) { |
|
73 // Allocate new string in the string buffer |
|
74 offset = StringBuffer.size(); |
|
75 StringBuffer.append(string.toLatin1()); |
|
76 StringBuffer.append('\0'); |
|
77 // Add offset to the target paths list |
|
78 Strings.insert(string, offset); |
|
79 } |
|
80 return offset; |
|
81 } |
|
82 |
|
83 void processFile(const QFileInfo &info, const QString &themename) |
|
84 { |
|
85 QString fullFilename = info.absoluteFilePath(); |
|
86 QString filename = info.fileName(); |
|
87 |
|
88 if (filename.endsWith(".svg") || |
|
89 filename.endsWith(".png") || |
|
90 filename.endsWith(".mng") || |
|
91 filename.endsWith(".gif") || |
|
92 filename.endsWith(".xpm") || |
|
93 filename.endsWith(".jpg") || |
|
94 filename.endsWith(".nvg") || |
|
95 filename.endsWith(".svgz") || |
|
96 filename.endsWith(".qpic")) { |
|
97 |
|
98 IndexItemInfo itemInfo; |
|
99 |
|
100 QString targetPath; |
|
101 |
|
102 // If not "hbdefault", which is in resource file, resolve target path for the icon |
|
103 if (!fullFilename.startsWith(':') && |
|
104 !fullFilename.contains("icons/hbdefault") && |
|
105 !fullFilename.contains("icons\\hbdefault")) { |
|
106 |
|
107 if (fullFilename.contains("scalable")) { |
|
108 targetPath = "/resource/hb/themes/icons/" + themename + "/scalable/"; |
|
109 } else { |
|
110 targetPath = "/resource/hb/themes/icons/" + themename + "/pixmap/"; |
|
111 } |
|
112 } else { |
|
113 // Resource file target path, used with "hbdefault" theme |
|
114 if (fullFilename.contains("scalable")) { |
|
115 targetPath = ":/themes/icons/hbdefault/scalable/"; |
|
116 } else { |
|
117 targetPath = ":/themes/icons/hbdefault/pixmap/"; |
|
118 } |
|
119 } |
|
120 |
|
121 itemInfo.item.folderOffset = getStringOffset(targetPath); |
|
122 itemInfo.item.extOffset = getStringOffset(filename.mid(filename.lastIndexOf('.'))); |
|
123 |
|
124 // Define iconname (remove file extension) |
|
125 QString iconname; |
|
126 |
|
127 int extIndex = filename.lastIndexOf('.'); |
|
128 if (extIndex > 0) { |
|
129 iconname = filename.left(extIndex); |
|
130 } else { |
|
131 iconname = filename; |
|
132 } |
|
133 |
|
134 itemInfo.item.iconnameOffset = getStringOffset(iconname); |
|
135 itemInfo.iconname = iconname; |
|
136 |
|
137 // Define default size |
|
138 itemInfo.item.defaultSize = getDefaultSize(fullFilename); |
|
139 |
|
140 QString mirroredFilepath = fullFilename; |
|
141 |
|
142 // Define mirrored filename if there is a separate mirrored version of the |
|
143 // icon in 'mirrored' folder and in that case get also its default size |
|
144 |
|
145 int index1 = mirroredFilepath.lastIndexOf('/'); |
|
146 int index2 = mirroredFilepath.lastIndexOf('\\'); |
|
147 |
|
148 int index = index1 < index2 ? index2 : index1; |
|
149 |
|
150 if (index>0) { |
|
151 mirroredFilepath = mirroredFilepath.left(index); |
|
152 mirroredFilepath.append(QString("/mirrored/")); |
|
153 |
|
154 QStringList extList; |
|
155 extList << ".svg" << ".png" << ".mng" << ".gif" << ".xpm" << ".jpg" << ".nvg" << ".svgz" << ".qpic"; |
|
156 |
|
157 foreach(QString ext, extList) { |
|
158 QString mirroredFilenameCandidate = mirroredFilepath + iconname + ext; |
|
159 |
|
160 if (QFile::exists(mirroredFilenameCandidate)) { |
|
161 itemInfo.item.mirroredExtOffset = getStringOffset(ext); |
|
162 itemInfo.item.mirroredDefaultSize = getDefaultSize(mirroredFilenameCandidate); |
|
163 break; |
|
164 } |
|
165 } |
|
166 } |
|
167 |
|
168 bool alreadyExists = false; |
|
169 |
|
170 // Check if there is already an item with the same iconname in the index |
|
171 foreach(const IndexItemInfo &info, IndexItems) { |
|
172 if (info.iconname == itemInfo.iconname) { |
|
173 alreadyExists = true; |
|
174 break; |
|
175 } |
|
176 } |
|
177 |
|
178 if (!alreadyExists) { |
|
179 IndexItems.append(itemInfo); |
|
180 |
|
181 if (verboseOn) { |
|
182 std::cout << "----------------------------------------------------------------\n"; |
|
183 std::cout << "Added item" << counter << "\n"; |
|
184 std::cout << "Iconname:" << &StringBuffer.data()[itemInfo.item.iconnameOffset] << "\n"; |
|
185 std::cout << "Folder:" << &StringBuffer.data()[itemInfo.item.folderOffset] << "\n"; |
|
186 std::cout << "Extension:" << &StringBuffer.data()[itemInfo.item.extOffset] << "\n"; |
|
187 std::cout << "Default size: width: " << itemInfo.item.defaultSize.width() << " height: " << itemInfo.item.defaultSize.height() << "\n"; |
|
188 if (itemInfo.item.mirroredExtOffset >= 0) { |
|
189 std::cout << "Mirrored extension:" << &StringBuffer.data()[itemInfo.item.mirroredExtOffset] << "\n"; |
|
190 } else { |
|
191 std::cout << "Mirrored extension: <empty>\n"; |
|
192 } |
|
193 std::cout << "Mirrored default size: width:" << itemInfo.item.mirroredDefaultSize.width() << " height: " << itemInfo.item.mirroredDefaultSize.height() << "\n"; |
|
194 } |
|
195 counter++; |
|
196 } else { // Icon already added in index with some other extension, do not add duplicates |
|
197 if (verboseOn) { |
|
198 std::cout << "----------------------------------------------------------------\n"; |
|
199 std::cout << "WARNING! Skipped already existing icon:" << fullFilename.toStdString() << "\n"; |
|
200 } |
|
201 } |
|
202 } |
|
203 } |
|
204 |
|
205 void adjustOffsets() { |
|
206 int adjustment = sizeof(HbThemeIndexHeaderV1) + IndexItems.count() * sizeof(HbThemeIndexItem); |
|
207 |
|
208 for (int i = 0; i<IndexItems.count(); ++i) { |
|
209 IndexItems[i].item.iconnameOffset += adjustment; |
|
210 IndexItems[i].item.folderOffset += adjustment; |
|
211 IndexItems[i].item.extOffset += adjustment; |
|
212 |
|
213 if (IndexItems[i].item.mirroredExtOffset >= 0) { |
|
214 IndexItems[i].item.mirroredExtOffset += adjustment; |
|
215 } |
|
216 } |
|
217 } |
|
218 |
|
219 void processDir(const QDir &dir, const QString &themename, const QString targetName, bool subDir = false) |
|
220 { |
|
221 if (!subDir) { |
|
222 IndexItems.clear(); |
|
223 Strings.clear(); |
|
224 StringBuffer.clear(); |
|
225 } |
|
226 |
|
227 QFileInfoList entries = dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot); |
|
228 for (int i=0; i<entries.count(); i++) { |
|
229 QFileInfo info = entries.at(i); |
|
230 QString file = info.absoluteFilePath(); |
|
231 if (info.isDir()) { |
|
232 // Process subdirs recursively |
|
233 QDir subDir(file); |
|
234 processDir(subDir, themename, targetName, true); |
|
235 } |
|
236 // Process file |
|
237 processFile(info, themename); |
|
238 } |
|
239 |
|
240 if (!subDir) { |
|
241 QDir targetDir(targetName); |
|
242 if (!targetDir.exists()) { |
|
243 targetDir.mkpath(targetName); |
|
244 } |
|
245 QString filename = targetName + themename + ".themeindex"; |
|
246 |
|
247 QFile::remove(filename); |
|
248 QFile indexFile(filename); |
|
249 if (!indexFile.open(QIODevice::ReadWrite)) { |
|
250 std::cout << "ERROR: could not open index file!\n"; |
|
251 return; |
|
252 } |
|
253 |
|
254 // Write the header in the beginning of the file |
|
255 HbThemeIndexHeaderV1 header; |
|
256 header.version = version; |
|
257 header.count = IndexItems.count(); |
|
258 |
|
259 qint64 ret = indexFile.write(reinterpret_cast<const char *>(&header), sizeof(HbThemeIndexHeaderV1)); |
|
260 assert(ret == sizeof(HbThemeIndexHeaderV1)); |
|
261 |
|
262 // Sort the list |
|
263 qSort(IndexItems); |
|
264 |
|
265 // Fix offsets in the items to be based on the beginning of the theme index instead of |
|
266 // the beginning of the string buffer area. |
|
267 adjustOffsets(); |
|
268 |
|
269 // Write the items in the file stream |
|
270 foreach(const IndexItemInfo &itemInfo, IndexItems) { |
|
271 ret = indexFile.write(reinterpret_cast<const char *>(&itemInfo.item), sizeof(HbThemeIndexItem)); |
|
272 assert(ret == sizeof(HbThemeIndexItem)); |
|
273 } |
|
274 |
|
275 // Write the string buffer in the stream |
|
276 ret = indexFile.write(StringBuffer.constData(), StringBuffer.size()); |
|
277 assert(ret == StringBuffer.size()); |
|
278 indexFile.close(); |
|
279 } |
|
280 } |
|
281 |
|
282 void showHelp() { |
|
283 std::cout << "Themeindexer.exe usage:\n\n"; |
|
284 std::cout << "themeindexer [-v] -f filename OR -n themename -s theme icons source directory -t theme index file target directory\n\n"; |
|
285 |
|
286 std::cout << "-n \t\tname of index file (\"<themename>.themeindex\").\n"; |
|
287 std::cout << "-s \t\ticons source directory is scanned recursively and all the"; |
|
288 std::cout << "\t\t\trecognized icon files are aded in the theme index.\n"; |
|
289 std::cout << "-t \t\ttarget directory for the index file.\n"; |
|
290 |
|
291 std::cout << "-f <filename>\tfile which contains multiple themes to be indexed. Each in its own row.\n"; |
|
292 std::cout << "-v \t\tverbose output\n\n"; |
|
293 |
|
294 std::cout << "Example 1:\n"; |
|
295 std::cout << "Themeindexer.exe -n theme1 -s c:/themes/icons/theme1/ -t c:/temp/\n\n"; |
|
296 std::cout << "Example 2:\n"; |
|
297 std::cout << "Themeindexer.exe -f c:/mythemes/themes.txt\n\n"; |
|
298 } |
|
299 |
|
300 void loadHbResource() |
|
301 { |
|
302 bool loadSuccess; |
|
303 // To load resources embedded in hb library |
|
304 QString resourceLibName(RESOURCE_LIB_NAME); |
|
305 QLibrary hbLib(resourceLibName); |
|
306 loadSuccess = hbLib.load(); |
|
307 |
|
308 if ( !loadSuccess ) { |
|
309 // Library may not be loaded, if it was built in debug mode and the name in debug mode is |
|
310 // different, change the name to debug version in that scenario |
|
311 #ifdef Q_OS_WIN32 |
|
312 resourceLibName += WIN32_DEBUG_SUFFIX; |
|
313 #elif defined(Q_OS_MAC) |
|
314 resourceLibName += MAC_DEBUG_SUFFIX; |
|
315 #endif |
|
316 // On symbian library name in debug mode is same as that in release mode, |
|
317 // so no need to do anything for that |
|
318 hbLib.setFileName(resourceLibName); |
|
319 loadSuccess = hbLib.load(); |
|
320 } |
|
321 } |
|
322 |
|
323 int main(int argc, char *argv[]) |
|
324 { |
|
325 QApplication app(argc, argv); |
|
326 |
|
327 if (argc <= 2) { |
|
328 showHelp(); |
|
329 } else { |
|
330 // Load HbCore resource to be able to index hbdefault theme |
|
331 loadHbResource(); |
|
332 |
|
333 QString filename; |
|
334 QString themename; |
|
335 QDir basedir; |
|
336 QString targetname; |
|
337 QStringList args(app.arguments()); |
|
338 |
|
339 for (int n = 0; n < args.count(); n++) { |
|
340 if (args[n].toLower() == "-n") { |
|
341 themename = args[n+1]; |
|
342 n++; |
|
343 } else if (args[n].toLower() == "-s") { |
|
344 basedir = QDir(args[n+1]); |
|
345 n++; |
|
346 } else if (args[n].toLower() == "-t") { |
|
347 targetname = args[n+1]; |
|
348 n++; |
|
349 } else if (args[n].toLower() == "-v") { |
|
350 verboseOn = true; |
|
351 } else if (args[n].toLower() == "-f") { |
|
352 filename = args[n+1]; |
|
353 } |
|
354 } |
|
355 |
|
356 |
|
357 if (filename.length() > 0) { |
|
358 if (!QFile::exists(filename)) { |
|
359 std::cout << "Error: file " << filename.toStdString() << " does not exist.\n"; |
|
360 } else { |
|
361 // Open file and parse lines. Each line should have three value separated with: |
|
362 QFile themesToBeIndexed(filename); |
|
363 if (themesToBeIndexed.open(QIODevice::ReadOnly | QIODevice::Text)) { |
|
364 QTextStream in(&themesToBeIndexed); |
|
365 |
|
366 while(!in.atEnd()) { |
|
367 QString line = in.readLine(); |
|
368 |
|
369 QStringList values = line.split(' '); |
|
370 if (values.count() == 3) { |
|
371 themename = values[0]; |
|
372 basedir = values[1]; |
|
373 targetname = values[2]; |
|
374 |
|
375 targetname.replace('\\', '/'); |
|
376 // Check that targetname has / at the end |
|
377 if (!targetname.endsWith('/')) { |
|
378 targetname.append('/'); |
|
379 } |
|
380 processDir(basedir, themename, targetname); |
|
381 } |
|
382 } |
|
383 |
|
384 themesToBeIndexed.close(); |
|
385 |
|
386 // Loop through themes string list and call processDir |
|
387 } else { |
|
388 std::cout << "Error: file " << filename.toStdString() << " could not be opened.\n"; |
|
389 } |
|
390 } |
|
391 } else { |
|
392 // Index only given theme |
|
393 |
|
394 targetname.replace('\\', '/'); |
|
395 // Check that targetname has / at the end |
|
396 if (!targetname.endsWith('/')) { |
|
397 targetname.append('/'); |
|
398 } |
|
399 |
|
400 processDir(basedir, themename, targetname); |
|
401 |
|
402 } |
|
403 } |
|
404 |
|
405 return 0; |
|
406 } |
|
407 |