1
/****************************************************************************
2
**
3
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4
** All rights reserved.
5
** Contact: Nokia Corporation (qt-info@nokia.com)
6
**
7
** This file is part of the QtCore module of the Qt Toolkit.
8
**
9
** $QT_BEGIN_LICENSE:LGPL$
10
** No Commercial Usage
11
** This file contains pre-release code and may not be distributed.
12
** You may use this file in accordance with the terms and conditions
13
** contained in the Technology Preview License Agreement accompanying
14
** this package.
15
**
16
** GNU Lesser General Public License Usage
17
** Alternatively, this file may be used under the terms of the GNU Lesser
18
** General Public License version 2.1 as published by the Free Software
19
** Foundation and appearing in the file LICENSE.LGPL included in the
20
** packaging of this file.  Please review the following information to
21
** ensure the GNU Lesser General Public License version 2.1 requirements
22
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23
**
24
** In addition, as a special exception, Nokia gives you certain additional
25
** rights.  These rights are described in the Nokia Qt LGPL Exception
26
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27
**
28
** If you have questions regarding the use of this file, please contact
29
** Nokia at qt-info@nokia.com.
30
**
31
**
32
**
33
**
34
**
35
**
36
**
37
**
38
** $QT_END_LICENSE$
39
**
40
****************************************************************************/
41
42
#include "qresource.h"
43
#include "qresource_p.h"
44
#include "qresource_iterator_p.h"
45
#include "qset.h"
46
#include "qhash.h"
47
#include "qmutex.h"
48
#include "qdebug.h"
49
#include "qlocale.h"
50
#include "qglobal.h"
51
#include "qvector.h"
52
#include "qdatetime.h"
53
#include "qbytearray.h"
54
#include "qstringlist.h"
55
#include <qshareddata.h>
56
#include <qplatformdefs.h>
57
#include "private/qabstractfileengine_p.h"
58
59
//#define DEBUG_RESOURCE_MATCH
60
61
QT_BEGIN_NAMESPACE
62
63
//resource glue
64
class QResourceRoot
65
{
66
    enum Flags
67
    {
68
        Compressed = 0x01,
69
        Directory = 0x02
70
    };
71
    const uchar *tree, *names, *payloads;
72
    inline int findOffset(int node) const { return node * 14; } //sizeof each tree element
73
    int hash(int node) const;
74
    QString name(int node) const;
75
    short flags(int node) const;
76
public:
77
    mutable QAtomicInt ref;
78
79
    inline QResourceRoot(): tree(0), names(0), payloads(0) {}
80
    inline QResourceRoot(const uchar *t, const uchar *n, const uchar *d) { setSource(t, n, d); }
81
    virtual ~QResourceRoot() { }
82
    int findNode(const QString &path, const QLocale &locale=QLocale()) const;
83
    inline bool isContainer(int node) const { return flags(node) & Directory; }
84
    inline bool isCompressed(int node) const { return flags(node) & Compressed; }
85
    const uchar *data(int node, qint64 *size) const;
86
    QStringList children(int node) const;
87
    virtual QString mappingRoot() const { return QString(); }
88
    bool mappingRootSubdir(const QString &path, QString *match=0) const;
89
    inline bool operator==(const QResourceRoot &other) const
90
    { return tree == other.tree && names == other.names && payloads == other.payloads; }
91
    inline bool operator!=(const QResourceRoot &other) const
92
    { return !operator==(other); }
93
    enum ResourceRootType { Resource_Builtin, Resource_File, Resource_Buffer };
94
    virtual ResourceRootType type() const { return Resource_Builtin; }
95
96
protected:
97
    inline void setSource(const uchar *t, const uchar *n, const uchar *d) {
98
        tree = t;
99
        names = n;
100
        payloads = d;
101
    }
102
};
103
104
Q_DECLARE_TYPEINFO(QResourceRoot, Q_MOVABLE_TYPE);
105
106
Q_GLOBAL_STATIC_WITH_ARGS(QMutex, resourceMutex, (QMutex::Recursive))
107
108
typedef QList<QResourceRoot*> ResourceList;
109
Q_GLOBAL_STATIC(ResourceList, resourceList)
110
111
Q_GLOBAL_STATIC(QStringList, resourceSearchPaths)
112
113
/*!
114
    \class QResource
115
    \brief The QResource class provides an interface for reading directly from resources.
116
117
    \ingroup io
118
    \mainclass
119
    \reentrant
120
    \since 4.2
121
122
    QResource is an object that represents a set of data (and possibly
123
    children) relating to a single resource entity. QResource gives direct
124
    access to the bytes in their raw format. In this way direct access
125
    allows reading data without buffer copying or indirection. Indirection
126
    is often useful when interacting with the resource entity as if it is a
127
    file, this can be achieved with QFile. The data and children behind a
128
    QResource are normally compiled into an application/library, but it is
129
    also possible to load a resource at runtime. When loaded at run time
130
    the resource file will be loaded as one big set of data and then given
131
    out in pieces via references into the resource tree.
132
133
    A QResource can either be loaded with an absolute path, either treated
134
    as a file system rooted with a \c{/} character, or in resource notation
135
    rooted with a \c{:} character. A relative resource can also be opened
136
    which will be found through the searchPaths().
137
138
    A QResource that is representing a file will have data backing it, this
139
    data can possibly be compressed, in which case qUncompress() must be
140
    used to access the real data; this happens implicitly when accessed
141
    through a QFile. A QResource that is representing a directory will have
142
    only children and no data.
143
144
    \section1 Dynamic Resource Loading
145
146
    A resource can be left out of an application's binary and loaded when
147
    it is needed at run-time by using the registerResource() function. The
148
    resource file passed into registerResource() must be a binary resource
149
    as created by rcc. Further information about binary resources can be
150
    found in \l{The Qt Resource System} documentation.
151
152
    This can often be useful when loading a large set of application icons
153
    that may change based on a setting, or that can be edited by a user and
154
    later recreated. The resource is immediately loaded into memory, either
155
    as a result of a single file read operation, or as a memory mapped file.
156
157
    This approach can prove to be a significant performance gain as only a
158
    single file will be loaded, and pieces of data will be given out via the
159
    path requested in setFileName().
160
161
    The unregisterResource() function removes a reference to a particular
162
    file. If there are QResources that currently reference resources related
163
    to the unregistered file, they will continue to be valid but the resource
164
    file itself will be removed from the resource roots, and thus no further
165
    QResource can be created pointing into this resource data. The resource
166
    itself will be unmapped from memory when the last QResource that points
167
    to it is destroyed.
168
169
    \sa {The Qt Resource System}, QFile, QDir, QFileInfo
170
*/
171
172
class QResourcePrivate {
173
public:
174
    inline QResourcePrivate(QResource *_q) : q_ptr(_q) { clear(); }
175
    inline ~QResourcePrivate() { clear(); }
176
177
    void ensureInitialized() const;
178
    void ensureChildren() const;
179
180
    bool load(const QString &file);
181
    void clear();
182
183
    QLocale locale;
184
    QString fileName, absoluteFilePath;
185
    QList<QResourceRoot*> related;
186
    uint container : 1;
187
    mutable uint compressed : 1;
188
    mutable qint64 size;
189
    mutable const uchar *data;
190
    mutable QStringList children;
191
192
    QResource *q_ptr;
193
    Q_DECLARE_PUBLIC(QResource)
194
};
195
196
void
197
QResourcePrivate::clear()
198
{
199
    absoluteFilePath.clear();
200
    compressed = 0;
201
    data = 0;
202
    size = 0;
203
    children.clear();
204
    container = 0;
205
    for(int i = 0; i < related.size(); ++i) {
206
        QResourceRoot *root = related.at(i);
207
        if(!root->ref.deref())
208
            delete root;
209
    }
210
    related.clear();
211
}
212
213
bool
214
QResourcePrivate::load(const QString &file)
215
{
216
    related.clear();
217
    QMutexLocker lock(resourceMutex());
218
    const ResourceList *list = resourceList();
219
    for(int i = 0; i < list->size(); ++i) {
220
        QResourceRoot *res = list->at(i);
221
        const int node = res->findNode(file);
222
        if(node != -1) {
223
            if(related.isEmpty()) {
224
                container = res->isContainer(node);
225
                if(!container) {
226
                    data = res->data(node, &size);
227
                    compressed = res->isCompressed(node);
228
                } else {
229
                    data = 0;
230
                    size = 0;
231
                    compressed = 0;
232
                }
233
            } else if(res->isContainer(node) != container) {
234
                qWarning("QResourceInfo: Resource [%s] has both data and children!", file.toLatin1().constData());
235
            }
236
            res->ref.ref();
237
            related.append(res);
238
        } else if(res->mappingRootSubdir(file)) {
239
            container = true;
240
            data = 0;
241
            size = 0;
242
            compressed = 0;
243
            res->ref.ref();
244
            related.append(res);
245
        }
246
    }
247
    return !related.isEmpty();
248
}
249
250
void
251
QResourcePrivate::ensureInitialized() const
252
{
253
    if(!related.isEmpty())
254
        return;
255
    QResourcePrivate *that = const_cast<QResourcePrivate *>(this);
256
    if(fileName == QLatin1String(":"))
257
        that->fileName += QLatin1Char('/');
258
    that->absoluteFilePath = fileName;
259
    if(!that->absoluteFilePath.startsWith(QLatin1Char(':')))
260
        that->absoluteFilePath.prepend(QLatin1Char(':'));
261
262
    QString path = fileName;
263
    if(path.startsWith(QLatin1Char(':')))
264
        path = path.mid(1);
265
266
    bool found = false;
267
    if(path.startsWith(QLatin1Char('/'))) {
268
        found = that->load(path);
269
    } else {
270
        QMutexLocker lock(resourceMutex());
271
        QStringList searchPaths = *resourceSearchPaths();
272
        searchPaths << QLatin1String("");
273
        for(int i = 0; i < searchPaths.size(); ++i) {
274
            const QString searchPath(searchPaths.at(i) + QLatin1Char('/') + path);
275
            if(that->load(searchPath)) {
276
                found = true;
277
                that->absoluteFilePath = QLatin1Char(':') + searchPath;
278
                break;
279
            }
280
        }
281
    }
282
}
283
284
void
285
QResourcePrivate::ensureChildren() const
286
{
287
    ensureInitialized();
288
    if(!children.isEmpty() || !container || related.isEmpty())
289
        return;
290
291
    QString path = absoluteFilePath, k;
292
    if(path.startsWith(QLatin1Char(':')))
293
        path = path.mid(1);
294
    QSet<QString> kids;
295
    for(int i = 0; i < related.size(); ++i) {
296
        QResourceRoot *res = related.at(i);
297
        if(res->mappingRootSubdir(path, &k) && !k.isEmpty()) {
298
            if(!kids.contains(k)) {
299
                children += k;
300
                kids.insert(k);
301
            }
302
        } else {
303
            const int node = res->findNode(path);
304
            if(node != -1) {
305
                QStringList related_children = res->children(node);
306
                for(int kid = 0; kid < related_children.size(); ++kid) {
307
                    k = related_children.at(kid);
308
                    if(!kids.contains(k)) {
309
                        children += k;
310
                        kids.insert(k);
311
                    }
312
                }
313
            }
314
        }
315
    }
316
}
317
318
/*!
319
    Constructs a QResource pointing to \a file. \a locale is used to
320
    load a specific localization of a resource data.
321
322
    \sa QFileInfo, searchPaths(), setFileName(), setLocale()
323
*/
324
325
QResource::QResource(const QString &file, const QLocale &locale) : d_ptr(new QResourcePrivate(this))
326
{
327
    Q_D(QResource);
328
    d->fileName = file;
329
    d->locale = locale;
330
}
331
332
/*!
333
    Releases the resources of the QResource object.
334
*/
335
QResource::~QResource()
336
{
337
    delete d_ptr;
338
}
339
340
/*!
341
    Sets a QResource to only load the localization of resource to for \a
342
    locale. If a resource for the specific locale is not found then the
343
    C locale is used.
344
345
    \sa setFileName()
346
*/
347
348
void QResource::setLocale(const QLocale &locale)
349
{
350
    Q_D(QResource);
351
    d->clear();
352
    d->locale = locale;
353
}
354
355
/*!
356
    Returns the locale used to locate the data for the QResource.
357
*/
358
359
QLocale QResource::locale() const
360
{
361
    Q_D(const QResource);
362
    return d->locale;
363
}
364
365
/*!
366
    Sets a QResource to point to \a file. \a file can either be absolute,
367
    in which case it is opened directly, if relative then the file will be
368
    tried to be found in searchPaths().
369
370
    \sa absoluteFilePath()
371
*/
372
373
void QResource::setFileName(const QString &file)
374
{
375
    Q_D(QResource);
376
    d->clear();
377
    d->fileName = file;
378
}
379
380
/*!
381
    Returns the full path to the file that this QResource represents as it
382
    was passed.
383
384
    \sa absoluteFilePath()
385
*/
386
387
QString QResource::fileName() const
388
{
389
    Q_D(const QResource);
390
    d->ensureInitialized();
391
    return d->fileName;
392
}
393
394
/*!
395
    Returns the real path that this QResource represents, if the resource
396
    was found via the searchPaths() it will be indicated in the path.
397
398
    \sa fileName()
399
*/
400
401
QString QResource::absoluteFilePath() const
402
{
403
    Q_D(const QResource);
404
    d->ensureInitialized();
405
    return d->absoluteFilePath;
406
}
407
408
/*!
409
    Returns true if the resource really exists in the resource hierarchy,
410
    false otherwise.
411
412
*/
413
414
bool QResource::isValid() const
415
{
416
    Q_D(const QResource);
417
    d->ensureInitialized();
418
    return !d->related.isEmpty();
419
}
420
421
/*!
422
    \fn bool QResource::isFile() const
423
424
    Returns true if the resource represents a file and thus has data
425
    backing it, false if it represents a directory.
426
427
    \sa isDir()
428
*/
429
430
431
/*!
432
    Returns true if the resource represents a file and the data backing it
433
    is in a compressed format, false otherwise.
434
435
    \sa data(), isFile()
436
*/
437
438
bool QResource::isCompressed() const
439
{
440
    Q_D(const QResource);
441
    d->ensureInitialized();
442
    return d->compressed;
443
}
444
445
/*!
446
    Returns the size of the data backing the resource.
447
448
    \sa data(), isFile()
449
*/
450
451
qint64 QResource::size() const
452
{
453
    Q_D(const QResource);
454
    d->ensureInitialized();
455
    return d->size;
456
}
457
458
/*!
459
    Returns direct access to a read only segment of data that this resource
460
    represents. If the resource is compressed the data returns is
461
    compressed and qUncompress() must be used to access the data. If the
462
    resource is a directory 0 is returned.
463
464
    \sa size(), isCompressed(), isFile()
465
*/
466
467
const uchar *QResource::data() const
468
{
469
    Q_D(const QResource);
470
    d->ensureInitialized();
471
    return d->data;
472
}
473
474
/*!
475
    Returns true if the resource represents a directory and thus may have
476
    children() in it, false if it represents a file.
477
478
    \sa isFile()
479
*/
480
481
bool QResource::isDir() const
482
{
483
    Q_D(const QResource);
484
    d->ensureInitialized();
485
    return d->container;
486
}
487
488
/*!
489
    Returns a list of all resources in this directory, if the resource
490
    represents a file the list will be empty.
491
492
    \sa isDir()
493
*/
494
495
QStringList QResource::children() const
496
{
497
    Q_D(const QResource);
498
    d->ensureChildren();
499
    return d->children;
500
}
501
502
/*!
503
  \obsolete
504
505
  Adds \a path to the search paths searched in to find resources that are
506
  not specified with an absolute path. The \a path must be an absolute
507
  path (start with \c{/}).
508
509
  The default search path is to search only in the root (\c{:/}). The last
510
  path added will be consulted first upon next QResource creation.
511
512
  Use QDir::addSearchPath() with a prefix instead.
513
*/
514
515
void
516
QResource::addSearchPath(const QString &path)
517
{
518
    if (!path.startsWith(QLatin1Char('/'))) {
519
        qWarning("QResource::addResourceSearchPath: Search paths must be absolute (start with /) [%s]",
520
                 path.toLocal8Bit().data());
521
        return;
522
    }
523
    QMutexLocker lock(resourceMutex());
524
    resourceSearchPaths()->prepend(path);
525
}
526
527
/*!
528
  Returns the current search path list. This list is consulted when
529
  creating a relative resource.
530
531
  \sa addSearchPath()
532
*/
533
534
QStringList
535
QResource::searchPaths()
536
{
537
    QMutexLocker lock(resourceMutex());
538
    return *resourceSearchPaths();
539
}
540
541
inline int QResourceRoot::hash(int node) const
542
{
543
    if(!node) //root
544
        return 0;
545
    const int offset = findOffset(node);
546
    int name_offset = (tree[offset+0] << 24) + (tree[offset+1] << 16) +
547
                      (tree[offset+2] << 8) + (tree[offset+3] << 0);
548
    name_offset += 2; //jump past name length
549
    return (names[name_offset+0] << 24) + (names[name_offset+1] << 16) +
550
           (names[name_offset+2] << 8) + (names[name_offset+3] << 0);
551
}
552
inline QString QResourceRoot::name(int node) const
553
{
554
    if(!node) // root
555
        return QString();
556
    const int offset = findOffset(node);
557
558
    QString ret;
559
    int name_offset = (tree[offset+0] << 24) + (tree[offset+1] << 16) +
560
                      (tree[offset+2] << 8) + (tree[offset+3] << 0);
561
    const short name_length = (names[name_offset+0] << 8) +
562
                              (names[name_offset+1] << 0);
563
    name_offset += 2;
564
    name_offset += 4; //jump past hash
565
    for(int i = 0; i < name_length*2; i+=2)
566
        ret += QChar(names[name_offset+i+1], names[name_offset+i]);
567
    return ret;
568
}
569
int QResourceRoot::findNode(const QString &_path, const QLocale &locale) const
570
{
571
    QString path = QDir::cleanPath(_path);
572
    // QDir::cleanPath does not remove two trailing slashes under _Windows_
573
    // due to support for UNC paths. Remove those manually.
574
    if (path.startsWith(QLatin1String("//")))
575
        path.remove(0, 1);
576
577
    {
578
        QString root = mappingRoot();
579
        if(!root.isEmpty()) {
580
            if(root == path) {
581
                path = QLatin1String("/");
582
            } else {
583
                if(!root.endsWith(QLatin1String("/")))
584
                    root += QLatin1String("/");
585
                if(path.size() >= root.size() && path.startsWith(root))
586
                    path = path.mid(root.length()-1);
587
                if(path.isEmpty())
588
                    path = QLatin1String("/");
589
            }
590
        }
591
    }
592
#ifdef DEBUG_RESOURCE_MATCH
593
    qDebug() << "!!!!" << "START" << path << locale.country() << locale.language();
594
#endif
595
596
    if(path == QLatin1String("/"))
597
        return 0;
598
599
    //the root node is always first
600
    int child_count = (tree[6] << 24) + (tree[7] << 16) +
601
                      (tree[8] << 8) + (tree[9] << 0);
602
    int child       = (tree[10] << 24) + (tree[11] << 16) +
603
                      (tree[12] << 8) + (tree[13] << 0);
604
605
    //now iterate up the tree
606
    int node = -1;
607
    QStringList segments = path.split(QLatin1Char('/'), QString::SkipEmptyParts);
608
#ifdef DEBUG_RESOURCE_MATCH
609
    qDebug() << "****" << segments;
610
#endif
611
    for(int i = 0; child_count && i < segments.size(); ++i) {
612
        const QString &segment = segments[i];
613
#ifdef DEBUG_RESOURCE_MATCH
614
        qDebug() << "  CHILDREN" << segment;
615
        for(int j = 0; j < child_count; ++j) {
616
            qDebug() << "   " << child+j << " :: " << name(child+j);
617
        }
618
#endif
619
        const int h = qHash(segment);
620
621
        //do the binary search for the hash
622
        int l = 0, r = child_count-1;
623
        int sub_node = (l+r+1)/2;
624
        while(r != l) {
625
            const int sub_node_hash = hash(child+sub_node);
626
            if(h == sub_node_hash)
627
                break;
628
            else if(h < sub_node_hash)
629
                r = sub_node - 1;
630
            else
631
                l = sub_node;
632
            sub_node = (l + r + 1) / 2;
633
        }
634
        sub_node += child;
635
636
        //now do the "harder" compares
637
        bool found = false;
638
        if(hash(sub_node) == h) {
639
            while(sub_node > child && hash(sub_node-1) == h) //backup for collisions
640
                --sub_node;
641
            for(; sub_node < child+child_count && hash(sub_node) == h; ++sub_node) { //here we go...
642
                if(name(sub_node) == segment) {
643
                    found = true;
644
                    int offset = findOffset(sub_node);
645
#ifdef DEBUG_RESOURCE_MATCH
646
                    qDebug() << "  TRY" << sub_node << name(sub_node) << offset;
647
#endif
648
                    offset += 4;  //jump past name
649
650
                    const short flags = (tree[offset+0] << 8) +
651
                                        (tree[offset+1] << 0);
652
                    offset += 2;
653
654
                    if(i == segments.size()-1) {
655
                        if(!(flags & Directory)) {
656
                            const short country = (tree[offset+0] << 8) +
657
                                                  (tree[offset+1] << 0);
658
                            offset += 2;
659
660
                            const short language = (tree[offset+0] << 8) +
661
                                                   (tree[offset+1] << 0);
662
                            offset += 2;
663
#ifdef DEBUG_RESOURCE_MATCH
664
                            qDebug() << "    " << "LOCALE" << country << language;
665
#endif
666
                            if(country == locale.country() && language == locale.language()) {
667
#ifdef DEBUG_RESOURCE_MATCH
668
                                qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node;
669
#endif
670
                                return sub_node;
671
                            } else if((country == QLocale::AnyCountry && language == locale.language()) ||
672
                                      (country == QLocale::AnyCountry && language == QLocale::C && node == -1)) {
673
                                node = sub_node;
674
                            }
675
                            continue;
676
                        } else {
677
#ifdef DEBUG_RESOURCE_MATCH
678
                            qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node;
679
#endif
680
681
                            return sub_node;
682
                        }
683
                    }
684
685
                    if(!(flags & Directory))
686
                        return -1;
687
688
                    child_count = (tree[offset+0] << 24) + (tree[offset+1] << 16) +
689
                                  (tree[offset+2] << 8) + (tree[offset+3] << 0);
690
                    offset += 4;
691
                    child = (tree[offset+0] << 24) + (tree[offset+1] << 16) +
692
                            (tree[offset+2] << 8) + (tree[offset+3] << 0);
693
                    break;
694
                }
695
            }
696
        }
697
        if(!found)
698
            break;
699
    }
700
#ifdef DEBUG_RESOURCE_MATCH
701
    qDebug() << "!!!!" << "FINISHED" << __LINE__ << node;
702
#endif
703
    return node;
704
}
705
short QResourceRoot::flags(int node) const
706
{
707
    if(node == -1)
708
        return 0;
709
    const int offset = findOffset(node) + 4; //jump past name
710
    return (tree[offset+0] << 8) + (tree[offset+1] << 0);
711
}
712
const uchar *QResourceRoot::data(int node, qint64 *size) const
713
{
714
    if(node == -1) {
715
        *size = 0;
716
        return 0;
717
    }
718
    int offset = findOffset(node) + 4; //jump past name
719
720
    const short flags = (tree[offset+0] << 8) + (tree[offset+1] << 0);
721
    offset += 2;
722
723
    offset += 4; //jump past locale
724
725
    if(!(flags & Directory)) {
726
        const int data_offset = (tree[offset+0] << 24) + (tree[offset+1] << 16) +
727
                                (tree[offset+2] << 8) + (tree[offset+3] << 0);
728
        const uint data_length = (payloads[data_offset+0] << 24) + (payloads[data_offset+1] << 16) +
729
                                 (payloads[data_offset+2] << 8) + (payloads[data_offset+3] << 0);
730
        const uchar *ret = payloads+data_offset+4;
731
        *size = data_length;
732
        return ret;
733
    }
734
    *size = 0;
735
    return 0;
736
}
737
QStringList QResourceRoot::children(int node) const
738
{
739
    if(node == -1)
740
        return QStringList();
741
    int offset = findOffset(node) + 4; //jump past name
742
743
    const short flags = (tree[offset+0] << 8) + (tree[offset+1] << 0);
744
    offset += 2;
745
746
    QStringList ret;
747
    if(flags & Directory) {
748
        const int child_count = (tree[offset+0] << 24) + (tree[offset+1] << 16) +
749
                                (tree[offset+2] << 8) + (tree[offset+3] << 0);
750
        offset += 4;
751
        const int child_off = (tree[offset+0] << 24) + (tree[offset+1] << 16) +
752
                              (tree[offset+2] << 8) + (tree[offset+3] << 0);
753
        for(int i = child_off; i < child_off+child_count; ++i)
754
            ret << name(i);
755
    }
756
    return ret;
757
}
758
bool QResourceRoot::mappingRootSubdir(const QString &path, QString *match) const
759
{
760
    const QString root = mappingRoot();
761
    if(!root.isEmpty()) {
762
        const QStringList root_segments = root.split(QLatin1Char('/'), QString::SkipEmptyParts),
763
                          path_segments = path.split(QLatin1Char('/'), QString::SkipEmptyParts);
764
        if(path_segments.size() <= root_segments.size()) {
765
            int matched = 0;
766
            for(int i = 0; i < path_segments.size(); ++i) {
767
                if(root_segments[i] != path_segments[i])
768
                    break;
769
                ++matched;
770
            }
771
            if(matched == path_segments.size()) {
772
                if(match && root_segments.size() > matched)
773
                    *match = root_segments.at(matched);
774
                return true;
775
            }
776
        }
777
    }
778
    return false;
779
}
780
781
Q_CORE_EXPORT bool qRegisterResourceData(int version, const unsigned char *tree,
782
                                         const unsigned char *name, const unsigned char *data)
