1 /*
2 * e-table-sorter.c
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18 #include "evolution-config.h"
19
20 #include <stdlib.h>
21 #include <string.h>
22
23 #include <glib/gi18n.h>
24
25 #include "e-table-sorter.h"
26 #include "e-table-sorting-utils.h"
27
28 #define d(x)
29
30 enum {
31 PROP_0,
32 PROP_SORT_INFO
33 };
34
35 /* Forward Declarations */
36 static void e_table_sorter_interface_init (ESorterInterface *iface);
37
38 G_DEFINE_TYPE_WITH_CODE (
39 ETableSorter,
40 e_table_sorter,
41 G_TYPE_OBJECT,
42 G_IMPLEMENT_INTERFACE (
43 E_TYPE_SORTER,
44 e_table_sorter_interface_init))
45
46 struct qsort_data {
47 ETableSorter *table_sorter;
48 gpointer *vals;
49 gint cols;
50 gint *ascending;
51 GCompareDataFunc *compare;
52 gpointer cmp_cache;
53 };
54
55 /* FIXME: Make it not cache the second and later columns (as if anyone cares.) */
56
57 static gint
qsort_callback(gconstpointer data1,gconstpointer data2,gpointer user_data)58 qsort_callback (gconstpointer data1,
59 gconstpointer data2,
60 gpointer user_data)
61 {
62 struct qsort_data *qd = (struct qsort_data *) user_data;
63 gint row1 = *(gint *) data1;
64 gint row2 = *(gint *) data2;
65 gint j;
66 gint sort_count;
67 gint comp_val = 0;
68 gint ascending = 1;
69
70 sort_count =
71 e_table_sort_info_sorting_get_count (
72 qd->table_sorter->sort_info) +
73 e_table_sort_info_grouping_get_count (
74 qd->table_sorter->sort_info);
75
76 for (j = 0; j < sort_count; j++) {
77 comp_val = (*(qd->compare[j]))(qd->vals[qd->cols * row1 + j], qd->vals[qd->cols * row2 + j], qd->cmp_cache);
78 ascending = qd->ascending[j];
79 if (comp_val != 0)
80 break;
81 }
82 if (comp_val == 0) {
83 if (row1 < row2)
84 comp_val = -1;
85 if (row1 > row2)
86 comp_val = 1;
87 }
88 if (!ascending)
89 comp_val = -comp_val;
90
91 return comp_val;
92 }
93
94 static void
table_sorter_clean(ETableSorter * table_sorter)95 table_sorter_clean (ETableSorter *table_sorter)
96 {
97 g_free (table_sorter->sorted);
98 table_sorter->sorted = NULL;
99
100 g_free (table_sorter->backsorted);
101 table_sorter->backsorted = NULL;
102
103 table_sorter->needs_sorting = -1;
104 }
105
106 static void
table_sorter_sort(ETableSorter * table_sorter)107 table_sorter_sort (ETableSorter *table_sorter)
108 {
109 gint rows;
110 gint i;
111 gint j;
112 gint cols;
113 gint group_cols;
114 struct qsort_data qd;
115
116 if (table_sorter->sorted)
117 return;
118
119 rows = e_table_model_row_count (table_sorter->source);
120 group_cols = e_table_sort_info_grouping_get_count (table_sorter->sort_info);
121 cols = e_table_sort_info_sorting_get_count (table_sorter->sort_info) + group_cols;
122
123 table_sorter->sorted = g_new (int, rows);
124 for (i = 0; i < rows; i++)
125 table_sorter->sorted[i] = i;
126
127 qd.cols = cols;
128 qd.table_sorter = table_sorter;
129
130 qd.vals = g_new (gpointer , rows * cols);
131 qd.ascending = g_new (int, cols);
132 qd.compare = g_new (GCompareDataFunc, cols);
133 qd.cmp_cache = e_table_sorting_utils_create_cmp_cache ();
134
135 for (j = 0; j < cols; j++) {
136 ETableColumnSpecification *spec;
137 ETableCol *col;
138 GtkSortType sort_type;
139
140 if (j < group_cols)
141 spec = e_table_sort_info_grouping_get_nth (
142 table_sorter->sort_info,
143 j, &sort_type);
144 else
145 spec = e_table_sort_info_sorting_get_nth (
146 table_sorter->sort_info,
147 j - group_cols, &sort_type);
148
149 col = e_table_header_get_column_by_spec (
150 table_sorter->full_header, spec);
151 if (col == NULL) {
152 gint last = e_table_header_count (
153 table_sorter->full_header) - 1;
154 col = e_table_header_get_column (
155 table_sorter->full_header, last);
156 }
157
158 for (i = 0; i < rows; i++) {
159 qd.vals[i * cols + j] = e_table_model_value_at (
160 table_sorter->source,
161 col->spec->model_col, i);
162 }
163
164 qd.compare[j] = col->compare;
165 qd.ascending[j] = (sort_type == GTK_SORT_ASCENDING);
166 }
167
168 g_qsort_with_data (table_sorter->sorted, rows, sizeof (gint), qsort_callback, &qd);
169
170 for (j = 0; j < cols; j++) {
171 ETableColumnSpecification *spec;
172 ETableCol *col;
173 GtkSortType sort_type;
174
175 if (j < group_cols)
176 spec = e_table_sort_info_grouping_get_nth (
177 table_sorter->sort_info,
178 j, &sort_type);
179 else
180 spec = e_table_sort_info_sorting_get_nth (
181 table_sorter->sort_info,
182 j - group_cols, &sort_type);
183
184 col = e_table_header_get_column_by_spec (
185 table_sorter->full_header, spec);
186 if (col == NULL) {
187 gint last = e_table_header_count (
188 table_sorter->full_header) - 1;
189 col = e_table_header_get_column (
190 table_sorter->full_header, last);
191 }
192
193 for (i = 0; i < rows; i++) {
194 e_table_model_free_value (table_sorter->source, col->spec->model_col, qd.vals[i * cols + j]);
195 }
196 }
197
198 g_free (qd.vals);
199 g_free (qd.ascending);
200 g_free (qd.compare);
201 e_table_sorting_utils_free_cmp_cache (qd.cmp_cache);
202 }
203
204 static void
table_sorter_backsort(ETableSorter * table_sorter)205 table_sorter_backsort (ETableSorter *table_sorter)
206 {
207 gint i, rows;
208
209 if (table_sorter->backsorted)
210 return;
211
212 table_sorter_sort (table_sorter);
213
214 rows = e_table_model_row_count (table_sorter->source);
215 table_sorter->backsorted = g_new0 (int, rows);
216
217 for (i = 0; i < rows; i++) {
218 table_sorter->backsorted[table_sorter->sorted[i]] = i;
219 }
220 }
221
222 static void
table_sorter_model_changed_cb(ETableModel * table_model,ETableSorter * table_sorter)223 table_sorter_model_changed_cb (ETableModel *table_model,
224 ETableSorter *table_sorter)
225 {
226 table_sorter_clean (table_sorter);
227 }
228
229 static void
table_sorter_model_row_changed_cb(ETableModel * table_model,gint row,ETableSorter * table_sorter)230 table_sorter_model_row_changed_cb (ETableModel *table_model,
231 gint row,
232 ETableSorter *table_sorter)
233 {
234 table_sorter_clean (table_sorter);
235 }
236
237 static void
table_sorter_model_cell_changed_cb(ETableModel * table_model,gint col,gint row,ETableSorter * table_sorter)238 table_sorter_model_cell_changed_cb (ETableModel *table_model,
239 gint col,
240 gint row,
241 ETableSorter *table_sorter)
242 {
243 table_sorter_clean (table_sorter);
244 }
245
246 static void
table_sorter_model_rows_inserted_cb(ETableModel * table_model,gint row,gint count,ETableSorter * table_sorter)247 table_sorter_model_rows_inserted_cb (ETableModel *table_model,
248 gint row,
249 gint count,
250 ETableSorter *table_sorter)
251 {
252 table_sorter_clean (table_sorter);
253 }
254
255 static void
table_sorter_model_rows_deleted_cb(ETableModel * table_model,gint row,gint count,ETableSorter * table_sorter)256 table_sorter_model_rows_deleted_cb (ETableModel *table_model,
257 gint row,
258 gint count,
259 ETableSorter *table_sorter)
260 {
261 table_sorter_clean (table_sorter);
262 }
263
264 static void
table_sorter_sort_info_changed_cb(ETableSortInfo * sort_info,ETableSorter * table_sorter)265 table_sorter_sort_info_changed_cb (ETableSortInfo *sort_info,
266 ETableSorter *table_sorter)
267 {
268 table_sorter_clean (table_sorter);
269 }
270
271 static void
table_sorter_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)272 table_sorter_set_property (GObject *object,
273 guint property_id,
274 const GValue *value,
275 GParamSpec *pspec)
276 {
277 ETableSorter *table_sorter = E_TABLE_SORTER (object);
278
279 switch (property_id) {
280 case PROP_SORT_INFO:
281 if (table_sorter->sort_info) {
282 if (table_sorter->sort_info_changed_id)
283 g_signal_handler_disconnect (
284 table_sorter->sort_info,
285 table_sorter->sort_info_changed_id);
286 if (table_sorter->group_info_changed_id)
287 g_signal_handler_disconnect (
288 table_sorter->sort_info,
289 table_sorter->group_info_changed_id);
290 g_object_unref (table_sorter->sort_info);
291 }
292
293 table_sorter->sort_info = g_value_dup_object (value);
294
295 table_sorter->sort_info_changed_id = g_signal_connect (
296 table_sorter->sort_info, "sort_info_changed",
297 G_CALLBACK (table_sorter_sort_info_changed_cb),
298 table_sorter);
299 table_sorter->group_info_changed_id = g_signal_connect (
300 table_sorter->sort_info, "group_info_changed",
301 G_CALLBACK (table_sorter_sort_info_changed_cb),
302 table_sorter);
303
304 table_sorter_clean (table_sorter);
305 break;
306 default:
307 break;
308 }
309 }
310
311 static void
table_sorter_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)312 table_sorter_get_property (GObject *object,
313 guint property_id,
314 GValue *value,
315 GParamSpec *pspec)
316 {
317 ETableSorter *table_sorter = E_TABLE_SORTER (object);
318 switch (property_id) {
319 case PROP_SORT_INFO:
320 g_value_set_object (value, table_sorter->sort_info);
321 break;
322 }
323 }
324
325 static void
table_sorter_dispose(GObject * object)326 table_sorter_dispose (GObject *object)
327 {
328 ETableSorter *table_sorter = E_TABLE_SORTER (object);
329
330 if (table_sorter->table_model_changed_id > 0) {
331 g_signal_handler_disconnect (
332 table_sorter->source,
333 table_sorter->table_model_changed_id);
334 table_sorter->table_model_changed_id = 0;
335 }
336
337 if (table_sorter->table_model_row_changed_id > 0) {
338 g_signal_handler_disconnect (
339 table_sorter->source,
340 table_sorter->table_model_row_changed_id);
341 table_sorter->table_model_row_changed_id = 0;
342 }
343
344 if (table_sorter->table_model_cell_changed_id > 0) {
345 g_signal_handler_disconnect (
346 table_sorter->source,
347 table_sorter->table_model_cell_changed_id);
348 table_sorter->table_model_cell_changed_id = 0;
349 }
350
351 if (table_sorter->table_model_rows_inserted_id > 0) {
352 g_signal_handler_disconnect (
353 table_sorter->source,
354 table_sorter->table_model_rows_inserted_id);
355 table_sorter->table_model_rows_inserted_id = 0;
356 }
357
358 if (table_sorter->table_model_rows_deleted_id > 0) {
359 g_signal_handler_disconnect (
360 table_sorter->source,
361 table_sorter->table_model_rows_deleted_id);
362 table_sorter->table_model_rows_deleted_id = 0;
363 }
364
365 if (table_sorter->sort_info_changed_id > 0) {
366 g_signal_handler_disconnect (
367 table_sorter->sort_info,
368 table_sorter->sort_info_changed_id);
369 table_sorter->sort_info_changed_id = 0;
370 }
371
372 if (table_sorter->group_info_changed_id > 0) {
373 g_signal_handler_disconnect (
374 table_sorter->sort_info,
375 table_sorter->group_info_changed_id);
376 table_sorter->group_info_changed_id = 0;
377 }
378
379 g_clear_object (&table_sorter->sort_info);
380 g_clear_object (&table_sorter->full_header);
381 g_clear_object (&table_sorter->source);
382
383 table_sorter_clean (table_sorter);
384
385 /* Chain up to parent's dispose() method. */
386 G_OBJECT_CLASS (e_table_sorter_parent_class)->dispose (object);
387 }
388
389 static gint
table_sorter_model_to_sorted(ESorter * sorter,gint row)390 table_sorter_model_to_sorted (ESorter *sorter,
391 gint row)
392 {
393 ETableSorter *table_sorter = E_TABLE_SORTER (sorter);
394 gint rows = e_table_model_row_count (table_sorter->source);
395
396 g_return_val_if_fail (row >= 0, -1);
397 g_return_val_if_fail (row < rows, -1);
398
399 if (e_sorter_needs_sorting (sorter))
400 table_sorter_backsort (table_sorter);
401
402 if (table_sorter->backsorted)
403 return table_sorter->backsorted[row];
404 else
405 return row;
406 }
407
408 static gint
table_sorter_sorted_to_model(ESorter * sorter,gint row)409 table_sorter_sorted_to_model (ESorter *sorter,
410 gint row)
411 {
412 ETableSorter *table_sorter = E_TABLE_SORTER (sorter);
413 gint rows = e_table_model_row_count (table_sorter->source);
414
415 g_return_val_if_fail (row >= 0, -1);
416 g_return_val_if_fail (row < rows, -1);
417
418 if (e_sorter_needs_sorting (sorter))
419 table_sorter_sort (table_sorter);
420
421 if (table_sorter->sorted)
422 return table_sorter->sorted[row];
423 else
424 return row;
425 }
426
427 static void
table_sorter_get_model_to_sorted_array(ESorter * sorter,gint ** array,gint * count)428 table_sorter_get_model_to_sorted_array (ESorter *sorter,
429 gint **array,
430 gint *count)
431 {
432 ETableSorter *table_sorter = E_TABLE_SORTER (sorter);
433
434 if (array || count) {
435 table_sorter_backsort (table_sorter);
436
437 if (array)
438 *array = table_sorter->backsorted;
439 if (count)
440 *count = e_table_model_row_count(table_sorter->source);
441 }
442 }
443
444 static void
table_sorter_get_sorted_to_model_array(ESorter * sorter,gint ** array,gint * count)445 table_sorter_get_sorted_to_model_array (ESorter *sorter,
446 gint **array,
447 gint *count)
448 {
449 ETableSorter *table_sorter = E_TABLE_SORTER (sorter);
450
451 if (array || count) {
452 table_sorter_sort (table_sorter);
453
454 if (array)
455 *array = table_sorter->sorted;
456 if (count)
457 *count = e_table_model_row_count(table_sorter->source);
458 }
459 }
460
461 static gboolean
table_sorter_needs_sorting(ESorter * sorter)462 table_sorter_needs_sorting (ESorter *sorter)
463 {
464 ETableSorter *table_sorter = E_TABLE_SORTER (sorter);
465
466 if (table_sorter->needs_sorting < 0) {
467 if (e_table_sort_info_sorting_get_count (table_sorter->sort_info) + e_table_sort_info_grouping_get_count (table_sorter->sort_info))
468 table_sorter->needs_sorting = 1;
469 else
470 table_sorter->needs_sorting = 0;
471 }
472 return table_sorter->needs_sorting;
473 }
474
475 static void
e_table_sorter_class_init(ETableSorterClass * class)476 e_table_sorter_class_init (ETableSorterClass *class)
477 {
478 GObjectClass *object_class;
479
480 object_class = G_OBJECT_CLASS (class);
481 object_class->set_property = table_sorter_set_property;
482 object_class->get_property = table_sorter_get_property;
483 object_class->dispose = table_sorter_dispose;
484
485 g_object_class_install_property (
486 object_class,
487 PROP_SORT_INFO,
488 g_param_spec_object (
489 "sort_info",
490 "Sort Info",
491 NULL,
492 E_TYPE_TABLE_SORT_INFO,
493 G_PARAM_READWRITE));
494 }
495
496 static void
e_table_sorter_interface_init(ESorterInterface * iface)497 e_table_sorter_interface_init (ESorterInterface *iface)
498 {
499 iface->model_to_sorted = table_sorter_model_to_sorted;
500 iface->sorted_to_model = table_sorter_sorted_to_model;
501 iface->get_model_to_sorted_array = table_sorter_get_model_to_sorted_array;
502 iface->get_sorted_to_model_array = table_sorter_get_sorted_to_model_array;
503 iface->needs_sorting = table_sorter_needs_sorting;
504 }
505
506 static void
e_table_sorter_init(ETableSorter * table_sorter)507 e_table_sorter_init (ETableSorter *table_sorter)
508 {
509 table_sorter->needs_sorting = -1;
510 }
511
512 ETableSorter *
e_table_sorter_new(ETableModel * source,ETableHeader * full_header,ETableSortInfo * sort_info)513 e_table_sorter_new (ETableModel *source,
514 ETableHeader *full_header,
515 ETableSortInfo *sort_info)
516 {
517 ETableSorter *table_sorter;
518
519 table_sorter = g_object_new (E_TYPE_TABLE_SORTER, NULL);
520 table_sorter->sort_info = g_object_ref (sort_info);
521 table_sorter->full_header = g_object_ref (full_header);
522 table_sorter->source = g_object_ref (source);
523
524 table_sorter->table_model_changed_id = g_signal_connect (
525 source, "model_changed",
526 G_CALLBACK (table_sorter_model_changed_cb), table_sorter);
527
528 table_sorter->table_model_row_changed_id = g_signal_connect (
529 source, "model_row_changed",
530 G_CALLBACK (table_sorter_model_row_changed_cb), table_sorter);
531
532 table_sorter->table_model_cell_changed_id = g_signal_connect (
533 source, "model_cell_changed",
534 G_CALLBACK (table_sorter_model_cell_changed_cb), table_sorter);
535
536 table_sorter->table_model_rows_inserted_id = g_signal_connect (
537 source, "model_rows_inserted",
538 G_CALLBACK (table_sorter_model_rows_inserted_cb), table_sorter);
539
540 table_sorter->table_model_rows_deleted_id = g_signal_connect (
541 source, "model_rows_deleted",
542 G_CALLBACK (table_sorter_model_rows_deleted_cb), table_sorter);
543
544 table_sorter->sort_info_changed_id = g_signal_connect (
545 sort_info, "sort_info_changed",
546 G_CALLBACK (table_sorter_sort_info_changed_cb), table_sorter);
547
548 table_sorter->group_info_changed_id = g_signal_connect (
549 sort_info, "group_info_changed",
550 G_CALLBACK (table_sorter_sort_info_changed_cb), table_sorter);
551
552 return table_sorter;
553 }
554
555