Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Iter to stl #1751 redux #1753

Draft
wants to merge 7 commits into
base: stable
Choose a base branch
from
1 change: 1 addition & 0 deletions gnucash/gnome-utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ set (gnome_utils_HEADERS
gnc-gui-query.h
gnc-icons.h
gnc-keyring.h
gnc-list-model-container.hpp
gnc-main-window.h
gnc-menu-extensions.h
gnc-plugin-file-history.h
Expand Down
187 changes: 187 additions & 0 deletions gnucash/gnome-utils/gnc-list-model-container.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/********************************************************************\
* gnc-tree-container.hpp
* *
* 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 2 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, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA [email protected] *
\********************************************************************/

#ifndef GNC_LIST_MODEL_CONTAINER_HPP
#define GNC_LIST_MODEL_CONTAINER_HPP

#include <string>
#include <optional>
#include <algorithm>
#include <memory>

class GncListModelData
{
public:
GncListModelData (GtkTreeModel* model, const GtkTreeIter& iter) : m_model{model}, m_iter{iter} {};

template <typename T>
T get_column (int column)
{
gpointer rv;
gtk_tree_model_get(m_model, &m_iter, column, &rv, -1);
return static_cast<T>(rv);
}

GtkTreeIter& get_iter () { return m_iter; };

int get_column_int (int column)
{
int rv;
gtk_tree_model_get(m_model, &m_iter, column, &rv, -1);
return rv;
}

std::string get_column_string (int column)
{
auto str = get_column<char*>(column);
std::string rv{str};
g_free (str);
return rv;
}

void set_columns (int unused, ...)
{
va_list var_args;
va_start (var_args, unused);
gtk_list_store_set_valist (GTK_LIST_STORE(m_model), &get_iter(), var_args);
va_end (var_args);
}

template <typename T>
void set_column (int column, T data) { gtk_list_store_set(GTK_LIST_STORE(m_model), &get_iter(), column, data, -1); }

// overloads the template when data is a std::string
void set_column (int column, const std::string& str) { set_column (column, str.c_str()); }

bool operator==(const GncListModelData& other) const
{
return (m_model == other.m_model) &&
(m_iter.stamp == other.m_iter.stamp) &&
(m_iter.user_data == other.m_iter.user_data) &&
(m_iter.user_data2 == other.m_iter.user_data2) &&
(m_iter.user_data3 == other.m_iter.user_data3);
}

private:
GtkTreeModel* m_model;
GtkTreeIter m_iter;
};

// Custom container class
template <typename ModelType = GncListModelData>
class GncListModelContainer
{
public:

// Custom iterator class
class GncListModelIter
{
public:
/* Set iterator traits queried by STL algorithms. These are
required for std::find_if etc to iterate through the
container. */
using iterator_category = std::forward_iterator_tag;
using value_type = ModelType;
using difference_type = std::ptrdiff_t;
using pointer = ModelType*;
using reference = ModelType&;

GncListModelIter(GtkTreeModel* model, std::optional<GtkTreeIter> iter) : m_model(model), m_iter(iter) {}

GncListModelIter(GtkTreeModel* model) : m_model (model)
{
GtkTreeIter iter;
m_iter = gtk_tree_model_get_iter_first(m_model, &iter) ? std::make_optional(iter) : std::nullopt;
}

GncListModelIter& operator++()
{
if (!m_iter.has_value())
throw "no value, cannot increment";
if (!gtk_tree_model_iter_next(m_model, &m_iter.value()))
m_iter = std::nullopt;
return *this;
}

ModelType operator*() const
{
if (!m_iter.has_value())
throw "no value, cannot dereference";
return ModelType (m_model, *m_iter);
}

std::unique_ptr<ModelType> operator->()
{
if (!m_iter.has_value())
throw "no value, cannot dereference";
return std::make_unique<ModelType> (m_model, *m_iter);
}

bool has_value() const { return m_iter.has_value(); };

bool operator==(const GncListModelIter& other) const
{
if (m_model != other.m_model)
return false;
if (!m_iter.has_value() && !other.m_iter.has_value())
return true;
if (!m_iter.has_value() || !other.m_iter.has_value())
return false;
return (ModelType (m_model, *m_iter) == ModelType (m_model, *other.m_iter));
}

bool operator!=(const GncListModelIter& other) const { return !(*this == other); }

private:
GtkTreeModel* m_model;
std::optional<GtkTreeIter> m_iter;
};

GncListModelContainer(GtkTreeModel* model) : m_model(model)
{
g_return_if_fail (GTK_IS_TREE_MODEL (m_model));
g_object_ref (m_model);
}

~GncListModelContainer () { g_object_unref (m_model); }

GncListModelIter begin() const { return GncListModelIter(m_model); };

GncListModelIter end() const { return GncListModelIter(m_model, std::nullopt); };

GncListModelIter append()
{
GtkTreeIter iter;
gtk_list_store_append (GTK_LIST_STORE(m_model), &iter);
return GncListModelIter(m_model, iter);
};

size_t size() const { return std::distance (begin(), end()); }

bool empty() const { return begin() == end(); };

void clear() { gtk_list_store_clear (GTK_LIST_STORE (m_model)); };

private:
GtkTreeModel* m_model;
};

#endif
18 changes: 18 additions & 0 deletions gnucash/gnome-utils/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,31 @@ set(test_autoclear_LIBS
gtest
)

set(test_list_model_container_SOURCES
test-list-model-container.cpp
)

set(test_list_model_container_INCLUDE_DIRS
)

set(test_list_model_container_LIBS
gnc-gnome-utils
gtest
)

gnc_add_test(test-autoclear "${test_autoclear_SOURCES}"
test_autoclear_INCLUDE_DIRS
test_autoclear_LIBS
)

gnc_add_test(test-list-model-container "${test_list_model_container_SOURCES}"
test_list_model_container_INCLUDE_DIRS
test_list_model_container_LIBS
)

gnc_add_scheme_tests(test-load-gnome-utils-module.scm)


set_dist_list(test_gnome_utils_DIST CMakeLists.txt test-load-gnome-utils-module.scm
${test_list_model_container_SOURCES}
${test_autoclear_SOURCES})
124 changes: 124 additions & 0 deletions gnucash/gnome-utils/test/test-list-model-container.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/********************************************************************
* test-tree-container.cpp: *
* *
* 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 2 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, you can retrieve it from *
* https://www.gnu.org/licenses/old-licenses/gpl-2.0.html *
* or contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA [email protected] *
********************************************************************/

#include "config.h"
#include <glib.h>
#include <gtk/gtk.h>
#include "../gnc-list-model-container.hpp"
#include <gtest/gtest.h>
#include <string>

enum {
COLUMN_STRING,
COLUMN_INT,
COLUMN_BOOLEAN,
N_COLUMNS
};


TEST(GncListModelContainer, Equality)
{
auto store1 = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
auto store2 = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);

GncListModelContainer container1{GTK_TREE_MODEL(store1)};
GncListModelContainer container2{GTK_TREE_MODEL(store2)};

// these are null tests
EXPECT_TRUE (container1.begin() == container1.begin());
EXPECT_TRUE (container1.end() == container1.end());

EXPECT_TRUE (container1.begin() == container1.end());
EXPECT_TRUE (container1.size() == 0);
EXPECT_TRUE (container2.size() == 0);
EXPECT_TRUE (container1.empty());
EXPECT_TRUE (container1.empty());

EXPECT_FALSE (container1.begin() == container2.begin());
EXPECT_FALSE (container1.end() == container2.end());

// both containers have identical contents
container1.append()->set_column (COLUMN_STRING, "1");
container2.append()->set_column (COLUMN_STRING, "1");

// the containers are now no longer empty
EXPECT_FALSE (container1.begin() == container1.end());
EXPECT_FALSE (container2.begin() == container2.end());
EXPECT_TRUE (container1.size() == 1);
EXPECT_TRUE (container2.size() == 1);

// however the iterators behave as expected -- iterators from
// store1 must differ from iterators from store2
EXPECT_FALSE (container1.begin() == container2.begin());
EXPECT_FALSE (container1.end() == container2.end());

g_object_unref (store1);
g_object_unref (store2);
}

