1 /*
2 * Copyright © 2020 Red Hat, Inc.
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 "gtkstringlist.h"
23
24 #include "gtkbuildable.h"
25 #include "gtkbuilderprivate.h"
26 #include "gtkintl.h"
27 #include "gtkprivate.h"
28
29 /**
30 * GtkStringList:
31 *
32 * `GtkStringList` is a list model that wraps an array of strings.
33 *
34 * The objects in the model have a "string" property.
35 *
36 * `GtkStringList` is well-suited for any place where you would
37 * typically use a `char*[]`, but need a list model.
38 *
39 * # GtkStringList as GtkBuildable
40 *
41 * The `GtkStringList` implementation of the `GtkBuildable` interface
42 * supports adding items directly using the <items> element and
43 * specifying <item> elements for each item. Each <item> element
44 * supports the regular translation attributes “translatable”,
45 * “context” and “comments”.
46 *
47 * Here is a UI definition fragment specifying a `GtkStringList`
48 *
49 * ```xml
50 * <object class="GtkStringList">
51 * <items>
52 * <item translatable="yes">Factory</item>
53 * <item translatable="yes">Home</item>
54 * <item translatable="yes">Subway</item>
55 * </items>
56 * </object>
57 * ```
58 */
59
60 /**
61 * GtkStringObject:
62 *
63 * `GtkStringObject` is the type of items in a `GtkStringList`.
64 *
65 * A `GtkStringObject` is a wrapper around a `const char*`; it has
66 * a [property@Gtk.StringObject:string] property.
67 */
68
69 #define GDK_ARRAY_ELEMENT_TYPE GtkStringObject *
70 #define GDK_ARRAY_NAME objects
71 #define GDK_ARRAY_TYPE_NAME Objects
72 #define GDK_ARRAY_FREE_FUNC g_object_unref
73 #include "gdk/gdkarrayimpl.c"
74
75 struct _GtkStringObject
76 {
77 GObject parent_instance;
78 char *string;
79 };
80
81 enum {
82 PROP_STRING = 1,
83 PROP_NUM_PROPERTIES
84 };
85
86 G_DEFINE_TYPE (GtkStringObject, gtk_string_object, G_TYPE_OBJECT);
87
88 static void
gtk_string_object_init(GtkStringObject * object)89 gtk_string_object_init (GtkStringObject *object)
90 {
91 }
92
93 static void
gtk_string_object_finalize(GObject * object)94 gtk_string_object_finalize (GObject *object)
95 {
96 GtkStringObject *self = GTK_STRING_OBJECT (object);
97
98 g_free (self->string);
99
100 G_OBJECT_CLASS (gtk_string_object_parent_class)->finalize (object);
101 }
102
103 static void
gtk_string_object_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)104 gtk_string_object_get_property (GObject *object,
105 guint property_id,
106 GValue *value,
107 GParamSpec *pspec)
108 {
109 GtkStringObject *self = GTK_STRING_OBJECT (object);
110
111 switch (property_id)
112 {
113 case PROP_STRING:
114 g_value_set_string (value, self->string);
115 break;
116
117 default:
118 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
119 break;
120 }
121 }
122
123 static void
gtk_string_object_class_init(GtkStringObjectClass * class)124 gtk_string_object_class_init (GtkStringObjectClass *class)
125 {
126 GObjectClass *object_class = G_OBJECT_CLASS (class);
127 GParamSpec *pspec;
128
129 object_class->finalize = gtk_string_object_finalize;
130 object_class->get_property = gtk_string_object_get_property;
131
132 /**
133 * GtkStringObject:string: (attributes org.gtk.Property.get=gtk_string_object_get_string)
134 *
135 * The string.
136 */
137 pspec = g_param_spec_string ("string", "String", "String",
138 NULL,
139 G_PARAM_READABLE |
140 G_PARAM_STATIC_STRINGS);
141
142 g_object_class_install_property (object_class, PROP_STRING, pspec);
143
144 }
145
146 static GtkStringObject *
gtk_string_object_new_take(char * string)147 gtk_string_object_new_take (char *string)
148 {
149 GtkStringObject *obj;
150
151 obj = g_object_new (GTK_TYPE_STRING_OBJECT, NULL);
152 obj->string = string;
153
154 return obj;
155 }
156
157 /**
158 * gtk_string_object_new:
159 * @string: (not nullable): The string to wrap
160 *
161 * Wraps a string in an object for use with `GListModel`.
162 *
163 * Returns: a new `GtkStringObject`
164 */
165 GtkStringObject *
gtk_string_object_new(const char * string)166 gtk_string_object_new (const char *string)
167 {
168 return gtk_string_object_new_take (g_strdup (string));
169 }
170
171 /**
172 * gtk_string_object_get_string: (attributes org.gtk.Method.get_property=string)
173 * @self: a `GtkStringObject`
174 *
175 * Returns the string contained in a `GtkStringObject`.
176 *
177 * Returns: the string of @self
178 */
179 const char *
gtk_string_object_get_string(GtkStringObject * self)180 gtk_string_object_get_string (GtkStringObject *self)
181 {
182 g_return_val_if_fail (GTK_IS_STRING_OBJECT (self), NULL);
183
184 return self->string;
185 }
186
187 struct _GtkStringList
188 {
189 GObject parent_instance;
190
191 Objects items;
192 };
193
194 struct _GtkStringListClass
195 {
196 GObjectClass parent_class;
197 };
198
199 static GType
gtk_string_list_get_item_type(GListModel * list)200 gtk_string_list_get_item_type (GListModel *list)
201 {
202 return G_TYPE_OBJECT;
203 }
204
205 static guint
gtk_string_list_get_n_items(GListModel * list)206 gtk_string_list_get_n_items (GListModel *list)
207 {
208 GtkStringList *self = GTK_STRING_LIST (list);
209
210 return objects_get_size (&self->items);
211 }
212
213 static gpointer
gtk_string_list_get_item(GListModel * list,guint position)214 gtk_string_list_get_item (GListModel *list,
215 guint position)
216 {
217 GtkStringList *self = GTK_STRING_LIST (list);
218
219 if (position >= objects_get_size (&self->items))
220 return NULL;
221
222 return g_object_ref (objects_get (&self->items, position));
223 }
224
225 static void
gtk_string_list_model_init(GListModelInterface * iface)226 gtk_string_list_model_init (GListModelInterface *iface)
227 {
228 iface->get_item_type = gtk_string_list_get_item_type;
229 iface->get_n_items = gtk_string_list_get_n_items;
230 iface->get_item = gtk_string_list_get_item;
231 }
232
233 typedef struct
234 {
235 GtkBuilder *builder;
236 GtkStringList *list;
237 GString *string;
238 const char *domain;
239 char *context;
240
241 guint translatable : 1;
242 guint is_text : 1;
243 } ItemParserData;
244
245 static void
item_start_element(GtkBuildableParseContext * context,const char * element_name,const char ** names,const char ** values,gpointer user_data,GError ** error)246 item_start_element (GtkBuildableParseContext *context,
247 const char *element_name,
248 const char **names,
249 const char **values,
250 gpointer user_data,
251 GError **error)
252 {
253 ItemParserData *data = (ItemParserData*)user_data;
254
255 if (strcmp (element_name, "items") == 0)
256 {
257 if (!_gtk_builder_check_parent (data->builder, context, "object", error))
258 return;
259
260 if (!g_markup_collect_attributes (element_name, names, values, error,
261 G_MARKUP_COLLECT_INVALID, NULL, NULL,
262 G_MARKUP_COLLECT_INVALID))
263 _gtk_builder_prefix_error (data->builder, context, error);
264 }
265 else if (strcmp (element_name, "item") == 0)
266 {
267 gboolean translatable = FALSE;
268 const char *msg_context = NULL;
269
270 if (!_gtk_builder_check_parent (data->builder, context, "items", error))
271 return;
272
273 if (!g_markup_collect_attributes (element_name, names, values, error,
274 G_MARKUP_COLLECT_BOOLEAN|G_MARKUP_COLLECT_OPTIONAL, "translatable", &translatable,
275 G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "comments", NULL,
276 G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "context", &msg_context,
277 G_MARKUP_COLLECT_INVALID))
278 {
279 _gtk_builder_prefix_error (data->builder, context, error);
280 return;
281 }
282
283 data->is_text = TRUE;
284 data->translatable = translatable;
285 data->context = g_strdup (msg_context);
286 }
287 else
288 {
289 _gtk_builder_error_unhandled_tag (data->builder, context,
290 "GtkStringList", element_name,
291 error);
292 }
293 }
294
295 static void
item_text(GtkBuildableParseContext * context,const char * text,gsize text_len,gpointer user_data,GError ** error)296 item_text (GtkBuildableParseContext *context,
297 const char *text,
298 gsize text_len,
299 gpointer user_data,
300 GError **error)
301 {
302 ItemParserData *data = (ItemParserData*)user_data;
303
304 if (data->is_text)
305 g_string_append_len (data->string, text, text_len);
306 }
307
308 static void
item_end_element(GtkBuildableParseContext * context,const char * element_name,gpointer user_data,GError ** error)309 item_end_element (GtkBuildableParseContext *context,
310 const char *element_name,
311 gpointer user_data,
312 GError **error)
313 {
314 ItemParserData *data = (ItemParserData*)user_data;
315
316 /* Append the translated strings */
317 if (data->string->len)
318 {
319 if (data->translatable)
320 {
321 const char *translated;
322
323 translated = _gtk_builder_parser_translate (data->domain,
324 data->context,
325 data->string->str);
326 g_string_assign (data->string, translated);
327 }
328
329 gtk_string_list_append (data->list, data->string->str);
330 }
331
332 data->translatable = FALSE;
333 g_string_set_size (data->string, 0);
334 g_clear_pointer (&data->context, g_free);
335 data->is_text = FALSE;
336 }
337
338 static const GtkBuildableParser item_parser =
339 {
340 item_start_element,
341 item_end_element,
342 item_text
343 };
344
345 static gboolean
gtk_string_list_buildable_custom_tag_start(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const char * tagname,GtkBuildableParser * parser,gpointer * parser_data)346 gtk_string_list_buildable_custom_tag_start (GtkBuildable *buildable,
347 GtkBuilder *builder,
348 GObject *child,
349 const char *tagname,
350 GtkBuildableParser *parser,
351 gpointer *parser_data)
352 {
353 if (strcmp (tagname, "items") == 0)
354 {
355 ItemParserData *data;
356
357 data = g_slice_new0 (ItemParserData);
358 data->builder = g_object_ref (builder);
359 data->list = g_object_ref (GTK_STRING_LIST (buildable));
360 data->domain = gtk_builder_get_translation_domain (builder);
361 data->string = g_string_new ("");
362
363 *parser = item_parser;
364 *parser_data = data;
365
366 return TRUE;
367 }
368
369 return FALSE;
370 }
371
372 static void
gtk_string_list_buildable_custom_finished(GtkBuildable * buildable,GtkBuilder * builder,GObject * child,const char * tagname,gpointer user_data)373 gtk_string_list_buildable_custom_finished (GtkBuildable *buildable,
374 GtkBuilder *builder,
375 GObject *child,
376 const char *tagname,
377 gpointer user_data)
378 {
379 if (strcmp (tagname, "items") == 0)
380 {
381 ItemParserData *data;
382
383 data = (ItemParserData*)user_data;
384 g_object_unref (data->list);
385 g_object_unref (data->builder);
386 g_string_free (data->string, TRUE);
387 g_slice_free (ItemParserData, data);
388 }
389 }
390
391 static void
gtk_string_list_buildable_init(GtkBuildableIface * iface)392 gtk_string_list_buildable_init (GtkBuildableIface *iface)
393 {
394 iface->custom_tag_start = gtk_string_list_buildable_custom_tag_start;
395 iface->custom_finished = gtk_string_list_buildable_custom_finished;
396 }
397
G_DEFINE_TYPE_WITH_CODE(GtkStringList,gtk_string_list,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,gtk_string_list_buildable_init)G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,gtk_string_list_model_init))398 G_DEFINE_TYPE_WITH_CODE (GtkStringList, gtk_string_list, G_TYPE_OBJECT,
399 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
400 gtk_string_list_buildable_init)
401 G_IMPLEMENT_INTERFACE (G_TYPE_LIST_MODEL,
402 gtk_string_list_model_init))
403
404 static void
405 gtk_string_list_dispose (GObject *object)
406 {
407 GtkStringList *self = GTK_STRING_LIST (object);
408
409 objects_clear (&self->items);
410
411 G_OBJECT_CLASS (gtk_string_list_parent_class)->dispose (object);
412 }
413
414 static void
gtk_string_list_class_init(GtkStringListClass * class)415 gtk_string_list_class_init (GtkStringListClass *class)
416 {
417 GObjectClass *gobject_class = G_OBJECT_CLASS (class);
418
419 gobject_class->dispose = gtk_string_list_dispose;
420 }
421
422 static void
gtk_string_list_init(GtkStringList * self)423 gtk_string_list_init (GtkStringList *self)
424 {
425 objects_init (&self->items);
426 }
427
428 /**
429 * gtk_string_list_new:
430 * @strings: (array zero-terminated=1) (nullable): The strings to put in the model
431 *
432 * Creates a new `GtkStringList` with the given @strings.
433 *
434 * Returns: a new `GtkStringList`
435 */
436 GtkStringList *
gtk_string_list_new(const char * const * strings)437 gtk_string_list_new (const char * const *strings)
438 {
439 GtkStringList *self;
440
441 self = g_object_new (GTK_TYPE_STRING_LIST, NULL);
442
443 gtk_string_list_splice (self, 0, 0, strings);
444
445 return self;
446 }
447
448 /**
449 * gtk_string_list_splice:
450 * @self: a `GtkStringList`
451 * @position: the position at which to make the change
452 * @n_removals: the number of strings to remove
453 * @additions: (array zero-terminated=1) (nullable): The strings to add
454 *
455 * Changes @self by removing @n_removals strings and adding @additions
456 * to it.
457 *
458 * This function is more efficient than [method@Gtk.StringList.append]
459 * and [method@Gtk.StringList.remove], because it only emits the
460 * ::items-changed signal once for the change.
461 *
462 * This function copies the strings in @additions.
463 *
464 * The parameters @position and @n_removals must be correct (ie:
465 * @position + @n_removals must be less than or equal to the length
466 * of the list at the time this function is called).
467 */
468 void
gtk_string_list_splice(GtkStringList * self,guint position,guint n_removals,const char * const * additions)469 gtk_string_list_splice (GtkStringList *self,
470 guint position,
471 guint n_removals,
472 const char * const *additions)
473 {
474 guint i, n_additions;
475
476 g_return_if_fail (GTK_IS_STRING_LIST (self));
477 g_return_if_fail (position + n_removals >= position); /* overflow */
478 g_return_if_fail (position + n_removals <= objects_get_size (&self->items));
479
480 if (additions)
481 n_additions = g_strv_length ((char **) additions);
482 else
483 n_additions = 0;
484
485 objects_splice (&self->items, position, n_removals, FALSE, NULL, n_additions);
486
487 for (i = 0; i < n_additions; i++)
488 {
489 *objects_index (&self->items, position + i) = gtk_string_object_new (additions[i]);
490 }
491
492 if (n_removals || n_additions)
493 g_list_model_items_changed (G_LIST_MODEL (self), position, n_removals, n_additions);
494 }
495
496 /**
497 * gtk_string_list_append:
498 * @self: a `GtkStringList`
499 * @string: the string to insert
500 *
501 * Appends @string to @self.
502 *
503 * The @string will be copied. See
504 * [method@Gtk.StringList.take] for a way to avoid that.
505 */
506 void
gtk_string_list_append(GtkStringList * self,const char * string)507 gtk_string_list_append (GtkStringList *self,
508 const char *string)
509 {
510 g_return_if_fail (GTK_IS_STRING_LIST (self));
511
512 objects_append (&self->items, gtk_string_object_new (string));
513
514 g_list_model_items_changed (G_LIST_MODEL (self), objects_get_size (&self->items) - 1, 0, 1);
515 }
516
517 /**
518 * gtk_string_list_take:
519 * @self: a `GtkStringList`
520 * @string: (transfer full): the string to insert
521 *
522 * Adds @string to self at the end, and takes
523 * ownership of it.
524 *
525 * This variant of [method@Gtk.StringList.append]
526 * is convenient for formatting strings:
527 *
528 * ```c
529 * gtk_string_list_take (self, g_strdup_print ("%d dollars", lots));
530 * ```
531 */
532 void
gtk_string_list_take(GtkStringList * self,char * string)533 gtk_string_list_take (GtkStringList *self,
534 char *string)
535 {
536 g_return_if_fail (GTK_IS_STRING_LIST (self));
537
538 objects_append (&self->items, gtk_string_object_new_take (string));
539
540 g_list_model_items_changed (G_LIST_MODEL (self), objects_get_size (&self->items) - 1, 0, 1);
541 }
542
543 /**
544 * gtk_string_list_remove:
545 * @self: a `GtkStringList`
546 * @position: the position of the string that is to be removed
547 *
548 * Removes the string at @position from @self.
549 *
550 * @position must be smaller than the current
551 * length of the list.
552 */
553 void
gtk_string_list_remove(GtkStringList * self,guint position)554 gtk_string_list_remove (GtkStringList *self,
555 guint position)
556 {
557 g_return_if_fail (GTK_IS_STRING_LIST (self));
558
559 gtk_string_list_splice (self, position, 1, NULL);
560 }
561
562 /**
563 * gtk_string_list_get_string:
564 * @self: a `GtkStringList`
565 * @position: the position to get the string for
566 *
567 * Gets the string that is at @position in @self.
568 *
569 * If @self does not contain @position items, %NULL is returned.
570 *
571 * This function returns the const char *. To get the
572 * object wrapping it, use g_list_model_get_item().
573 *
574 * Returns: (nullable): the string at the given position
575 */
576 const char *
gtk_string_list_get_string(GtkStringList * self,guint position)577 gtk_string_list_get_string (GtkStringList *self,
578 guint position)
579 {
580 g_return_val_if_fail (GTK_IS_STRING_LIST (self), NULL);
581
582 if (position >= objects_get_size (&self->items))
583 return NULL;
584
585 return objects_get (&self->items, position)->string;
586 }
587