1/*
2 * Copyright © 2019 Matthias Clasen
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Authors: Matthias Clasen <mclasen@redhat.com>
18 */
19
20#include "config.h"
21
22#include "gtkcolumnviewsorterprivate.h"
23
24#include "gtkcolumnviewcolumnprivate.h"
25#include "gtkintl.h"
26#include "gtktypebuiltins.h"
27
28typedef struct
29{
30 GtkColumnViewColumn *column;
31 GtkSorter *sorter;
32 gboolean inverted;
33 gulong changed_id;
34} Sorter;
35
36static void
37free_sorter (gpointer data)
38{
39 Sorter *s = data;
40
41 g_signal_handler_disconnect (instance: s->sorter, handler_id: s->changed_id);
42 g_object_unref (object: s->sorter);
43 g_object_unref (object: s->column);
44 g_free (mem: s);
45}
46
47struct _GtkColumnViewSorter
48{
49 GtkSorter parent_instance;
50
51 GSequence *sorters;
52};
53
54G_DEFINE_TYPE (GtkColumnViewSorter, gtk_column_view_sorter, GTK_TYPE_SORTER)
55
56static GtkOrdering
57gtk_column_view_sorter_compare (GtkSorter *sorter,
58 gpointer item1,
59 gpointer item2)
60{
61 GtkColumnViewSorter *self = GTK_COLUMN_VIEW_SORTER (ptr: sorter);
62 GtkOrdering result = GTK_ORDERING_EQUAL;
63 GSequenceIter *iter;
64
65 for (iter = g_sequence_get_begin_iter (seq: self->sorters);
66 !g_sequence_iter_is_end (iter);
67 iter = g_sequence_iter_next (iter))
68 {
69 Sorter *s = g_sequence_get (iter);
70
71 result = gtk_sorter_compare (self: s->sorter, item1, item2);
72 if (s->inverted)
73 result = - result;
74
75 if (result != GTK_ORDERING_EQUAL)
76 break;
77 }
78
79 return result;
80}
81
82static GtkSorterOrder
83gtk_column_view_sorter_get_order (GtkSorter *sorter)
84{
85 GtkColumnViewSorter *self = GTK_COLUMN_VIEW_SORTER (ptr: sorter);
86 GtkSorterOrder result = GTK_SORTER_ORDER_NONE;
87 GSequenceIter *iter;
88
89 for (iter = g_sequence_get_begin_iter (seq: self->sorters);
90 !g_sequence_iter_is_end (iter);
91 iter = g_sequence_iter_next (iter))
92 {
93 Sorter *s = g_sequence_get (iter);
94
95 switch (gtk_sorter_get_order (self: s->sorter))
96 {
97 case GTK_SORTER_ORDER_PARTIAL:
98 result = GTK_SORTER_ORDER_PARTIAL;
99 break;
100 case GTK_SORTER_ORDER_NONE:
101 break;
102 case GTK_SORTER_ORDER_TOTAL:
103 return GTK_SORTER_ORDER_TOTAL;
104 default:
105 g_assert_not_reached ();
106 break;
107 }
108 }
109
110 return result;
111}
112
113static void
114gtk_column_view_sorter_dispose (GObject *object)
115{
116 GtkColumnViewSorter *self = GTK_COLUMN_VIEW_SORTER (ptr: object);
117
118 /* The sorter is owned by the columview and is unreffed
119 * after the columns, so the sequence must be empty at
120 * this point.
121 * The sorter can outlive the columview it comes from
122 * (the model might still have a ref), but that does
123 * not change the fact that all columns will be gone.
124 */
125 g_assert (g_sequence_is_empty (self->sorters));
126 g_clear_pointer (&self->sorters, g_sequence_free);
127
128 G_OBJECT_CLASS (gtk_column_view_sorter_parent_class)->dispose (object);
129}
130
131static void
132gtk_column_view_sorter_class_init (GtkColumnViewSorterClass *class)
133{
134 GtkSorterClass *sorter_class = GTK_SORTER_CLASS (ptr: class);
135 GObjectClass *object_class = G_OBJECT_CLASS (class);
136
137 sorter_class->compare = gtk_column_view_sorter_compare;
138 sorter_class->get_order = gtk_column_view_sorter_get_order;
139
140 object_class->dispose = gtk_column_view_sorter_dispose;
141}
142
143static void
144gtk_column_view_sorter_init (GtkColumnViewSorter *self)
145{
146 self->sorters = g_sequence_new (data_destroy: free_sorter);
147}
148
149GtkColumnViewSorter *
150gtk_column_view_sorter_new (void)
151{
152 return g_object_new (GTK_TYPE_COLUMN_VIEW_SORTER, NULL);
153}
154
155static void
156gtk_column_view_sorter_changed_cb (GtkSorter *sorter, int change, gpointer data)
157{
158 gtk_sorter_changed (self: GTK_SORTER (ptr: data), change: GTK_SORTER_CHANGE_DIFFERENT);
159}
160
161static gboolean
162remove_column (GtkColumnViewSorter *self,
163 GtkColumnViewColumn *column)
164{
165 GSequenceIter *iter;
166
167 for (iter = g_sequence_get_begin_iter (seq: self->sorters);
168 !g_sequence_iter_is_end (iter);
169 iter = g_sequence_iter_next (iter))
170 {
171 Sorter *s = g_sequence_get (iter);
172
173 if (s->column == column)
174 {
175 g_sequence_remove (iter);
176 return TRUE;
177 }
178 }
179
180 return FALSE;
181}
182
183gboolean
184gtk_column_view_sorter_add_column (GtkColumnViewSorter *self,
185 GtkColumnViewColumn *column)
186{
187 GSequenceIter *iter;
188 GtkSorter *sorter;
189 Sorter *s, *first;
190
191 g_return_val_if_fail (GTK_IS_COLUMN_VIEW_SORTER (self), FALSE);
192 g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (column), FALSE);
193
194 sorter = gtk_column_view_column_get_sorter (self: column);
195 if (sorter == NULL)
196 return FALSE;
197
198 iter = g_sequence_get_begin_iter (seq: self->sorters);
199 if (!g_sequence_iter_is_end (iter))
200 {
201 first = g_sequence_get (iter);
202 if (first->column == column)
203 {
204 first->inverted = !first->inverted;
205 goto out;
206 }
207 }
208 else
209 first = NULL;
210
211 remove_column (self, column);
212
213 s = g_new (Sorter, 1);
214 s->column = g_object_ref (column);
215 s->sorter = g_object_ref (sorter);
216 s->changed_id = g_signal_connect (sorter, "changed", G_CALLBACK (gtk_column_view_sorter_changed_cb), self);
217 s->inverted = FALSE;
218
219 g_sequence_insert_before (iter, data: s);
220
221 /* notify the previous first column to stop drawing an arrow */
222 if (first)
223 gtk_column_view_column_notify_sort (self: first->column);
224
225out:
226 gtk_sorter_changed (self: GTK_SORTER (ptr: self), change: GTK_SORTER_CHANGE_DIFFERENT);
227
228 gtk_column_view_column_notify_sort (self: column);
229
230 return TRUE;
231}
232
233gboolean
234gtk_column_view_sorter_remove_column (GtkColumnViewSorter *self,
235 GtkColumnViewColumn *column)
236{
237 g_return_val_if_fail (GTK_IS_COLUMN_VIEW_SORTER (self), FALSE);
238 g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (column), FALSE);
239
240 if (remove_column (self, column))
241 {
242 gtk_sorter_changed (self: GTK_SORTER (ptr: self), change: GTK_SORTER_CHANGE_DIFFERENT);
243 gtk_column_view_column_notify_sort (self: column);
244 return TRUE;
245 }
246
247 return FALSE;
248}
249
250gboolean
251gtk_column_view_sorter_set_column (GtkColumnViewSorter *self,
252 GtkColumnViewColumn *column,
253 gboolean inverted)
254{
255 GtkSorter *sorter;
256 Sorter *s;
257
258 g_return_val_if_fail (GTK_IS_COLUMN_VIEW_SORTER (self), FALSE);
259 g_return_val_if_fail (GTK_IS_COLUMN_VIEW_COLUMN (column), FALSE);
260
261 sorter = gtk_column_view_column_get_sorter (self: column);
262 if (sorter == NULL)
263 return FALSE;
264
265 g_object_ref (column);
266
267 g_sequence_remove_range (begin: g_sequence_get_begin_iter (seq: self->sorters),
268 end: g_sequence_get_end_iter (seq: self->sorters));
269
270 s = g_new (Sorter, 1);
271 s->column = g_object_ref (column);
272 s->sorter = g_object_ref (sorter);
273 s->changed_id = g_signal_connect (sorter, "changed", G_CALLBACK (gtk_column_view_sorter_changed_cb), self);
274 s->inverted = inverted;
275
276 g_sequence_prepend (seq: self->sorters, data: s);
277
278 gtk_sorter_changed (self: GTK_SORTER (ptr: self), change: GTK_SORTER_CHANGE_DIFFERENT);
279
280 gtk_column_view_column_notify_sort (self: column);
281
282 g_object_unref (object: column);
283
284 return TRUE;
285}
286
287void
288gtk_column_view_sorter_clear (GtkColumnViewSorter *self)
289{
290 GSequenceIter *iter;
291 Sorter *s;
292 GtkColumnViewColumn *column;
293
294 g_return_if_fail (GTK_IS_COLUMN_VIEW_SORTER (self));
295
296 if (g_sequence_is_empty (seq: self->sorters))
297 return;
298
299 iter = g_sequence_get_begin_iter (seq: self->sorters);
300 s = g_sequence_get (iter);
301
302 column = g_object_ref (s->column);
303
304 g_sequence_remove_range (begin: iter, end: g_sequence_get_end_iter (seq: self->sorters));
305
306 gtk_sorter_changed (self: GTK_SORTER (ptr: self), change: GTK_SORTER_CHANGE_DIFFERENT);
307
308 gtk_column_view_column_notify_sort (self: column);
309
310 g_object_unref (object: column);
311}
312
313GtkColumnViewColumn *
314gtk_column_view_sorter_get_sort_column (GtkColumnViewSorter *self,
315 gboolean *inverted)
316{
317 GSequenceIter *iter;
318 Sorter *s;
319
320 g_return_val_if_fail (GTK_IS_COLUMN_VIEW_SORTER (self), NULL);
321
322 if (g_sequence_is_empty (seq: self->sorters))
323 return NULL;
324
325 iter = g_sequence_get_begin_iter (seq: self->sorters);
326 s = g_sequence_get (iter);
327
328 *inverted = s->inverted;
329
330 return s->column;
331}
332

source code of gtk/gtk/gtkcolumnviewsorter.c