gtk/gtk/gtkstringlist.c
Benjamin Otte 96e1b85c2c gdkarray: Add a "stolen" boolean to splice()
If set to TRUE, does not call the free func for the removed items.

This can be used to move items between arrays without having to do the
refcounting dance.
2020-12-24 06:38:45 +01:00

573 lines
16 KiB
C

/*
* Copyright © 2020 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
* Authors: Matthias Clasen <mclasen@redhat.com>
*/
#include "config.h"
#include "gtkstringlist.h"
#include "gtkbuildable.h"
#include "gtkbuilderprivate.h"
#include "gtkintl.h"
#include "gtkprivate.h"
/**
* SECTION:gtkstringlist
* @title: GtkStringList
* @short_description: A list model for strings
* @see_also: #GListModel
*
* #GtkStringList is a list model that wraps an array of strings.
*
* The objects in the model have a "string" property.
*
* GtkStringList is well-suited for any place where you would
* typically use a `char*[]`, but need a list model.
*
* # GtkStringList as GtkBuildable
*
* The GtkStringList implementation of the GtkBuildable interface
* supports adding items directly using the <items> element and
* specifying <item> elements for each item. Each <item> element
* supports the regular translation attributes “translatable”,
* “context” and “comments”.
*
* Here is a UI definition fragment specifying a GtkStringList
* |[
* <object class="GtkStringList">
* <items>
* <item translatable="yes">Factory</item>
* <item translatable="yes">Home</item>
* <item translatable="yes">Subway</item>
* </items>
* </object>
* ]|
*/
#define GDK_ARRAY_ELEMENT_TYPE GtkStringObject *
#define GDK_ARRAY_NAME objects
#define GDK_ARRAY_TYPE_NAME Objects
#define GDK_ARRAY_FREE_FUNC g_object_unref
#include "gdk/gdkarrayimpl.c"
struct _GtkStringObject
{
GObject parent_instance;
char *string;
};
enum {
PROP_STRING = 1,
PROP_NUM_PROPERTIES
};
G_DEFINE_TYPE (GtkStringObject, gtk_string_object, G_TYPE_OBJECT);
static void
gtk_string_object_init (GtkStringObject *object)
{
}
static void
gtk_string_object_finalize (GObject *object)
{
GtkStringObject *self = GTK_STRING_OBJECT (object);
g_free (self->string);
G_OBJECT_CLASS (gtk_string_object_parent_class)->finalize (object);
}
static void
gtk_string_object_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GtkStringObject *self = GTK_STRING_OBJECT (object);
switch (property_id)
{
case PROP_STRING:
g_value_set_string (value, self->string);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gtk_string_object_class_init (GtkStringObjectClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GParamSpec *pspec;
object_class->finalize = gtk_string_object_finalize;
object_class->get_property = gtk_string_object_get_property;
pspec = g_param_spec_string ("string", "String", "String",
NULL,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_STRING, pspec);
}
static GtkStringObject *
gtk_string_object_new_take (char *string)
{
GtkStringObject *obj;
obj = g_object_new (GTK_TYPE_STRING_OBJECT, NULL);
obj->string = string;
return obj;
}
/**
* gtk_string_object_new:
* @string: (not nullable): The string to wrap
*
* Wraps a string in an object for use with #GListModel
*
* Returns: a new #GtkStringObject
**/
GtkStringObject *
gtk_string_object_new (const char *string)
{
return gtk_string_object_new_take (g_strdup (string));
}
/**
* gtk_string_object_get_string:
* @self: a #GtkStringObject
*
* Returns the string contained in a #GtkStringObject.
*
* Returns: the string of @self
*/
const char *
gtk_string_object_get_string (GtkStringObject *self)
{
g_return_val_if_fail (GTK_IS_STRING_OBJECT (self), NULL);
return self->string;
}
struct _GtkStringList
{
GObject parent_instance;
Objects items;
};
struct _GtkStringListClass
{
GObjectClass parent_class;
};
static GType
gtk_string_list_get_item_type (GListModel *list)
{
return G_TYPE_OBJECT;
}
static guint
gtk_string_list_get_n_items (GListModel *list)
{
GtkStringList *self = GTK_STRING_LIST (list);
return objects_get_size (&self->items);
}
static gpointer
gtk_string_list_get_item (GListModel *list,
guint position)
{
GtkStringList *self = GTK_STRING_LIST (list);
if (position >= objects_get_size (&self->items))
return NULL;
return g_object_ref (objects_get (&self->items, position));
}
static void
gtk_string_list_model_init (GListModelInterface *iface)
{
iface->get_item_type = gtk_string_list_get_item_type;
iface->get_n_items = gtk_string_list_get_n_items;
iface->get_item = gtk_string_list_get_item;
}
typedef struct
{
GtkBuilder *builder;
GtkStringList *list;
GString *string;
const char *domain;
char *context;
guint translatable : 1;
guint is_text : 1;
} ItemParserData;
static void
item_start_element (GtkBuildableParseContext *context,
const char *element_name,
const char **names,
const char **values,
gpointer user_data,
GError **error)
{
ItemParserData *data = (ItemParserData*)user_data;
if (strcmp (element_name, "items") == 0)
{
if (!_gtk_builder_check_parent (data->builder, context, "object", error))
return;
if (!g_markup_collect_attributes (element_name, names, values, error,
G_MARKUP_COLLECT_INVALID, NULL, NULL,
G_MARKUP_COLLECT_INVALID))
_gtk_builder_prefix_error (data->builder, context, error);
}
else if (strcmp (element_name, "item") == 0)
{
gboolean translatable = FALSE;
const char *msg_context = NULL;
if (!_gtk_builder_check_parent (data->builder, context, "items", error))
return;
if (!g_markup_collect_attributes (element_name, names, values, error,
G_MARKUP_COLLECT_BOOLEAN|G_MARKUP_COLLECT_OPTIONAL, "translatable", &translatable,
G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "comments", NULL,
G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "context", &msg_context,
G_MARKUP_COLLECT_INVALID))
{
_gtk_builder_prefix_error (data->builder, context, error);
return;
}
data->is_text = TRUE;
data->translatable = translatable;
data->context = g_strdup (msg_context);
}
else
{
_gtk_builder_error_unhandled_tag (data->builder, context,
"GtkStringList", element_name,
error);
}
}
static void
item_text (GtkBuildableParseContext *context,
const char *text,
gsize text_len,
gpointer user_data,
GError **error)
{
ItemParserData *data = (ItemParserData*)user_data;
if (data->is_text)
g_string_append_len (data->string, text, text_len);
}
static void
item_end_element (GtkBuildableParseContext *context,
const char *element_name,
gpointer user_data,
GError **error)
{
ItemParserData *data = (ItemParserData*)user_data;
/* Append the translated strings */
if (data->string->len)
{
if (data->translatable)
{
const char *translated;
translated = _gtk_builder_parser_translate (data->domain,
data->context,
data->string->str);
g_string_assign (data->string, translated);
}
gtk_string_list_append (data->list, data->string->str);
}
data->translatable = FALSE;
g_string_set_size (data->string, 0);
g_clear_pointer (&data->context, g_free);
data->is_text = FALSE;
}
static const GtkBuildableParser item_parser =
{
item_start_element,
item_end_element,
item_text
};
static gboolean
gtk_string_list_buildable_custom_tag_start (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const char *tagname,
GtkBuildableParser *parser,
gpointer *parser_data)
{
if (strcmp (tagname, "items") == 0)
{
ItemParserData *data;
data = g_slice_new0 (ItemParserData);
data->builder = g_object_ref (builder);
data->list = g_object_ref (GTK_STRING_LIST (buildable));
data->domain = gtk_builder_get_translation_domain (builder);
data->string = g_string_new ("");
*parser = item_parser;
*parser_data = data;
return TRUE;
}
return FALSE;
}
static void
gtk_string_list_buildable_custom_finished (GtkBuildable *buildable,
GtkBuilder *builder,
GObject *child,
const char *tagname,
gpointer user_data)
{
if (strcmp (tagname, "items") == 0)
{
ItemParserData *data;
data = (ItemParserData*)user_data;
g_object_unref (data->list);
g_object_unref (data->builder);
g_string_free (data->string, TRUE);
g_slice_free (ItemParserData, data);
}
}
static void
gtk_string_list_buildable_init (GtkBuildableIface *iface)
{
iface->custom_tag_start = gtk_string_list_buildable_custom_tag_start;
iface->custom_finished = gtk_string_list_buildable_custom_finished;
}
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))
static void
gtk_string_list_dispose (GObject *object)
{
GtkStringList *self = GTK_STRING_LIST (object);
objects_clear (&self->items);
G_OBJECT_CLASS (gtk_string_list_parent_class)->dispose (object);
}
static void
gtk_string_list_class_init (GtkStringListClass *class)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (class);
gobject_class->dispose = gtk_string_list_dispose;
}
static void
gtk_string_list_init (GtkStringList *self)
{
objects_init (&self->items);
}
/**
* gtk_string_list_new:
* @strings: (array zero-terminated=1) (nullable): The strings to put in the model
*
* Creates a new #GtkStringList with the given @strings.
*
* Returns: a new #GtkStringList
*/
GtkStringList *
gtk_string_list_new (const char * const *strings)
{
GtkStringList *self;
self = g_object_new (GTK_TYPE_STRING_LIST, NULL);
gtk_string_list_splice (self, 0, 0, strings);
return self;
}
/**
* gtk_string_list_splice:
* @self: a #GtkStringList
* @position: the position at which to make the change
* @n_removals: the number of strings to remove
* @additions: (array zero-terminated=1) (nullable): The strings to add
*
* Changes @self by removing @n_removals strings and adding @additions
* to it.
*
* This function is more efficient than gtk_string_list_append() and
* gtk_string_list_remove(), because it only emits
* #GListModel::items-changed once for the change.
*
* This function copies the strings in @additions.
*
* The parameters @position and @n_removals must be correct (ie:
* @position + @n_removals must be less than or equal to the length
* of the list at the time this function is called).
*/
void
gtk_string_list_splice (GtkStringList *self,
guint position,
guint n_removals,
const char * const *additions)
{
guint i, n_additions;
g_return_if_fail (GTK_IS_STRING_LIST (self));
g_return_if_fail (position + n_removals >= position); /* overflow */
g_return_if_fail (position + n_removals <= objects_get_size (&self->items));
if (additions)
n_additions = g_strv_length ((char **) additions);
else
n_additions = 0;
objects_splice (&self->items, position, n_removals, FALSE, NULL, n_additions);
for (i = 0; i < n_additions; i++)
{
*objects_index (&self->items, position + i) = gtk_string_object_new (additions[i]);
}
if (n_removals || n_additions)
g_list_model_items_changed (G_LIST_MODEL (self), position, n_removals, n_additions);
}
/**
* gtk_string_list_append:
* @self: a #GtkStringList
* @string: the string to insert
*
* Appends @string to @self.
*
* The @string will be copied. See gtk_string_list_take()
* for a way to avoid that.
*/
void
gtk_string_list_append (GtkStringList *self,
const char *string)
{
g_return_if_fail (GTK_IS_STRING_LIST (self));
objects_append (&self->items, gtk_string_object_new (string));
g_list_model_items_changed (G_LIST_MODEL (self), objects_get_size (&self->items) - 1, 0, 1);
}
/**
* gtk_string_list_take:
* @self: a #GtkStringList
* @string: (transfer full): the string to insert
*
* Adds @string to self at the end, and takes
* ownership of it.
*
* This variant of gtk_string_list_append() is
* convenient for formatting strings:
*
* |[
* gtk_string_list_take (self, g_strdup_print ("%d dollars", lots));
* ]|
*/
void
gtk_string_list_take (GtkStringList *self,
char *string)
{
g_return_if_fail (GTK_IS_STRING_LIST (self));
objects_append (&self->items, gtk_string_object_new_take (string));
g_list_model_items_changed (G_LIST_MODEL (self), objects_get_size (&self->items) - 1, 0, 1);
}
/**
* gtk_string_list_remove:
* @self: a #GtkStringList
* @position: the position of the string that is to be removed
*
* Removes the string at @position from @self. @position must
* be smaller than the current length of the list.
*/
void
gtk_string_list_remove (GtkStringList *self,
guint position)
{
g_return_if_fail (GTK_IS_STRING_LIST (self));
gtk_string_list_splice (self, position, 1, NULL);
}
/**
* gtk_string_list_get_string:
* @self: a #GtkStringList
* @position: the position to get the string for
*
* Gets the string that is at @position in @self. If @self
* does not contain @position items, %NULL is returned.
*
* This function returns the const char *. To get the
* object wrapping it, use g_list_model_get_item().
*
* Returns: (nullable): the string at the given position
*/
const char *
gtk_string_list_get_string (GtkStringList *self,
guint position)
{
g_return_val_if_fail (GTK_IS_STRING_LIST (self), NULL);
if (position >= objects_get_size (&self->items))
return NULL;
return objects_get (&self->items, position)->string;
}