Initial import from the monolithic Qt.
[profile/ivi/qtdeclarative.git] / src / declarative / graphicsitems / qdeclarativeloader.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 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 QtDeclarative 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 "private/qdeclarativeloader_p_p.h"
43
44 #include <qdeclarativeinfo.h>
45 #include <qdeclarativeengine_p.h>
46 #include <qdeclarativeglobal_p.h>
47
48 QT_BEGIN_NAMESPACE
49
50 QDeclarativeLoaderPrivate::QDeclarativeLoaderPrivate()
51     : item(0), component(0), ownComponent(false), updatingSize(false),
52       itemWidthValid(false), itemHeightValid(false)
53 {
54 }
55
56 QDeclarativeLoaderPrivate::~QDeclarativeLoaderPrivate()
57 {
58 }
59
60 void QDeclarativeLoaderPrivate::itemGeometryChanged(QDeclarativeItem *resizeItem, const QRectF &newGeometry, const QRectF &oldGeometry)
61 {
62     if (resizeItem == item) {
63         if (!updatingSize && newGeometry.width() != oldGeometry.width())
64             itemWidthValid = true;
65         if (!updatingSize && newGeometry.height() != oldGeometry.height())
66             itemHeightValid = true;
67         _q_updateSize(false);
68     }
69     QDeclarativeItemChangeListener::itemGeometryChanged(resizeItem, newGeometry, oldGeometry);
70 }
71
72 void QDeclarativeLoaderPrivate::clear()
73 {
74     if (ownComponent) {
75         component->deleteLater();
76         component = 0;
77         ownComponent = false;
78     }
79     source = QUrl();
80
81     if (item) {
82         if (QDeclarativeItem *qmlItem = qobject_cast<QDeclarativeItem*>(item)) {
83             QDeclarativeItemPrivate *p =
84                     static_cast<QDeclarativeItemPrivate *>(QGraphicsItemPrivate::get(qmlItem));
85             p->removeItemChangeListener(this, QDeclarativeItemPrivate::Geometry);
86         }
87
88         // We can't delete immediately because our item may have triggered
89         // the Loader to load a different item.
90         if (item->scene()) {
91             item->scene()->removeItem(item);
92         } else {
93             item->setParentItem(0);
94             item->setVisible(false);
95         }
96         item->deleteLater();
97         item = 0;
98     }
99 }
100
101 void QDeclarativeLoaderPrivate::initResize()
102 {
103     Q_Q(QDeclarativeLoader);
104     if (QDeclarativeItem *qmlItem = qobject_cast<QDeclarativeItem*>(item)) {
105         QDeclarativeItemPrivate *p =
106                 static_cast<QDeclarativeItemPrivate *>(QGraphicsItemPrivate::get(qmlItem));
107         p->addItemChangeListener(this, QDeclarativeItemPrivate::Geometry);
108         // We may override the item's size, so we need to remember
109         // whether the item provided its own valid size.
110         itemWidthValid = p->widthValid;
111         itemHeightValid = p->heightValid;
112     } else if (item && item->isWidget()) {
113         QGraphicsWidget *widget = static_cast<QGraphicsWidget*>(item);
114         widget->installEventFilter(q);
115     }
116     _q_updateSize();
117 }
118
119 /*!
120     \qmlclass Loader QDeclarativeLoader
121     \ingroup qml-utility-elements
122     \since 4.7
123     \inherits Item
124
125     \brief The Loader item allows dynamically loading an Item-based
126     subtree from a URL or Component.
127
128     Loader is used to dynamically load visual QML components. It can load a
129     QML file (using the \l source property) or a \l Component object (using 
130     the \l sourceComponent property). It is useful for delaying the creation 
131     of a component until it is required: for example, when a component should 
132     be created on demand, or when a component should not be created 
133     unnecessarily for performance reasons.
134
135     Here is a Loader that loads "Page1.qml" as a component when the 
136     \l MouseArea is clicked:
137
138     \snippet doc/src/snippets/declarative/loader/simple.qml 0
139
140     The loaded item can be accessed using the \l item property.
141
142     If the \l source or \l sourceComponent changes, any previously instantiated
143     items are destroyed. Setting \l source to an empty string or setting
144     \l sourceComponent to \c undefined destroys the currently loaded item,
145     freeing resources and leaving the Loader empty.
146
147     \section2 Loader sizing behavior
148
149     Loader is like any other visual item and must be positioned and sized
150     accordingly to become visible.
151
152     \list
153     \o If an explicit size is not specified for the Loader, the Loader
154     is automatically resized to the size of the loaded item once the
155     component is loaded.
156     \o If the size of the Loader is specified explicitly by setting
157     the width, height or by anchoring, the loaded item will be resized
158     to the size of the Loader.
159     \endlist
160
161     In both scenarios the size of the item and the Loader are identical.
162     This ensures that anchoring to the Loader is equivalent to anchoring
163     to the loaded item.
164
165     \table
166     \row
167     \o sizeloader.qml
168     \o sizeitem.qml
169     \row
170     \o \snippet doc/src/snippets/declarative/loader/sizeloader.qml 0
171     \o \snippet doc/src/snippets/declarative/loader/sizeitem.qml 0
172     \row
173     \o The red rectangle will be sized to the size of the root item.
174     \o The red rectangle will be 50x50, centered in the root item.
175     \endtable
176
177
178     \section2 Receiving signals from loaded items
179
180     Any signals emitted from the loaded item can be received using the 
181     \l Connections element. For example, the following \c application.qml
182     loads \c MyItem.qml, and is able to receive the \c message signal from
183     the loaded item through a \l Connections object:
184
185     \table
186     \row 
187     \o application.qml
188     \o MyItem.qml
189     \row
190     \o \snippet doc/src/snippets/declarative/loader/connections.qml 0
191     \o \snippet doc/src/snippets/declarative/loader/MyItem.qml 0
192     \endtable
193
194     Alternatively, since \c MyItem.qml is loaded within the scope of the
195     Loader, it could also directly call any function defined in the Loader or
196     its parent \l Item.
197
198
199     \section2 Focus and key events
200
201     Loader is a focus scope. Its \l {Item::}{focus} property must be set to 
202     \c true for any of its children to get the \e {active focus}. (See 
203     \l{qmlfocus#Acquiring Focus and Focus Scopes}{the focus documentation page} 
204     for more details.) Any key events received in the loaded item should likely
205     also be \l {KeyEvent::}{accepted} so they are not propagated to the Loader.
206
207     For example, the following \c application.qml loads \c KeyReader.qml when
208     the \l MouseArea is clicked.  Notice the \l {Item::}{focus} property is 
209     set to \c true for the Loader as well as the \l Item in the dynamically 
210     loaded object:
211
212     \table
213     \row 
214     \o application.qml
215     \o KeyReader.qml
216     \row
217     \o \snippet doc/src/snippets/declarative/loader/focus.qml 0
218     \o \snippet doc/src/snippets/declarative/loader/KeyReader.qml 0
219     \endtable
220
221     Once \c KeyReader.qml is loaded, it accepts key events and sets 
222     \c event.accepted to \c true so that the event is not propagated to the
223     parent \l Rectangle.
224
225     \sa {dynamic-object-creation}{Dynamic Object Creation}
226 */
227
228 QDeclarativeLoader::QDeclarativeLoader(QDeclarativeItem *parent)
229   : QDeclarativeImplicitSizeItem(*(new QDeclarativeLoaderPrivate), parent)
230 {
231     Q_D(QDeclarativeLoader);
232     d->flags |= QGraphicsItem::ItemIsFocusScope;
233 }
234
235 QDeclarativeLoader::~QDeclarativeLoader()
236 {
237     Q_D(QDeclarativeLoader);
238     if (d->item) {
239         if (QDeclarativeItem *qmlItem = qobject_cast<QDeclarativeItem*>(d->item)) {
240             QDeclarativeItemPrivate *p =
241                     static_cast<QDeclarativeItemPrivate *>(QGraphicsItemPrivate::get(qmlItem));
242             p->removeItemChangeListener(d, QDeclarativeItemPrivate::Geometry);
243         }
244     }
245 }
246
247 /*!
248     \qmlproperty url Loader::source
249     This property holds the URL of the QML component to instantiate.
250
251     Note the QML component must be an \l{Item}-based component. The loader
252     cannot load non-visual components.
253
254     To unload the currently loaded item, set this property to an empty string,
255     or set \l sourceComponent to \c undefined. Setting \c source to a
256     new URL will also cause the item created by the previous URL to be unloaded.
257
258     \sa sourceComponent, status, progress
259 */
260 QUrl QDeclarativeLoader::source() const
261 {
262     Q_D(const QDeclarativeLoader);
263     return d->source;
264 }
265
266 void QDeclarativeLoader::setSource(const QUrl &url)
267 {
268     Q_D(QDeclarativeLoader);
269     if (d->source == url)
270         return;
271
272     d->clear();
273
274     d->source = url;
275
276     if (d->source.isEmpty()) {
277         emit sourceChanged();
278         emit statusChanged();
279         emit progressChanged();
280         emit itemChanged();
281         return;
282     }
283
284     d->component = new QDeclarativeComponent(qmlEngine(this), d->source, this);
285     d->ownComponent = true;
286
287     if (isComponentComplete())
288         d->load();
289 }
290
291 /*!
292     \qmlproperty Component Loader::sourceComponent
293     This property holds the \l{Component} to instantiate.
294
295     \qml
296     Item {
297         Component {
298             id: redSquare
299             Rectangle { color: "red"; width: 10; height: 10 }
300         }
301
302         Loader { sourceComponent: redSquare }
303         Loader { sourceComponent: redSquare; x: 10 }
304     }
305     \endqml
306
307     To unload the currently loaded item, set this property to an empty string
308     or \c undefined.
309
310     \sa source, progress
311 */
312
313 QDeclarativeComponent *QDeclarativeLoader::sourceComponent() const
314 {
315     Q_D(const QDeclarativeLoader);
316     return d->component;
317 }
318
319 void QDeclarativeLoader::setSourceComponent(QDeclarativeComponent *comp)
320 {
321     Q_D(QDeclarativeLoader);
322     if (comp == d->component)
323         return;
324
325     d->clear();
326
327     d->component = comp;
328     d->ownComponent = false;
329
330     if (!d->component) {
331         emit sourceChanged();
332         emit statusChanged();
333         emit progressChanged();
334         emit itemChanged();
335         return;
336     }
337
338     if (isComponentComplete())
339         d->load();
340 }
341
342 void QDeclarativeLoader::resetSourceComponent()
343 {
344     setSourceComponent(0);
345 }
346
347 void QDeclarativeLoaderPrivate::load()
348 {
349     Q_Q(QDeclarativeLoader);
350
351     if (!q->isComponentComplete() || !component)
352         return;
353
354     if (!component->isLoading()) {
355         _q_sourceLoaded();
356     } else {
357         QObject::connect(component, SIGNAL(statusChanged(QDeclarativeComponent::Status)),
358                 q, SLOT(_q_sourceLoaded()));
359         QObject::connect(component, SIGNAL(progressChanged(qreal)),
360                 q, SIGNAL(progressChanged()));
361         emit q->statusChanged();
362         emit q->progressChanged();
363         emit q->sourceChanged();
364         emit q->itemChanged();
365     }
366 }
367
368 void QDeclarativeLoaderPrivate::_q_sourceLoaded()
369 {
370     Q_Q(QDeclarativeLoader);
371
372     if (component) {
373         if (!component->errors().isEmpty()) {
374             QDeclarativeEnginePrivate::warning(qmlEngine(q), component->errors());
375             emit q->sourceChanged();
376             emit q->statusChanged();
377             emit q->progressChanged();
378             return;
379         }
380
381         QDeclarativeContext *creationContext = component->creationContext();
382         if (!creationContext) creationContext = qmlContext(q);
383         QDeclarativeContext *ctxt = new QDeclarativeContext(creationContext);
384         ctxt->setContextObject(q);
385
386         QDeclarativeGuard<QDeclarativeComponent> c = component;
387         QObject *obj = component->beginCreate(ctxt);
388         if (component != c) {
389             // component->create could trigger a change in source that causes
390             // component to be set to something else. In that case we just
391             // need to cleanup.
392             if (c)
393                 c->completeCreate();
394             delete obj;
395             delete ctxt;
396             return;
397         }
398         if (obj) {
399             item = qobject_cast<QGraphicsObject *>(obj);
400             if (item) {
401                 QDeclarative_setParent_noEvent(ctxt, obj);
402                 QDeclarative_setParent_noEvent(item, q);
403                 item->setParentItem(q);
404 //                item->setFocus(true);
405                 initResize();
406             } else {
407                 qmlInfo(q) << QDeclarativeLoader::tr("Loader does not support loading non-visual elements.");
408                 delete obj;
409                 delete ctxt;
410             }
411         } else {
412             if (!component->errors().isEmpty())
413                 QDeclarativeEnginePrivate::warning(qmlEngine(q), component->errors());
414             delete obj;
415             delete ctxt;
416             source = QUrl();
417         }
418         component->completeCreate();
419         emit q->sourceChanged();
420         emit q->statusChanged();
421         emit q->progressChanged();
422         emit q->itemChanged();
423         emit q->loaded();
424     }
425 }
426
427 /*!
428     \qmlproperty enumeration Loader::status
429
430     This property holds the status of QML loading.  It can be one of:
431     \list
432     \o Loader.Null - no QML source has been set
433     \o Loader.Ready - the QML source has been loaded
434     \o Loader.Loading - the QML source is currently being loaded
435     \o Loader.Error - an error occurred while loading the QML source
436     \endlist
437
438     Use this status to provide an update or respond to the status change in some way.
439     For example, you could:
440
441     \list
442     \o Trigger a state change:
443     \qml
444         State { name: 'loaded'; when: loader.status == Loader.Ready }
445     \endqml
446
447     \o Implement an \c onStatusChanged signal handler:
448     \qml
449         Loader {
450             id: loader
451             onStatusChanged: if (loader.status == Loader.Ready) console.log('Loaded')
452         }
453     \endqml
454
455     \o Bind to the status value:
456     \qml
457         Text { text: loader.status == Loader.Ready ? 'Loaded' : 'Not loaded' }
458     \endqml
459     \endlist
460
461     Note that if the source is a local file, the status will initially be Ready (or Error). While
462     there will be no onStatusChanged signal in that case, the onLoaded will still be invoked.
463
464     \sa progress
465 */
466
467 QDeclarativeLoader::Status QDeclarativeLoader::status() const
468 {
469     Q_D(const QDeclarativeLoader);
470
471     if (d->component)
472         return static_cast<QDeclarativeLoader::Status>(d->component->status());
473
474     if (d->item)
475         return Ready;
476
477     return d->source.isEmpty() ? Null : Error;
478 }
479
480 void QDeclarativeLoader::componentComplete()
481 {
482     Q_D(QDeclarativeLoader);
483
484     QDeclarativeItem::componentComplete();
485     d->load();
486 }
487
488
489 /*!
490     \qmlsignal Loader::onLoaded()
491
492     This handler is called when the \l status becomes \c Loader.Ready, or on successful
493     initial load.
494 */
495
496
497 /*!
498 \qmlproperty real Loader::progress
499
500 This property holds the progress of loading QML data from the network, from
501 0.0 (nothing loaded) to 1.0 (finished).  Most QML files are quite small, so
502 this value will rapidly change from 0 to 1.
503
504 \sa status
505 */
506 qreal QDeclarativeLoader::progress() const
507 {
508     Q_D(const QDeclarativeLoader);
509
510     if (d->item)
511         return 1.0;
512
513     if (d->component)
514         return d->component->progress();
515
516     return 0.0;
517 }
518
519 void QDeclarativeLoaderPrivate::_q_updateSize(bool loaderGeometryChanged)
520 {
521     Q_Q(QDeclarativeLoader);
522     if (!item || updatingSize)
523         return;
524
525     updatingSize = true;
526     if (QDeclarativeItem *qmlItem = qobject_cast<QDeclarativeItem*>(item)) {
527         if (!itemWidthValid)
528             q->setImplicitWidth(qmlItem->implicitWidth());
529         else
530             q->setImplicitWidth(qmlItem->width());
531         if (loaderGeometryChanged && q->widthValid())
532             qmlItem->setWidth(q->width());
533         if (!itemHeightValid)
534             q->setImplicitHeight(qmlItem->implicitHeight());
535         else
536             q->setImplicitHeight(qmlItem->height());
537         if (loaderGeometryChanged && q->heightValid())
538             qmlItem->setHeight(q->height());
539     } else if (item && item->isWidget()) {
540         QGraphicsWidget *widget = static_cast<QGraphicsWidget*>(item);
541         QSizeF widgetSize = widget->size();
542         q->setImplicitWidth(widgetSize.width());
543         if (loaderGeometryChanged && q->widthValid())
544             widgetSize.setWidth(q->width());
545         q->setImplicitHeight(widgetSize.height());
546         if (loaderGeometryChanged && q->heightValid())
547             widgetSize.setHeight(q->height());
548         if (widget->size() != widgetSize)
549             widget->resize(widgetSize);
550     }
551     updatingSize = false;
552 }
553
554 /*!
555     \qmlproperty Item Loader::item
556     This property holds the top-level item that is currently loaded.
557 */
558 QGraphicsObject *QDeclarativeLoader::item() const
559 {
560     Q_D(const QDeclarativeLoader);
561     return d->item;
562 }
563
564 void QDeclarativeLoader::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
565 {
566     Q_D(QDeclarativeLoader);
567     if (newGeometry != oldGeometry) {
568         d->_q_updateSize();
569     }
570     QDeclarativeItem::geometryChanged(newGeometry, oldGeometry);
571 }
572
573 QVariant QDeclarativeLoader::itemChange(GraphicsItemChange change, const QVariant &value)
574 {
575     Q_D(QDeclarativeLoader);
576     if (change == ItemSceneHasChanged) {
577         if (d->item && d->item->isWidget()) {
578             d->item->removeEventFilter(this);
579             d->item->installEventFilter(this);
580         }
581     }
582     return QDeclarativeItem::itemChange(change, value);
583 }
584
585 bool QDeclarativeLoader::eventFilter(QObject *watched, QEvent *e)
586 {
587     Q_D(QDeclarativeLoader);
588     if (watched == d->item && e->type() == QEvent::GraphicsSceneResize) {
589         if (d->item && d->item->isWidget())
590             d->_q_updateSize(false);
591     }
592     return QDeclarativeItem::eventFilter(watched, e);
593 }
594
595 #include <moc_qdeclarativeloader_p.cpp>
596
597 QT_END_NAMESPACE