TEST(GncListModelContainer, Basic)
{
auto store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_INT, G_TYPE_BOOLEAN);
GncListModelContainer container{GTK_TREE_MODEL(store)};

// test empty
EXPECT_TRUE (container.empty());

for (size_t i = 0; i < 10; i++)
{
auto str = std::string("string ") + std::to_string(i);
auto iter = container.append ();
iter->set_columns (0,
COLUMN_STRING, str.c_str(),
COLUMN_INT, i,
COLUMN_BOOLEAN, (i % 2) == 0,
-1);
}

// test non-empty
EXPECT_FALSE (container.empty());

// test size
EXPECT_TRUE (10 == container.size());

auto int_is_five = [](auto it){ return it.get_column_int(COLUMN_INT) == 5; };
auto iter_found = std::find_if (container.begin(), container.end(), int_is_five);
EXPECT_TRUE (iter_found.has_value());
EXPECT_EQ ("string 5", iter_found->get_column_string (COLUMN_STRING));

g_object_unref (store);
}

int main(int argc, char** argv)
{
if (gtk_init_check (nullptr, nullptr))
std::cout << "gtk init completed!" << std::endl;
else
std::cout << "no display present!" << std::endl;

// Initialize the Google Test framework
::testing::InitGoogleTest(&argc, argv);

// Run tests
return RUN_ALL_TESTS();
}
Loading