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