File indexing completed on 2025-02-02 04:26:10
0001 /* SPDX-FileCopyrightText: 2024 Noah Davis <noahadvs@gmail.com> 0002 * SPDX-License-Identifier: LGPL-2.0-or-later 0003 */ 0004 0005 #pragma once 0006 0007 #include "Traits.h" 0008 #include <span> 0009 0010 class HistoryItem; 0011 class History; 0012 0013 /* Basically the same as owner_equal from C++26. 0014 * Needed to compare weak_ptrs without calling lock() all the time. 0015 * 0016 * From https://en.cppreference.com/w/cpp/memory/owner_equal: 0017 * 0018 * This function object provides owner-based (as opposed to value-based) mixed-type equal comparison 0019 * of both std::weak_ptr and std::shared_ptr. The comparison is such that two smart pointers compare 0020 * equivalent only if they are both empty or if they share ownership, even if the values of the raw 0021 * pointers obtained by get() are different (e.g. because they point at different subobjects within 0022 * the same object). 0023 * 0024 * NOTE: This is not what you want if you're using shared_ptr's aliasing constructor and you want 0025 * to compare the actual pointed to addresses, but the aliasing constructor is rarely needed. 0026 */ 0027 template<typename T1, typename T2> 0028 inline bool operator==(const std::weak_ptr<T1> &lhs, const std::weak_ptr<T2> &rhs) noexcept 0029 { 0030 return !lhs.owner_before(rhs) && !rhs.owner_before(lhs); 0031 } 0032 template<typename T1, typename T2> 0033 inline bool operator==(const std::weak_ptr<T1> &lhs, const std::shared_ptr<T2> &rhs) noexcept 0034 { 0035 return !lhs.owner_before(rhs) && !rhs.owner_before(lhs); 0036 } 0037 template<typename T1, typename T2> 0038 inline bool operator==(const std::shared_ptr<T1> &lhs, const std::weak_ptr<T2> &rhs) noexcept 0039 { 0040 return !lhs.owner_before(rhs) && !rhs.owner_before(lhs); 0041 } 0042 0043 /** 0044 * A class that represents a state change in the undo/redo history. 0045 * 0046 * This is meant to be mostly externally immutable when managed in history. 0047 * This way we're not trying to keep track of changes happening in a million different places. 0048 * With this trait based structure, we're describing how to render an item in a generic way instead 0049 * of giving every combination a distinct class. 0050 * 0051 * We aren't using Qt's Undo Framework because it doesn't fit our needs. We want to use shared 0052 * pointers, we'd have to make major changes to the way AnnotationDocument works to switch and we'd 0053 * need a lot of different classes for different types of commands. 0054 * 0055 * We're using a tuple for traits instead of a container because the standard APIs for dealing 0056 * with multiple types are a bit nicer. Otherwise, we'd need to use something like std::variant. 0057 * That isn't terrible, but it's an extra layer to deal with and requires more code to be written. 0058 * If the memory usage of a tuple turns out to be too wasteful, we can switch to an unordered set or 0059 * unordered map of variants. 0060 * 0061 * We're using std::optional for traits because it provides a null state we can use to say that a 0062 * trait isn't enabled. The memory that the underlying object uses is allocated, but the object is 0063 * not constructed until you set a value. It may be less memory efficient than a pointer, but 0064 * std::optional should be fast when setting values since the memory is already allocated. 0065 */ 0066 class HistoryItem 0067 { 0068 public: 0069 using unique_ptr = std::unique_ptr<HistoryItem>; 0070 using shared_ptr = std::shared_ptr<HistoryItem>; 0071 using const_shared_ptr = std::shared_ptr<const HistoryItem>; 0072 using weak_ptr = shared_ptr::weak_type; 0073 using const_weak_ptr = const_shared_ptr::weak_type; 0074 0075 bool operator==(const HistoryItem &other) const = default; 0076 0077 bool hasParent() const; 0078 HistoryItem::const_weak_ptr parent() const; 0079 0080 bool hasChild() const; 0081 HistoryItem::const_weak_ptr child() const; 0082 0083 // Get a const reference to the tuple of all traits. 0084 const Traits::OptTuple &traits() const; 0085 0086 // Get a reference to the tuple of all traits. 0087 Traits::OptTuple &traits(); // can modify 0088 0089 // Whether this item's traits and parent properties are valid. 0090 bool isValid() const; 0091 0092 // Whether this item can be seen by a user. Does not account for child items. 0093 bool visibleTraits() const; 0094 0095 // The area that this item renders over. 0096 // This uses the parent's renderRect() when this item is not visible. 0097 QRectF renderRect() const; 0098 0099 // Set the parent as the child's parent and the child as the parent's child. 0100 // I tried inheriting std::enable_shared_from_this to create a more automatic solution with 0101 // constructors, but weak_from_this() always immediately expired and shared_from_this() 0102 // caused segfaults. 0103 static void setItemRelations(shared_ptr parent, shared_ptr child); 0104 static void setItemRelations(const_shared_ptr parent, const_shared_ptr child); 0105 static void setItemRelations(shared_ptr parent, const_shared_ptr child); 0106 static void setItemRelations(const_shared_ptr parent, shared_ptr child); 0107 0108 protected: 0109 friend History; 0110 friend QDebug operator<<(QDebug debug, const HistoryItem &item); 0111 friend QDebug operator<<(QDebug debug, const HistoryItem *item); 0112 // The parent and child are mutable because they need to be changed even when this is const. 0113 // Using optional as a way to detect when parent has been set previously. 0114 mutable std::optional<HistoryItem::const_weak_ptr> m_parent; 0115 mutable HistoryItem::const_weak_ptr m_child; 0116 Traits::OptTuple m_traits; 0117 }; 0118 0119 QDebug operator<<(QDebug debug, const HistoryItem &item); 0120 QDebug operator<<(QDebug debug, const HistoryItem *item); 0121 0122 /** 0123 * A class for managing an undo list and a redo list. 0124 * It is possible to assign variables to different history objects and keep multiple of them. 0125 * Redo objects are stored in reverse chronological order. This is because QList/vector 0126 * has O(1) complexity when inserting/erasing at the end and maximum O(n) complexity when 0127 * inserting/erasing at the start. 0128 */ 0129 class History 0130 { 0131 public: 0132 using List = QList<HistoryItem::shared_ptr>; 0133 using ConstList = QList<HistoryItem::const_shared_ptr>; 0134 using ConstSpan = std::span<const HistoryItem::const_shared_ptr>; 0135 0136 struct ListsChangedResult { 0137 bool undoListChanged = false; 0138 bool redoListChanged = false; 0139 }; 0140 0141 struct ItemReplacedResult { 0142 HistoryItem::shared_ptr item; 0143 bool redoListChanged = false; 0144 }; 0145 0146 struct Lists { 0147 History::List undoList; 0148 History::List redoList; 0149 }; 0150 0151 History() = default; 0152 0153 // Construct from two history lists. 0154 History(const List &undoList, const List &redoList); 0155 0156 bool operator==(const History &other) const; 0157 0158 const ConstList &undoList() const; 0159 const ConstList &redoList() const; 0160 0161 // The index of the last undo object or -1 if the list is empty. 0162 List::size_type currentIndex() const; 0163 0164 // The object at the end of the undo list or null if not available. 0165 HistoryItem::shared_ptr currentItem() const; 0166 0167 // Get filtered copies of the undo and redo lists using the given function. 0168 Lists filteredLists(std::function<bool(History::List::const_reference)> function) const; 0169 0170 // Push a new object onto the end of the undo list and clear the redo list. 0171 // Returns whether the undo and redo lists changed. 0172 ListsChangedResult push(List::const_reference item); 0173 0174 // Pop the last object on the undo list. 0175 // Returns the popped object if successful or a default constructed object if not 0176 // and also whether the redo list changed. 0177 ItemReplacedResult pop(); 0178 0179 // Move the last object of the undo list to the end of the redo list. 0180 // Returns true if successful. 0181 bool undo(); 0182 0183 // Move the last object of the redo list to the end of the undo list. 0184 // Returns true if successful. 0185 bool redo(); 0186 0187 // Clear the redo list. 0188 // Returns true if the size changed. 0189 bool clearRedoList(); 0190 0191 // Clear the undo and redo lists. 0192 // Returns whether or not the lists changed. 0193 ListsChangedResult clearLists(); 0194 0195 // Whether the item is visible, in the undo list and without a child also in the undo list. 0196 bool itemVisible(ConstList::const_reference item) const; 0197 0198 protected: 0199 friend QDebug operator<<(QDebug debug, const History &history); 0200 // These are not public because we need to manage the child and parent traits of each item. 0201 bool clearUndoList(); 0202 bool eraseInvalidRedoItems(); 0203 0204 List m_undoList; 0205 List m_redoList; 0206 }; 0207 0208 QDebug operator<<(QDebug debug, const History &history);