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; + } +}; + +}