File indexing completed on 2024-04-28 04:41:47
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 "abstractitemadapter.h" 0019 0020 // libstdc++ 0021 #include <atomic> 0022 0023 // Qt 0024 #include <QtCore/QTimer> 0025 #include <QQmlContext> 0026 #include <QQuickItem> 0027 #include <QQmlEngine> 0028 0029 // KQuickItemViews 0030 #include "private/statetracker/viewitem_p.h" 0031 #include "adapters/selectionadapter.h" 0032 #include "adapters/geometryadapter.h" 0033 #include "private/selectionadapter_p.h" 0034 #include "private/geostrategyselector_p.h" 0035 #include "viewport.h" 0036 #include "private/indexmetadata_p.h" 0037 #include "private/viewport_p.h" 0038 #include "modeladapter.h" 0039 #include "private/statetracker/index_p.h" 0040 #include "viewbase.h" 0041 #include "contextadapterfactory.h" 0042 #include "contextadapter.h" 0043 0044 class AbstractItemAdapterPrivate : public QObject 0045 { 0046 Q_OBJECT 0047 public: 0048 typedef bool(AbstractItemAdapterPrivate::*StateF)(); 0049 0050 // Helpers 0051 inline void load(); 0052 0053 // Actions 0054 bool attach (); 0055 bool refresh(); 0056 bool move (); 0057 bool flush (); 0058 bool remove (); 0059 bool nothing(); 0060 bool error (); 0061 bool destroy(); 0062 bool detach (); 0063 bool hide (); 0064 0065 static const StateTracker::ViewItem::State m_fStateMap [7][7]; 0066 static const StateF m_fStateMachine[7][7]; 0067 0068 mutable QSharedPointer<AbstractItemAdapter::SelectionLocker> m_pLocker; 0069 0070 mutable QQuickItem *m_pContainer {nullptr}; 0071 mutable QQuickItem *m_pContent {nullptr}; 0072 mutable QQmlContext *m_pContext {nullptr}; 0073 0074 // Helpers 0075 bool loadDelegate(QQuickItem* parentI) const; 0076 0077 // Attributes 0078 AbstractItemAdapter* q_ptr; 0079 0080 public Q_SLOTS: 0081 void slotDestroyed(); 0082 }; 0083 0084 /* 0085 * The visual elements state changes. 0086 * 0087 * Note that the ::FAILED elements will always try to self-heal themselves and 0088 * go back into FAILED once the self-healing itself failed. 0089 */ 0090 #define S StateTracker::ViewItem::State:: 0091 const StateTracker::ViewItem::State AbstractItemAdapterPrivate::m_fStateMap[7][7] = { 0092 /* ATTACH ENTER_BUFFER ENTER_VIEW UPDATE MOVE LEAVE_BUFFER DETACH */ 0093 /*POOLING */ { S POOLING, S BUFFER, S ERROR , S ERROR , S ERROR , S ERROR , S POOLED }, 0094 /*POOLED */ { S POOLED , S BUFFER, S ACTIVE, S ERROR , S ERROR , S ERROR , S DANGLING }, 0095 /*BUFFER */ { S ERROR , S ERROR , S ACTIVE, S BUFFER, S ERROR , S POOLING, S DANGLING }, 0096 /*ACTIVE */ { S ERROR , S BUFFER, S ERROR , S ACTIVE, S ACTIVE, S BUFFER , S POOLING }, 0097 /*FAILED */ { S ERROR , S BUFFER, S ACTIVE, S ACTIVE, S ACTIVE, S POOLED , S DANGLING }, 0098 /*DANGLING*/ { S ERROR , S ERROR , S ERROR , S ERROR , S ERROR , S ERROR , S DANGLING }, 0099 /*ERROR */ { S ERROR , S ERROR , S ERROR , S ERROR , S ERROR , S ERROR , S DANGLING }, 0100 }; 0101 #undef S 0102 0103 #define A &AbstractItemAdapterPrivate:: 0104 const AbstractItemAdapterPrivate::StateF AbstractItemAdapterPrivate::m_fStateMachine[7][7] = { 0105 /* ATTACH ENTER_BUFFER ENTER_VIEW UPDATE MOVE LEAVE_BUFFER DETACH */ 0106 /*POOLING */ { A error , A error , A error , A error , A error , A error , A nothing }, 0107 /*POOLED */ { A nothing, A attach , A move , A error , A error , A error , A destroy }, 0108 /*BUFFER */ { A error , A error , A move , A refresh, A error , A detach , A destroy }, 0109 /*ACTIVE */ { A error , A nothing, A nothing, A refresh, A move , A hide , A detach }, 0110 /*FAILED */ { A error , A nothing, A nothing, A nothing, A nothing, A nothing, A destroy }, 0111 /*DANGLING*/ { A error , A error , A error , A error , A error , A error , A destroy }, 0112 /*error */ { A error , A error , A error , A error , A error , A error , A destroy }, 0113 }; 0114 #undef A 0115 0116 AbstractItemAdapter::AbstractItemAdapter(Viewport* r) : 0117 s_ptr(new StateTracker::ViewItem(r)), d_ptr(new AbstractItemAdapterPrivate) 0118 { 0119 d_ptr->q_ptr = this; 0120 s_ptr->d_ptr = this; 0121 } 0122 0123 AbstractItemAdapter::~AbstractItemAdapter() 0124 { 0125 if (d_ptr->m_pContainer) 0126 delete d_ptr->m_pContainer; 0127 0128 if (d_ptr->m_pContext) 0129 delete d_ptr->m_pContext; 0130 0131 delete d_ptr; 0132 } 0133 0134 ViewBase* AbstractItemAdapter::view() const 0135 { 0136 return s_ptr->m_pViewport->modelAdapter()->view(); 0137 } 0138 0139 void AbstractItemAdapter::resetPosition() 0140 { 0141 // 0142 } 0143 0144 int AbstractItemAdapter::row() const 0145 { 0146 return s_ptr->row(); 0147 } 0148 0149 int AbstractItemAdapter::column() const 0150 { 0151 return s_ptr->column(); 0152 } 0153 0154 QPersistentModelIndex AbstractItemAdapter::index() const 0155 { 0156 return s_ptr->index(); 0157 } 0158 0159 AbstractItemAdapter *AbstractItemAdapter::next(Qt::Edge e) const 0160 { 0161 const auto i = s_ptr->next(e); 0162 return i ? i->d_ptr : nullptr; 0163 } 0164 0165 AbstractItemAdapter* AbstractItemAdapter::parent() const 0166 { 0167 return nullptr; 0168 } 0169 0170 void AbstractItemAdapter::updateGeometry() 0171 { 0172 s_ptr->updateGeometry(); 0173 } 0174 0175 void StateTracker::ViewItem::setSelected(bool v) 0176 { 0177 d_ptr->setSelected(v); 0178 } 0179 0180 QRectF StateTracker::ViewItem::currentGeometry() const 0181 { 0182 return d_ptr->geometry(); 0183 } 0184 0185 QQuickItem* StateTracker::ViewItem::item() const 0186 { 0187 return d_ptr->container(); 0188 } 0189 0190 bool AbstractItemAdapterPrivate::attach() 0191 { 0192 if (m_pContainer) 0193 m_pContainer->setVisible(true); 0194 0195 return q_ptr->attach(); 0196 } 0197 0198 bool AbstractItemAdapterPrivate::refresh() 0199 { 0200 return q_ptr->refresh(); 0201 } 0202 0203 bool AbstractItemAdapterPrivate::move() 0204 { 0205 const bool ret = q_ptr->move(); 0206 0207 // Views should apply the geometry they have been told to apply. Otherwise 0208 // all optimizations brakes. 0209 // Q_ASSERT((!ret) || q_ptr->s_ptr->m_pMetadata->isInSync()); 0210 0211 return ret; 0212 } 0213 0214 bool AbstractItemAdapterPrivate::flush() 0215 { 0216 return q_ptr->flush(); 0217 } 0218 0219 bool AbstractItemAdapterPrivate::remove() 0220 { 0221 bool ret = q_ptr->remove(); 0222 0223 m_pContainer->setParentItem(nullptr); 0224 q_ptr->s_ptr->m_pMetadata = nullptr; 0225 0226 return ret; 0227 } 0228 0229 bool AbstractItemAdapterPrivate::hide() 0230 { 0231 Q_ASSERT(m_pContainer); 0232 if (!m_pContainer) 0233 return false; 0234 0235 m_pContainer->setVisible(false); 0236 0237 return true; 0238 } 0239 0240 // This methodwrap the removal of the element from the view 0241 bool AbstractItemAdapterPrivate::detach() 0242 { 0243 m_pContainer->setParentItem(nullptr); 0244 remove(); 0245 0246 //FIXME 0247 q_ptr->s_ptr->m_pMetadata << IndexMetadata::ViewAction::DETACH; 0248 Q_ASSERT(q_ptr->s_ptr->state() == StateTracker::ViewItem::State::POOLED); 0249 0250 return true; 0251 } 0252 0253 bool AbstractItemAdapterPrivate::nothing() 0254 { 0255 return true; 0256 } 0257 0258 #pragma GCC diagnostic push 0259 #pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn" 0260 bool AbstractItemAdapterPrivate::error() 0261 { 0262 Q_ASSERT(false); 0263 return true; 0264 } 0265 #pragma GCC diagnostic pop 0266 0267 bool AbstractItemAdapterPrivate::destroy() 0268 { 0269 auto ptrCopy = m_pLocker; 0270 0271 //FIXME manage to add to the pool without a SEGFAULT 0272 if (m_pContainer) { 0273 m_pContainer->setParentItem(nullptr); 0274 delete m_pContainer; 0275 } 0276 0277 m_pContainer = nullptr; 0278 m_pContent = nullptr; 0279 0280 if (ptrCopy) { 0281 QTimer::singleShot(0,[this, ptrCopy]() { 0282 if (!ptrCopy) 0283 // else the reference will be dropped and the destructor called 0284 delete q_ptr; 0285 }); 0286 if (m_pLocker) 0287 m_pLocker.clear(); 0288 } 0289 else { 0290 delete q_ptr; 0291 } 0292 0293 return true; 0294 } 0295 0296 bool StateTracker::ViewItem::performAction(IndexMetadata::ViewAction a) 0297 { 0298 const int s = (int)m_State; 0299 0300 m_State = d_ptr->d_ptr->m_fStateMap [s][(int)a]; 0301 Q_ASSERT(m_State != StateTracker::ViewItem::State::ERROR); 0302 0303 return (d_ptr->d_ptr ->* d_ptr->d_ptr->m_fStateMachine[s][(int)a])(); 0304 } 0305 0306 int StateTracker::ViewItem::depth() const 0307 { 0308 return m_pMetadata->indexTracker()->depth(); 0309 } 0310 0311 QPair<QWeakPointer<AbstractItemAdapter::SelectionLocker>, AbstractItemAdapter*> 0312 AbstractItemAdapter::weakReference() const 0313 { 0314 if (d_ptr->m_pLocker) 0315 return {d_ptr->m_pLocker, const_cast<AbstractItemAdapter*>(this)}; 0316 0317 d_ptr->m_pLocker = QSharedPointer<SelectionLocker>(new SelectionLocker()); 0318 0319 0320 return {d_ptr->m_pLocker, const_cast<AbstractItemAdapter*>(this)}; 0321 } 0322 0323 void AbstractItemAdapterPrivate::load() 0324 { 0325 if (m_pContext || m_pContainer) 0326 return; 0327 0328 // Usually if we get here something already failed 0329 if (!q_ptr->s_ptr->m_pViewport->modelAdapter()->delegate()) { 0330 Q_ASSERT(false); 0331 qDebug() << "Cannot attach, there is no delegate"; 0332 return; 0333 } 0334 0335 loadDelegate(q_ptr->view()->contentItem()); 0336 0337 if (!m_pContainer) { 0338 qDebug() << "Item failed to load" << q_ptr->index().data(); 0339 return; 0340 } 0341 0342 if (!m_pContainer->z()) 0343 m_pContainer->setZ(1); 0344 0345 /*d()->m_DepthChart[depth()] = std::max( 0346 d()->m_DepthChart[depth()], 0347 pair.first->height() 0348 );*/ 0349 0350 // QtQuick can decide to destroy it even with C++ ownership, so be it 0351 connect(m_pContainer, &QObject::destroyed, this, &AbstractItemAdapterPrivate::slotDestroyed); 0352 0353 Q_ASSERT(q_ptr->s_ptr->m_pMetadata); 0354 0355 q_ptr->s_ptr->m_pMetadata->contextAdapter()->context(); 0356 0357 Q_ASSERT(q_ptr->s_ptr->m_pMetadata->contextAdapter()->context() == m_pContext); 0358 0359 if (q_ptr->s_ptr->m_pViewport->s_ptr->m_pGeoAdapter->capabilities() & GeometryAdapter::Capabilities::HAS_AHEAD_OF_TIME) { 0360 q_ptr->s_ptr->m_pMetadata->performAction( 0361 IndexMetadata::GeometryAction::MODIFY 0362 ); 0363 } 0364 else { 0365 //TODO it should still call the GeometryAdapter, but most of them are 0366 // currently too buggy for that to help. 0367 q_ptr->s_ptr->m_pMetadata->setSize( 0368 QSizeF(m_pContainer->width(), m_pContainer->height()) 0369 ); 0370 } 0371 0372 Q_ASSERT(q_ptr->s_ptr->m_pMetadata->geometryTracker()->state() != StateTracker::Geometry::State::INIT); 0373 } 0374 0375 QQmlContext *AbstractItemAdapter::context() const 0376 { 0377 d_ptr->load(); 0378 return d_ptr->m_pContext; 0379 } 0380 0381 QQuickItem *AbstractItemAdapter::container() const 0382 { 0383 d_ptr->load(); 0384 return d_ptr->m_pContainer; 0385 } 0386 0387 QQuickItem *AbstractItemAdapter::content() const 0388 { 0389 d_ptr->load(); 0390 return d_ptr->m_pContent; 0391 } 0392 0393 Viewport *AbstractItemAdapter::viewport() const 0394 { 0395 return s_ptr->m_pViewport; 0396 } 0397 0398 QRectF AbstractItemAdapter::geometry() const 0399 { 0400 if (!d_ptr->m_pContainer) 0401 return {}; 0402 0403 const QPointF p = container()->mapFromItem(view()->contentItem(), {0,0}); 0404 return { 0405 -p.x(), 0406 -p.y(), 0407 container()->width(), 0408 container()->height() 0409 }; 0410 } 0411 0412 QRectF AbstractItemAdapter::decoratedGeometry() const 0413 { 0414 return s_ptr->m_pMetadata->decoratedGeometry(); 0415 } 0416 0417 qreal AbstractItemAdapter::borderDecoration(Qt::Edge e) const 0418 { 0419 return s_ptr->m_pMetadata->borderDecoration(e); 0420 } 0421 0422 void AbstractItemAdapter::setBorderDecoration(Qt::Edge e, qreal r) 0423 { 0424 s_ptr->m_pMetadata->setBorderDecoration(e, r); 0425 } 0426 0427 bool AbstractItemAdapter::refresh() 0428 { 0429 return true; 0430 } 0431 0432 bool AbstractItemAdapter::remove() 0433 { 0434 return true; 0435 } 0436 0437 bool AbstractItemAdapter::move() 0438 { 0439 using Cap = GeometryAdapter::Capabilities; 0440 const auto a = s_ptr->m_pViewport->geometryAdapter(); 0441 Q_ASSERT(a); 0442 0443 const auto size = a->sizeHint(index(), this); 0444 0445 if (content() && a->capabilities() & Cap::FORCE_DELEGATE_SIZE) { 0446 content()->setSize(size); 0447 } 0448 0449 const auto pos = a->positionHint(index(), this); 0450 container()->setSize(size); 0451 0452 container()->setX(pos.x()); 0453 container()->setY(pos.y()); 0454 0455 return true; 0456 } 0457 0458 bool AbstractItemAdapter::attach() 0459 { 0460 Q_ASSERT(index().isValid()); 0461 0462 return container();// && move(); 0463 } 0464 0465 bool AbstractItemAdapter::flush() 0466 { 0467 return true; 0468 } 0469 0470 void AbstractItemAdapter::setSelected(bool /*s*/) 0471 {} 0472 0473 bool AbstractItemAdapterPrivate::loadDelegate(QQuickItem* parentI) const 0474 { 0475 const auto delegate = q_ptr->s_ptr->m_pViewport->modelAdapter()->delegate(); 0476 if (!delegate) { 0477 qWarning() << "No delegate is set"; 0478 return false; 0479 } 0480 0481 const auto engine = q_ptr->s_ptr->m_pViewport->s_ptr->engine(); 0482 0483 auto pctx = q_ptr->s_ptr->m_pMetadata->contextAdapter()->context(); 0484 0485 // Create a parent item to hold the delegate and all children 0486 auto container = qobject_cast<QQuickItem *>(q_ptr->s_ptr->m_pViewport->s_ptr->component()->create(pctx)); 0487 container->setWidth(q_ptr->view()->width()); 0488 engine->setObjectOwnership(container, QQmlEngine::CppOwnership); 0489 container->setParentItem(parentI); 0490 0491 m_pContext = pctx; 0492 m_pContainer = container; 0493 0494 // Create a context with all the tree roles 0495 auto ctx = new QQmlContext(pctx); 0496 0497 // Create the delegate 0498 m_pContent = qobject_cast<QQuickItem *>(delegate->create(ctx)); 0499 0500 // It allows the children to be added anyway 0501 if(!m_pContent) { 0502 if (!delegate->errorString().isEmpty()) 0503 qWarning() << delegate->errorString(); 0504 0505 return true; 0506 } 0507 0508 engine->setObjectOwnership(m_pContent, QQmlEngine::CppOwnership); 0509 0510 m_pContent->setWidth(q_ptr->view()->width()); 0511 m_pContent->setParentItem(container); 0512 0513 const auto a = q_ptr->s_ptr->m_pViewport->geometryAdapter(); 0514 0515 // Resize the container 0516 if (!(a->capabilities() & GeometryAdapter::Capabilities::FORCE_DELEGATE_SIZE)) 0517 container->setHeight(m_pContent->height()); 0518 0519 // Make sure it can be resized dynamically 0520 QObject::connect(m_pContent, &QQuickItem::heightChanged, container, [container, this]() { 0521 const auto a = q_ptr->s_ptr->m_pViewport->geometryAdapter(); 0522 if (!(a->capabilities() & GeometryAdapter::Capabilities::FORCE_DELEGATE_SIZE)) 0523 container->setHeight(m_pContent->height()); 0524 }); 0525 0526 return true; 0527 } 0528 0529 void StateTracker::ViewItem::updateGeometry() 0530 { 0531 const auto sm = m_pViewport->modelAdapter()->selectionAdapter(); 0532 0533 if (sm && sm->selectionModel() && sm->selectionModel()->currentIndex() == index()) 0534 sm->s_ptr->updateSelection(index()); 0535 0536 m_pViewport->s_ptr->geometryUpdated(m_pMetadata); 0537 } 0538 0539 QQmlContext *StateTracker::ViewItem::context() const 0540 { 0541 return d_ptr->d_ptr->m_pContext; 0542 } 0543 0544 void AbstractItemAdapter::setCollapsed(bool v) 0545 { 0546 s_ptr->setCollapsed(v); 0547 } 0548 0549 bool AbstractItemAdapter::isCollapsed() const 0550 { 0551 return s_ptr->isCollapsed(); 0552 } 0553 0554 QSizeF AbstractItemAdapter::sizeHint() const 0555 { 0556 return s_ptr->m_pMetadata->sizeHint(); 0557 } 0558 0559 QPersistentModelIndex StateTracker::ViewItem::index() const 0560 { 0561 return QModelIndex(m_pMetadata->indexTracker()->index()); 0562 } 0563 0564 /** 0565 * Flatten the tree as a linked list. 0566 * 0567 * Returns the previous non-failed item. 0568 */ 0569 StateTracker::ViewItem* StateTracker::ViewItem::up() const 0570 { 0571 Q_ASSERT(m_State == State::ACTIVE 0572 || m_State == State::BUFFER 0573 || m_State == State::FAILED 0574 || m_State == State::POOLING 0575 || m_State == State::DANGLING //FIXME add a new state for 0576 // "deletion in progress" or swap the f call and set state 0577 ); 0578 0579 auto ret = m_pMetadata->indexTracker()->up(); 0580 //TODO support collapsed nodes 0581 0582 // Linearly look for a valid element. Doing this here allows the views 0583 // that implement this (abstract) class to work without having to always 0584 // check if some of their item failed to load. This is non-fatal in the 0585 // other Qt views, so it isn't fatal here either. 0586 while (ret && !ret->metadata()->viewTracker()) 0587 ret = ret->up(); 0588 0589 if (ret && ret->metadata()->viewTracker()) 0590 Q_ASSERT(ret->metadata()->viewTracker()->m_State != State::POOLING && ret->metadata()->viewTracker()->m_State != State::DANGLING); 0591 0592 auto vi = ret ? ret->metadata()->viewTracker() : nullptr; 0593 0594 // Do not allow navigating past the loaded edges 0595 if (vi && (vi->m_State == State::POOLING || vi->m_State == State::POOLED)) 0596 return nullptr; 0597 0598 return vi; 0599 } 0600 0601 /** 0602 * Flatten the tree as a linked list. 0603 * 0604 * Returns the next non-failed item. 0605 */ 0606 StateTracker::ViewItem* StateTracker::ViewItem::down() const 0607 { 0608 Q_ASSERT(m_State == State::ACTIVE 0609 || m_State == State::BUFFER 0610 || m_State == State::FAILED 0611 || m_State == State::POOLING 0612 || m_State == State::DANGLING //FIXME add a new state for 0613 // "deletion in progress" or swap the f call and set state 0614 ); 0615 0616 auto ret = m_pMetadata->indexTracker(); 0617 //TODO support collapsed entries 0618 0619 // Recursively look for a valid element. Doing this here allows the views 0620 // that implement this (abstract) class to work without having to always 0621 // check if some of their item failed to load. This is non-fatal in the 0622 // other Qt views, so it isn't fatal here either. 0623 while (ret && !ret->metadata()->viewTracker()) 0624 ret = ret->down(); 0625 0626 auto vi = ret ? ret->metadata()->viewTracker() : nullptr; 0627 0628 // Do not allow navigating past the loaded edges 0629 if (vi && (vi->m_State == State::POOLING || vi->m_State == State::POOLED)) 0630 return nullptr; 0631 0632 return vi; 0633 } 0634 0635 StateTracker::ViewItem *StateTracker::ViewItem::next(Qt::Edge e) const 0636 { 0637 switch (e) { 0638 case Qt::TopEdge: 0639 return up(); 0640 case Qt::BottomEdge: 0641 return down(); 0642 case Qt::LeftEdge: 0643 return left(); 0644 case Qt::RightEdge: 0645 return right(); 0646 } 0647 0648 Q_ASSERT(false); 0649 return {}; 0650 } 0651 0652 int StateTracker::ViewItem::row() const 0653 { 0654 return m_pMetadata->indexTracker()->effectiveRow(); 0655 } 0656 0657 int StateTracker::ViewItem::column() const 0658 { 0659 return m_pMetadata->indexTracker()->effectiveColumn(); 0660 } 0661 0662 void StateTracker::ViewItem::setCollapsed(bool v) 0663 { 0664 m_pMetadata->setCollapsed(v); 0665 } 0666 0667 bool StateTracker::ViewItem::isCollapsed() const 0668 { 0669 return m_pMetadata->isCollapsed(); 0670 } 0671 0672 StateTracker::ViewItem::State StateTracker::ViewItem::state() const 0673 { 0674 return m_State; 0675 } 0676 0677 void AbstractItemAdapterPrivate::slotDestroyed() 0678 { 0679 m_pContainer = nullptr; 0680 m_pContent = nullptr; 0681 } 0682 0683 #include <abstractitemadapter.moc>