783
{
784
    QMutexLocker lock(resourceMutex());
785
    if(version == 0x01 && resourceList()) {
786
        bool found = false;
787
        QResourceRoot res(tree, name, data);
788
        for(int i = 0; i < resourceList()->size(); ++i) {
789
            if(*resourceList()->at(i) == res) {
790
                found = true;
791
                break;
792
            }
793
        }
794
        if(!found) {
795
            QResourceRoot *root = new QResourceRoot(tree, name, data);
796
            root->ref.ref();
797
            resourceList()->append(root);
798
        }
799
        return true;
800
    }
801
    return false;
802
}
803
804
Q_CORE_EXPORT bool qUnregisterResourceData(int version, const unsigned char *tree,
805
                                           const unsigned char *name, const unsigned char *data)
806
{
807
    QMutexLocker lock(resourceMutex());
808
    if(version == 0x01 && resourceList()) {
809
        QResourceRoot res(tree, name, data);
810
        for(int i = 0; i < resourceList()->size(); ) {
811
            if(*resourceList()->at(i) == res) {
812
                QResourceRoot *root = resourceList()->takeAt(i);
813
                if(!root->ref.deref())
814
                    delete root;
815
            } else {
816
                ++i;
817
            }
818
        }
819
        return true;
820
    }
821
    return false;
822
}
823
824
//run time resource creation
825
826
class QDynamicBufferResourceRoot: public QResourceRoot
827
{
828
    QString root;
829
    const uchar *buffer;
830
831
public:
832
    inline QDynamicBufferResourceRoot(const QString &_root) : root(_root), buffer(0) { }
833
    inline ~QDynamicBufferResourceRoot() { }
834
    inline const uchar *mappingBuffer() const { return buffer; }
835
    virtual QString mappingRoot() const { return root; }
836
    virtual ResourceRootType type() const { return Resource_Buffer; }
837
838
    bool registerSelf(const uchar *b) {
839
        //setup the data now
840
        int offset = 0;
841
842
        //magic number
843
        if(b[offset+0] != 'q' || b[offset+1] != 'r' ||
844
           b[offset+2] != 'e' || b[offset+3] != 's') {
845
            return false;
846
        }
847
        offset += 4;
848
849
        const int version = (b[offset+0] << 24) + (b[offset+1] << 16) +
850
                         (b[offset+2] << 8) + (b[offset+3] << 0);
851
        offset += 4;
852
853
        const int tree_offset = (b[offset+0] << 24) + (b[offset+1] << 16) +
854
                                (b[offset+2] << 8) + (b[offset+3] << 0);
855
        offset += 4;
856
857
        const int data_offset = (b[offset+0] << 24) + (b[offset+1] << 16) +
858
                                (b[offset+2] << 8) + (b[offset+3] << 0);
859
        offset += 4;
860
861
        const int name_offset = (b[offset+0] << 24) + (b[offset+1] << 16) +
862
                                (b[offset+2] << 8) + (b[offset+3] << 0);
863
        offset += 4;
864
865
        if(version == 0x01) {
866
            buffer = b;
867
            setSource(b+tree_offset, b+name_offset, b+data_offset);
868
            return true;
869
        }
870
        return false;
871
    }
872
};
873
874
#if defined(Q_OS_UNIX)
875
#define QT_USE_MMAP
876
#endif
877
878
// most of the headers below are already included in qplatformdefs.h
879
// also this lacks Large File support but that's probably irrelevant
880
#if defined(QT_USE_MMAP)
881
// for mmap
882
QT_BEGIN_INCLUDE_NAMESPACE
883
#include <sys/mman.h>
884
#include <errno.h>
885
QT_END_INCLUDE_NAMESPACE
886
#endif
887
888
889
890
class QDynamicFileResourceRoot: public QDynamicBufferResourceRoot
891
{
892
    QString fileName;
893
    // for mmap'ed files, this is what needs to be unmapped.
894
    uchar *unmapPointer;
895
    unsigned int unmapLength;
896
897
public:
898
    inline QDynamicFileResourceRoot(const QString &_root) : QDynamicBufferResourceRoot(_root), unmapPointer(0), unmapLength(0) { }
899
    ~QDynamicFileResourceRoot() {
900
#if defined(QT_USE_MMAP)
901
        if (unmapPointer) {
902
            munmap((char*)unmapPointer, unmapLength);
903
            unmapPointer = 0;
904
            unmapLength = 0;
905
        } else
906
#endif
907
        {
908
            delete [] (uchar *)mappingBuffer();
909
        }
910
    }
911
    QString mappingFile() const { return fileName; }
912
    virtual ResourceRootType type() const { return Resource_File; }
913
914
    bool registerSelf(const QString &f) {
915
        bool fromMM = false;
916
        uchar *data = 0;
917
        unsigned int data_len = 0;
918
919
#ifdef QT_USE_MMAP
920
921
#ifndef MAP_FILE
922
#define MAP_FILE 0
923
#endif
924
#ifndef MAP_FAILED
925
#define MAP_FAILED -1
926
#endif
927
928
        int fd = QT_OPEN(QFile::encodeName(f), O_RDONLY,
929
#if defined(Q_OS_WIN)
930
                         _S_IREAD | _S_IWRITE
931
#else
932
                         0666
933
#endif
934
            );
935
        if (fd >= 0) {
936
            QT_STATBUF st;
937
            if (!QT_FSTAT(fd, &st)) {
938
                uchar *ptr;
939
                ptr = reinterpret_cast<uchar *>(
940
                    mmap(0, st.st_size,             // any address, whole file
941
                         PROT_READ,                 // read-only memory
942
                         MAP_FILE | MAP_PRIVATE,    // swap-backed map from file
943
                         fd, 0));                   // from offset 0 of fd
944
                if (ptr && ptr != reinterpret_cast<uchar *>(MAP_FAILED)) {
945
                    data = ptr;
946
                    data_len = st.st_size;
947
                    fromMM = true;
948
                }
949
            }
950
            ::close(fd);
951
        }
952
#endif // QT_USE_MMAP
953
        if(!data) {
954
            QFile file(f);
955
            if (!file.exists())
956
                return false;
957
            data_len = file.size();
958
            data = new uchar[data_len];
959
960
            bool ok = false;
961
            if (file.open(QIODevice::ReadOnly))
962
                ok = (data_len == (uint)file.read((char*)data, data_len));
963
            if (!ok) {
964
                delete [] data;
965
                data = 0;
966
                data_len = 0;
967
                return false;
968
            }
969
            fromMM = false;
970
        }
971
        if(data && QDynamicBufferResourceRoot::registerSelf(data)) {
972
            if(fromMM) {
973
                unmapPointer = data;
974
                unmapLength = data_len;
975
            }
976
            fileName = f;
977
            return true;
978
        }
979
        return false;
980
    }
981
};
982
983
static QString qt_resource_fixResourceRoot(QString r) {
984
    if(!r.isEmpty()) {
985
        if(r.startsWith(QLatin1Char(':')))
986
            r = r.mid(1);
987
        if(!r.isEmpty())
988
            r = QDir::cleanPath(r);
989
    }
990
    return r;
991
}
992
993
994
/*!
995
   \fn bool QResource::registerResource(const QString &rccFileName, const QString &mapRoot)
996
997
   Registers the resource with the given \a rccFileName at the location in the
998
   resource tree specified by \a mapRoot, and returns true if the file is
999
   successfully opened; otherwise returns false.
1000
1001
   \sa unregisterResource()
1002
*/
1003
1004
bool
1005
QResource::registerResource(const QString &rccFilename, const QString &resourceRoot)
1006
{
1007
    QString r = qt_resource_fixResourceRoot(resourceRoot);
1008
    if(!r.isEmpty() && r[0] != QLatin1Char('/')) {
1009
        qWarning("QDir::registerResource: Registering a resource [%s] must be rooted in an absolute path (start with /) [%s]",
1010
                 rccFilename.toLocal8Bit().data(), resourceRoot.toLocal8Bit().data());
1011
        return false;
1012
    }
1013
1014
    QDynamicFileResourceRoot *root = new QDynamicFileResourceRoot(r);
1015
    if(root->registerSelf(rccFilename)) {
1016
        root->ref.ref();
1017
        QMutexLocker lock(resourceMutex());
1018
        resourceList()->append(root);
1019
        return true;
1020
    }
1021
    delete root;
1022
    return false;
1023
}
1024
1025
/*!
1026
  \fn bool QResource::unregisterResource(const QString &rccFileName, const QString &mapRoot)
1027
1028
  Unregisters the resource with the given \a rccFileName at the location in
1029
  the resource tree specified by \a mapRoot, and returns true if the
1030
  resource is successfully unloaded and no references exist for the
1031
  resource; otherwise returns false.
1032
1033
  \sa registerResource()
1034
*/
1035
1036
bool
1037
QResource::unregisterResource(const QString &rccFilename, const QString &resourceRoot)
1038
{
1039
    QString r = qt_resource_fixResourceRoot(resourceRoot);
1040
1041
    QMutexLocker lock(resourceMutex());
1042
    ResourceList *list = resourceList();
1043
    for(int i = 0; i < list->size(); ++i) {
1044
        QResourceRoot *res = list->at(i);
1045
        if(res->type() == QResourceRoot::Resource_File) {
1046
	    QDynamicFileResourceRoot *root = reinterpret_cast<QDynamicFileResourceRoot*>(res);
1047
	    if(root->mappingFile() == rccFilename && root->mappingRoot() == r) {
1048
                resourceList()->removeAt(i);
1049
                if(!root->ref.deref()) {
1050
                    delete root;
1051
                    return true;
1052
                }
1053
                return false;
1054
            }
1055
	}
1056
    }
1057
    return false;
1058
}
1059
1060
1061
/*!
1062
   \fn bool QResource::registerResource(const uchar *rccData, const QString &mapRoot)
1063
   \since 4.3
1064
1065
   Registers the resource with the given \a rccData at the location in the
1066
   resource tree specified by \a mapRoot, and returns true if the file is
1067
   successfully opened; otherwise returns false.
1068
1069
   \warning The data must remain valid throughout the life of any QFile
1070
   that may reference the resource data.
1071
1072
   \sa unregisterResource()
1073
*/
1074
1075
bool
1076
QResource::registerResource(const uchar *rccData, const QString &resourceRoot)
1077
{
1078
    QString r = qt_resource_fixResourceRoot(resourceRoot);
1079
    if(!r.isEmpty() && r[0] != QLatin1Char('/')) {
1080
        qWarning("QDir::registerResource: Registering a resource [%p] must be rooted in an absolute path (start with /) [%s]",
1081
                 rccData, resourceRoot.toLocal8Bit().data());
1082
        return false;
1083
    }
1084
1085
    QDynamicBufferResourceRoot *root = new QDynamicBufferResourceRoot(r);
1086
    if(root->registerSelf(rccData)) {
1087
        root->ref.ref();
1088
        QMutexLocker lock(resourceMutex());
1089
        resourceList()->append(root);
1090
        return true;
1091
    }
1092
    delete root;
1093
    return false;
1094
}
1095
1096
/*!
1097
  \fn bool QResource::unregisterResource(const uchar *rccData, const QString &mapRoot)
1098
  \since 4.3
1099
1100
  Unregisters the resource with the given \a rccData at the location in the
1101
  resource tree specified by \a mapRoot, and returns true if the resource is
1102
  successfully unloaded and no references exist into the resource; otherwise returns false.
1103
1104
  \sa registerResource()
1105
*/
1106
1107
bool
1108
QResource::unregisterResource(const uchar *rccData, const QString &resourceRoot)
1109
{
1110
    QString r = qt_resource_fixResourceRoot(resourceRoot);
1111
1112
    QMutexLocker lock(resourceMutex());
1113
    ResourceList *list = resourceList();
1114
    for(int i = 0; i < list->size(); ++i) {
1115
        QResourceRoot *res = list->at(i);
1116
        if(res->type() == QResourceRoot::Resource_Buffer) {
1117
	    QDynamicBufferResourceRoot *root = reinterpret_cast<QDynamicBufferResourceRoot*>(res);
1118
	    if(root->mappingBuffer() == rccData && root->mappingRoot() == r) {
1119
                resourceList()->removeAt(i);
1120
                if(!root->ref.deref()) {
1121
                    delete root;
1122
                    return true;
1123
                }
1124
		return false;
1125
            }
1126
	}
1127
    }
1128
    return false;
1129
}
1130
1131
//file type handler
1132
class QResourceFileEngineHandler : public QAbstractFileEngineHandler
1133
{
1134
public:
1135
    QResourceFileEngineHandler() { }
1136
    ~QResourceFileEngineHandler() { }
1137
    QAbstractFileEngine *create(const QString &path) const;
1138
};
1139
QAbstractFileEngine *QResourceFileEngineHandler::create(const QString &path) const
1140
{
1141
    if (path.size() > 0 && path.startsWith(QLatin1Char(':')))
1142
        return new QResourceFileEngine(path);
1143
    return 0;
1144
}
1145
1146
//resource engine
1147
class QResourceFileEnginePrivate : public QAbstractFileEnginePrivate
1148
{
1149
protected:
1150
    Q_DECLARE_PUBLIC(QResourceFileEngine)
1151
private:
1152
    uchar *map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags);
