Commit be2e7220 authored by Thomas BOUTROUE's avatar Thomas BOUTROUE

Add FastObjectList model/view components for C++/QML

Fix issue with ***Container's going to 0x0 because relayout while hidden
parent 9c4ec790
Pipeline #978 passed with stage
in 13 seconds
#include "QtQmlTricks.h"
#include "QQmlContainerEnums.h"
#include "QQmlObjectListModel.h"
#include "QQuickColumnContainer.h"
#include "QQuickContainerAttachedObject.h"
#include "QQuickExtraAnchors.h"
#include "QQuickGridContainer.h"
#include "QQuickRowContainer.h"
#include "QQuickFastObjectListView.h"
#include "QQmlFastObjectListModel.h"
#include "QQmlContainerEnums.h"
#include "QQmlObjectListModel.h"
#include <qqml.h>
......@@ -15,10 +17,12 @@ void QtQmlTricks::registerComponents (void) {
qmlRegisterType<QQuickColumnContainer> ("QtQmlTricks", 3, 0, "ColumnContainer");
qmlRegisterType<QQuickGridContainer> ("QtQmlTricks", 3, 0, "GridContainer");
qmlRegisterType<QQuickRowContainer> ("QtQmlTricks", 3, 0, "RowContainer");
qmlRegisterType<QQuickFastObjectListView> ("QtQmlTricks", 3, 0, "FastObjectListView");
qmlRegisterUncreatableType<VerticalDirections> ("QtQmlTricks", 3, 0, "VerticalDirections", "Enum-class !");
qmlRegisterUncreatableType<HorizontalDirections> ("QtQmlTricks", 3, 0, "HorizontalDirections", "Enum-class !");
qmlRegisterUncreatableType<FlowDirections> ("QtQmlTricks", 3, 0, "FlowDirections", "Enum-class !");
qmlRegisterUncreatableType<QQuickExtraAnchors> ("QtQmlTricks", 3, 0, "ExtraAnchors", "Attached-object class !");
qmlRegisterUncreatableType<QQuickContainerAttachedObject> ("QtQmlTricks", 3, 0, "Container", "Attached-object class !");
qmlRegisterUncreatableType<QQmlObjectListModelBase> ("QtQmlTricks", 3, 0, "ObjectListModel", "Abstract base class !");
qmlRegisterUncreatableType<QQmlFastObjectListModelBase> ("QtQmlTricks", 3, 0, "FastObjectListModel", "Abstract base class !");
}
#ifndef QQMLFASTOBJECTLISTMODEL_H
#define QQMLFASTOBJECTLISTMODEL_H
#include <QObject>
#include <QList>
class QQmlFastObjectListModelBase : public QObject {
Q_OBJECT
Q_PROPERTY (int count READ getCount NOTIFY countChanged)
Q_PROPERTY (QObject * firstItem READ getFirstItem NOTIFY firstChanged)
Q_PROPERTY (QObject * lastItem READ getLastItem NOTIFY lastChanged)
public:
explicit QQmlFastObjectListModelBase (QObject * parent = Q_NULLPTR) : QObject { parent } { }
virtual ~QQmlFastObjectListModelBase (void) { }
virtual int getCount (void) const { return 0; }
virtual QObject * getFirstItem (void) const { return Q_NULLPTR; }
virtual QObject * getLastItem (void) const { return Q_NULLPTR; }
virtual QObject * getItem (const int) const { return Q_NULLPTR; }
signals:
void countChanged (void);
void firstChanged (void);
void lastChanged (void);
void itemsCleared (void);
void itemInserted (QObject * item, const int idx);
void itemRemoved (QObject * item, const int idx);
};
template<class T> class QQmlFastObjectListModel : public QQmlFastObjectListModelBase {
private:
QList<T *> m_itemsList { };
public:
explicit QQmlFastObjectListModel (QObject * parent = Q_NULLPTR) : QQmlFastObjectListModelBase { parent } { }
virtual ~QQmlFastObjectListModel (void) { }
using Iterator = typename QList<T *>::const_iterator;
Iterator begin (void) const {
return m_itemsList.constBegin ();
}
Iterator end (void) const {
return m_itemsList.constEnd ();
}
int count (void) const {
return m_itemsList.count ();
}
int indexOf (T * item) const {
return (item ? m_itemsList.indexOf (item) : -1);
}
bool isEmpty (void) const {
return m_itemsList.isEmpty ();
}
bool contains (T * item) const {
return (item != Q_NULLPTR && m_itemsList.contains (item));
}
T * getFirst (void) const {
return (!isEmpty () ? m_itemsList.first () : Q_NULLPTR);
}
T * getLast (void) const {
return (!isEmpty () ? m_itemsList.last () : Q_NULLPTR);
}
T * getAt (const int idx) const {
return (idx >= 0 && idx < count () ? m_itemsList.at (idx) : Q_NULLPTR);
}
void append (T * item) {
if (item != Q_NULLPTR && !contains (item)) {
const int idx { m_itemsList.count () };
m_itemsList.append (item);
emit itemInserted (item, idx);
emit lastChanged ();
if (count () == 1) {
emit firstChanged ();
}
emit countChanged ();
}
}
void appendList (const QList<T *> itemsList) {
if (!itemsList.isEmpty ()) {
for (T * item : itemsList) {
int idx { m_itemsList.count () };
if (item != Q_NULLPTR && !contains (item)) {
m_itemsList.append (item);
emit itemInserted (item, idx);
if (count () == 1) {
emit firstChanged ();
}
++idx;
}
}
emit lastChanged ();
emit countChanged ();
}
}
void prepend (T * item) {
if (item != Q_NULLPTR && !contains (item)) {
const int idx { 0 };
m_itemsList.prepend (item);
emit itemInserted (item, idx);
emit firstChanged ();
if (count () == 1) {
emit lastChanged ();
}
emit countChanged ();
}
}
void insert (T * item, const int idx) {
if (item != Q_NULLPTR && !contains (item)) {
m_itemsList.insert (idx, item);
emit itemInserted (item, idx);
if (idx == 0) {
emit firstChanged ();
}
if (idx == count ()) {
emit lastChanged ();
}
emit countChanged ();
}
}
void remove (T * item) {
if (item != Q_NULLPTR) {
const int idx { m_itemsList.indexOf (item) };
if (idx >= 0 && idx < count ()) {
m_itemsList.removeAt (idx);
emit itemRemoved (item, idx);
if (idx == 0) {
emit firstChanged ();
}
if (idx == count ()) {
emit lastChanged ();
}
emit countChanged ();
}
}
}
void clear (void) {
m_itemsList.clear ();
emit itemsCleared ();
emit firstChanged ();
emit lastChanged ();
emit countChanged ();
}
protected: // API for QQuickFastObjectListView only
int getCount (void) const Q_DECL_FINAL {
return count ();
}
QObject * getItem (const int idx) const Q_DECL_FINAL {
return getAt (idx);
}
QObject * getFirstItem (void) const Q_DECL_FINAL {
return getFirst ();
}
QObject * getLastItem (void) const Q_DECL_FINAL {
return getLast ();
}
};
#define QML_FASTOBJMODEL_PROPERTY(NAME,TYPE) \
private: Q_PROPERTY (QQmlFastObjectListModelBase * NAME READ get_##NAME CONSTANT) \
public: QQmlFastObjectListModel<TYPE> NAME { }; \
public: QQmlFastObjectListModelBase * get_##NAME (void) { return &NAME; } \
private:
#endif // QQMLFASTOBJECTLISTMODEL_H
......@@ -65,7 +65,9 @@ void QQuickAbstractContainerBase::itemChange (ItemChange change, const ItemChang
}
void QQuickAbstractContainerBase::updatePolish (void) {
QQuickItem::updatePolish ();
relayout ();
emit layoutDone ();
if (isVisible ()) {
QQuickItem::updatePolish ();
relayout ();
emit layoutDone ();
}
}
#include "QQuickFastObjectListView.h"
#include "QQmlFastObjectListModel.h"
#include <QGuiApplication>
#include <QQuickWindow>
#include <QQmlContext>
#include <QQmlEngine>
#include <QDebug>
#include <QEvent>
#include <QElapsedTimer>
#include <QtMath>
#include <QMetaObject>
#include <QMetaProperty>
#include <QMetaMethod>
#include <QQmlProperty>
QQuickFastObjectListView::QQuickFastObjectListView (QQuickItem * parent)
: QQuickItem { parent }
, m_current { Q_NULLPTR }
, m_behavior { FREE_MOVE }
, m_spaceBefore { 0 }
, m_spaceAfter { 0 }
{
setFiltersChildMouseEvents (true);
}
QQuickFastObjectListView::~QQuickFastObjectListView (void) {
qDeleteAll (m_holdersList);
}
void QQuickFastObjectListView::classBegin (void) { }
void QQuickFastObjectListView::componentComplete (void) {
connect (this, &QQuickFastObjectListView::widthChanged, this, &QQuickFastObjectListView::doMarkDirty);
connect (this, &QQuickFastObjectListView::heightChanged, this, &QQuickFastObjectListView::doMarkDirty);
connect (this, &QQuickFastObjectListView::visibleChanged, this, &QQuickFastObjectListView::doMarkDirty);
connect (this, &QQuickFastObjectListView::modelChanged, this, &QQuickFastObjectListView::doMarkDirty);
connect (this, &QQuickFastObjectListView::delegateChanged, this, &QQuickFastObjectListView::doMarkDirty);
connect (this, &QQuickFastObjectListView::modelChanged, this, &QQuickFastObjectListView::doMarkDirty);
connect (this, &QQuickFastObjectListView::currentChanged , this, &QQuickFastObjectListView::doMarkDirty);
connect (this, &QQuickFastObjectListView::behaviorChanged, this, &QQuickFastObjectListView::doMarkDirty);
connect (this, &QQuickFastObjectListView::spaceBeforeChanged, this, &QQuickFastObjectListView::doMarkDirty);
connect (this, &QQuickFastObjectListView::spaceAfterChanged, this, &QQuickFastObjectListView::doMarkDirty);
if (parentItem ()) {
if (QObject * anchors = { property (ANCHORS).value<QObject *> () }) {
anchors->setProperty (TOP, parentItem ()->property (TOP));
anchors->setProperty (LEFT, parentItem ()->property (LEFT));
anchors->setProperty (RIGHT, parentItem ()->property (RIGHT));
}
}
QQuickItem * item { parentItem () };
while (item != Q_NULLPTR) {
if (item->inherits (FLICKABLE)) {
m_flickable = item;
connect (m_flickable, &QQuickItem::widthChanged, this, &QQuickFastObjectListView::doMarkDirty);
connect (m_flickable, &QQuickItem::heightChanged, this, &QQuickFastObjectListView::doMarkDirty);
QQmlProperty propertyContentY { m_flickable, CONTENT_Y };
propertyContentY.connectNotifySignal (this, SLOT (doMarkDirty()));
QQmlProperty propertyFlickingV { m_flickable, FLICKING_V };
propertyFlickingV.connectNotifySignal (this, SLOT (onUserInteraction()));
QQmlProperty propertyDraggingV { m_flickable, DRAGGING_V };
propertyDraggingV.connectNotifySignal (this, SLOT (onUserInteraction()));
break;
}
item = item->parentItem ();
}
if (!m_flickable) {
qWarning () << "The FastObjectListView must be placed in a Flickable to work !";
}
QQuickItem::componentComplete ();
}
QQmlFastObjectListModelBase * QQuickFastObjectListView::getModel () const {
return m_model;
}
QQmlComponent * QQuickFastObjectListView::getDelegate (void) const {
return m_delegate;
}
void QQuickFastObjectListView::setModel (QQmlFastObjectListModelBase * model) {
if (m_model != model) {
doDetachFromModel ();
m_model = model;
doAttachToModel ();
doResetInstances ();
doPrepareInstances ();
emit modelChanged ();
}
}
void QQuickFastObjectListView::setDelegate (QQmlComponent * delegate) {
if (m_delegate != delegate) {
m_delegate = delegate;
doResetInstances ();
doPrepareInstances ();
emit delegateChanged ();
}
}
void QQuickFastObjectListView::doMarkDirty (void) {
polish ();
}
void QQuickFastObjectListView::doResetInstances (void) {
if (!m_holdersList.isEmpty ()) {
for (Holder * holder : m_holdersList) {
doEliminate (holder);
}
m_holdersList.clear ();
}
}
void QQuickFastObjectListView::doPrepareInstances (void) {
if (m_model != Q_NULLPTR && m_delegate != Q_NULLPTR) {
QElapsedTimer bench { };
bench.restart ();
const int nb { m_model->getCount () };
m_holdersList.reserve (nb);
for (int idx { 0 }; idx < nb; ++idx) {
Holder * holder { new Holder };
holder->modelItem = m_model->getItem (idx);
m_holdersList.append (holder);
doInstantiate (holder);
}
qWarning () << "PREPARED" << nb << "instances in" << bench.elapsed () << "msec";
}
}
void QQuickFastObjectListView::doAttachToModel (void) {
if (m_model != Q_NULLPTR) {
connect (m_model, &QQmlFastObjectListModelBase::itemInserted, this, &QQuickFastObjectListView::onItemInserted);
connect (m_model, &QQmlFastObjectListModelBase::itemRemoved, this, &QQuickFastObjectListView::onItemRemoved);
connect (m_model, &QQmlFastObjectListModelBase::itemsCleared, this, &QQuickFastObjectListView::onItemsCleared);
}
}
void QQuickFastObjectListView::doDetachFromModel (void) {
if (m_model != Q_NULLPTR) {
disconnect (m_model, &QQmlFastObjectListModelBase::itemInserted, this, &QQuickFastObjectListView::onItemInserted);
disconnect (m_model, &QQmlFastObjectListModelBase::itemRemoved, this, &QQuickFastObjectListView::onItemRemoved);
disconnect (m_model, &QQmlFastObjectListModelBase::itemsCleared, this, &QQuickFastObjectListView::onItemsCleared);
}
}
void QQuickFastObjectListView::doInstantiate (Holder * holder) {
if (holder != Q_NULLPTR) {
if (QQmlContext * context = { qmlContext (this) }) {
if (QQmlContext * subContext { new QQmlContext { context, this } }) {
subContext->setContextProperty (MODEL_ITEM, holder->modelItem);
if (QObject * tmp { m_delegate->create (subContext) }) {
if (QQuickItem * instance = { qobject_cast<QQuickItem *> (tmp) }) {
instance->setParent (this);
instance->setParentItem (this);
if (QObject * tmp = { instance->property (ANCHORS).value<QObject *> () }) {
tmp->setProperty (LEFT, this->property (LEFT));
tmp->setProperty (RIGHT, this->property (RIGHT));
}
connect (instance, &QQuickItem::heightChanged, this, &QQuickFastObjectListView::doMarkDirty);
holder->delegateInstance = instance;
holder->context = subContext;
}
else {
tmp->deleteLater ();
subContext->deleteLater ();
}
}
}
}
}
}
void QQuickFastObjectListView::doEliminate (Holder * holder) {
if (holder != Q_NULLPTR) {
if (holder->delegateInstance != Q_NULLPTR) {
holder->delegateInstance->setParentItem (Q_NULLPTR);
holder->delegateInstance->deleteLater ();
holder->delegateInstance = Q_NULLPTR;
}
if (holder->context != Q_NULLPTR) {
holder->context->deleteLater ();
holder->context = Q_NULLPTR;
}
}
}
void QQuickFastObjectListView::onItemsCleared (void) {
doResetInstances ();
}
void QQuickFastObjectListView::onUserInteraction (void) {
if (m_flickable && (m_flickable->property (DRAGGING_V).toBool () || m_flickable->property (FLICKING_V).toBool ())) {
set_behavior (FREE_MOVE);
set_current (Q_NULLPTR);
}
}
void QQuickFastObjectListView::onItemInserted (QObject * item, const int idx) {
if (item != Q_NULLPTR) {
Holder * holder { new Holder };
holder->modelItem = item;
m_holdersList.insert (idx, holder);
doInstantiate (holder);
polish ();
}
}
void QQuickFastObjectListView::onItemRemoved (QObject * item, const int idx) {
Q_UNUSED (item)
if (idx >= 0 && idx < m_holdersList.count ()) {
Holder * holder { m_holdersList.at (idx) };
doEliminate (holder);
delete m_holdersList.takeAt (idx);
}
polish ();
}
void QQuickFastObjectListView::updatePolish (void) {
if (m_flickable != Q_NULLPTR) {
const int viewportW { qFloor (m_flickable->width ()) };
const int viewportH { qFloor (m_flickable->height ()) };
const int contentY { qRound (m_flickable->property (CONTENT_Y).toReal ()) };
int endY { m_spaceBefore };
QQuickItem * currentDelegate { Q_NULLPTR };
for (Holder * holder : m_holdersList) {
if (holder->delegateInstance != Q_NULLPTR) {
holder->delegateInstance->setY (endY);
holder->delegateInstance->setWidth (viewportW);
holder->delegateInstance->setVisible (!(((-contentY + holder->delegateInstance->y () + holder->delegateInstance->height ()) < (viewportH * -1)) ||
((-contentY + holder->delegateInstance->y ()) > (viewportH * 2))));
endY += qCeil (holder->delegateInstance->height ());
if (m_current == holder->modelItem) {
currentDelegate = holder->delegateInstance;
}
}
}
endY += m_spaceAfter;
setImplicitHeight (endY);
m_flickable->setProperty (CONTENT_W, viewportW);
m_flickable->setProperty (CONTENT_H, implicitHeight ());
const int roomH { (viewportH - m_spaceBefore - m_spaceAfter) };
switch (m_behavior) {
case KEEP_AT_TOP: {
if (currentDelegate) {
doChangePositionY (qCeil (currentDelegate->y ()) - m_spaceBefore);
}
break;
}
case KEEP_AT_BOTTOM: {
if (currentDelegate) {
if (currentDelegate->height () < roomH) {
doChangePositionY (qFloor (currentDelegate->y () + currentDelegate->height () - viewportH + m_spaceAfter));
}
else {
doChangePositionY (qCeil (currentDelegate->y ()) - m_spaceBefore);
}
}
break;
}
case KEEP_CENTERED: {
if (currentDelegate) {
if (currentDelegate->height () < roomH) {
doChangePositionY (qRound (currentDelegate->y () - (roomH - currentDelegate->height ()) / 2) - m_spaceBefore);
}
else {
doChangePositionY (qCeil (currentDelegate->y ()) - m_spaceBefore);
}
}
break;
}
case FREE_MOVE: break;
default: break;
}
}
}
void QQuickFastObjectListView::doChangePositionY (const int posY) {
const int maxY { (qCeil (implicitHeight ()) - qFloor (m_flickable->height ())) };
m_flickable->setProperty (CONTENT_Y, ((posY < 0) ? 0 : (posY > maxY ? maxY : posY)));
}
#ifndef QQUICKFASTOBJECTLISTVIEW_H
#define QQUICKFASTOBJECTLISTVIEW_H
#include <QQuickItem>
#include <QQmlComponent>
#include <QDateTime>
#include <QPropertyAnimation>
#include <QEasingCurve>
#include "QmlPropertyHelpers.h"
class QQmlFastObjectListModelBase;
class QQuickFastObjectListView : public QQuickItem {
Q_OBJECT
QML_WRITABLE_PTR_PROPERTY (current, QObject)
QML_WRITABLE_VAR_PROPERTY (behavior, int)
QML_WRITABLE_VAR_PROPERTY (spaceBefore, int)
QML_WRITABLE_VAR_PROPERTY (spaceAfter, int)
Q_PROPERTY (QQmlFastObjectListModelBase * model READ getModel WRITE setModel NOTIFY modelChanged)
Q_PROPERTY (QQmlComponent * delegate READ getDelegate WRITE setDelegate NOTIFY delegateChanged)
struct Holder {
QObject * modelItem { Q_NULLPTR };
QQuickItem * delegateInstance { Q_NULLPTR };
QQmlContext * context { Q_NULLPTR };
};
public:
explicit QQuickFastObjectListView (QQuickItem * parent = Q_NULLPTR);
virtual ~QQuickFastObjectListView (void);
enum Behavior {
FREE_MOVE = 0,
KEEP_AT_TOP,
KEEP_AT_BOTTOM,
KEEP_CENTERED,
};
Q_ENUM (Behavior)
QQmlFastObjectListModelBase * getModel (void) const;
QQmlComponent * getDelegate (void) const;
void setModel (QQmlFastObjectListModelBase * model);
void setDelegate (QQmlComponent * delegate);
signals:
void modelChanged (void);
void delegateChanged (void);
protected slots:
void doMarkDirty (void);
void doResetInstances (void);
void doPrepareInstances (void);
void doAttachToModel (void);
void doDetachFromModel (void);
void doChangePositionY (const int posY);
void doInstantiate (Holder * holder);
void doEliminate (Holder * holder);
void onItemInserted (QObject * item, const int idx);
void onItemRemoved (QObject * item, const int idx);
void onItemsCleared (void);
void onUserInteraction (void);
protected:
void classBegin (void) Q_DECL_FINAL;
void componentComplete (void) Q_DECL_FINAL;
void updatePolish (void) Q_DECL_FINAL;
private:
const QString MODEL_ITEM { "modelItem" };
const char * FLICKABLE { "QQuickFlickable" };
const char * FLICKING_V { "flickingVertically" };
const char * DRAGGING_V { "draggingVertically" };
const char * CONTENT_Y { "contentY" };
const char * CONTENT_W { "contentWidth" };
const char * CONTENT_H { "contentHeight" };
const char * ANCHORS { "anchors" };
const char * TOP { "top" };
const char * LEFT { "left" };
const char * RIGHT { "right" };
QQuickItem * m_flickable { Q_NULLPTR };
QQmlComponent * m_delegate { Q_NULLPTR };
QQmlFastObjectListModelBase * m_model { Q_NULLPTR };
QList<Holder *> m_holdersList { };
};
#endif // QQUICKFASTOBJECTLISTVIEW_H
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment