Files
evolution/e-util/e-table-sorting-utils.c
Milan Crha 8752647eca Make Calendar, Memos and Tasks views non-UI-blocking
The Calendar, Memos and Tasks views use to do D-Bus calls to
the backends on the main (UI) thread, which could result in UI
freezes, until the operation was done on the backend (and server)
side. This commit fixes that by invoking the operations in
a dedicated thread. It has few additional advantages too:
- operations can be cancelled
- proper error reporting to a user
- less code duplication between the views for common operations

There had been fixed some performance issues when selecting/unselecting
sources in the source selector as well.
2014-10-06 12:33:03 +02:00

574 lines
16 KiB
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.
*
* 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 Lesser General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
*
* Authors:
* Chris Lahey <clahey@ximian.com>
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "e-table-sorting-utils.h"
#include <string.h>
#include <camel/camel.h>
#include "e-misc-utils.h"
#define d(x)
/* This takes source rows. */
static gint
etsu_compare (ETableModel *source,
ETableSortInfo *sort_info,
ETableHeader *full_header,
gint row1,
gint row2,
gpointer cmp_cache)
{
gint j;
gint sort_count = e_table_sort_info_sorting_get_count (sort_info);
gint comp_val = 0;
GtkSortType sort_type = GTK_SORT_ASCENDING;
for (j = 0; j < sort_count; j++) {
ETableColumnSpecification *spec;
ETableCol *col;
spec = e_table_sort_info_sorting_get_nth (
sort_info, j, &sort_type);
col = e_table_header_get_column_by_spec (full_header, spec);
if (col == NULL) {
gint last = e_table_header_count (full_header) - 1;
col = e_table_header_get_column (full_header, last);
}
comp_val = (*col->compare) (
e_table_model_value_at (
source, col->spec->compare_col, row1),
e_table_model_value_at (
source, col->spec->compare_col, row2),
cmp_cache);
if (comp_val != 0)
break;
}
if (comp_val == 0) {
if (row1 < row2)
comp_val = -1;
if (row1 > row2)
comp_val = 1;
}
if (sort_type == GTK_SORT_DESCENDING)
comp_val = -comp_val;
return comp_val;
}
typedef struct {
gint cols;
gpointer *vals;
GtkSortType *sort_type;
GCompareDataFunc *compare;
gpointer cmp_cache;
} ETableSortClosure;
typedef struct {
ETreeModel *tree;
ETableSortInfo *sort_info;
ETableHeader *full_header;
gpointer cmp_cache;
} ETreeSortClosure;
/* FIXME: Make it not cache the second and later columns (as if anyone cares.) */
static gint
e_sort_callback (gconstpointer data1,
gconstpointer data2,
gpointer user_data)
{
gint row1 = *(gint *) data1;
gint row2 = *(gint *) data2;
ETableSortClosure *closure = user_data;
gint j;
gint sort_count = closure->cols;
gint comp_val = 0;
GtkSortType sort_type = GTK_SORT_ASCENDING;
for (j = 0; j < sort_count; j++) {
comp_val = (*(closure->compare[j])) (
closure->vals[closure->cols * row1 + j],
closure->vals[closure->cols * row2 + j],
closure->cmp_cache);
sort_type = closure->sort_type[j];
if (comp_val != 0)
break;
}
if (comp_val == 0) {
if (row1 < row2)
comp_val = -1;
if (row1 > row2)
comp_val = 1;
}
if (sort_type == GTK_SORT_DESCENDING)
comp_val = -comp_val;
return comp_val;
}
void
e_table_sorting_utils_sort (ETableModel *source,
ETableSortInfo *sort_info,
ETableHeader *full_header,
gint *map_table,
gint rows)
{
gint total_rows;
gint i;
gint j;
gint cols;
ETableSortClosure closure;
g_return_if_fail (E_IS_TABLE_MODEL (source));
g_return_if_fail (E_IS_TABLE_SORT_INFO (sort_info));
g_return_if_fail (E_IS_TABLE_HEADER (full_header));
total_rows = e_table_model_row_count (source);
cols = e_table_sort_info_sorting_get_count (sort_info);
closure.cols = cols;
closure.vals = g_new (gpointer, total_rows * cols);
closure.sort_type = g_new (GtkSortType, cols);
closure.compare = g_new (GCompareDataFunc, cols);
closure.cmp_cache = e_table_sorting_utils_create_cmp_cache ();
for (j = 0; j < cols; j++) {
ETableColumnSpecification *spec;
ETableCol *col;
spec = e_table_sort_info_sorting_get_nth (
sort_info, j, &closure.sort_type[j]);
col = e_table_header_get_column_by_spec (full_header, spec);
if (col == NULL) {
gint last = e_table_header_count (full_header) - 1;
col = e_table_header_get_column (full_header, last);
}
for (i = 0; i < rows; i++) {
closure.vals[map_table[i] * cols + j] = e_table_model_value_at (source, col->spec->compare_col, map_table[i]);
}
closure.compare[j] = col->compare;
}
g_qsort_with_data (
map_table, rows, sizeof (gint), e_sort_callback, &closure);
for (j = 0; j < cols; j++) {
ETableColumnSpecification *spec;
ETableCol *col;
spec = e_table_sort_info_sorting_get_nth (
sort_info, j, &closure.sort_type[j]);
col = e_table_header_get_column_by_spec (full_header, spec);
if (col == NULL) {
gint last = e_table_header_count (full_header) - 1;
col = e_table_header_get_column (full_header, last);
}
for (i = 0; i < rows; i++) {
e_table_model_free_value (source, col->spec->compare_col, closure.vals[map_table[i] * cols + j]);
}
}
g_free (closure.vals);
g_free (closure.sort_type);
g_free (closure.compare);
e_table_sorting_utils_free_cmp_cache (closure.cmp_cache);
}
gboolean
e_table_sorting_utils_affects_sort (ETableSortInfo *sort_info,
ETableHeader *full_header,
gint compare_col)
{
gint j;
gint cols;
g_return_val_if_fail (E_IS_TABLE_SORT_INFO (sort_info), TRUE);
g_return_val_if_fail (E_IS_TABLE_HEADER (full_header), TRUE);
cols = e_table_sort_info_sorting_get_count (sort_info);
for (j = 0; j < cols; j++) {
ETableColumnSpecification *spec;
ETableCol *col;
spec = e_table_sort_info_sorting_get_nth (
sort_info, j, NULL);
col = e_table_header_get_column_by_spec (full_header, spec);
if (col == NULL) {
gint last = e_table_header_count (full_header) - 1;
col = e_table_header_get_column (full_header, last);
}
if (compare_col == col->spec->compare_col)
return TRUE;
}
return FALSE;
}
/* FIXME: This could be done in time log n instead of time n with a binary search. */
gint
e_table_sorting_utils_insert (ETableModel *source,
ETableSortInfo *sort_info,
ETableHeader *full_header,
gint *map_table,
gint rows,
gint row)
{
gint i;
gpointer cmp_cache = e_table_sorting_utils_create_cmp_cache ();
i = 0;
/* handle insertions when we have a 'sort group' */
while (i < rows && etsu_compare (source, sort_info, full_header, map_table[i], row, cmp_cache) < 0)
i++;
e_table_sorting_utils_free_cmp_cache (cmp_cache);
return i;
}
/* FIXME: This could be done in time log n instead of time n with a binary search. */
gint
e_table_sorting_utils_check_position (ETableModel *source,
ETableSortInfo *sort_info,
ETableHeader *full_header,
gint *map_table,
gint rows,
gint view_row)
{
gint i;
gint row;
gpointer cmp_cache;
i = view_row;
row = map_table[i];
cmp_cache = e_table_sorting_utils_create_cmp_cache ();
i = view_row;
if (i < rows - 1 && etsu_compare (source, sort_info, full_header, map_table[i + 1], row, cmp_cache) < 0) {
i++;
while (i < rows - 1 && etsu_compare (source, sort_info, full_header, map_table[i], row, cmp_cache) < 0)
i++;
} else if (i > 0 && etsu_compare (source, sort_info, full_header, map_table[i - 1], row, cmp_cache) > 0) {
i--;
while (i > 0 && etsu_compare (source, sort_info, full_header, map_table[i], row, cmp_cache) > 0)
i--;
}
e_table_sorting_utils_free_cmp_cache (cmp_cache);
return i;
}
/* This takes source rows. */
static gint
etsu_tree_compare (ETreeModel *source,
ETableSortInfo *sort_info,
ETableHeader *full_header,
ETreePath path1,
ETreePath path2,
gpointer cmp_cache)
{
gint j;
gint sort_count = e_table_sort_info_sorting_get_count (sort_info);
gint comp_val = 0;
GtkSortType sort_type = GTK_SORT_ASCENDING;
for (j = 0; j < sort_count; j++) {
ETableColumnSpecification *spec;
ETableCol *col;
spec = e_table_sort_info_sorting_get_nth (
sort_info, j, &sort_type);
col = e_table_header_get_column_by_spec (full_header, spec);
if (col == NULL) {
gint last = e_table_header_count (full_header) - 1;
col = e_table_header_get_column (full_header, last);
}
comp_val = (*col->compare) (
e_tree_model_value_at (
source, path1, col->spec->compare_col),
e_tree_model_value_at (
source, path2, col->spec->compare_col),
cmp_cache);
if (comp_val != 0)
break;
}
if (sort_type == GTK_SORT_DESCENDING)
comp_val = -comp_val;
return comp_val;
}
static gint
e_sort_tree_callback (gconstpointer data1,
gconstpointer data2,
gpointer user_data)
{
ETreePath *path1 = *(ETreePath *) data1;
ETreePath *path2 = *(ETreePath *) data2;
ETreeSortClosure *closure = user_data;
return etsu_tree_compare (closure->tree, closure->sort_info, closure->full_header, path1, path2, closure->cmp_cache);
}
void
e_table_sorting_utils_tree_sort (ETreeModel *source,
ETableSortInfo *sort_info,
ETableHeader *full_header,
ETreePath *map_table,
gint count)
{
ETableSortClosure closure;
gint cols;
gint i, j;
gint *map;
ETreePath *map_copy;
g_return_if_fail (E_IS_TREE_MODEL (source));
g_return_if_fail (E_IS_TABLE_SORT_INFO (sort_info));
g_return_if_fail (E_IS_TABLE_HEADER (full_header));
cols = e_table_sort_info_sorting_get_count (sort_info);
closure.cols = cols;
closure.vals = g_new (gpointer , count * cols);
closure.sort_type = g_new (GtkSortType, cols);
closure.compare = g_new (GCompareDataFunc, cols);
closure.cmp_cache = e_table_sorting_utils_create_cmp_cache ();
for (j = 0; j < cols; j++) {
ETableColumnSpecification *spec;
ETableCol *col;
spec = e_table_sort_info_sorting_get_nth (
sort_info, j, &closure.sort_type[j]);
col = e_table_header_get_column_by_spec (full_header, spec);
if (col == NULL) {
gint last = e_table_header_count (full_header) - 1;
col = e_table_header_get_column (full_header, last);
}
for (i = 0; i < count; i++) {
closure.vals[i * cols + j] = e_tree_model_sort_value_at (source, map_table[i], col->spec->compare_col);
}
closure.compare[j] = col->compare;
}
map = g_new (int, count);
for (i = 0; i < count; i++) {
map[i] = i;
}
g_qsort_with_data (
map, count, sizeof (gint), e_sort_callback, &closure);
map_copy = g_new (ETreePath, count);
for (i = 0; i < count; i++) {
map_copy[i] = map_table[i];
}
for (i = 0; i < count; i++) {
map_table[i] = map_copy[map[i]];
}
for (j = 0; j < cols; j++) {
ETableColumnSpecification *spec;
ETableCol *col;
spec = e_table_sort_info_sorting_get_nth (
sort_info, j, &closure.sort_type[j]);
col = e_table_header_get_column_by_spec (full_header, spec);
if (col == NULL) {
gint last = e_table_header_count (full_header) - 1;
col = e_table_header_get_column (full_header, last);
}
for (i = 0; i < count; i++) {
e_tree_model_free_value (source, col->spec->compare_col, closure.vals[i * cols + j]);
}
}
g_free (map);
g_free (map_copy);
g_free (closure.vals);
g_free (closure.sort_type);
g_free (closure.compare);
e_table_sorting_utils_free_cmp_cache (closure.cmp_cache);
}
/* FIXME: This could be done in time log n instead of time n with a binary search. */
gint
e_table_sorting_utils_tree_check_position (ETreeModel *source,
ETableSortInfo *sort_info,
ETableHeader *full_header,
ETreePath *map_table,
gint count,
gint old_index)
{
gint i;
ETreePath path;
gpointer cmp_cache = e_table_sorting_utils_create_cmp_cache ();
i = old_index;
path = map_table[i];
if (i < count - 1 && etsu_tree_compare (source, sort_info, full_header, map_table[i + 1], path, cmp_cache) < 0) {
i++;
while (i < count - 1 && etsu_tree_compare (source, sort_info, full_header, map_table[i], path, cmp_cache) < 0)
i++;
} else if (i > 0 && etsu_tree_compare (source, sort_info, full_header, map_table[i - 1], path, cmp_cache) > 0) {
i--;
while (i > 0 && etsu_tree_compare (source, sort_info, full_header, map_table[i], path, cmp_cache) > 0)
i--;
}
e_table_sorting_utils_free_cmp_cache (cmp_cache);
return i;
}
/* FIXME: This does not pay attention to making sure that it's a stable insert. This needs to be fixed. */
gint
e_table_sorting_utils_tree_insert (ETreeModel *source,
ETableSortInfo *sort_info,
ETableHeader *full_header,
ETreePath *map_table,
gint count,
ETreePath path)
{
gsize start;
gsize end;
ETreeSortClosure closure;
closure.tree = source;
closure.sort_info = sort_info;
closure.full_header = full_header;
closure.cmp_cache = e_table_sorting_utils_create_cmp_cache ();
e_bsearch (
&path, map_table, count, sizeof (ETreePath),
e_sort_tree_callback, &closure, &start, &end);
e_table_sorting_utils_free_cmp_cache (closure.cmp_cache);
return end;
}
/**
* e_table_sorting_utils_create_cmp_cache:
*
* Creates a new compare cache, which is storing pairs of string keys and
* string values. This can be accessed by
* e_table_sorting_utils_lookup_cmp_cache() and
* e_table_sorting_utils_add_to_cmp_cache().
*
* Returned pointer should be freed with
* e_table_sorting_utils_free_cmp_cache().
**/
gpointer
e_table_sorting_utils_create_cmp_cache (void)
{
return g_hash_table_new_full (
(GHashFunc) g_str_hash,
(GEqualFunc) g_str_equal,
(GDestroyNotify) camel_pstring_free,
(GDestroyNotify) g_free);
}
/**
* e_table_sorting_utils_free_cmp_cache:
* @cmp_cache: a compare cache; cannot be %NULL
*
* Frees a compare cache previously created with
* e_table_sorting_utils_create_cmp_cache().
**/
void
e_table_sorting_utils_free_cmp_cache (gpointer cmp_cache)
{
g_return_if_fail (cmp_cache != NULL);
g_hash_table_destroy (cmp_cache);
}
/**
* e_table_sorting_utils_add_to_cmp_cache:
* @cmp_cache: a compare cache; cannot be %NULL
* @key: unique key to a cache; cannot be %NULL
* @value: value to store for a key
*
* Adds a new value for a given key to a compare cache. If such key
* already exists in a cache then its value will be replaced.
* Note: Given @value will be stolen and later freed with g_free.
**/
void
e_table_sorting_utils_add_to_cmp_cache (gpointer cmp_cache,
const gchar *key,
gchar *value)
{
g_return_if_fail (cmp_cache != NULL);
g_return_if_fail (key != NULL);
g_hash_table_insert (
cmp_cache, (gchar *) camel_pstring_strdup (key), value);
}
/**
* e_table_sorting_utils_lookup_cmp_cache:
* @cmp_cache: a compare cache
* @key: unique key to a cache
*
* Looks ups @key in @cmp_cache, which is passed in GCompareDataFunc as 'data'.
* Returns %NULL when not found or the cache wasn't provided.
**/
const gchar *
e_table_sorting_utils_lookup_cmp_cache (gpointer cmp_cache,
const gchar *key)
{
g_return_val_if_fail (key != NULL, NULL);
if (cmp_cache == NULL)
return NULL;
return g_hash_table_lookup (cmp_cache, key);
}