mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
Merge c7f19c97c8
into 5ce3a9dd1d
This commit is contained in:
commit
92fb925b09
@ -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
|
||||
|
187
gnucash/gnome-utils/gnc-list-model-container.hpp
Normal file
187
gnucash/gnome-utils/gnc-list-model-container.hpp
Normal 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
|
@ -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
gnucash/gnome-utils/test/test-list-model-container.cpp
Normal file
124
gnucash/gnome-utils/test/test-list-model-container.cpp
Normal 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();
|
||||
}
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user