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
75struct _GtkStringObject
76{
77 GObject parent_instance;
78 char *string;
79};
80
81enum {
82 PROP_STRING = 1,
83 PROP_NUM_PROPERTIES
84};
85
86G_DEFINE_TYPE (GtkStringObject, gtk_string_object, G_TYPE_OBJECT);
87
88static void
89gtk_string_object_init (GtkStringObject *object)
90{
91}
92
93static void
94gtk_string_object_finalize (GObject *object)
95{
96 GtkStringObject *self = GTK_STRING_OBJECT (ptr: object);
97
98 g_free (mem: self->string);
99
100 G_OBJECT_CLASS (gtk_string_object_parent_class)->finalize (object);
101}
102
103static void
104gtk_string_object_get_property (GObject *object,
105 guint property_id,
106 GValue *value,
107 GParamSpec *pspec)
108{
109 GtkStringObject *self = GTK_STRING_OBJECT (ptr: object);
110
111 switch (property_id)
112 {
113 case PROP_STRING:
114 g_value_set_string (value, v_string: self->string);
115 break;
116
117 default:
118 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
119 break;
120 }
121}
122
123static void
124gtk_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 (name: "string", nick: "String", blurb: "String",
138 NULL,
139 flags: G_PARAM_READABLE |
140 G_PARAM_STATIC_STRINGS);
141
142 g_object_class_install_property (oclass: object_class, property_id: PROP_STRING, pspec);
143
144}
145
146static GtkStringObject *
147gtk_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 */
165GtkStringObject *
166gtk_string_object_new (const char *string)
167{
168 return gtk_string_object_new_take (string: g_strdup (str: 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 */
179const char *
180gtk_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
187struct _GtkStringList
188{
189 GObject parent_instance;
190
191 Objects items;
192};
193
194struct _GtkStringListClass
195{
196 GObjectClass parent_class;
197};
198
199static GType
200gtk_string_list_get_item_type (GListModel *list)
201{
202 return G_TYPE_OBJECT;
203}
204
205static guint
206gtk_string_list_get_n_items (GListModel *list)
207{
208 GtkStringList *self = GTK_STRING_LIST (ptr: list);
209
210 return objects_get_size (self: &self->items);
211}
212
213static gpointer
214gtk_string_list_get_item (GListModel *list,
215 guint position)
216{
217 GtkStringList *self = GTK_STRING_LIST (ptr: list);
218
219 if (position >= objects_get_size (self: &self->items))
220 return NULL;
221
222 return g_object_ref (objects_get (&self->items, position));
223}
224
225static void
226gtk_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
233typedef 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
245static void
246item_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 (s1: element_name, s2: "items") == 0)
256 {
257 if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "object", error))
258 return;
259
260 if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error,
261 first_type: G_MARKUP_COLLECT_INVALID, NULL, NULL,
262 G_MARKUP_COLLECT_INVALID))
263 _gtk_builder_prefix_error (builder: data->builder, context, error);
264 }
265 else if (strcmp (s1: element_name, s2: "item") == 0)
266 {
267 gboolean translatable = FALSE;
268 const char *msg_context = NULL;
269
270 if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "items", error))
271 return;
272
273 if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error,
274 first_type: G_MARKUP_COLLECT_BOOLEAN|G_MARKUP_COLLECT_OPTIONAL, first_attr: "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 (builder: data->builder, context, error);
280 return;
281 }
282
283 data->is_text = TRUE;
284 data->translatable = translatable;
285 data->context = g_strdup (str: msg_context);
286 }
287 else
288 {
289 _gtk_builder_error_unhandled_tag (builder: data->builder, context,
290 object: "GtkStringList", element_name,
291 error);
292 }
293}
294
295static void
296item_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 (string: data->string, val: text, len: text_len);
306}
307
308static void
309item_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 (domain: data->domain,
324 context: data->context,
325 text: data->string->str);
326 g_string_assign (string: data->string, rval: translated);
327 }
328
329 gtk_string_list_append (self: data->list, string: data->string->str);
330 }
331
332 data->translatable = FALSE;
333 g_string_set_size (string: data->string, len: 0);
334 g_clear_pointer (&data->context, g_free);
335 data->is_text = FALSE;
336}
337
338static const GtkBuildableParser item_parser =
339{
340 item_start_element,
341 item_end_element,
342 item_text
343};
344
345static gboolean
346gtk_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 (s1: tagname, s2: "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 (init: "");
362
363 *parser = item_parser;
364 *parser_data = data;
365
366 return TRUE;
367 }
368
369 return FALSE;
370}
371
372static void
373gtk_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 (s1: tagname, s2: "items") == 0)
380 {
381 ItemParserData *data;
382
383 data = (ItemParserData*)user_data;
384 g_object_unref (object: data->list);
385 g_object_unref (object: data->builder);
386 g_string_free (string: data->string, TRUE);
387 g_slice_free (ItemParserData, data);
388 }
389}
390
391static void
392gtk_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
398G_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
404static void
405gtk_string_list_dispose (GObject *object)
406{
407 GtkStringList *self = GTK_STRING_LIST (ptr: object);
408
409 objects_clear (self: &self->items);
410
411 G_OBJECT_CLASS (gtk_string_list_parent_class)->dispose (object);
412}
413
414static void
415gtk_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
422static void
423gtk_string_list_init (GtkStringList *self)
424{
425 objects_init (self: &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 */
436GtkStringList *
437gtk_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, position: 0, n_removals: 0, additions: 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 */
468void
469gtk_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 (str_array: (char **) additions);
482 else
483 n_additions = 0;
484
485 objects_splice (self: &self->items, pos: position, removed: n_removals, FALSE, NULL, added: n_additions);
486
487 for (i = 0; i < n_additions; i++)
488 {
489 *objects_index (self: &self->items, pos: position + i) = gtk_string_object_new (string: additions[i]);
490 }
491
492 if (n_removals || n_additions)
493 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position, removed: n_removals, added: 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 */
506void
507gtk_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: &self->items, value: gtk_string_object_new (string));
513
514 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position: objects_get_size (self: &self->items) - 1, removed: 0, added: 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 */
532void
533gtk_string_list_take (GtkStringList *self,
534 char *string)
535{
536 g_return_if_fail (GTK_IS_STRING_LIST (self));
537
538 objects_append (self: &self->items, value: gtk_string_object_new_take (string));
539
540 g_list_model_items_changed (list: G_LIST_MODEL (ptr: self), position: objects_get_size (self: &self->items) - 1, removed: 0, added: 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 */
553void
554gtk_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, n_removals: 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 */
576const char *
577gtk_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: &self->items))
583 return NULL;
584
585 return objects_get (self: &self->items, pos: position)->string;
586}
587

source code of gtk/gtk/gtkstringlist.c