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>