This commit is contained in:
Christopher Lam 2025-02-10 08:47:37 +08:00 committed by GitHub
commit 92fb925b09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 354 additions and 31 deletions

View File

@ -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

View File

@ -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 gnu@gnu.org *
\********************************************************************/
#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

View File

@ -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})

View File

@ -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 gnu@gnu.org *
********************************************************************/
#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();
}

View File

@ -31,12 +31,15 @@
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include "gnc-list-model-container.hpp"
#include "import-match-picker.h"
#include "qof.h"
#include "gnc-ui-util.h"
#include "dialog-utils.h"
#include "gnc-prefs.h"
#include <algorithm>
/********************************************************************\
* Constants *
\********************************************************************/
@ -98,32 +101,22 @@ downloaded_transaction_append(GNCImportMatchPicker * matcher,
g_return_if_fail (matcher);
g_return_if_fail (transaction_info);
auto found = false;
auto store = GTK_LIST_STORE(gtk_tree_view_get_model(matcher->downloaded_view));
auto split = gnc_import_TransInfo_get_fsplit(transaction_info);
auto trans = gnc_import_TransInfo_get_trans(transaction_info);
GncListModelContainer container{GTK_TREE_MODEL(store)};
/* Has the transaction already been added? */
GtkTreeIter iter;
if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter))
{
do
{
GNCImportTransInfo *local_info;
gtk_tree_model_get(GTK_TREE_MODEL(store), &iter,
DOWNLOADED_COL_INFO_PTR, &local_info,
-1);
if (local_info == transaction_info)
{
found = TRUE;
break;
}
}
while (gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter));
}
if (!found)
gtk_list_store_append(store, &iter);
auto it_matches = [&transaction_info](auto it)
{ return it.template get_column<GNCImportTransInfo*>(DOWNLOADED_COL_INFO_PTR) == transaction_info; };
// find the GncTreeIter whose DOWNLOADED_COL_INFO_PTR matches transaction_info
auto iter = std::find_if (container.begin(), container.end(), it_matches);
// not found. append a new GtkTreeIter in container.
if (iter == container.end())
iter = container.append();
// now iter is a GncTreeIter; iter->get_iter() is the GtkTreeIter
auto account = xaccAccountGetName(xaccSplitGetAccount(split));
auto date = qof_print_date(xaccTransGetDate(trans));
auto amount = g_strdup (xaccPrintAmount(xaccSplitGetAmount(split), gnc_split_amount_print_info(split, TRUE)));
@ -137,17 +130,17 @@ downloaded_transaction_append(GNCImportMatchPicker * matcher,
auto imbalance = g_strdup (xaccPrintAmount (xaccTransGetImbalanceValue(trans),
gnc_commodity_print_info (xaccTransGetCurrency (trans), TRUE)));
gtk_list_store_set (store, &iter,
DOWNLOADED_COL_ACCOUNT, account,
DOWNLOADED_COL_DATE, date,
DOWNLOADED_COL_AMOUNT, amount,
DOWNLOADED_COL_DESCRIPTION, desc,
DOWNLOADED_COL_MEMO, memo,
DOWNLOADED_COL_BALANCED, imbalance,
DOWNLOADED_COL_INFO_PTR, transaction_info,
-1);
iter->set_columns (0,
DOWNLOADED_COL_ACCOUNT, account,
DOWNLOADED_COL_DATE, date,
DOWNLOADED_COL_AMOUNT, amount,
DOWNLOADED_COL_DESCRIPTION, desc,
DOWNLOADED_COL_MEMO, memo,
DOWNLOADED_COL_BALANCED, imbalance,
DOWNLOADED_COL_INFO_PTR, transaction_info,
-1);
gtk_tree_selection_select_iter (gtk_tree_view_get_selection(matcher->downloaded_view), &iter);
gtk_tree_selection_select_iter (gtk_tree_view_get_selection(matcher->downloaded_view), &iter->get_iter());
g_free (date);
g_free (amount);