Skip to content
Snippets Groups Projects
Commit 3443dc66 authored by Andreas Traczyk's avatar Andreas Traczyk Committed by Sébastien Blin
Browse files

conversations: use collection proxy for filtered and sorted views

Provides a STL container proxy class with methods for generating
sorted and filtered views of the underlying data similar to
qsortfilterproxymodel.

This helps to prevent excessive copying and reduces the complexity
of the code used to manage conversation list filtering/sorting.
The container mutations occur on references and the copy-ctor +
copy assignment operators are now deleted for the conversation
Info type.

Change-Id: I50b01e3c427563d8e32402f97dec79c8dcc865cf
parent e9c4c4ee
Branches
No related tags found
No related merge requests found
......@@ -399,6 +399,7 @@ SET(libringclient_interface_LIB_HDRS
SET( libringclient_extra_LIB_HDRS
src/typedefs.h
src/containerview.h
)
IF(${ENABLE_LIBWRAP} MATCHES true OR ${ENABLE_TEST} MATCHES true)
......
......@@ -32,6 +32,12 @@ namespace conversation {
struct Info
{
Info() = default;
Info(const Info& other) = delete;
Info(Info&& other) = default;
Info& operator=(const Info& other) = delete;
Info& operator=(Info&& other) = default;
QString uid = "";
QString accountId;
VectorString participants;
......
......@@ -23,6 +23,7 @@
#include "api/conversation.h"
#include "api/profile.h"
#include "api/datatransfer.h"
#include "containerview.h"
#include <QObject>
#include <QVector>
......@@ -30,7 +31,6 @@
#include <memory>
#include <deque>
#include <optional>
namespace lrc {
......@@ -56,9 +56,6 @@ class NewAccountModel;
enum class ConferenceableItem { CALL, CONTACT };
Q_ENUM_NS(ConferenceableItem)
template<typename T>
using OptRef = std::optional<std::reference_wrapper<T>>;
struct AccountConversation
{
QString convId;
......@@ -70,7 +67,6 @@ struct AccountConversation
* for calls and contacts contain only one element
* for conferences contains multiple entries
*/
typedef QVector<QVector<AccountConversation>> ConferenceableValue;
/**
......@@ -81,6 +77,7 @@ class LIB_EXPORT ConversationModel : public QObject
Q_OBJECT
public:
using ConversationQueue = std::deque<conversation::Info>;
using ConversationQueueProxy = ContainerView<ConversationQueue>;
const account::Info& owner;
......@@ -95,7 +92,7 @@ public:
* Get conversations which should be shown client side
* @return conversations filtered with the current filter
*/
const ConversationQueue& allFilteredConversations() const;
const ConversationQueueProxy& allFilteredConversations() const;
/**
* Get conversation for a given uid
......@@ -130,7 +127,7 @@ public:
* Get a custom filtered set of conversations
* @return conversations filtered
*/
const ConversationQueue& getFilteredConversations(
const ConversationQueueProxy& getFilteredConversations(
const profile::Type& filter = profile::Type::INVALID,
bool forceUpdate = false,
const bool includeBanned = false) const;
......@@ -139,7 +136,7 @@ public:
* @param row
* @return a copy of the conversation
*/
conversation::Info filteredConversation(unsigned int row) const;
OptRef<conversation::Info> filteredConversation(unsigned row) const;
/**
* Get the search results
......@@ -152,7 +149,7 @@ public:
* @param row
* @return a copy of the conversation
*/
conversation::Info searchResultForRow(unsigned int row) const;
OptRef<conversation::Info> searchResultForRow(unsigned row) const;
/**
* Update the searchResults
......@@ -322,9 +319,9 @@ Q_SIGNALS:
*/
void conversationUpdated(const QString& uid) const;
/**
* Emitted when conversations are sorted by last interaction
* Emitted when the conversations list is modified
*/
void modelSorted() const;
void modelChanged() const;
/**
* Emitted when filter has changed
*/
......
/*
* Copyright (C) 2020 by Savoir-faire Linux
* Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "typedefs.h"
#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <optional>
#include <type_traits>
template<class T>
using OptRef = typename std::optional<std::reference_wrapper<T>>;
// Some SFINAE helpers to clarify what happened when someone tries
// to make a ContainerView<float> or something wacky like that.
namespace detail {
template<typename... Ts>
struct has_defs
{};
template<typename T, typename _ = void>
struct is_container : std::false_type
{};
template<typename T>
struct is_container<T,
std::conditional_t<false,
has_defs<decltype(std::declval<T>().front()),
decltype(std::declval<T>().begin()),
decltype(std::declval<T>().end())>,
void>> : public std::true_type
{};
} // namespace detail
// Extra compile-time check to clarify why compilation has failed.
// Otherwise, the error message will be "'x' uses undefined struct ContainerView<decltype(x), void>"
template<class BaseType, class Enable = void>
struct ContainerView
{
static_assert(detail::is_container<BaseType>::value == true,
"Template parameter is probably not a container!");
};
template<class BaseType>
struct ContainerView<BaseType, std::enable_if_t<detail::is_container<BaseType>::value>>
{
private:
// Form a type by rebinding the underlying container type to a reference
// wrapped value type.
template<class ContainerType, class NewType>
struct rebind;
template<class ValueType, class... Args, template<class...> class ContainerType, class NewType>
struct rebind<ContainerType<ValueType, Args...>, NewType>
{
using type = ContainerType<NewType, typename rebind<Args, NewType>::type...>;
};
using value_type = std::remove_reference_t<decltype(std::declval<BaseType>().front())>;
using const_reference = const value_type&;
using view_type = typename rebind<BaseType, std::reference_wrapper<value_type>>::type;
public:
using FilterCallback = std::function<bool(const_reference)>;
using SortCallback = std::function<bool(const_reference, const_reference)>;
using OnEntryCallback = std::function<void(const_reference)>;
ContainerView() = default;
ContainerView(const BaseType& container)
{
data_ = std::make_optional(std::ref(container));
auto& dataSource = std::remove_const_t<BaseType&>(data());
view_.assign(dataSource.begin(), dataSource.end());
};
ContainerView(const ContainerView& other)
: data_(std::nullopt)
, view_(other().begin(), other().end())
, dirty_(other.dirty_)
, sortCallback_(other.sortCallback_)
, filterCallback_(other.filterCallback_) {};
ContainerView& operator=(const ContainerView& other) = default;
ContainerView& operator=(ContainerView&& other) = default;
ContainerView(ContainerView&& other) noexcept = default;
// Allow concatenation of views.
ContainerView& operator+=(const ContainerView& rhs)
{
view_.insert(view_.cend(), rhs.get().cbegin(), rhs.get().cend());
return *this;
}
friend ContainerView operator+(ContainerView lhs, const ContainerView& rhs)
{
lhs += rhs;
return lhs;
}
// Reset the underlying container and initialize the view.
ContainerView& reset(const BaseType& container)
{
data_ = std::make_optional(std::ref(container));
auto& dataSource = std::remove_const_t<BaseType&>(data());
view_.assign(dataSource.begin(), dataSource.end());
invalidate();
return *this;
}
// Alternately, reset the view to another view and disregard underlying data.
ContainerView& reset(const ContainerView& other)
{
data_ = std::nullopt;
auto& dataSource = std::remove_const_t<ContainerView&>(other);
view_.assign(dataSource.get().begin(), dataSource.get().end());
invalidate();
return *this;
}
// Sort the reference wrapped elements of the view container with the
// given predicate or the stored one.
ContainerView& sort(SortCallback&& pred = {})
{
if (!dirty_) {
std::cout << "view not dirty, no-op sort" << std::endl;
return *this;
}
if (auto&& sortCallback = pred ? pred : sortCallback_)
std::sort(view_.begin(), view_.end(), sortCallback);
else
std::cout << "no sort function specified or bound" << std::endl;
return *this;
}
// Filter the reference wrapped elements of the view container with the
// given predicate or the stored one.
// Only done if the view has been invalidated(e.g. the underlying container
// has been updated)
ContainerView& filter(FilterCallback&& pred = {})
{
if (!dirty_) {
std::cout << "view not dirty, no-op filter" << std::endl;
return *this;
}
if (auto&& filterCallback = pred ? pred : filterCallback_) {
if (data_.has_value()) {
auto& dataSource = std::remove_const_t<BaseType&>(data());
applyFilter(dataSource, filterCallback);
} else {
auto viewSource = view_;
applyFilter(viewSource, filterCallback);
}
} else
std::cout << "no filter function specified or bound" << std::endl;
return *this;
}
// Iterate over the the reference wrapped elements of the view container
// and execute a callback on each element.
ContainerView& for_each(OnEntryCallback&& pred)
{
for (const auto& e : view_)
pred(e);
return *this;
}
// Store a non-static member function as a SortCallback.
// The member function must match that of a binary predicate.
template<typename T, typename... Args>
void bindSortCallback(T* inst, bool (T::*func)(Args...))
{
bindCallback(sortCallback_, inst, func);
}
// Overload for function objects.
template<typename Func = SortCallback>
void bindSortCallback(Func&& func)
{
sortCallback_ = func;
}
// Store a non-static member function as a FilterCallback.
// The member function must match that of a unary predicate.
template<typename T, typename... Args>
void bindFilterCallback(T* inst, bool (T::*func)(Args...))
{
bindCallback(filterCallback_, inst, func);
}
// Overload for function objects.
template<typename Func = FilterCallback>
void bindFilterCallback(Func&& func)
{
filterCallback_ = func;
}
// Basic container operations should be avoided.
size_t size() const { return view_.size(); }
const_reference at(size_t pos) const { return view_.at(pos); }
void clear() { view_.clear(); }
// TODO: re-filtering ?? should maybe observe underlying data in order to
// not track this manually.
bool isDirty() const noexcept { return dirty_; };
ContainerView& invalidate() noexcept
{
dirty_ = true;
return *this;
};
ContainerView& validate() noexcept
{
dirty_ = false;
return *this;
};
// Returns whether or not this view has a concrete data source
// or is just a view of a view.
constexpr bool hasUnderlyingData() const noexcept { return data_.has_value(); }
// Access the view.
constexpr const view_type& operator()() const noexcept { return view_; }
constexpr const view_type& get() const noexcept { return view_; }
private:
// A reference to the optional underlying container data source.
// If not used, the view can be constructed from another proxy
// container view, and refiltered and sorted.
OptRef<const BaseType> data_;
// The 'view' is a container of reference wrapped values suitable
// for container operations like sorting and filtering.
view_type view_;
// TODO: remove this invalidation flag if possible.
bool dirty_ {true};
// Stores the sorting/filtering predicates that can be re-applied
// instead of passing a lambda as a parameter to sort().
FilterCallback filterCallback_ {};
// Same as above but for sorting.
SortCallback sortCallback_ {};
// A generic non-static member function storing function.
template<typename C, typename T, typename... Args>
void bindCallback(C& callback, T* inst, bool (T::*func)(Args...))
{
// Using a lambda instead of std::bind works better for functions with an
// unknown number of parameters. e.g. callback = std::bind(func, inst, _1, ???);
callback = [=](Args... args) -> bool {
return (inst->*func)(args...);
};
}
// Hard unbox and unwrap underlying data container.
const BaseType& data() { return data_.value().get(); }
// Actually filter the view.
template<typename T, typename Func = FilterCallback>
ContainerView& applyFilter(T& source, Func&& pred)
{
view_.clear();
std::copy_if(source.begin(), source.end(), std::back_inserter(view_), pred);
return *this;
}
};
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment