gnucash stable: Multiple changes pushed

John Ralls jralls at code.gnucash.org
Sat Sep 19 20:14:10 EDT 2026


Updated	 via  https://github.com/Gnucash/gnucash/commit/3c9cfbb9 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/1969d781 (commit)
	 via  https://github.com/Gnucash/gnucash/commit/06f7324e (commit)
	from  https://github.com/Gnucash/gnucash/commit/1e5f5d18 (commit)



commit 3c9cfbb91661688034e09168a0631804a639f95e
Merge: 1e5f5d184d 1969d781e4
Author: John Ralls <jralls at ceridwen.us>
Date:   Sat Sep 19 17:08:55 2026 -0700

    Merge Roy Hansen's 'improve/natural-sort-id-columns' into stable.


commit 1969d781e4067c80d5e33ccb054c614d5694bf13
Author: Roy Hansen <roy at royhansen.no>
Date:   Fri Sep 11 14:18:53 2026 +0200

    Sort Id columns naturally in qof-query, which is used in gnucash search
    dialogs.
    
    Changed sorting to be natural (chunks), instead of sequential ascii-sort.
    Number chunks are compared as whole numbers instead of one at a time.

diff --git a/gnucash/gnome-search/search-core-type.c b/gnucash/gnome-search/search-core-type.c
index 6612ec624c..e29204b193 100644
--- a/gnucash/gnome-search/search-core-type.c
+++ b/gnucash/gnome-search/search-core-type.c
@@ -238,6 +238,8 @@ init_table (void)
 {
     gnc_search_core_register_type (QOF_TYPE_STRING,
                                    (GNCSearchCoreNew) gnc_search_string_new);
+    gnc_search_core_register_type (QOF_TYPE_NATURAL,
+                                   (GNCSearchCoreNew) gnc_search_string_new);
     gnc_search_core_register_type (QOF_TYPE_DATE,
                                    (GNCSearchCoreNew) gnc_search_date_new);
     gnc_search_core_register_type (QOF_TYPE_INT64,
diff --git a/libgnucash/engine/gncCustomer.c b/libgnucash/engine/gncCustomer.c
index 9786b3dd7a..88c40823a3 100644
--- a/libgnucash/engine/gncCustomer.c
+++ b/libgnucash/engine/gncCustomer.c
@@ -927,7 +927,7 @@ gboolean gncCustomerRegister (void)
 {
     static QofParam params[] =
     {
-        { CUSTOMER_ID, QOF_TYPE_STRING, (QofAccessFunc)gncCustomerGetID, (QofSetterFunc)gncCustomerSetID },
+        { CUSTOMER_ID, QOF_TYPE_NATURAL, (QofAccessFunc)gncCustomerGetID, (QofSetterFunc)gncCustomerSetID },
         { CUSTOMER_NAME, QOF_TYPE_STRING, (QofAccessFunc)gncCustomerGetName, (QofSetterFunc)gncCustomerSetName },
         { CUSTOMER_NOTES, QOF_TYPE_STRING, (QofAccessFunc)gncCustomerGetNotes, (QofSetterFunc)gncCustomerSetNotes },
         {
diff --git a/libgnucash/engine/gncEmployee.c b/libgnucash/engine/gncEmployee.c
index fd9518f88d..74e4324cf2 100644
--- a/libgnucash/engine/gncEmployee.c
+++ b/libgnucash/engine/gncEmployee.c
@@ -913,7 +913,7 @@ gboolean gncEmployeeRegister (void)
 {
     static QofParam params[] =
     {
-        { EMPLOYEE_ID, QOF_TYPE_STRING, (QofAccessFunc)gncEmployeeGetID, (QofSetterFunc)gncEmployeeSetID },
+        { EMPLOYEE_ID, QOF_TYPE_NATURAL, (QofAccessFunc)gncEmployeeGetID, (QofSetterFunc)gncEmployeeSetID },
         {
             EMPLOYEE_USERNAME, QOF_TYPE_STRING, (QofAccessFunc)gncEmployeeGetUsername,
             (QofSetterFunc)gncEmployeeSetUsername
diff --git a/libgnucash/engine/gncInvoice.c b/libgnucash/engine/gncInvoice.c
index bd2aaba6d1..6e4cc497ce 100644
--- a/libgnucash/engine/gncInvoice.c
+++ b/libgnucash/engine/gncInvoice.c
@@ -2213,14 +2213,14 @@ gboolean gncInvoiceRegister (void)
 {
     static QofParam params[] =
     {
-        { INVOICE_ID,        QOF_TYPE_STRING,  (QofAccessFunc)gncInvoiceGetID,     (QofSetterFunc)gncInvoiceSetID },
+        { INVOICE_ID,        QOF_TYPE_NATURAL, (QofAccessFunc)gncInvoiceGetID, (QofSetterFunc)gncInvoiceSetID },
         { INVOICE_OWNER,     GNC_ID_OWNER,     (QofAccessFunc)gncInvoiceGetOwner, NULL },
         { INVOICE_OPENED,    QOF_TYPE_DATE,    (QofAccessFunc)gncInvoiceGetDateOpened, (QofSetterFunc)gncInvoiceSetDateOpened },
         { INVOICE_DUE,       QOF_TYPE_DATE,    (QofAccessFunc)gncInvoiceGetDateDue, NULL },
         { INVOICE_POSTED,    QOF_TYPE_DATE,    (QofAccessFunc)gncInvoiceGetDatePosted, (QofSetterFunc)gncInvoiceSetDatePosted },
         { INVOICE_IS_POSTED, QOF_TYPE_BOOLEAN, (QofAccessFunc)gncInvoiceIsPosted, NULL },
         { INVOICE_IS_PAID,   QOF_TYPE_BOOLEAN, (QofAccessFunc)gncInvoiceIsPaid,    NULL },
-        { INVOICE_BILLINGID, QOF_TYPE_STRING,  (QofAccessFunc)gncInvoiceGetBillingID, (QofSetterFunc)gncInvoiceSetBillingID },
+        { INVOICE_BILLINGID, QOF_TYPE_NATURAL, (QofAccessFunc)gncInvoiceGetBillingID, (QofSetterFunc)gncInvoiceSetBillingID },
         { INVOICE_NOTES,     QOF_TYPE_STRING,  (QofAccessFunc)gncInvoiceGetNotes,   (QofSetterFunc)gncInvoiceSetNotes },
         { INVOICE_DOCLINK, QOF_TYPE_STRING, (QofAccessFunc)gncInvoiceGetDocLink, (QofSetterFunc)gncInvoiceSetDocLink },
         { INVOICE_ACC,       GNC_ID_ACCOUNT,   (QofAccessFunc)gncInvoiceGetPostedAcc, (QofSetterFunc)gncInvoiceSetPostedAcc },
diff --git a/libgnucash/engine/gncVendor.c b/libgnucash/engine/gncVendor.c
index 7d0340c707..52471aaf0f 100644
--- a/libgnucash/engine/gncVendor.c
+++ b/libgnucash/engine/gncVendor.c
@@ -989,7 +989,7 @@ gboolean gncVendorRegister (void)
 {
     static QofParam params[] =
     {
-        { VENDOR_ID, QOF_TYPE_STRING, (QofAccessFunc)gncVendorGetID, (QofSetterFunc)gncVendorSetID },
+        { VENDOR_ID, QOF_TYPE_NATURAL, (QofAccessFunc)gncVendorGetID, (QofSetterFunc)gncVendorSetID },
         { VENDOR_NAME, QOF_TYPE_STRING, (QofAccessFunc)gncVendorGetName, (QofSetterFunc)gncVendorSetName },
         { VENDOR_ADDR,    GNC_ID_ADDRESS, (QofAccessFunc)gncVendorGetAddr, (QofSetterFunc)qofVendorSetAddr },
         { VENDOR_NOTES,   QOF_TYPE_STRING, (QofAccessFunc)gncVendorGetNotes, (QofSetterFunc)gncVendorSetNotes },
diff --git a/libgnucash/engine/qofclass.h b/libgnucash/engine/qofclass.h
index f671df69a8..d5d074fad7 100644
--- a/libgnucash/engine/qofclass.h
+++ b/libgnucash/engine/qofclass.h
@@ -88,6 +88,7 @@ single reference between two known objects.
  */
 
 #define QOF_TYPE_STRING    "string"
+#define QOF_TYPE_NATURAL   "natural"
 #define QOF_TYPE_DATE      "date"
 #define QOF_TYPE_NUMERIC   "numeric"
 #define QOF_TYPE_DEBCRED   "debcred"
diff --git a/libgnucash/engine/qofquerycore.cpp b/libgnucash/engine/qofquerycore.cpp
index 42acabdd60..e572720b35 100644
--- a/libgnucash/engine/qofquerycore.cpp
+++ b/libgnucash/engine/qofquerycore.cpp
@@ -27,6 +27,7 @@
 #include <glib.h>
 #include <stdlib.h>
 
+#include "gnc-glib-utils.h"
 #include "qof.h"
 #include "qofquerycore-p.h"
 
@@ -205,6 +206,31 @@ string_compare_func (gpointer a, gpointer b, gint options,
     return g_strcmp0 (s1, s2);
 }
 
+static int
+natural_compare_func (gpointer a, gpointer b, gint options,
+                      QofParam *getter)
+{
+    const char *s1, *s2;
+    g_return_val_if_fail (a && b && getter && getter->param_getfcn, COMPARE_ERROR);
+
+    s1 = ((query_string_getter)getter->param_getfcn) (a, getter);
+    s2 = ((query_string_getter)getter->param_getfcn) (b, getter);
+
+    if (options == QOF_STRING_MATCH_CASEINSENSITIVE) {
+      // There is no case-insensitive natural sort.
+      // Downcasing/folding a/b for every compare
+      // operation is too wasteful. 
+      //
+      // The best option is to downcase/fold only once
+      // for each item and then store that as a property
+      // that we use in natural_compare_func, but that
+      // will require some work outside natural_compare_func,
+      // and I dont think it is worth the extra code. 
+    }
+
+    return safe_utf8_collate_natural(s1, s2);
+}
+
 int
 qof_string_number_compare_func (gpointer a, gpointer b, gint options,
                                 QofParam *getter)
@@ -1460,6 +1486,11 @@ static void init_tables (void)
             string_copy_predicate, string_free_pdata, string_to_string,
             string_predicate_equal
         },
+        {
+            QOF_TYPE_NATURAL, string_match_predicate, natural_compare_func,
+            string_copy_predicate, string_free_pdata, string_to_string,
+            string_predicate_equal
+        },
         {
             QOF_TYPE_DATE, date_match_predicate, date_compare_func,
             date_copy_predicate, date_free_pdata, date_to_string,

commit 06f7324e589cf5f4c0dd5e221b8bc3f67544acf1
Author: Roy Hansen <roy at royhansen.no>
Date:   Fri Sep 11 14:09:36 2026 +0200

    Sort Id columns naturally in gnc-tree-views.
    
    Changed sorting to be natural (chunks), instead of sequential ascii-sort.
    Number chunks are compared as whole numbers instead of one at a time.
    
    After this change, customer-overview, vendor-overview and
    employee-overview will naturally sort "id" columns.

diff --git a/gnucash/gnome-utils/gnc-tree-view-owner.c b/gnucash/gnome-utils/gnc-tree-view-owner.c
index d1b1762845..68976ea84b 100644
--- a/gnucash/gnome-utils/gnc-tree-view-owner.c
+++ b/gnucash/gnome-utils/gnc-tree-view-owner.c
@@ -242,6 +242,35 @@ sort_by_string (GtkTreeModel *f_model,
     return gncOwnerCompare(owner1, owner2);
 }
 
+static gint
+sort_by_string_natural (GtkTreeModel *f_model,
+                        GtkTreeIter *f_iter1,
+                        GtkTreeIter *f_iter2,
+                        gpointer user_data)
+{
+    GtkTreeModel *model;
+    GtkTreeIter iter1, iter2;
+    gint column = GPOINTER_TO_INT(user_data);
+    gchar *str1, *str2;
+    const GncOwner *owner1, *owner2;
+    gint result;
+
+    model = sort_cb_setup_w_iters(f_model, f_iter1, f_iter2, &iter1, &iter2, &owner1, &owner2);
+
+    gtk_tree_model_get(GTK_TREE_MODEL(model), &iter1,  column, &str1, -1);
+    gtk_tree_model_get(GTK_TREE_MODEL(model), &iter2,  column, &str2, -1);
+    
+    result = safe_utf8_collate_natural(str1, str2);
+
+    g_free(str1);
+    g_free(str2);
+
+    if (result != 0)
+        return result;
+
+    return gncOwnerCompare(owner1, owner2);
+}
+
 static gint
 sort_by_boolean (GtkTreeModel *f_model,
                  GtkTreeIter *f_iter1,
@@ -390,7 +419,7 @@ gnc_tree_view_owner_new (GncOwnerType owner_type)
                                         NULL, "1-123-1234",
                                         GNC_TREE_MODEL_OWNER_COL_ID,
                                         GNC_TREE_VIEW_COLUMN_VISIBLE_ALWAYS,
-                                        sort_by_string);
+                                        sort_by_string_natural);
     gnc_tree_view_add_text_column(GNC_TREE_VIEW(view), _("Currency"), GNC_OWNER_TREE_CURRENCY_COL,
                                   NULL, sample_currency,
                                   GNC_TREE_MODEL_OWNER_COL_CURRENCY,
diff --git a/libgnucash/core-utils/gnc-glib-utils.c b/libgnucash/core-utils/gnc-glib-utils.c
index 0b55982625..591781d809 100644
--- a/libgnucash/core-utils/gnc-glib-utils.c
+++ b/libgnucash/core-utils/gnc-glib-utils.c
@@ -51,6 +51,35 @@ safe_utf8_collate (const char * da, const char * db)
     return 0;
 }
 
+int
+safe_utf8_collate_natural (const char * da, const char * db)
+{
+    if (da && !(*da))
+        da = NULL;
+
+    if (db && !(*db))
+        db = NULL;
+
+    if (da && db)
+    {
+        gchar *a = g_utf8_collate_key_for_filename(da ? da : "", -1);
+        gchar *b = g_utf8_collate_key_for_filename(db ? db : "", -1);
+
+        int result = strcmp(a, b);
+
+        g_free(a);
+        g_free(b);
+
+        return result;
+    }
+    else if (da)
+        return 1;
+    else if (db)
+        return -1;
+
+    return 0;
+}
+
 /********************************************************************
  * The following definitions are from gutf8.c, for use by
  * gnc_utf8_validate().  These are all verbatim copies, except for
diff --git a/libgnucash/core-utils/gnc-glib-utils.h b/libgnucash/core-utils/gnc-glib-utils.h
index a2770aaaf8..c32e03be66 100644
--- a/libgnucash/core-utils/gnc-glib-utils.h
+++ b/libgnucash/core-utils/gnc-glib-utils.h
@@ -59,6 +59,18 @@ extern "C" {
  *  compares after str2. */
 int safe_utf8_collate (const char *str1, const char *str2);
 
+/** Collate two UTF-8 strings _naturally_.  This function performs basic argument
+ *  checking before calling g_utf8_collate_key_for_filename.
+ *
+ *  @param str1 The first string.
+ *
+ *  @param str2 The first string.
+ *
+ *  @return Same return value as g_utf8_collate. The values are: < 0
+ *  if str1 compares before str2, 0 if they compare equal, > 0 if str1
+ *  compares after str2. */
+int safe_utf8_collate_natural (const char *str1, const char *str2);
+
 /**
  * @brief Validates UTF-8 encoded text for use in GnuCash.
  *



Summary of changes:
 gnucash/gnome-search/search-core-type.c   |  2 ++
 gnucash/gnome-utils/gnc-tree-view-owner.c | 31 ++++++++++++++++++++++++++++++-
 libgnucash/core-utils/gnc-glib-utils.c    | 29 +++++++++++++++++++++++++++++
 libgnucash/core-utils/gnc-glib-utils.h    | 12 ++++++++++++
 libgnucash/engine/gncCustomer.c           |  2 +-
 libgnucash/engine/gncEmployee.c           |  2 +-
 libgnucash/engine/gncInvoice.c            |  4 ++--
 libgnucash/engine/gncVendor.c             |  2 +-
 libgnucash/engine/qofclass.h              |  1 +
 libgnucash/engine/qofquerycore.cpp        | 31 +++++++++++++++++++++++++++++++
 10 files changed, 110 insertions(+), 6 deletions(-)



More information about the gnucash-changes mailing list