File indexing completed on 2024-11-24 04:53:27

0001 /* Copyright (C) 2006 - 2016 Jan Kundrát <jkt@kde.org>
0002 
0003    This file is part of the Trojita Qt IMAP e-mail client,
0004    http://trojita.flaska.net/
0005 
0006    This program is free software; you can redistribute it and/or
0007    modify it under the terms of the GNU General Public License as
0008    published by the Free Software Foundation; either version 2 of
0009    the License or (at your option) version 3 or any later version
0010    accepted by the membership of KDE e.V. (or its successor approved
0011    by the membership of KDE e.V.), which shall act as a proxy
0012    defined in Section 14 of version 3 of the license.
0013 
0014    This program is distributed in the hope that it will be useful,
0015    but WITHOUT ANY WARRANTY; without even the implied warranty of
0016    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0017    GNU General Public License for more details.
0018 
0019    You should have received a copy of the GNU General Public License
0020    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0021 */
0022 
0023 #include "Common/StashingReverseIterator.h"
0024 #include "UiUtils/QaimDfsIterator.h"
0025 
0026 namespace UiUtils {
0027 
0028 QaimDfsIterator::QaimDfsIterator(const QModelIndex &index)
0029     : m_current(index)
0030     , m_model(index.model())
0031 {
0032     Q_ASSERT(m_model);
0033 }
0034 
0035 QaimDfsIterator::QaimDfsIterator(const QModelIndex &index, const QAbstractItemModel *model)
0036     : m_current(index)
0037     , m_model(model)
0038 {
0039     Q_ASSERT(m_model);
0040     Q_ASSERT(!index.isValid() || m_model == index.model());
0041 }
0042 
0043 QaimDfsIterator::QaimDfsIterator()
0044     : m_model(nullptr)
0045 {
0046 }
0047 
0048 QaimDfsIterator &QaimDfsIterator::operator++()
0049 {
0050     bool wentUp = false;
0051     while (m_current.isValid()) {
0052         // if there are (unvisited) children, descent into them
0053         auto firstChild = m_model->index(0, 0, m_current);
0054         if (!wentUp && firstChild.isValid()) {
0055             m_current = firstChild;
0056             return *this;
0057         }
0058 
0059         // if there are siblings, go there; we surely haven't been there yet
0060         auto nextSibling = m_current.sibling(m_current.row() + 1, 0);
0061         if (nextSibling.isValid()) {
0062             m_current = nextSibling;
0063             return *this;
0064         }
0065 
0066         // else, check our parent
0067         m_current = m_current.parent();
0068         wentUp = true;
0069     }
0070     Q_ASSERT(!m_current.isValid());
0071     return *this;
0072 }
0073 
0074 QaimDfsIterator &QaimDfsIterator::operator--()
0075 {
0076     auto deepestChild = [](const QAbstractItemModel *model, QModelIndex where) -> QModelIndex {
0077         while (model->hasChildren(where)) {
0078             where = model->index(model->rowCount(where) - 1, 0, where);
0079         }
0080         return where;
0081     };
0082 
0083     // special case: do not wrap around
0084     if (!m_model) {
0085         return *this;
0086     }
0087 
0088     // special case: if we're at the end, find "the last child"
0089     if (!m_current.isValid()) {
0090         m_current = deepestChild(m_model, QModelIndex());
0091         return *this;
0092     }
0093 
0094     // check if we can visit our previous sibling
0095     auto previousSibling = m_current.sibling(m_current.row() - 1, 0);
0096     if (previousSibling.isValid()) {
0097         // actually, its children, if there are any
0098         m_current = deepestChild(previousSibling.model(), previousSibling);
0099         return *this;
0100     }
0101 
0102     // OK, we're a first child of something, apparently
0103     Q_ASSERT(m_current.row() == 0);
0104     if (!m_current.parent().isValid()) {
0105         // a special case: prevent circling back to the very end
0106         m_model = nullptr;
0107     }
0108     m_current = m_current.parent();
0109     return *this;
0110 }
0111 
0112 const QModelIndex &QaimDfsIterator::operator*() const
0113 {
0114     return m_current;
0115 }
0116 
0117 const QModelIndex *QaimDfsIterator::operator->() const
0118 {
0119     return &m_current;
0120 }
0121 
0122 bool QaimDfsIterator::operator==(const QaimDfsIterator &other)
0123 {
0124     if (m_model) {
0125         return m_current == other.m_current;
0126     } else {
0127         return !other.m_model || !other.m_current.isValid();
0128     }
0129 }
0130 
0131 bool QaimDfsIterator::operator!=(const QaimDfsIterator &other)
0132 {
0133     return m_current != other.m_current;
0134     // yes, this ignores m_model, which means that an iterator which got decremented too much
0135     // won't be considered different from one which is at the end
0136 }
0137 
0138 /** @short Search between start1 and end1, wrap around to start2 and continue to end2 */
0139 template <typename Iterator>
0140 static void wrappedFind(Iterator start1, Iterator end1, Iterator start2, Iterator end2,
0141                         std::function<bool(typename std::iterator_traits<Iterator>::reference)> itValidityChecker,
0142                         std::function<bool(typename std::iterator_traits<Iterator>::reference)> matcher,
0143                         std::function<void(typename std::iterator_traits<Iterator>::reference)> onSuccess,
0144                         std::function<void()> onFailure)
0145 {
0146     auto it = start1;
0147     auto rejectedResult = end1;
0148     if (itValidityChecker(*it)) {
0149         it = std::find_if(it, end1, matcher);
0150     }
0151 
0152     if (!itValidityChecker(*it)) {
0153         it = std::find_if(start2, end2, matcher);
0154         rejectedResult = end2;
0155     }
0156 
0157     if (itValidityChecker(*it) && it != rejectedResult) {
0158         onSuccess(*it);
0159     } else {
0160         onFailure();
0161     }
0162 }
0163 
0164 void gotoNext(const QAbstractItemModel *model, const QModelIndex &currentIndex,
0165               std::function<bool(const QModelIndex &)> matcher,
0166               std::function<void(const QModelIndex &)> onSuccess,
0167               std::function<void()> onFailure)
0168 {
0169     auto it = UiUtils::QaimDfsIterator(currentIndex, model);
0170     ++it;
0171     // explicitly specify the template to help GCC 4.8.5 realize that our lambda is copmatible with that std::function
0172     UiUtils::wrappedFind<decltype(it)>(
0173                 it,
0174                 QaimDfsIterator(QModelIndex(), model),
0175                 QaimDfsIterator(model->index(0, 0, QModelIndex()), model),
0176                 QaimDfsIterator(currentIndex, model),
0177                 [](const QModelIndex &idx) { return idx.isValid(); },
0178                 matcher,
0179                 onSuccess,
0180                 onFailure);
0181 }
0182 
0183 void gotoPrevious(const QAbstractItemModel *model, const QModelIndex &currentIndex,
0184                   std::function<bool(const QModelIndex &)> matcher,
0185                   std::function<void(const QModelIndex &)> onSuccess,
0186                   std::function<void()> onFailure)
0187 {
0188     auto it = Common::make_stashing_reverse_iterator(QaimDfsIterator(currentIndex, model));
0189     --it;
0190 
0191     UiUtils::wrappedFind<decltype(it)>(
0192                 Common::make_stashing_reverse_iterator(QaimDfsIterator(currentIndex, model)),
0193                 Common::make_stashing_reverse_iterator(QaimDfsIterator(model->index(0, 0, QModelIndex()), model)),
0194                 Common::make_stashing_reverse_iterator(QaimDfsIterator(QModelIndex(), model)),
0195                 it,
0196                 [](const QModelIndex &idx) { return idx.isValid(); },
0197                 matcher,
0198                 onSuccess,
0199                 onFailure);
0200 }
0201 
0202 }