From d73116e5d5f288945e03d077d7f4cab5708e5724 Mon Sep 17 00:00:00 2001 From: sin-ack Date: Thu, 13 May 2021 09:07:43 +0000 Subject: [PATCH] LibGUI: Implement persistent indices for models This patch adds persistent indices to models. A PersistentModelIndex is a ModelIndex that will survive most model updates (provided that the data the PersistentModelIndex points to has not been removed by the model's data store). PersistentModelIndex objects can be safely held by objects outside the model they originated from. --- Userland/Libraries/LibGUI/CMakeLists.txt | 1 + Userland/Libraries/LibGUI/Forward.h | 2 + Userland/Libraries/LibGUI/Model.cpp | 20 ++++ Userland/Libraries/LibGUI/Model.h | 5 + Userland/Libraries/LibGUI/ModelIndex.h | 5 +- .../Libraries/LibGUI/PersistentModelIndex.cpp | 96 +++++++++++++++++++ .../Libraries/LibGUI/PersistentModelIndex.h | 93 ++++++++++++++++++ 7 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 Userland/Libraries/LibGUI/PersistentModelIndex.cpp create mode 100644 Userland/Libraries/LibGUI/PersistentModelIndex.h diff --git a/Userland/Libraries/LibGUI/CMakeLists.txt b/Userland/Libraries/LibGUI/CMakeLists.txt index 84104f270e..a6d749541e 100644 --- a/Userland/Libraries/LibGUI/CMakeLists.txt +++ b/Userland/Libraries/LibGUI/CMakeLists.txt @@ -71,6 +71,7 @@ set(SOURCES Painter.cpp PasswordInputDialog.cpp PasswordInputDialogGML.h + PersistentModelIndex.cpp ProcessChooser.cpp Progressbar.cpp RadioButton.cpp diff --git a/Userland/Libraries/LibGUI/Forward.h b/Userland/Libraries/LibGUI/Forward.h index a01589be6c..bfa243ed48 100644 --- a/Userland/Libraries/LibGUI/Forward.h +++ b/Userland/Libraries/LibGUI/Forward.h @@ -49,6 +49,8 @@ class MultiView; class OpacitySlider; class PaintEvent; class Painter; +class PersistentHandle; +class PersistentModelIndex; class RadioButton; class ResizeCorner; class ResizeEvent; diff --git a/Userland/Libraries/LibGUI/Model.cpp b/Userland/Libraries/LibGUI/Model.cpp index 5a0406a1bb..1e37d989bf 100644 --- a/Userland/Libraries/LibGUI/Model.cpp +++ b/Userland/Libraries/LibGUI/Model.cpp @@ -6,6 +6,7 @@ #include #include +#include namespace GUI { @@ -71,6 +72,25 @@ void Model::unregister_client(ModelClient& client) m_clients.remove(&client); } +WeakPtr Model::register_persistent_index(Badge, const ModelIndex& index) +{ + if (!index.is_valid()) + return {}; + + auto it = m_persistent_handles.find(index); + // Easy modo: we already have a handle for this model index. + if (it != m_persistent_handles.end()) { + return it->value->make_weak_ptr(); + } + + // Hard modo: create a new persistent handle. + auto handle = adopt_own(*new PersistentHandle(index)); + auto weak_handle = handle->make_weak_ptr(); + m_persistent_handles.set(index, move(handle)); + + return weak_handle; +} + RefPtr Model::mime_data(const ModelSelection& selection) const { auto mime_data = Core::MimeData::construct(); diff --git a/Userland/Libraries/LibGUI/Model.h b/Userland/Libraries/LibGUI/Model.h index 42cf5afcbc..98b56b0f41 100644 --- a/Userland/Libraries/LibGUI/Model.h +++ b/Userland/Libraries/LibGUI/Model.h @@ -8,10 +8,13 @@ #include #include +#include #include #include #include +#include #include +#include #include #include #include @@ -84,6 +87,8 @@ public: void register_client(ModelClient&); void unregister_client(ModelClient&); + WeakPtr register_persistent_index(Badge, ModelIndex const&); + protected: Model(); diff --git a/Userland/Libraries/LibGUI/ModelIndex.h b/Userland/Libraries/LibGUI/ModelIndex.h index 92d040c704..a2d939c9d3 100644 --- a/Userland/Libraries/LibGUI/ModelIndex.h +++ b/Userland/Libraries/LibGUI/ModelIndex.h @@ -77,7 +77,10 @@ struct Formatter : Formatter { template<> struct Traits : public GenericTraits { - static unsigned hash(const GUI::ModelIndex& index) { return pair_int_hash(index.row(), index.column()); } + static unsigned hash(const GUI::ModelIndex& index) + { + return pair_int_hash(pair_int_hash(index.row(), index.column()), reinterpret_cast(index.internal_data())); + } }; } diff --git a/Userland/Libraries/LibGUI/PersistentModelIndex.cpp b/Userland/Libraries/LibGUI/PersistentModelIndex.cpp new file mode 100644 index 0000000000..0312bf123e --- /dev/null +++ b/Userland/Libraries/LibGUI/PersistentModelIndex.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2021, sin-ack + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace GUI { + +PersistentModelIndex::PersistentModelIndex(ModelIndex const& index) +{ + if (!index.is_valid()) + return; + + auto* model = const_cast(index.model()); + m_handle = model->register_persistent_index({}, index); +} + +int PersistentModelIndex::row() const +{ + if (!has_valid_handle()) + return -1; + return m_handle->m_index.row(); +} + +int PersistentModelIndex::column() const +{ + if (!has_valid_handle()) + return -1; + return m_handle->m_index.column(); +} + +PersistentModelIndex PersistentModelIndex::parent() const +{ + if (!has_valid_handle()) + return {}; + return { m_handle->m_index.parent() }; +} + +PersistentModelIndex PersistentModelIndex::sibling_at_column(int column) const +{ + if (!has_valid_handle()) + return {}; + + return { m_handle->m_index.sibling_at_column(column) }; +} + +Variant PersistentModelIndex::data(ModelRole role) const +{ + if (!has_valid_handle()) + return {}; + return { m_handle->m_index.data(role) }; +} + +PersistentModelIndex::operator ModelIndex() const +{ + if (!has_valid_handle()) + return {}; + else + return m_handle->m_index; +} + +bool PersistentModelIndex::operator==(PersistentModelIndex const& other) const +{ + bool is_this_valid = has_valid_handle(); + bool is_other_valid = other.has_valid_handle(); + + if (!is_this_valid && !is_other_valid) + return true; + if (is_this_valid != is_other_valid) + return false; + + return m_handle->m_index == other.m_handle->m_index; +} + +bool PersistentModelIndex::operator!=(PersistentModelIndex const& other) const +{ + return !(*this == other); +} + +bool PersistentModelIndex::operator==(ModelIndex const& other) const +{ + if (!has_valid_handle()) { + return !other.is_valid(); + } + + return m_handle->m_index == other; +} + +bool PersistentModelIndex::operator!=(ModelIndex const& other) const +{ + return !(*this == other); +} + +} diff --git a/Userland/Libraries/LibGUI/PersistentModelIndex.h b/Userland/Libraries/LibGUI/PersistentModelIndex.h new file mode 100644 index 0000000000..1ef3f39fb6 --- /dev/null +++ b/Userland/Libraries/LibGUI/PersistentModelIndex.h @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2021, sin-ack + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace GUI { + +/// A PersistentHandle is an internal data structure used to keep track of the +/// target of multiple PersistentModelIndex instances. +class PersistentHandle : public Weakable { + friend Model; + friend PersistentModelIndex; + friend AK::Traits; + + PersistentHandle(ModelIndex const& index) + : m_index(index) + { + } + + ModelIndex m_index; +}; + +class PersistentModelIndex { +public: + PersistentModelIndex() { } + PersistentModelIndex(ModelIndex const&); + PersistentModelIndex(PersistentModelIndex const&) = default; + PersistentModelIndex(PersistentModelIndex&&) = default; + + PersistentModelIndex& operator=(PersistentModelIndex const&) = default; + PersistentModelIndex& operator=(PersistentModelIndex&&) = default; + + bool is_valid() const { return has_valid_handle() && m_handle->m_index.is_valid(); } + bool has_valid_handle() const { return !m_handle.is_null(); } + + int row() const; + int column() const; + PersistentModelIndex parent() const; + PersistentModelIndex sibling_at_column(int column) const; + Variant data(ModelRole = ModelRole::Display) const; + + void* internal_data() const + { + if (has_valid_handle()) + return m_handle->m_index.internal_data(); + else + return nullptr; + } + + operator ModelIndex() const; + bool operator==(PersistentModelIndex const&) const; + bool operator!=(PersistentModelIndex const&) const; + bool operator==(ModelIndex const&) const; + bool operator!=(ModelIndex const&) const; + +private: + friend AK::Traits; + + WeakPtr m_handle; +}; + +} + +namespace AK { + +template<> +struct Formatter : Formatter { + void format(FormatBuilder& builder, const GUI::PersistentModelIndex& value) + { + return Formatter::format(builder, "PersistentModelIndex({},{},{})", value.row(), value.column(), value.internal_data()); + } +}; + +template<> +struct Traits : public GenericTraits { + static unsigned hash(const GUI::PersistentModelIndex& index) + { + if (index.has_valid_handle()) + return Traits::hash(index.m_handle->m_index); + else + return 0; + } +}; + +}