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 }