1 /*
2  * Copyright © 2018 Benjamin Otte
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: Benjamin Otte <otte@gnome.org>
18  */
19 
20 
21 /*
22  * GtkListListModel:
23  *
24  * `GtkListListModel` is a `GListModel` implementation that takes a list API
25  * and provides it as a `GListModel`.
26  */
27 
28 #include "config.h"
29 
30 #include "gtklistlistmodelprivate.h"
31 
32 struct _GtkListListModel
33 {
34   GObject parent_instance;
35 
36   guint n_items;
37   gpointer (* get_first) (gpointer);
38   gpointer (* get_next) (gpointer, gpointer);
39   gpointer (* get_previous) (gpointer, gpointer);
40   gpointer (* get_last) (gpointer);
41   gpointer (* get_item) (gpointer, gpointer);
42   gpointer data;
43   GDestroyNotify notify;
44 };
45 
46 struct _GtkListListModelClass
47 {
48   GObjectClass parent_class;
49 };
50 
51 static GType
gtk_list_list_model_get_item_type(GListModel * list)52 gtk_list_list_model_get_item_type (GListModel *list)
53 {
54   return G_TYPE_OBJECT;
55 }
56 
57 static guint
gtk_list_list_model_get_n_items(GListModel * list)58 gtk_list_list_model_get_n_items (GListModel *list)
59 {
60   GtkListListModel *self = GTK_LIST_LIST_MODEL (list);
61 
62   return self->n_items;
63 }
64 
65 static gpointer
gtk_list_list_model_get_item(GListModel * list,guint position)66 gtk_list_list_model_get_item (GListModel *list,
67                               guint       position)
68 {
69   GtkListListModel *self = GTK_LIST_LIST_MODEL (list);
70   gpointer result;
71   guint i;
72 
73   if (position >= self->n_items)
74     {
75       return NULL;
76     }
77   else if (self->get_last &&
78            position >= self->n_items / 2)
79     {
80       result = self->get_last (self->data);
81 
82       for (i = self->n_items - 1; i > position; i--)
83         {
84           result = self->get_previous (result, self->data);
85         }
86     }
87   else
88     {
89       result = self->get_first (self->data);
90 
91       for (i = 0; i < position; i++)
92         {
93           result = self->get_next (result, self->data);
94         }
95     }
96 
97   return self->get_item (result, self->data);
98 }
99 
100 static void
gtk_list_list_model_list_model_init(GListModelInterface * iface)101 gtk_list_list_model_list_model_init (GListModelInterface *iface)
102 {
103   iface->get_item_type = gtk_list_list_model_get_item_type;
104   iface->get_n_items = gtk_list_list_model_get_n_items;
105   iface->get_item = gtk_list_list_model_get_item;
106 }
107 
G_DEFINE_TYPE_WITH_CODE(GtkListListModel,gtk_list_list_model,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,gtk_list_list_model_list_model_init))108 G_DEFINE_TYPE_WITH_CODE (GtkListListModel, gtk_list_list_model,
109                          G_TYPE_OBJECT,
110                          G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL, gtk_list_list_model_list_model_init))
111 
112 static void
113 gtk_list_list_model_dispose (GObject *object)
114 {
115   GtkListListModel *self = GTK_LIST_LIST_MODEL (object);
116 
117   if (self->notify)
118     self->notify (self->data);
119 
120   self->n_items = 0;
121   self->notify = NULL;
122 
123   G_OBJECT_CLASS (gtk_list_list_model_parent_class)->dispose (object);
124 }
125 
126 static void
gtk_list_list_model_class_init(GtkListListModelClass * klass)127 gtk_list_list_model_class_init (GtkListListModelClass *klass)
128 {
129   GObjectClass *object_class = G_OBJECT_CLASS (klass);
130 
131   object_class->dispose = gtk_list_list_model_dispose;
132 }
133 
134 static void
gtk_list_list_model_init(GtkListListModel * self)135 gtk_list_list_model_init (GtkListListModel *self)
136 {
137 }
138 
139 GtkListListModel *
gtk_list_list_model_new(gpointer (* get_first)(gpointer),gpointer (* get_next)(gpointer,gpointer),gpointer (* get_previous)(gpointer,gpointer),gpointer (* get_last)(gpointer),gpointer (* get_item)(gpointer,gpointer),gpointer data,GDestroyNotify notify)140 gtk_list_list_model_new (gpointer       (* get_first) (gpointer),
141                          gpointer       (* get_next) (gpointer, gpointer),
142                          gpointer       (* get_previous) (gpointer, gpointer),
143                          gpointer       (* get_last) (gpointer),
144                          gpointer       (* get_item) (gpointer, gpointer),
145                          gpointer       data,
146                          GDestroyNotify notify)
147 {
148   guint n_items;
149   gpointer item;
150 
151   n_items = 0;
152   for (item = get_first (data);
153        item != NULL;
154        item = get_next (item, data))
155     n_items++;
156 
157   return gtk_list_list_model_new_with_size (n_items,
158                                             get_first,
159                                             get_next,
160                                             get_previous,
161                                             get_last,
162                                             get_item,
163                                             data,
164                                             notify);
165 }
166 
167 GtkListListModel *
gtk_list_list_model_new_with_size(guint n_items,gpointer (* get_first)(gpointer),gpointer (* get_next)(gpointer,gpointer),gpointer (* get_previous)(gpointer,gpointer),gpointer (* get_last)(gpointer),gpointer (* get_item)(gpointer,gpointer),gpointer data,GDestroyNotify notify)168 gtk_list_list_model_new_with_size (guint          n_items,
169                                    gpointer       (* get_first) (gpointer),
170                                    gpointer       (* get_next) (gpointer, gpointer),
171                                    gpointer       (* get_previous) (gpointer, gpointer),
172                                    gpointer       (* get_last) (gpointer),
173                                    gpointer       (* get_item) (gpointer, gpointer),
174                                    gpointer       data,
175                                    GDestroyNotify notify)
176 {
177   GtkListListModel *result;
178 
179   g_return_val_if_fail (get_first != NULL, NULL);
180   g_return_val_if_fail (get_next != NULL, NULL);
181   g_return_val_if_fail (get_previous != NULL, NULL);
182   g_return_val_if_fail (get_item != NULL, NULL);
183 
184   result = g_object_new (GTK_TYPE_LIST_LIST_MODEL, NULL);
185 
186   result->n_items = n_items;
187   result->get_first = get_first;
188   result->get_next = get_next;
189   result->get_previous = get_previous;
190   result->get_last = get_last;
191   result->get_item = get_item;
192   result->data = data;
193   result->notify = notify;
194 
195   return result;
196 }
197 
198 static guint
gtk_list_list_model_find(GtkListListModel * self,gpointer item)199 gtk_list_list_model_find (GtkListListModel *self,
200                           gpointer          item)
201 {
202   guint position;
203   gpointer x;
204 
205   position = 0;
206   for (x = self->get_first (self->data);
207        x != item;
208        x = self->get_next (x, self->data))
209     position++;
210 
211   return position;
212 }
213 
214 void
gtk_list_list_model_item_added(GtkListListModel * self,gpointer item)215 gtk_list_list_model_item_added (GtkListListModel *self,
216                                 gpointer          item)
217 {
218   g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
219   g_return_if_fail (item != NULL);
220 
221   gtk_list_list_model_item_added_at (self, gtk_list_list_model_find (self, item));
222 }
223 
224 void
gtk_list_list_model_item_added_at(GtkListListModel * self,guint position)225 gtk_list_list_model_item_added_at (GtkListListModel *self,
226                                    guint             position)
227 {
228   g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
229   g_return_if_fail (position <= self->n_items);
230 
231   self->n_items += 1;
232 
233   g_list_model_items_changed (G_LIST_MODEL (self), position, 0, 1);
234 }
235 
236 void
gtk_list_list_model_item_removed(GtkListListModel * self,gpointer previous)237 gtk_list_list_model_item_removed (GtkListListModel *self,
238                                   gpointer          previous)
239 {
240   guint position;
241 
242   g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
243 
244   if (previous == NULL)
245     position = 0;
246   else
247     position = 1 + gtk_list_list_model_find (self, previous);
248 
249   gtk_list_list_model_item_removed_at (self, position);
250 }
251 
252 void
gtk_list_list_model_item_moved(GtkListListModel * self,gpointer item,gpointer previous_previous)253 gtk_list_list_model_item_moved (GtkListListModel *self,
254                                 gpointer          item,
255                                 gpointer          previous_previous)
256 {
257   guint position, previous_position;
258   guint min, max;
259 
260   g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
261   g_return_if_fail (item != previous_previous);
262 
263   position = gtk_list_list_model_find (self, item);
264 
265   if (previous_previous == NULL)
266     {
267       previous_position = 0;
268     }
269   else
270     {
271       previous_position = gtk_list_list_model_find (self, previous_previous);
272       if (position > previous_position)
273         previous_position++;
274     }
275 
276   /* item didn't move */
277   if (position == previous_position)
278     return;
279 
280   min = MIN (position, previous_position);
281   max = MAX (position, previous_position) + 1;
282   g_list_model_items_changed (G_LIST_MODEL (self), min, max - min, max - min);
283 }
284 
285 void
gtk_list_list_model_item_removed_at(GtkListListModel * self,guint position)286 gtk_list_list_model_item_removed_at (GtkListListModel *self,
287                                      guint             position)
288 {
289   g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
290   g_return_if_fail (position < self->n_items);
291 
292   self->n_items -= 1;
293 
294   g_list_model_items_changed (G_LIST_MODEL (self), position, 1, 0);
295 }
296 
297 void
gtk_list_list_model_clear(GtkListListModel * self)298 gtk_list_list_model_clear (GtkListListModel *self)
299 {
300   guint n_items;
301 
302   g_return_if_fail (GTK_IS_LIST_LIST_MODEL (self));
303 
304   n_items = self->n_items;
305 
306   if (self->notify)
307     self->notify (self->data);
308 
309   self->n_items = 0;
310   self->notify = NULL;
311 
312   if (n_items > 0)
313     g_list_model_items_changed (G_LIST_MODEL (self), 0, n_items, 0);
314 }
315 
316 
317