File indexing completed on 2025-02-09 05:17:54

0001 /* SPDX-FileCopyrightText: 2024 Noah Davis <noahadvs@gmail.com>
0002  * SPDX-License-Identifier: LGPL-2.0-or-later
0003  */
0004 
0005 #include "History.h"
0006 #include <QDebug>
0007 
0008 using namespace Qt::StringLiterals;
0009 
0010 bool HistoryItem::hasParent() const
0011 {
0012     return m_parent && !m_parent->expired();
0013 }
0014 
0015 HistoryItem::const_weak_ptr HistoryItem::parent() const
0016 {
0017     return m_parent.value_or(const_weak_ptr{});
0018 }
0019 
0020 bool HistoryItem::hasChild() const
0021 {
0022     return !m_child.expired();
0023 }
0024 
0025 HistoryItem::const_weak_ptr HistoryItem::child() const
0026 {
0027     return m_child;
0028 }
0029 
0030 const Traits::OptTuple &HistoryItem::traits() const
0031 {
0032     return m_traits;
0033 }
0034 
0035 Traits::OptTuple &HistoryItem::traits()
0036 {
0037     return m_traits;
0038 }
0039 
0040 bool HistoryItem::isValid() const
0041 {
0042     return Traits::isValid(m_traits) && (!m_parent || !m_parent->expired());
0043 }
0044 
0045 bool HistoryItem::visibleTraits() const
0046 {
0047     return Traits::isVisible(m_traits);
0048 }
0049 
0050 QRectF HistoryItem::renderRect() const
0051 {
0052     auto visualRect = Traits::visualRect(m_traits);
0053     if (visualRect.isEmpty() && hasParent()) {
0054         auto parent = m_parent->lock();
0055         return parent ? parent->renderRect() : visualRect;
0056     } else {
0057         return visualRect;
0058     }
0059 }
0060 
0061 void HistoryItem::setItemRelations(shared_ptr parent, shared_ptr child)
0062 {
0063     if (child) {
0064         child->m_parent = parent;
0065     }
0066     if (parent) {
0067         parent->m_child = child;
0068     }
0069 }
0070 void HistoryItem::setItemRelations(const_shared_ptr parent, const_shared_ptr child)
0071 {
0072     if (child) {
0073         child->m_parent = parent;
0074     }
0075     if (parent) {
0076         parent->m_child = child;
0077     }
0078 }
0079 void HistoryItem::setItemRelations(shared_ptr parent, const_shared_ptr child)
0080 {
0081     if (child) {
0082         child->m_parent = parent;
0083     }
0084     if (parent) {
0085         parent->m_child = child;
0086     }
0087 }
0088 void HistoryItem::setItemRelations(const_shared_ptr parent, shared_ptr child)
0089 {
0090     if (child) {
0091         child->m_parent = parent;
0092     }
0093     if (parent) {
0094         parent->m_child = child;
0095     }
0096 }
0097 
0098 QDebug operator<<(QDebug debug, const HistoryItem &item)
0099 {
0100     QDebugStateSaver stateSaver(debug);
0101     debug.nospace();
0102     debug << "HistoryItem(";
0103     debug << (const void *)&item;
0104     auto parent = item.m_parent.value_or(HistoryItem::const_weak_ptr{}).lock().get();
0105     debug << ",\n    parent=" << (const void *)parent;
0106     auto child = item.m_child.lock().get();
0107     debug << ",\n    child=" << (const void *)child;
0108     debug << ",\n    isValid=" << item.isValid();
0109     debug << ",\n    visibleTraits=" << item.visibleTraits();
0110     debug << ",\n    renderRect=" << item.renderRect();
0111     debug << ')';
0112     return debug;
0113 }
0114 
0115 QDebug operator<<(QDebug debug, const HistoryItem *item)
0116 {
0117     QDebugStateSaver stateSaver(debug);
0118     debug.nospace();
0119     if (item) {
0120         debug << *item;
0121     } else {
0122         debug << "HistoryItem(0x0)";
0123     }
0124     return debug;
0125 }
0126 
0127 //---
0128 
0129 template<typename It>
0130     requires(std::same_as<It, History::List::iterator> || std::same_as<It, History::List::reverse_iterator>)
0131 History::List::size_type eraseInvalidItems(History::List &list, It first, It last)
0132 {
0133     if (first == last) {
0134         return 0;
0135     }
0136     History::List::size_type removedCount = 0;
0137     for (auto it = first; it != last; ++it) {
0138         auto &item = *it;
0139         if (!item || !item->isValid()) {
0140             // std::next(it).base() makes it safe to erase with reverse iterators.
0141             if constexpr (std::same_as<It, History::List::reverse_iterator>) {
0142                 list.erase(std::next(it).base());
0143             } else {
0144                 list.erase(it);
0145             }
0146             ++removedCount;
0147         }
0148     }
0149     return removedCount;
0150 }
0151 
0152 //---
0153 
0154 History::History(const List &undoList, const List &redoList)
0155     : m_undoList(undoList)
0156     , m_redoList(redoList)
0157 {
0158 }
0159 
0160 bool History::operator==(const History &other) const
0161 {
0162     return m_undoList == other.m_undoList && m_redoList == other.m_redoList;
0163 }
0164 
0165 const History::ConstList &History::undoList() const
0166 {
0167     // reinterpret_cast can be dangerous. It's like telling the compiler to
0168     // act like this type is another type regardless of whether or not they're really compatible.
0169     // List and ConstList should be compatible. ConstList's only difference from List is that
0170     // ConstList has a const value type.
0171     return reinterpret_cast<const History::ConstList &>(m_undoList);
0172 }
0173 
0174 const History::ConstList &History::redoList() const
0175 {
0176     return reinterpret_cast<const History::ConstList &>(m_redoList);
0177 }
0178 
0179 History::List::size_type History::currentIndex() const
0180 {
0181     return m_undoList.size() - 1;
0182 }
0183 
0184 HistoryItem::shared_ptr History::currentItem() const
0185 {
0186     if (!m_undoList.empty()) {
0187         return m_undoList.back();
0188     }
0189     return {};
0190 }
0191 
0192 History::Lists History::filteredLists(std::function<bool(History::List::const_reference)> function) const
0193 {
0194     History::Lists lists;
0195     for (auto it = m_undoList.cbegin(); it != m_undoList.cend(); ++it) {
0196         if (function(*it)) {
0197             lists.undoList.push_back(*it);
0198         }
0199     }
0200     for (auto it = m_redoList.cbegin(); it != m_redoList.cend(); ++it) {
0201         if (function(*it)) {
0202             lists.redoList.push_back(*it);
0203         }
0204     }
0205     return lists;
0206 }
0207 
0208 History::ListsChangedResult History::push(const HistoryItem::shared_ptr &item)
0209 {
0210     if (!item) {
0211         return {false, false};
0212     }
0213     if (!m_undoList.empty() && (!m_undoList.back() || !m_undoList.back()->isValid())) {
0214         m_undoList.pop_back();
0215     }
0216     m_undoList.push_back(item);
0217     return {true, clearRedoList()};
0218 }
0219 
0220 History::ItemReplacedResult History::pop()
0221 {
0222     if (m_undoList.empty()) {
0223         return {nullptr, false};
0224     }
0225     auto item = std::move(m_undoList.back());
0226     m_undoList.erase(m_undoList.cend() - 1);
0227     return {item, eraseInvalidRedoItems()};
0228 }
0229 
0230 bool History::undo()
0231 {
0232     if (m_undoList.empty()) {
0233         return false;
0234     }
0235     m_redoList.push_back(std::move(m_undoList.back()));
0236     m_undoList.erase(m_undoList.cend() - 1);
0237     return true;
0238 }
0239 
0240 bool History::redo()
0241 {
0242     if (m_redoList.empty()) {
0243         return false;
0244     }
0245     m_undoList.push_back(std::move(m_redoList.back()));
0246     m_redoList.erase(m_redoList.cend() - 1);
0247     return true;
0248 }
0249 
0250 bool History::clearRedoList()
0251 {
0252     if (m_redoList.empty()) {
0253         return false;
0254     }
0255     const auto oldSize = m_redoList.size();
0256     while (!m_redoList.empty()) {
0257         m_redoList.erase(m_redoList.cend() - 1);
0258     }
0259     return oldSize != m_redoList.size();
0260 }
0261 
0262 bool History::clearUndoList()
0263 {
0264     if (m_undoList.empty()) {
0265         return false;
0266     }
0267     const auto oldSize = m_undoList.size();
0268     while (!m_undoList.empty()) {
0269         m_undoList.erase(m_undoList.cend() - 1);
0270     }
0271     return oldSize != m_undoList.size();
0272 }
0273 
0274 History::ListsChangedResult History::clearLists()
0275 {
0276     return {clearUndoList(), clearRedoList()};
0277 }
0278 
0279 bool History::itemVisible(ConstList::const_reference item) const
0280 {
0281     if (!item || !item->visibleTraits()) {
0282         return false;
0283     }
0284     auto child = item->m_child.lock();
0285     // Searching in reverse order should be a bit faster.
0286     // If the returned iterator is equal to crend, then the child wasn't found.
0287     return !child || std::find(m_undoList.crbegin(), m_undoList.crend(), child) == m_undoList.crend();
0288 }
0289 
0290 bool History::eraseInvalidRedoItems()
0291 {
0292     // Erase in chronological order so that later item Parent traits become invalidated.
0293     return eraseInvalidItems(m_redoList, m_redoList.rbegin(), m_redoList.rend());
0294 }
0295 
0296 QDebug operator<<(QDebug debug, const History &history)
0297 {
0298     QDebugStateSaver stateSaver(debug);
0299     debug.nospace();
0300     debug << "History(";
0301     debug << (const void *)&history;
0302     debug << ",\n  undoList.size()=" << history.m_undoList.size();
0303     debug << ",\n  redoList.size()=" << history.m_redoList.size();
0304     debug << ')';
0305     return debug;
0306 }