File indexing completed on 2024-04-28 04:41:52

0001 /***************************************************************************
0002  *   Copyright (C) 2017 by Emmanuel Lepage Vallee                          *
0003  *   Author : Emmanuel Lepage Vallee <emmanuel.lepage@kde.org>             *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 3 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  *                                                                         *
0010  *   This program is distributed in the hope that it will be useful,       *
0011  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
0012  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
0013  *   GNU General Public License for more details.                          *
0014  *                                                                         *
0015  *   You should have received a copy of the GNU General Public License     *
0016  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
0017  **************************************************************************/
0018 #include "listview.h"
0019 
0020 // KQuickItemViews
0021 #include "adapters/abstractitemadapter.h"
0022 #include "adapters/contextadapter.h"
0023 class ListViewItem;
0024 
0025 // Qt
0026 #include <QQmlEngine>
0027 #include <QQmlContext>
0028 #include <QtCore/QItemSelectionModel>
0029 
0030 
0031 /**
0032  * Holds the metadata associated with a section.
0033  *
0034  * A section is created when a ListViewItem has a property that differs
0035  * from the previous ListViewItem in the list. It can also cross reference
0036  * into an external model to provide more flexibility.
0037  */
0038 struct ListViewSection final
0039 {
0040     explicit ListViewSection(
0041         ListViewItem* owner,
0042         const QVariant&    value
0043     );
0044     virtual ~ListViewSection();
0045 
0046     QQuickItem*      m_pItem    {nullptr};
0047     QQmlContext*     m_pContent {nullptr};
0048     int              m_Index    {   0   };
0049     int              m_RefCount {   1   };
0050     QVariant         m_Value    {       };
0051     ListViewSection* m_pPrevious{nullptr};
0052     ListViewSection* m_pNext    {nullptr};
0053 
0054     ListViewPrivate* d_ptr {nullptr};
0055 
0056     // Mutator
0057     QQuickItem* item(QQmlComponent* component);
0058 
0059     // Helpers
0060     void reparentSection(ListViewItem* newParent, ViewBase* view);
0061 
0062     // Getter
0063     ListViewItem *owner() const;
0064 
0065     // Setter
0066     void setOwner(ListViewItem *o);
0067 
0068 private:
0069     ListViewItem *m_pOwner {nullptr};
0070 };
0071 
0072 class ListViewItem : public AbstractItemAdapter
0073 {
0074 public:
0075     explicit ListViewItem(Viewport* r);
0076     virtual ~ListViewItem();
0077 
0078     // Actions
0079     virtual bool attach () override;
0080     virtual bool move   () override;
0081     virtual bool remove () override;
0082 
0083     ListViewSection* m_pSection {nullptr};
0084 
0085     // Setters
0086     ListViewSection* setSection(ListViewSection* s, const QVariant& val);
0087 
0088     ListViewPrivate* d() const;
0089 };
0090 
0091 class ListViewPrivate : public QObject
0092 {
0093     Q_OBJECT
0094 public:
0095     explicit ListViewPrivate(ListView* p) : QObject(p), q_ptr(p){}
0096 
0097     // When all elements are assumed to have the same height, life is easy
0098     QVector<qreal>         m_DepthChart {   0   };
0099     ListViewSections* m_pSections  {nullptr};
0100 
0101     // Sections
0102     QQmlComponent* m_pDelegate   {nullptr};
0103     QString        m_Property    {       };
0104     QStringList    m_Roles       {       };
0105     int            m_CachedRole  {   0   };
0106     mutable bool   m_IndexLoaded { false };
0107     ListViewSection* m_pFirstSection {nullptr};
0108     QSharedPointer<QAbstractItemModel> m_pSectionModel;
0109 
0110     // Helpers
0111     ListViewSection* getSection(ListViewItem* i);
0112     void reloadSectionIndices() const;
0113 
0114     ListView* q_ptr;
0115 
0116 public Q_SLOTS:
0117     void slotCurrentIndexChanged(const QModelIndex& index);
0118 };
0119 
0120 ListView::ListView(QQuickItem* parent) : SingleModelViewBase(new ItemFactory<ListViewItem>(), parent),
0121     d_ptr(new ListViewPrivate(this))
0122 {
0123     connect(this, &SingleModelViewBase::currentIndexChanged,
0124         d_ptr, &ListViewPrivate::slotCurrentIndexChanged);
0125 }
0126 
0127 ListViewItem::~ListViewItem()
0128 {
0129     // If this item is the section owner, assert before crashing
0130     if (m_pSection && m_pSection->owner() == this) {
0131         if (m_pSection->m_RefCount == 1)
0132             delete m_pSection;
0133         else {
0134             auto u = static_cast<ListViewItem*>(next(Qt::TopEdge));
0135             auto d = static_cast<ListViewItem*>(next(Qt::BottomEdge));
0136             if (u && u->m_pSection == m_pSection) {
0137                 m_pSection->setOwner(u);
0138             }
0139             else if(d && d->m_pSection == m_pSection) {
0140                 m_pSection->setOwner(d);
0141                 Q_ASSERT(d == m_pSection->owner());
0142             }
0143             else
0144                 Q_ASSERT(false); //TODO
0145         }
0146     }
0147 
0148     // Happens on reset
0149     if (m_pSection && --m_pSection->m_RefCount <= 0)
0150         delete m_pSection;
0151 }
0152 
0153 ListView::~ListView()
0154 {
0155     applyModelChanges(nullptr);
0156 
0157     if (d_ptr->m_pSections)
0158         delete d_ptr->m_pSections;
0159 
0160     d_ptr->m_pSections = nullptr;
0161 }
0162 
0163 int ListView::count() const
0164 {
0165     return rawModel() ? rawModel()->rowCount() : 0;
0166 }
0167 
0168 int ListView::currentIndex() const
0169 {
0170     return selectionModel()->currentIndex().row();
0171 }
0172 
0173 void ListView::setCurrentIndex(int index)
0174 {
0175     if (!rawModel())
0176         return;
0177 
0178     SingleModelViewBase::setCurrentIndex(
0179         rawModel()->index(index, 0),
0180         QItemSelectionModel::ClearAndSelect
0181     );
0182 }
0183 
0184 ListViewSections* ListView::section() const
0185 {
0186     if (!d_ptr->m_pSections) {
0187         d_ptr->m_pSections = new ListViewSections(
0188             const_cast<ListView*>(this)
0189         );
0190     }
0191 
0192     return d_ptr->m_pSections;
0193 }
0194 
0195 ListViewSection::ListViewSection(ListViewItem* owner, const QVariant& value) :
0196     m_Value(value), d_ptr(owner->d()), m_pOwner(owner)
0197 {
0198     m_pContent = new QQmlContext(owner->view()->rootContext());
0199     m_pContent->setContextProperty("section", value);
0200     owner->setBorderDecoration(Qt::TopEdge, 45.0);
0201 }
0202 
0203 QQuickItem* ListViewSection::item(QQmlComponent* component)
0204 {
0205     if (m_pItem)
0206         return m_pItem;
0207 
0208     m_pItem = qobject_cast<QQuickItem*>(component->create(
0209         m_pContent
0210     ));
0211 
0212     m_pItem->setParentItem(owner()->view()->contentItem());
0213 
0214     return m_pItem;
0215 }
0216 
0217 ListViewSection* ListViewItem::setSection(ListViewSection* s, const QVariant& val)
0218 {
0219     if ((!s) || s->m_Value != val)
0220         return nullptr;
0221 
0222     const auto p = static_cast<ListViewItem*>(next(Qt::TopEdge));
0223     const auto n = static_cast<ListViewItem*>(next(Qt::BottomEdge));
0224 
0225     // Garbage collect or change the old section owner
0226     if (m_pSection) {
0227         if (--m_pSection->m_RefCount <= 0)
0228             delete m_pSection;
0229         else if (p && n && p->m_pSection && p->m_pSection != m_pSection && n->m_pSection == m_pSection)
0230             m_pSection->setOwner(n);
0231         else if (p && p->m_pSection != m_pSection)
0232             Q_ASSERT(false); // There is a bug somewhere else
0233     }
0234 
0235     m_pSection = s;
0236     s->m_RefCount++;
0237 
0238     if ((!p) || p->m_pSection != s)
0239         s->setOwner(this);
0240 
0241     return s;
0242 }
0243 
0244 static void applyRoles(QQmlContext* ctx, const QModelIndex& self)
0245 {
0246     auto m = self.model();
0247 
0248     if (Q_UNLIKELY(!m))
0249         return;
0250 
0251     Q_ASSERT(self.model());
0252     const auto hash = self.model()->roleNames();
0253 
0254     // Add all roles to the
0255     for (auto i = hash.constBegin(); i != hash.constEnd(); ++i)
0256         ctx->setContextProperty(i.value() , self.data(i.key()));
0257 
0258     // Set extra index to improve ListView compatibility
0259     ctx->setContextProperty(QStringLiteral("index"        ) , self.row()        );
0260     ctx->setContextProperty(QStringLiteral("rootIndex"    ) , self              );
0261     ctx->setContextProperty(QStringLiteral("rowCount"     ) , m->rowCount(self) );
0262 }
0263 
0264 /**
0265  * No lookup is performed, it is based on the previous entry and nothing else.
0266  *
0267  * This view only supports list. If it's with a tree, it will break and "don't
0268  * do this".
0269  */
0270 ListViewSection* ListViewPrivate::getSection(ListViewItem* i)
0271 {
0272     if (m_pSections->property().isEmpty() || !m_pDelegate)
0273         return nullptr;
0274 
0275     const auto val = q_ptr->rawModel()->data(i->index(), m_pSections->role());
0276 
0277     if (i->m_pSection && i->m_pSection->m_Value == val)
0278         return i->m_pSection;
0279 
0280     const auto prev = static_cast<ListViewItem*>(i->next(Qt::TopEdge));
0281     const auto next = static_cast<ListViewItem*>(i->next(Qt::BottomEdge));
0282 
0283     // The section owner isn't currently loaded
0284     if ((!prev) && i->row() > 0) {
0285         //Q_ASSERT(false); //TODO when GC is enabled, the assert is to make sure I don't forget
0286         return nullptr;
0287     }
0288 
0289     Q_ASSERT(!i->m_pSection); //TODO
0290 
0291     // Check if the nearby sections are compatible
0292     for (auto& s : {
0293         prev ? prev->m_pSection : nullptr, m_pFirstSection, next ? next->m_pSection : nullptr
0294     }) if (auto ret = i->setSection(s, val))
0295             return ret;
0296 
0297     // Create a section
0298     i->m_pSection = new ListViewSection(i, val);
0299     Q_ASSERT(i->m_pSection->m_RefCount == 1);
0300 
0301     // Update the double linked list
0302     if (prev && prev->m_pSection) {
0303 
0304         if (prev->m_pSection->m_pNext) {
0305             prev->m_pSection->m_pNext->m_pPrevious = i->m_pSection;
0306             i->m_pSection->m_pNext = prev->m_pSection->m_pNext;
0307         }
0308 
0309         prev->m_pSection->m_pNext  = i->m_pSection;
0310         i->m_pSection->m_pPrevious = prev->m_pSection;
0311         i->m_pSection->m_Index     = prev->m_pSection->m_Index + 1;
0312 
0313         Q_ASSERT(prev->m_pSection != prev->m_pSection->m_pNext);
0314         Q_ASSERT(i->m_pSection->m_pPrevious !=  i->m_pSection);
0315     }
0316 
0317     m_pFirstSection = m_pFirstSection ?
0318         m_pFirstSection : i->m_pSection;
0319 
0320     Q_ASSERT(m_pFirstSection);
0321 
0322     if (m_pSectionModel && !m_IndexLoaded)
0323         reloadSectionIndices();
0324 
0325     if (m_pSectionModel) {
0326         const auto idx = m_pSectionModel->index(i->m_pSection->m_Index, 0);
0327         Q_ASSERT((!idx.isValid()) || idx.model() == m_pSectionModel);
0328 
0329         //note: If you wish to fork this class, you can create a second context
0330         // manager and avoid the private API. Given it is available, this isn't
0331         // done here.
0332         applyRoles( i->m_pSection->m_pContent, idx);
0333     }
0334 
0335     // Create the item *after* applyRoles to avoid O(N) number of reloads
0336     q_ptr->rootContext()->engine()->setObjectOwnership(
0337         i->m_pSection->item(m_pDelegate), QQmlEngine::CppOwnership
0338     );
0339 
0340     return i->m_pSection;
0341 }
0342 
0343 /**
0344  * Set indices for each sections to the right value.
0345  */
0346 void ListViewPrivate::reloadSectionIndices() const
0347 {
0348     int idx = 0;
0349     for (auto i = m_pFirstSection; i; i = i->m_pNext) {
0350         Q_ASSERT(i != i->m_pNext);
0351         i->m_Index = idx++;
0352     }
0353 
0354     //FIXME this assumes all sections are always loaded, this isn't correct
0355 
0356     m_IndexLoaded = m_pFirstSection != nullptr;
0357 }
0358 
0359 ListViewItem::ListViewItem(Viewport* r) : AbstractItemAdapter(r)
0360 {
0361 }
0362 
0363 ListViewPrivate* ListViewItem::d() const
0364 {
0365     return static_cast<ListView*>(view())->ListView::d_ptr;
0366 }
0367 
0368 bool ListViewItem::attach()
0369 {
0370     // This will trigger the lazy-loading of the item
0371     if (!container())
0372         return false;
0373 
0374     // When the item resizes itself
0375     QObject::connect(container(), &QQuickItem::heightChanged, container(), [this](){
0376         updateGeometry();
0377     });
0378 
0379     return move();
0380 }
0381 
0382 void ListViewSection::setOwner(ListViewItem* newParent)
0383 {
0384     if (m_pOwner == newParent)
0385         return;
0386 
0387     if (m_pOwner->container()) {
0388         auto otherAnchors = qvariant_cast<QObject*>(newParent->container()->property("anchors"));
0389         auto anchors = qvariant_cast<QObject*>(m_pOwner->container()->property("anchors"));
0390 
0391         const auto newPrevious = static_cast<ListViewItem*>(m_pOwner->next(Qt::TopEdge));
0392         Q_ASSERT(newPrevious != m_pOwner);
0393 
0394         // Prevent a loop while moving
0395         if (otherAnchors && otherAnchors->property("top") == m_pOwner->container()->property("bottom")) {
0396             anchors->setProperty("top", {});
0397             otherAnchors->setProperty("top", {});
0398             otherAnchors->setProperty("y", {});
0399         }
0400 
0401         anchors->setProperty("top", newParent->container() ?
0402             newParent->container()->property("bottom") : QVariant()
0403         );
0404 
0405         // Set the old owner new anchors
0406         if (newPrevious && newPrevious->container())
0407             anchors->setProperty("top", newPrevious->container()->property("bottom"));
0408 
0409         otherAnchors->setProperty("top", m_pItem->property("bottom"));
0410     }
0411     else
0412         newParent->container()->setY(0);
0413 
0414     // Cleanup the previous owner decoration
0415     if (m_pOwner) {
0416         m_pOwner->setBorderDecoration(Qt::TopEdge, 0);
0417     }
0418 
0419     m_pOwner = newParent;
0420 
0421     // Update the owner decoration size
0422     m_pOwner->setBorderDecoration(Qt::TopEdge, 45.0);
0423 }
0424 
0425 void ListViewSection::reparentSection(ListViewItem* newParent, ViewBase* view)
0426 {
0427     if (!m_pItem)
0428         return;
0429 
0430     if (newParent && newParent->container()) {
0431         auto anchors = qvariant_cast<QObject*>(m_pItem->property("anchors"));
0432         anchors->setProperty("top", newParent->container()->property("bottom"));
0433 
0434         m_pItem->setParentItem(owner()->view()->contentItem());
0435     }
0436     else {
0437         auto anchors = qvariant_cast<QObject*>(m_pItem->property("anchors"));
0438         anchors->setProperty("top", {});
0439         m_pItem->setY(0);
0440     }
0441 
0442     if (!m_pItem->width())
0443         m_pItem->setWidth(view->contentItem()->width());
0444 
0445     // Update the chain //FIXME crashes
0446     /*if (newParent && newParent->m_pSection && newParent->m_pSection->m_pNext != this) {
0447         Q_ASSERT(newParent->m_pSection != this);
0448         Q_ASSERT(newParent->m_pSection->m_pNext != this);
0449         Q_ASSERT(newParent->m_pSection->m_pNext->m_pPrevious != this);
0450         m_pNext = newParent->m_pSection->m_pNext;
0451         newParent->m_pSection->m_pNext = this;
0452         m_pPrevious = newParent->m_pSection;
0453         if (m_pNext) {
0454             m_pNext->m_pPrevious = this;
0455             Q_ASSERT(m_pNext != this);
0456         }
0457         Q_ASSERT(m_pPrevious != this);
0458     }
0459     else if (!newParent) {
0460         m_pPrevious = nullptr;
0461         m_pNext = d_ptr->m_pFirstSection;
0462         d_ptr->m_pFirstSection = this;
0463     }*/
0464 }
0465 
0466 bool ListViewItem::move()
0467 {
0468     if (!container())
0469         return false;
0470 
0471     auto prev = static_cast<ListViewItem*>(next(Qt::TopEdge));
0472 
0473     const QQuickItem* prevItem = nullptr;
0474 
0475     if (d()->m_pSections)
0476         if (auto sec = d()->getSection(this)) {
0477             // The item is no longer the first in the section
0478             if (sec->owner() == this) {
0479 
0480                 ListViewItem* newOwner = nullptr;
0481                 while (prev && prev->m_pSection == sec) {
0482                     newOwner = prev;
0483                     prev = static_cast<ListViewItem*>(prev->next(Qt::TopEdge));
0484                 }
0485 
0486                 if (newOwner) {
0487                     if (newOwner == static_cast<ListViewItem*>(next(Qt::TopEdge)))
0488                         prev = newOwner;
0489 
0490                     sec->setOwner(newOwner);
0491                 }
0492                 sec->reparentSection(prev, view());
0493 
0494                 if (sec->m_pItem)
0495                     prevItem = sec->m_pItem;
0496 
0497             }
0498             else if (sec->owner()->row() > row()) { //TODO remove once correctly implemented
0499                 //HACK to force reparenting when the elements move up
0500                 sec->setOwner(this);
0501                 sec->reparentSection(prev, view());
0502                 prevItem = sec->m_pItem;
0503             }
0504         }
0505 
0506     // Reset the "real" previous element
0507     prev = static_cast<ListViewItem*>(next(Qt::TopEdge));
0508 
0509     const qreal y = d()->m_DepthChart.first()*row();
0510 
0511     if (container()->width() != view()->contentItem()->width())
0512         container()->setWidth(view()->contentItem()->width());
0513 
0514     prevItem = prevItem ? prevItem : prev ? prev->container() : nullptr;
0515 
0516     // So other items can be GCed without always resetting to 0x0, note that it
0517     // might be a good idea to extend Flickable to support a virtual
0518     // origin point.
0519     if (!prevItem)
0520         container()->setY(y);
0521     else {
0522         // Row can be 0 if there is a section
0523         Q_ASSERT(row() || (!prev) || (!prev->container()));
0524 
0525         // Prevent loops when swapping 2 items
0526         auto otherAnchors = qvariant_cast<QObject*>(prevItem->property("anchors"));
0527         auto anchors = qvariant_cast<QObject*>(container()->property("anchors"));
0528 
0529         if (otherAnchors && otherAnchors->property("top") == container()->property("bottom")) {
0530             anchors->setProperty("top", {});
0531             otherAnchors->setProperty("top", {});
0532             otherAnchors->setProperty("y", {});
0533         }
0534 
0535         // Avoid creating too many race conditions
0536         if (anchors->property("top") != prevItem->property("bottom"))
0537             anchors->setProperty("top", prevItem->property("bottom"));
0538 
0539     }
0540 
0541     updateGeometry();
0542 
0543     return true;
0544 }
0545 
0546 bool ListViewItem::remove()
0547 {
0548     if (m_pSection && --m_pSection->m_RefCount <= 0) {
0549         delete m_pSection;
0550     }
0551     else if (m_pSection && m_pSection->owner() == this) {
0552         // Reparent the section
0553         if (auto n = static_cast<ListViewItem*>(next(Qt::BottomEdge))) {
0554             if (n->m_pSection == m_pSection)
0555                 m_pSection->setOwner(n);
0556             /*else
0557                 Q_ASSERT(false);*/
0558         }
0559         /*else
0560             Q_ASSERT(false);*/
0561     }
0562 
0563     m_pSection = nullptr;
0564 
0565     //TODO move back into treeview2
0566     //TODO check if the item has references, if it does, just release the shared
0567     // pointer and move on.
0568     return true;
0569 }
0570 
0571 ListViewSections::ListViewSections(ListView* parent) :
0572     QObject(parent), d_ptr(parent->d_ptr)
0573 {
0574 }
0575 
0576 ListViewSection::~ListViewSection()
0577 {
0578 
0579     if (m_pPrevious) {
0580         Q_ASSERT(m_pPrevious != m_pNext);
0581         m_pPrevious->m_pNext = m_pNext;
0582     }
0583 
0584     if (m_pNext)
0585         m_pNext->m_pPrevious = m_pPrevious;
0586 
0587     if (this == d_ptr->m_pFirstSection)
0588         d_ptr->m_pFirstSection = m_pNext;
0589 
0590     d_ptr->m_IndexLoaded = false;
0591 
0592     if (m_pItem)
0593         delete m_pItem;
0594 
0595     if (m_pContent)
0596         delete m_pContent;
0597 }
0598 
0599 ListViewSections::~ListViewSections()
0600 {}
0601 
0602 QQmlComponent* ListViewSections::delegate() const
0603 {
0604     return d_ptr->m_pDelegate;
0605 }
0606 
0607 void ListViewSections::setDelegate(QQmlComponent* component)
0608 {
0609     d_ptr->m_pDelegate = component;
0610 }
0611 
0612 QString ListViewSections::property() const
0613 {
0614     return d_ptr->m_Property;
0615 }
0616 
0617 int ListViewSections::role() const
0618 {
0619     if (d_ptr->m_Property.isEmpty() || !d_ptr->q_ptr->rawModel())
0620         return Qt::DisplayRole;
0621 
0622     if (d_ptr->m_CachedRole)
0623         return d_ptr->m_CachedRole;
0624 
0625     const auto roles = d_ptr->q_ptr->rawModel()->roleNames();
0626 
0627     if (!(d_ptr->m_CachedRole = roles.key(d_ptr->m_Property.toLatin1()))) {
0628         qWarning() << d_ptr->m_Property << "is not a model property";
0629         return Qt::DisplayRole;
0630     }
0631 
0632     return d_ptr->m_CachedRole;
0633 }
0634 
0635 void ListViewSections::setProperty(const QString& property)
0636 {
0637     d_ptr->m_Property = property;
0638 }
0639 
0640 QStringList ListViewSections::roles() const
0641 {
0642     return d_ptr->m_Roles;
0643 }
0644 
0645 void ListViewSections::setRoles(const QStringList& list)
0646 {
0647     d_ptr->m_Roles = list;
0648 }
0649 
0650 QSharedPointer<QAbstractItemModel> ListViewSections::model() const
0651 {
0652     return d_ptr->m_pSectionModel;
0653 }
0654 
0655 void ListViewSections::setModel(const QSharedPointer<QAbstractItemModel>& m)
0656 {
0657     d_ptr->m_pSectionModel = m;
0658 }
0659 
0660 void ListViewPrivate::slotCurrentIndexChanged(const QModelIndex& index)
0661 {
0662     emit q_ptr->indexChanged(index.row());
0663 }
0664 
0665 ListViewItem *ListViewSection::owner() const
0666 {
0667     return m_pOwner;
0668 }
0669 
0670 void ListView::applyModelChanges(QAbstractItemModel* m)
0671 {
0672     // Delete the sections
0673     while(auto sec = d_ptr->m_pFirstSection)
0674         delete sec;
0675 
0676     d_ptr->m_pFirstSection = nullptr;
0677 }
0678 
0679 #include <listview.moc>