1153
    bool unmap(uchar *ptr);
1154
    qint64 offset;
1155
    QResource resource;
1156
    QByteArray uncompressed;
1157
protected:
1158
    QResourceFileEnginePrivate() : offset(0) { }
1159
};
1160
1161
bool QResourceFileEngine::mkdir(const QString &, bool) const
1162
{
1163
    return false;
1164
}
1165
1166
bool QResourceFileEngine::rmdir(const QString &, bool) const
1167
{
1168
    return false;
1169
}
1170
1171
bool QResourceFileEngine::setSize(qint64)
1172
{
1173
    return false;
1174
}
1175
1176
QStringList QResourceFileEngine::entryList(QDir::Filters filters, const QStringList &filterNames) const
1177
{
1178
    return QAbstractFileEngine::entryList(filters, filterNames);
1179
}
1180
1181
bool QResourceFileEngine::caseSensitive() const
1182
{
1183
    return true;
1184
}
1185
1186
QResourceFileEngine::QResourceFileEngine(const QString &file) :
1187
    QAbstractFileEngine(*new QResourceFileEnginePrivate)
1188
{
1189
    Q_D(QResourceFileEngine);
1190
    d->resource.setFileName(file);
1191
    if(d->resource.isCompressed() && d->resource.size()) {
1192
#ifndef QT_NO_COMPRESS
1193
        d->uncompressed = qUncompress(d->resource.data(), d->resource.size());
1194
#else
1195
        Q_ASSERT(!"QResourceFileEngine::open: Qt built without support for compression");
1196
#endif
1197
    }
1198
}
1199
1200
QResourceFileEngine::~QResourceFileEngine()
1201
{
1202
}
1203
1204
void QResourceFileEngine::setFileName(const QString &file)
1205
{
1206
    Q_D(QResourceFileEngine);
1207
    d->resource.setFileName(file);
1208
}
1209
1210
bool QResourceFileEngine::open(QIODevice::OpenMode flags)
1211
{
1212
    Q_D(QResourceFileEngine);
1213
    if (d->resource.fileName().isEmpty()) {
1214
        qWarning("QResourceFileEngine::open: Missing file name");
1215
        return false;
1216
    }
1217
    if(flags & QIODevice::WriteOnly)
1218
        return false;
1219
    if(!d->resource.isValid())
1220
       return false;
1221
    return true;
1222
}
1223
1224
bool QResourceFileEngine::close()
1225
{
1226
    Q_D(QResourceFileEngine);
1227
    d->offset = 0;
1228
    d->uncompressed.clear();
1229
    return true;
1230
}
1231
1232
bool QResourceFileEngine::flush()
1233
{
1234
    return false;
1235
}
1236
1237
qint64 QResourceFileEngine::read(char *data, qint64 len)
1238
{
1239
    Q_D(QResourceFileEngine);
1240
    if(len > size()-d->offset)
1241
        len = size()-d->offset;
1242
    if(len <= 0)
1243
        return 0;
1244
    if(d->resource.isCompressed())
1245
        memcpy(data, d->uncompressed.constData()+d->offset, len);
1246
    else
1247
        memcpy(data, d->resource.data()+d->offset, len);
1248
    d->offset += len;
1249
    return len;
1250
}
1251
1252
qint64 QResourceFileEngine::write(const char *, qint64)
1253
{
1254
    return -1;
1255
}
1256
1257
bool QResourceFileEngine::remove()
1258
{
1259
    return false;
1260
}
1261
1262
bool QResourceFileEngine::copy(const QString &)
1263
{
1264
    return false;
1265
}
1266
1267
bool QResourceFileEngine::rename(const QString &)
1268
{
1269
    return false;
1270
}
1271
1272
bool QResourceFileEngine::link(const QString &)
1273
{
1274
    return false;
1275
}
1276
1277
qint64 QResourceFileEngine::size() const
1278
{
1279
    Q_D(const QResourceFileEngine);
1280
    if(!d->resource.isValid())
1281
        return 0;
1282
    if(d->resource.isCompressed())
1283
        return d->uncompressed.size();
1284
    return d->resource.size();
1285
}
1286
1287
qint64 QResourceFileEngine::pos() const
1288
{
1289
    Q_D(const QResourceFileEngine);
1290
    return d->offset;
1291
}
1292
1293
bool QResourceFileEngine::atEnd() const
1294
{
1295
    Q_D(const QResourceFileEngine);
1296
    if(!d->resource.isValid())
1297
        return true;
1298
    return d->offset == size();
1299
}
1300
1301
bool QResourceFileEngine::seek(qint64 pos)
1302
{
1303
    Q_D(QResourceFileEngine);
1304
    if(!d->resource.isValid())
1305
        return false;
1306
1307
    if(d->offset > size())
1308
        return false;
1309
    d->offset = pos;
1310
    return true;
1311
}
1312
1313
bool QResourceFileEngine::isSequential() const
1314
{
1315
    return false;
1316
}
1317
1318
QAbstractFileEngine::FileFlags QResourceFileEngine::fileFlags(QAbstractFileEngine::FileFlags type) const
1319
{
1320
    Q_D(const QResourceFileEngine);
1321
    QAbstractFileEngine::FileFlags ret = 0;
1322
    if(!d->resource.isValid())
1323
        return ret;
1324
1325
    if(type & PermsMask)
1326
        ret |= QAbstractFileEngine::FileFlags(ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm);
1327
    if(type & TypesMask) {
1328
        if(d->resource.isDir())
1329
            ret |= DirectoryType;
1330
        else
1331
            ret |= FileType;
1332
    }
1333
    if(type & FlagsMask) {
1334
        ret |= ExistsFlag;
1335
        if(d->resource.absoluteFilePath() == QLatin1String(":/"))
1336
            ret |= RootFlag;
1337
    }
1338
    return ret;
1339
}
1340
1341
bool QResourceFileEngine::setPermissions(uint)
1342
{
1343
    return false;
1344
}
1345
1346
QString QResourceFileEngine::fileName(FileName file) const
1347
{
1348
    Q_D(const QResourceFileEngine);
1349
    if(file == BaseName) {
1350
	int slash = d->resource.fileName().lastIndexOf(QLatin1Char('/'));
1351
	if (slash == -1)
1352
	    return d->resource.fileName();
1353
	return d->resource.fileName().mid(slash + 1);
1354
    } else if(file == PathName || file == AbsolutePathName) {
1355
        const QString path = (file == AbsolutePathName) ? d->resource.absoluteFilePath() : d->resource.fileName();
1356
	const int slash = path.lastIndexOf(QLatin1Char('/'));
1357
	if (slash != -1)
1358
	    return path.left(slash);
1359
    } else if(file == CanonicalName || file == CanonicalPathName) {
1360
        const QString absoluteFilePath = d->resource.absoluteFilePath();
1361
        if(file == CanonicalPathName) {
1362
            const int slash = absoluteFilePath.lastIndexOf(QLatin1Char('/'));
1363
            if (slash != -1)
1364
                return absoluteFilePath.left(slash);
1365
        }
1366
        return absoluteFilePath;
1367
    }
1368
    return d->resource.fileName();
1369
}
1370
1371
bool QResourceFileEngine::isRelativePath() const
1372
{
1373
    return false;
1374
}
1375
1376
uint QResourceFileEngine::ownerId(FileOwner) const
1377
{
1378
    static const uint nobodyID = (uint) -2;
1379
    return nobodyID;
1380
}
1381
1382
QString QResourceFileEngine::owner(FileOwner) const
1383
{
1384
    return QString();
1385
}
1386
1387
QDateTime QResourceFileEngine::fileTime(FileTime) const
1388
{
1389
    return QDateTime();
1390
}
1391
1392
/*!
1393
    \internal
1394
*/
1395
QAbstractFileEngine::Iterator *QResourceFileEngine::beginEntryList(QDir::Filters filters,
1396
                                                                   const QStringList &filterNames)
1397
{
1398
    return new QResourceFileEngineIterator(filters, filterNames);
1399
}
1400
1401
/*!
1402
    \internal
1403
*/
1404
QAbstractFileEngine::Iterator *QResourceFileEngine::endEntryList()
1405
{
1406
    return 0;
1407
}
1408
1409
bool QResourceFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output)
1410
{
1411
    Q_D(QResourceFileEngine);
1412
    if (extension == MapExtension) {
1413
        const MapExtensionOption *options = (MapExtensionOption*)(option);
1414
        MapExtensionReturn *returnValue = static_cast<MapExtensionReturn*>(output);
1415
        returnValue->address = d->map(options->offset, options->size, options->flags);
1416
        return (returnValue->address != 0);
1417
    }
1418
    if (extension == UnMapExtension) {
1419
        UnMapExtensionOption *options = (UnMapExtensionOption*)option;
1420
        return d->unmap(options->address);
1421
    }
1422
    return false;
1423
}
1424
1425
bool QResourceFileEngine::supportsExtension(Extension extension) const
1426
{
1427
    return (extension == UnMapExtension || extension == MapExtension);
1428
}
1429
1430
uchar *QResourceFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags)
1431
{
1432
    Q_Q(QResourceFileEngine);
1433
    Q_UNUSED(flags);
1434
    if (!resource.isValid()
1435
        || offset < 0
1436
        || size < 0
1437
        || offset + size > resource.size()
1438
        || (size == 0)) {
1439
        q->setError(QFile::UnspecifiedError, QString());
1440
        return 0;
1441
    }
1442
    uchar *address = const_cast<uchar *>(resource.data());
1443
    return (address + offset);
1444
}
1445
1446
bool QResourceFileEnginePrivate::unmap(uchar *ptr)
1447
{
1448
    Q_UNUSED(ptr);
1449
    return true;
1450
}
1451
1452
//Initialization and cleanup
1453
Q_GLOBAL_STATIC(QResourceFileEngineHandler, resource_file_handler)
1454
1455
static int qt_force_resource_init() { resource_file_handler(); return 1; }
1456
Q_CORE_EXPORT void qInitResourceIO() { resource_file_handler(); }
1457
static int qt_forced_resource_init = qt_force_resource_init();
1458
Q_CONSTRUCTOR_FUNCTION(qt_force_resource_init)
1459
1460
QT_END_NAMESPACE