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);