From 5bb38296b15e72400f355e1986fef630e2ddfac8 Mon Sep 17 00:00:00 2001 From: Linus Groh Date: Thu, 12 May 2022 21:36:15 +0200 Subject: [PATCH] LibGUI: Add TreeViewModel as a simpler interface for building TreeViews Having to subclass GUI::Model for even the simplest type of hand-built TreeView makes them a bit unpleasant to work with at the moment. :^) This adds such a GUI::Model subclass that is specifically designed for adding nodes to a TreeView manually, supporting text and an optional icon by default, and allowing for further data when subclassing the Node class. --- Userland/Libraries/LibGUI/CMakeLists.txt | 1 + Userland/Libraries/LibGUI/TreeViewModel.cpp | 83 +++++++++++++++++++ Userland/Libraries/LibGUI/TreeViewModel.h | 90 +++++++++++++++++++++ 3 files changed, 174 insertions(+) create mode 100644 Userland/Libraries/LibGUI/TreeViewModel.cpp create mode 100644 Userland/Libraries/LibGUI/TreeViewModel.h diff --git a/Userland/Libraries/LibGUI/CMakeLists.txt b/Userland/Libraries/LibGUI/CMakeLists.txt index 2ca3f61030..d503ad53ff 100644 --- a/Userland/Libraries/LibGUI/CMakeLists.txt +++ b/Userland/Libraries/LibGUI/CMakeLists.txt @@ -106,6 +106,7 @@ set(SOURCES ToolbarContainer.cpp Tray.cpp TreeView.cpp + TreeViewModel.cpp UndoStack.cpp ValueSlider.cpp Variant.cpp diff --git a/Userland/Libraries/LibGUI/TreeViewModel.cpp b/Userland/Libraries/LibGUI/TreeViewModel.cpp new file mode 100644 index 0000000000..362df13472 --- /dev/null +++ b/Userland/Libraries/LibGUI/TreeViewModel.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include + +namespace GUI { + +ModelIndex TreeViewModel::index(int row, int column, ModelIndex const& parent) const +{ + if (!parent.is_valid()) { + if (static_cast(row) >= m_nodes.size()) + return {}; + return create_index(row, column, &m_nodes[row]); + } + auto const& parent_node = *static_cast(parent.internal_data()); + if (static_cast(row) >= parent_node.child_nodes().size()) + return {}; + auto const* child = &parent_node.child_nodes()[row]; + return create_index(row, column, child); +} + +ModelIndex TreeViewModel::parent_index(ModelIndex const& index) const +{ + if (!index.is_valid()) + return {}; + auto const& child_node = *static_cast(index.internal_data()); + auto const* parent_node = child_node.parent_node(); + if (parent_node == nullptr) + return {}; + if (parent_node->parent_node() == nullptr) { + for (size_t row = 0; row < m_nodes.size(); row++) + if (m_nodes.ptr_at(row).ptr() == parent_node) + return create_index(static_cast(row), 0, parent_node); + VERIFY_NOT_REACHED(); + } + for (size_t row = 0; row < parent_node->parent_node()->child_nodes().size(); row++) { + auto const* child_node_at_row = parent_node->parent_node()->child_nodes().ptr_at(row).ptr(); + if (child_node_at_row == parent_node) + return create_index(static_cast(row), 0, parent_node); + } + VERIFY_NOT_REACHED(); +} + +int TreeViewModel::row_count(ModelIndex const& index) const +{ + if (!index.is_valid()) + return static_cast(m_nodes.size()); + auto const& node = *static_cast(index.internal_data()); + return static_cast(node.child_nodes().size()); +} + +Variant TreeViewModel::data(ModelIndex const& index, ModelRole role) const +{ + auto const& node = *static_cast(index.internal_data()); + switch (role) { + case ModelRole::Display: + return node.text(); + case ModelRole::Icon: + if (node.icon().has_value()) + return *node.icon(); + return {}; + default: + return {}; + } +} + +Optional TreeViewModel::index_for_node(Node const& node, ModelIndex const& parent) const +{ + for (int row = 0; row < row_count(parent); ++row) { + auto row_index = this->index(row, 0); + auto const* row_node = static_cast(row_index.internal_data()); + if (&node == row_node) + return row_index; + if (auto index = index_for_node(node, row_index); index.has_value()) + return index; + } + return {}; +} + +} diff --git a/Userland/Libraries/LibGUI/TreeViewModel.h b/Userland/Libraries/LibGUI/TreeViewModel.h new file mode 100644 index 0000000000..664b59ccf0 --- /dev/null +++ b/Userland/Libraries/LibGUI/TreeViewModel.h @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022, Linus Groh + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace GUI { + +class TreeViewModel final : public Model { +public: + static NonnullRefPtr create() + { + return adopt_ref(*new TreeViewModel()); + } + + virtual ~TreeViewModel() override = default; + + virtual int row_count(ModelIndex const& = {}) const override; + virtual int column_count(ModelIndex const& = {}) const override { return 1; } + virtual Variant data(ModelIndex const&, ModelRole role) const override; + virtual ModelIndex parent_index(ModelIndex const&) const override; + virtual ModelIndex index(int row, int column, ModelIndex const& parent = {}) const override; + + class Node + : public RefCounted + , public Weakable { + public: + Node(String text, Optional icon, Node* parent_node = nullptr) + : m_text(move(text)) + , m_icon(move(icon)) + , m_parent_node(parent_node) + { + } + + virtual ~Node() = default; + + template + NonnullRefPtr add_node(String text, Optional icon, Args&&... args) requires(IsBaseOf) + { + auto node = adopt_ref(*new NodeType(move(text), move(icon), this, forward(args)...)); + m_child_nodes.append(*static_cast(node.ptr())); + return node; + } + + String const& text() const { return m_text; } + Optional const& icon() const { return m_icon; } + + Node const* parent_node() const { return m_parent_node; } + Node* parent_node() { return m_parent_node; } + + NonnullRefPtrVector const& child_nodes() const { return m_child_nodes; } + NonnullRefPtrVector& child_nodes() { return m_child_nodes; } + + private: + String m_text; + Optional m_icon; + WeakPtr m_parent_node; + NonnullRefPtrVector m_child_nodes; + }; + + NonnullRefPtrVector const& nodes() const { return m_nodes; } + NonnullRefPtrVector& nodes() { return m_nodes; } + + template + NonnullRefPtr add_node(String text, Optional icon, Args&&... args) requires(IsBaseOf) + { + auto node = adopt_ref(*new NodeType(move(text), move(icon), nullptr, forward(args)...)); + m_nodes.append(*static_cast(node.ptr())); + return node; + } + + Optional index_for_node(Node const&, ModelIndex const& parent = {}) const; + +private: + TreeViewModel() = default; + + NonnullRefPtrVector m_nodes; +}; + +}