Replace ETableSortColumn with separate ETableColumnSpecification and GtkSortType parameters in the "get_nth" and "set_nth" functions. Makes some other parts of the code simpler since it no longer has to translate a column number to a column specification.
528 lines
14 KiB
C
528 lines
14 KiB
C
/*
|
|
* e-table-sorter.c
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) version 3.
|
|
*
|
|
* 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with the program; if not, see <http://www.gnu.org/licenses/>
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <glib/gi18n.h>
|
|
|
|
#include "e-table-sorter.h"
|
|
#include "e-table-sorting-utils.h"
|
|
|
|
#define d(x)
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_SORT_INFO
|
|
};
|
|
|
|
/* Forward Declarations */
|
|
static void e_table_sorter_interface_init (ESorterInterface *interface);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (
|
|
ETableSorter,
|
|
e_table_sorter,
|
|
G_TYPE_OBJECT,
|
|
G_IMPLEMENT_INTERFACE (
|
|
E_TYPE_SORTER,
|
|
e_table_sorter_interface_init))
|
|
|
|
struct qsort_data {
|
|
ETableSorter *table_sorter;
|
|
gpointer *vals;
|
|
gint cols;
|
|
gint *ascending;
|
|
GCompareDataFunc *compare;
|
|
gpointer cmp_cache;
|
|
};
|
|
|
|
/* FIXME: Make it not cache the second and later columns (as if anyone cares.) */
|
|
|
|
static gint
|
|
qsort_callback (gconstpointer data1,
|
|
gconstpointer data2,
|
|
gpointer user_data)
|
|
{
|
|
struct qsort_data *qd = (struct qsort_data *) user_data;
|
|
gint row1 = *(gint *) data1;
|
|
gint row2 = *(gint *) data2;
|
|
gint j;
|
|
gint sort_count;
|
|
gint comp_val = 0;
|
|
gint ascending = 1;
|
|
|
|
sort_count =
|
|
e_table_sort_info_sorting_get_count (
|
|
qd->table_sorter->sort_info) +
|
|
e_table_sort_info_grouping_get_count (
|
|
qd->table_sorter->sort_info);
|
|
|
|
for (j = 0; j < sort_count; j++) {
|
|
comp_val = (*(qd->compare[j]))(qd->vals[qd->cols * row1 + j], qd->vals[qd->cols * row2 + j], qd->cmp_cache);
|
|
ascending = qd->ascending[j];
|
|
if (comp_val != 0)
|
|
break;
|
|
}
|
|
if (comp_val == 0) {
|
|
if (row1 < row2)
|
|
comp_val = -1;
|
|
if (row1 > row2)
|
|
comp_val = 1;
|
|
}
|
|
if (!ascending)
|
|
comp_val = -comp_val;
|
|
|
|
return comp_val;
|
|
}
|
|
|
|
static void
|
|
table_sorter_clean (ETableSorter *table_sorter)
|
|
{
|
|
g_free (table_sorter->sorted);
|
|
table_sorter->sorted = NULL;
|
|
|
|
g_free (table_sorter->backsorted);
|
|
table_sorter->backsorted = NULL;
|
|
|
|
table_sorter->needs_sorting = -1;
|
|
}
|
|
|
|
static void
|
|
table_sorter_sort (ETableSorter *table_sorter)
|
|
{
|
|
gint rows;
|
|
gint i;
|
|
gint j;
|
|
gint cols;
|
|
gint group_cols;
|
|
struct qsort_data qd;
|
|
|
|
if (table_sorter->sorted)
|
|
return;
|
|
|
|
rows = e_table_model_row_count (table_sorter->source);
|
|
group_cols = e_table_sort_info_grouping_get_count (table_sorter->sort_info);
|
|
cols = e_table_sort_info_sorting_get_count (table_sorter->sort_info) + group_cols;
|
|
|
|
table_sorter->sorted = g_new (int, rows);
|
|
for (i = 0; i < rows; i++)
|
|
table_sorter->sorted[i] = i;
|
|
|
|
qd.cols = cols;
|
|
qd.table_sorter = table_sorter;
|
|
|
|
qd.vals = g_new (gpointer , rows * cols);
|
|
qd.ascending = g_new (int, cols);
|
|
qd.compare = g_new (GCompareDataFunc, cols);
|
|
qd.cmp_cache = e_table_sorting_utils_create_cmp_cache ();
|
|
|
|
for (j = 0; j < cols; j++) {
|
|
ETableColumnSpecification *spec;
|
|
ETableCol *col;
|
|
GtkSortType sort_type;
|
|
|
|
if (j < group_cols)
|
|
spec = e_table_sort_info_grouping_get_nth (
|
|
table_sorter->sort_info,
|
|
j, &sort_type);
|
|
else
|
|
spec = e_table_sort_info_sorting_get_nth (
|
|
table_sorter->sort_info,
|
|
j - group_cols, &sort_type);
|
|
|
|
col = e_table_header_get_column_by_spec (
|
|
table_sorter->full_header, spec);
|
|
if (col == NULL) {
|
|
gint last = e_table_header_count (
|
|
table_sorter->full_header) - 1;
|
|
col = e_table_header_get_column (
|
|
table_sorter->full_header, last);
|
|
}
|
|
|
|
for (i = 0; i < rows; i++) {
|
|
qd.vals[i * cols + j] = e_table_model_value_at (
|
|
table_sorter->source,
|
|
col->spec->model_col, i);
|
|
}
|
|
|
|
qd.compare[j] = col->compare;
|
|
qd.ascending[j] = (sort_type == GTK_SORT_ASCENDING);
|
|
}
|
|
|
|
g_qsort_with_data (table_sorter->sorted, rows, sizeof (gint), qsort_callback, &qd);
|
|
|
|
g_free (qd.vals);
|
|
g_free (qd.ascending);
|
|
g_free (qd.compare);
|
|
e_table_sorting_utils_free_cmp_cache (qd.cmp_cache);
|
|
}
|
|
|
|
static void
|
|
table_sorter_backsort (ETableSorter *table_sorter)
|
|
{
|
|
gint i, rows;
|
|
|
|
if (table_sorter->backsorted)
|
|
return;
|
|
|
|
table_sorter_sort (table_sorter);
|
|
|
|
rows = e_table_model_row_count (table_sorter->source);
|
|
table_sorter->backsorted = g_new0 (int, rows);
|
|
|
|
for (i = 0; i < rows; i++) {
|
|
table_sorter->backsorted[table_sorter->sorted[i]] = i;
|
|
}
|
|
}
|
|
|
|
static void
|
|
table_sorter_model_changed_cb (ETableModel *table_model,
|
|
ETableSorter *table_sorter)
|
|
{
|
|
table_sorter_clean (table_sorter);
|
|
}
|
|
|
|
static void
|
|
table_sorter_model_row_changed_cb (ETableModel *table_model,
|
|
gint row,
|
|
ETableSorter *table_sorter)
|
|
{
|
|
table_sorter_clean (table_sorter);
|
|
}
|
|
|
|
static void
|
|
table_sorter_model_cell_changed_cb (ETableModel *table_model,
|
|
gint col,
|
|
gint row,
|
|
ETableSorter *table_sorter)
|
|
{
|
|
table_sorter_clean (table_sorter);
|
|
}
|
|
|
|
static void
|
|
table_sorter_model_rows_inserted_cb (ETableModel *table_model,
|
|
gint row,
|
|
gint count,
|
|
ETableSorter *table_sorter)
|
|
{
|
|
table_sorter_clean (table_sorter);
|
|
}
|
|
|
|
static void
|
|
table_sorter_model_rows_deleted_cb (ETableModel *table_model,
|
|
gint row,
|
|
gint count,
|
|
ETableSorter *table_sorter)
|
|
{
|
|
table_sorter_clean (table_sorter);
|
|
}
|
|
|
|
static void
|
|
table_sorter_sort_info_changed_cb (ETableSortInfo *sort_info,
|
|
ETableSorter *table_sorter)
|
|
{
|
|
table_sorter_clean (table_sorter);
|
|
}
|
|
|
|
static void
|
|
table_sorter_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ETableSorter *table_sorter = E_TABLE_SORTER (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_SORT_INFO:
|
|
if (table_sorter->sort_info) {
|
|
if (table_sorter->sort_info_changed_id)
|
|
g_signal_handler_disconnect (
|
|
table_sorter->sort_info,
|
|
table_sorter->sort_info_changed_id);
|
|
if (table_sorter->group_info_changed_id)
|
|
g_signal_handler_disconnect (
|
|
table_sorter->sort_info,
|
|
table_sorter->group_info_changed_id);
|
|
g_object_unref (table_sorter->sort_info);
|
|
}
|
|
|
|
table_sorter->sort_info = g_value_dup_object (value);
|
|
|
|
table_sorter->sort_info_changed_id = g_signal_connect (
|
|
table_sorter->sort_info, "sort_info_changed",
|
|
G_CALLBACK (table_sorter_sort_info_changed_cb),
|
|
table_sorter);
|
|
table_sorter->group_info_changed_id = g_signal_connect (
|
|
table_sorter->sort_info, "group_info_changed",
|
|
G_CALLBACK (table_sorter_sort_info_changed_cb),
|
|
table_sorter);
|
|
|
|
table_sorter_clean (table_sorter);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
table_sorter_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
ETableSorter *table_sorter = E_TABLE_SORTER (object);
|
|
switch (property_id) {
|
|
case PROP_SORT_INFO:
|
|
g_value_set_object (value, table_sorter->sort_info);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
table_sorter_dispose (GObject *object)
|
|
{
|
|
ETableSorter *table_sorter = E_TABLE_SORTER (object);
|
|
|
|
if (table_sorter->table_model_changed_id > 0) {
|
|
g_signal_handler_disconnect (
|
|
table_sorter->source,
|
|
table_sorter->table_model_changed_id);
|
|
table_sorter->table_model_changed_id = 0;
|
|
}
|
|
|
|
if (table_sorter->table_model_row_changed_id > 0) {
|
|
g_signal_handler_disconnect (
|
|
table_sorter->source,
|
|
table_sorter->table_model_row_changed_id);
|
|
table_sorter->table_model_row_changed_id = 0;
|
|
}
|
|
|
|
if (table_sorter->table_model_cell_changed_id > 0) {
|
|
g_signal_handler_disconnect (
|
|
table_sorter->source,
|
|
table_sorter->table_model_cell_changed_id);
|
|
table_sorter->table_model_cell_changed_id = 0;
|
|
}
|
|
|
|
if (table_sorter->table_model_rows_inserted_id > 0) {
|
|
g_signal_handler_disconnect (
|
|
table_sorter->source,
|
|
table_sorter->table_model_rows_inserted_id);
|
|
table_sorter->table_model_rows_inserted_id = 0;
|
|
}
|
|
|
|
if (table_sorter->table_model_rows_deleted_id > 0) {
|
|
g_signal_handler_disconnect (
|
|
table_sorter->source,
|
|
table_sorter->table_model_rows_deleted_id);
|
|
table_sorter->table_model_rows_deleted_id = 0;
|
|
}
|
|
|
|
if (table_sorter->sort_info_changed_id > 0) {
|
|
g_signal_handler_disconnect (
|
|
table_sorter->sort_info,
|
|
table_sorter->sort_info_changed_id);
|
|
table_sorter->sort_info_changed_id = 0;
|
|
}
|
|
|
|
if (table_sorter->group_info_changed_id > 0) {
|
|
g_signal_handler_disconnect (
|
|
table_sorter->sort_info,
|
|
table_sorter->group_info_changed_id);
|
|
table_sorter->group_info_changed_id = 0;
|
|
}
|
|
|
|
g_clear_object (&table_sorter->sort_info);
|
|
g_clear_object (&table_sorter->full_header);
|
|
g_clear_object (&table_sorter->source);
|
|
|
|
/* Chain up to parent's dispose() method. */
|
|
G_OBJECT_CLASS (e_table_sorter_parent_class)->dispose (object);
|
|
}
|
|
|
|
static gint
|
|
table_sorter_model_to_sorted (ESorter *sorter,
|
|
gint row)
|
|
{
|
|
ETableSorter *table_sorter = E_TABLE_SORTER (sorter);
|
|
gint rows = e_table_model_row_count (table_sorter->source);
|
|
|
|
g_return_val_if_fail (row >= 0, -1);
|
|
g_return_val_if_fail (row < rows, -1);
|
|
|
|
if (e_sorter_needs_sorting (sorter))
|
|
table_sorter_backsort (table_sorter);
|
|
|
|
if (table_sorter->backsorted)
|
|
return table_sorter->backsorted[row];
|
|
else
|
|
return row;
|
|
}
|
|
|
|
static gint
|
|
table_sorter_sorted_to_model (ESorter *sorter,
|
|
gint row)
|
|
{
|
|
ETableSorter *table_sorter = E_TABLE_SORTER (sorter);
|
|
gint rows = e_table_model_row_count (table_sorter->source);
|
|
|
|
g_return_val_if_fail (row >= 0, -1);
|
|
g_return_val_if_fail (row < rows, -1);
|
|
|
|
if (e_sorter_needs_sorting (sorter))
|
|
table_sorter_sort (table_sorter);
|
|
|
|
if (table_sorter->sorted)
|
|
return table_sorter->sorted[row];
|
|
else
|
|
return row;
|
|
}
|
|
|
|
static void
|
|
table_sorter_get_model_to_sorted_array (ESorter *sorter,
|
|
gint **array,
|
|
gint *count)
|
|
{
|
|
ETableSorter *table_sorter = E_TABLE_SORTER (sorter);
|
|
|
|
if (array || count) {
|
|
table_sorter_backsort (table_sorter);
|
|
|
|
if (array)
|
|
*array = table_sorter->backsorted;
|
|
if (count)
|
|
*count = e_table_model_row_count(table_sorter->source);
|
|
}
|
|
}
|
|
|
|
static void
|
|
table_sorter_get_sorted_to_model_array (ESorter *sorter,
|
|
gint **array,
|
|
gint *count)
|
|
{
|
|
ETableSorter *table_sorter = E_TABLE_SORTER (sorter);
|
|
|
|
if (array || count) {
|
|
table_sorter_sort (table_sorter);
|
|
|
|
if (array)
|
|
*array = table_sorter->sorted;
|
|
if (count)
|
|
*count = e_table_model_row_count(table_sorter->source);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
table_sorter_needs_sorting (ESorter *sorter)
|
|
{
|
|
ETableSorter *table_sorter = E_TABLE_SORTER (sorter);
|
|
|
|
if (table_sorter->needs_sorting < 0) {
|
|
if (e_table_sort_info_sorting_get_count (table_sorter->sort_info) + e_table_sort_info_grouping_get_count (table_sorter->sort_info))
|
|
table_sorter->needs_sorting = 1;
|
|
else
|
|
table_sorter->needs_sorting = 0;
|
|
}
|
|
return table_sorter->needs_sorting;
|
|
}
|
|
|
|
static void
|
|
e_table_sorter_class_init (ETableSorterClass *class)
|
|
{
|
|
GObjectClass *object_class;
|
|
|
|
object_class = G_OBJECT_CLASS (class);
|
|
object_class->set_property = table_sorter_set_property;
|
|
object_class->get_property = table_sorter_get_property;
|
|
object_class->dispose = table_sorter_dispose;
|
|
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_SORT_INFO,
|
|
g_param_spec_object (
|
|
"sort_info",
|
|
"Sort Info",
|
|
NULL,
|
|
E_TYPE_TABLE_SORT_INFO,
|
|
G_PARAM_READWRITE));
|
|
}
|
|
|
|
static void
|
|
e_table_sorter_interface_init (ESorterInterface *interface)
|
|
{
|
|
interface->model_to_sorted = table_sorter_model_to_sorted;
|
|
interface->sorted_to_model = table_sorter_sorted_to_model;
|
|
interface->get_model_to_sorted_array = table_sorter_get_model_to_sorted_array;
|
|
interface->get_sorted_to_model_array = table_sorter_get_sorted_to_model_array;
|
|
interface->needs_sorting = table_sorter_needs_sorting;
|
|
}
|
|
|
|
static void
|
|
e_table_sorter_init (ETableSorter *table_sorter)
|
|
{
|
|
table_sorter->needs_sorting = -1;
|
|
}
|
|
|
|
ETableSorter *
|
|
e_table_sorter_new (ETableModel *source,
|
|
ETableHeader *full_header,
|
|
ETableSortInfo *sort_info)
|
|
{
|
|
ETableSorter *table_sorter;
|
|
|
|
table_sorter = g_object_new (E_TYPE_TABLE_SORTER, NULL);
|
|
table_sorter->sort_info = g_object_ref (sort_info);
|
|
table_sorter->full_header = g_object_ref (full_header);
|
|
table_sorter->source = g_object_ref (source);
|
|
|
|
table_sorter->table_model_changed_id = g_signal_connect (
|
|
source, "model_changed",
|
|
G_CALLBACK (table_sorter_model_changed_cb), table_sorter);
|
|
|
|
table_sorter->table_model_row_changed_id = g_signal_connect (
|
|
source, "model_row_changed",
|
|
G_CALLBACK (table_sorter_model_row_changed_cb), table_sorter);
|
|
|
|
table_sorter->table_model_cell_changed_id = g_signal_connect (
|
|
source, "model_cell_changed",
|
|
G_CALLBACK (table_sorter_model_cell_changed_cb), table_sorter);
|
|
|
|
table_sorter->table_model_rows_inserted_id = g_signal_connect (
|
|
source, "model_rows_inserted",
|
|
G_CALLBACK (table_sorter_model_rows_inserted_cb), table_sorter);
|
|
|
|
table_sorter->table_model_rows_deleted_id = g_signal_connect (
|
|
source, "model_rows_deleted",
|
|
G_CALLBACK (table_sorter_model_rows_deleted_cb), table_sorter);
|
|
|
|
table_sorter->sort_info_changed_id = g_signal_connect (
|
|
sort_info, "sort_info_changed",
|
|
G_CALLBACK (table_sorter_sort_info_changed_cb), table_sorter);
|
|
|
|
table_sorter->group_info_changed_id = g_signal_connect (
|
|
sort_info, "group_info_changed",
|
|
G_CALLBACK (table_sorter_sort_info_changed_cb), table_sorter);
|
|
|
|
return table_sorter;
|
|
}
|
|
|