From d04c833002331d868e95a1036426609b219ceb79 Mon Sep 17 00:00:00 2001 From: AnotherTest Date: Sat, 4 Jul 2020 22:36:23 +0430 Subject: [PATCH] LibGUI: Add FilteringProxyModel This model does not support nested indices well, in that it flattens them all out. That's a FIXME for the future as it does its job for now. --- Libraries/LibGUI/CMakeLists.txt | 1 + Libraries/LibGUI/FilteringProxyModel.cpp | 122 +++++++++++++++++++++++ Libraries/LibGUI/FilteringProxyModel.h | 71 +++++++++++++ Libraries/LibGUI/Model.h | 6 +- 4 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 Libraries/LibGUI/FilteringProxyModel.cpp create mode 100644 Libraries/LibGUI/FilteringProxyModel.h diff --git a/Libraries/LibGUI/CMakeLists.txt b/Libraries/LibGUI/CMakeLists.txt index 88ab51cffe..d7cace8e85 100644 --- a/Libraries/LibGUI/CMakeLists.txt +++ b/Libraries/LibGUI/CMakeLists.txt @@ -26,6 +26,7 @@ set(SOURCES Event.cpp FilePicker.cpp FileSystemModel.cpp + FilteringProxyModel.cpp FontDatabase.cpp Frame.cpp GroupBox.cpp diff --git a/Libraries/LibGUI/FilteringProxyModel.cpp b/Libraries/LibGUI/FilteringProxyModel.cpp new file mode 100644 index 0000000000..7d14b29a13 --- /dev/null +++ b/Libraries/LibGUI/FilteringProxyModel.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +namespace GUI { + +ModelIndex FilteringProxyModel::index(int row, int column, const ModelIndex& parent_index) const +{ + int parent_row = parent_index.row(); + if (!parent_index.is_valid()) + parent_row = 0; + + return create_index(parent_row + row, column); +} + +int FilteringProxyModel::row_count(const ModelIndex&) const +{ + return m_matching_indices.size(); +} + +int FilteringProxyModel::column_count(const ModelIndex& index) const +{ + if (!index.is_valid()) + return {}; + + if ((size_t)index.row() > m_matching_indices.size() || index.row() < 0) + return 0; + + return m_model.column_count(m_matching_indices[index.row()]); +} + +Variant FilteringProxyModel::data(const ModelIndex& index, Role role) const +{ + if (!index.is_valid()) + return {}; + + if ((size_t)index.row() > m_matching_indices.size() || index.row() < 0) + return 0; + + return m_model.data(m_matching_indices[index.row()], role); +} + +void FilteringProxyModel::update() +{ + m_model.update(); + filter(); + did_update(); +} + +void FilteringProxyModel::filter() +{ + m_matching_indices.clear(); + + Function add_matching = [&](ModelIndex& parent_index) { + for (auto i = 0; i < m_model.row_count(parent_index); ++i) { + auto index = m_model.index(i, 0, parent_index); + if (!index.is_valid()) + continue; + + auto filter_matches = m_model.data_matches(index, m_filter_term); + bool matches = filter_matches == TriState::True; + if (filter_matches == TriState::Unknown) { + auto data = m_model.data(index, Role::Display); + if (data.is_string() && data.as_string().contains(m_filter_term)) + matches = true; + } + if (matches) + m_matching_indices.append(index); + + add_matching(index); + } + }; + + ModelIndex parent_index; + add_matching(parent_index); +} + +void FilteringProxyModel::set_filter_term(const StringView& term) +{ + if (m_filter_term == term) + return; + m_filter_term = term; + update(); +} + +ModelIndex FilteringProxyModel::map(const ModelIndex& index) const +{ + if (!index.is_valid()) + return {}; + + auto row = index.row(); + if (m_matching_indices.size() > (size_t)row) + return m_matching_indices[row]; + + return {}; +} + +} diff --git a/Libraries/LibGUI/FilteringProxyModel.h b/Libraries/LibGUI/FilteringProxyModel.h new file mode 100644 index 0000000000..f5296c3e6b --- /dev/null +++ b/Libraries/LibGUI/FilteringProxyModel.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace GUI { + +class FilteringProxyModel final : public Model { +public: + static NonnullRefPtr construct(Model& model) + { + return adopt(*new FilteringProxyModel(model)); + } + + virtual ~FilteringProxyModel() override {}; + + virtual int row_count(const ModelIndex& = ModelIndex()) const override; + virtual int column_count(const ModelIndex& = ModelIndex()) const override; + virtual Variant data(const ModelIndex&, Role = Role::Display) const override; + virtual void update() override; + virtual ModelIndex index(int row, int column = 0, const ModelIndex& parent = ModelIndex()) const override; + + void set_filter_term(const StringView& term); + + ModelIndex map(const ModelIndex&) const; + +private: + void filter(); + explicit FilteringProxyModel(Model& model) + : m_model(model) + { + } + + Model& m_model; + + // Maps row to actual model index. + Vector m_matching_indices; + + String m_filter_term; +}; + +} diff --git a/Libraries/LibGUI/Model.h b/Libraries/LibGUI/Model.h index 29933023ba..d12a0f2afc 100644 --- a/Libraries/LibGUI/Model.h +++ b/Libraries/LibGUI/Model.h @@ -67,6 +67,7 @@ public: Font, DragData, TextAlignment, + Search, Custom = 0x100, // Applications are free to use roles above this number as they please }; @@ -76,12 +77,13 @@ public: virtual int column_count(const ModelIndex& = ModelIndex()) const = 0; virtual String column_name(int) const { return {}; } virtual Variant data(const ModelIndex&, Role = Role::Display) const = 0; + virtual TriState data_matches(const ModelIndex&, Variant) const { return TriState::Unknown; } virtual void update() = 0; virtual ModelIndex parent_index(const ModelIndex&) const { return {}; } virtual ModelIndex index(int row, int column = 0, const ModelIndex& = ModelIndex()) const { return create_index(row, column); } virtual ModelIndex sibling(int row, int column, const ModelIndex& parent) const; virtual bool is_editable(const ModelIndex&) const { return false; } - virtual void set_data(const ModelIndex&, const Variant&) {} + virtual void set_data(const ModelIndex&, const Variant&) { } virtual int tree_column() const { return 0; } virtual bool accepts_drag(const ModelIndex&, const StringView& data_type); @@ -95,7 +97,7 @@ public: virtual int key_column() const { return -1; } virtual SortOrder sort_order() const { return SortOrder::None; } - virtual void set_key_column_and_sort_order(int, SortOrder) {} + virtual void set_key_column_and_sort_order(int, SortOrder) { } virtual StringView drag_data_type() const { return {}; }