diff --git a/NEWS b/NEWS index 5037f3e1a9..c47eb47d36 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,10 @@ Overview of Changes from GTK+ 2.16.x to 2.17.0 ============================================== +* GtkBuilder: + - Scale marks can now be specified in builder markup + - GtkAssistant action widgets can be added in builder markup + * Changes that are relevant for theme authors - GtkEntry now has a ::invisible-char style property that allows themes to set the preferred invisible character diff --git a/docs/reference/gtk/tmpl/gtkbuilder.sgml b/docs/reference/gtk/tmpl/gtkbuilder.sgml index fe10530f82..8dc1eb54a7 100644 --- a/docs/reference/gtk/tmpl/gtkbuilder.sgml +++ b/docs/reference/gtk/tmpl/gtkbuilder.sgml @@ -229,7 +229,8 @@ respective objects, see GtkUIManager, GtkActionGroup. GtkMenuItem, -GtkAssistant. +GtkAssistant, +GtkScale. diff --git a/docs/reference/gtk/tmpl/gtkscale.sgml b/docs/reference/gtk/tmpl/gtkscale.sgml index 3955e9f0ff..2ac9a18fe6 100644 --- a/docs/reference/gtk/tmpl/gtkscale.sgml +++ b/docs/reference/gtk/tmpl/gtkscale.sgml @@ -7,18 +7,28 @@ Base class for GtkHScale and GtkVScale A #GtkScale is a slider control used to select a numeric value. -To use it, you'll probably want to investigate the methods on +To use it, you'll probably want to investigate the methods on its base class, #GtkRange, in addition to the methods for #GtkScale itself. -To set the value of a scale, you would normally use gtk_range_set_value(). -To detect changes to the value, you would normally use the "value_changed" +To set the value of a scale, you would normally use gtk_range_set_value(). +To detect changes to the value, you would normally use the "value_changed" signal. The #GtkScale widget is an abstract class, used only for deriving the -subclasses #GtkHScale and #GtkVScale. To create a scale widget, +subclasses #GtkHScale and #GtkVScale. To create a scale widget, call gtk_hscale_new_with_range() or gtk_vscale_new_with_range(). +GtkScale as GtkBuildable + +GtkScale supports a custom <marks> element, which +can contain multiple <mark> elements. The "value" and "position" +attributes have the same meaning as gtk_scale_add_mark() parameters of the +same name. If the element is not empty, its content is taken as the markup +to show at the mark. It can be translated with the usual "translatable and +"context" attributes. + + @@ -39,9 +49,9 @@ the accessor functions. -@scale: -@value: -@Returns: +@scale: +@value: +@Returns: diff --git a/gtk/gtkscale.c b/gtk/gtkscale.c index b738d6dd17..87aa469da4 100644 --- a/gtk/gtkscale.c +++ b/gtk/gtkscale.c @@ -38,6 +38,8 @@ #include "gtkbindings.h" #include "gtkprivate.h" #include "gtkintl.h" +#include "gtkbuildable.h" +#include "gtkbuilderprivate.h" #include "gtkalias.h" @@ -56,7 +58,7 @@ typedef struct _GtkScaleMark GtkScaleMark; struct _GtkScaleMark { gdouble value; - const gchar *markup; + gchar *markup; GtkPositionType position; }; @@ -110,8 +112,24 @@ static gboolean gtk_scale_expose (GtkWidget *widget, static void gtk_scale_real_get_layout_offsets (GtkScale *scale, gint *x, gint *y); +static void gtk_scale_buildable_interface_init (GtkBuildableIface *iface); +static gboolean gtk_scale_buildable_custom_tag_start (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + GMarkupParser *parser, + gpointer *data); +static void gtk_scale_buildable_custom_finished (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + gpointer user_data); + + +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkScale, gtk_scale, GTK_TYPE_RANGE, + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, + gtk_scale_buildable_interface_init)) -G_DEFINE_ABSTRACT_TYPE (GtkScale, gtk_scale, GTK_TYPE_RANGE) static gboolean single_string_accumulator (GSignalInvocationHint *ihint, @@ -1355,6 +1373,240 @@ gtk_scale_add_mark (GtkScale *scale, gtk_widget_queue_resize (GTK_WIDGET (scale)); } +static GtkBuildableIface *parent_buildable_iface; + +static void +gtk_scale_buildable_interface_init (GtkBuildableIface *iface) +{ + parent_buildable_iface = g_type_interface_peek_parent (iface); + iface->custom_tag_start = gtk_scale_buildable_custom_tag_start; + iface->custom_finished = gtk_scale_buildable_custom_finished; +} + +typedef struct +{ + GtkScale *scale; + GtkBuilder *builder; + GSList *marks; +} MarksSubparserData; + +typedef struct +{ + gdouble value; + GtkPositionType position; + GString *markup; + gchar *context; + gboolean translatable; +} MarkData; + +static void +mark_data_free (MarkData *data) +{ + g_string_free (data->markup, TRUE); + g_free (data->context); + g_slice_free (MarkData, data); +} + +static void +marks_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **names, + const gchar **values, + gpointer user_data, + GError **error) +{ + MarksSubparserData *parser_data = (MarksSubparserData*)user_data; + guint i; + gint line_number, char_number; + + if (strcmp (element_name, "marks") == 0) + ; + else if (strcmp (element_name, "mark") == 0) + { + gdouble value; + gboolean has_value = FALSE; + GtkPositionType position = GTK_POS_BOTTOM; + const gchar *msg_context = NULL; + gboolean translatable = FALSE; + MarkData *mark; + + for (i = 0; names[i]; i++) + { + if (strcmp (names[i], "translatable") == 0) + { + if (!_gtk_builder_boolean_from_string (values[i], &translatable, error)) + return; + } + else if (strcmp (names[i], "comments") == 0) + { + /* do nothing, comments are for translators */ + } + else if (strcmp (names[i], "context") == 0) + msg_context = values[i]; + else if (strcmp (names[i], "value") == 0) + { + GValue gvalue = { 0, }; + + if (!gtk_builder_value_from_string_type (parser_data->builder, G_TYPE_DOUBLE, values[i], &gvalue, error)) + return; + + value = g_value_get_double (&gvalue); + has_value = TRUE; + } + else if (strcmp (names[i], "position") == 0) + { + GValue gvalue = { 0, }; + + if (!gtk_builder_value_from_string_type (parser_data->builder, GTK_TYPE_POSITION_TYPE, values[i], &gvalue, error)) + return; + + position = g_value_get_enum (&gvalue); + } + else + { + g_markup_parse_context_get_position (context, + &line_number, + &char_number); + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_INVALID_ATTRIBUTE, + "%s:%d:%d '%s' is not a valid attribute of <%s>", + "", + line_number, char_number, names[i], "mark"); + return; + } + } + + if (!has_value) + { + g_markup_parse_context_get_position (context, + &line_number, + &char_number); + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_MISSING_ATTRIBUTE, + "%s:%d:%d <%s> requires attribute \"%s\"", + "", + line_number, char_number, "mark", + "value"); + return; + } + + mark = g_slice_new (MarkData); + mark->value = value; + mark->position = position; + mark->markup = g_string_new (""); + mark->context = g_strdup (msg_context); + mark->translatable = translatable; + + parser_data->marks = g_slist_prepend (parser_data->marks, mark); + } + else + { + g_markup_parse_context_get_position (context, + &line_number, + &char_number); + g_set_error (error, + GTK_BUILDER_ERROR, + GTK_BUILDER_ERROR_MISSING_ATTRIBUTE, + "%s:%d:%d unsupported tag for GtkScale: \"%s\"", + "", + line_number, char_number, element_name); + return; + } +} + +static void +marks_text (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + MarksSubparserData *data = (MarksSubparserData*)user_data; + + if (strcmp (g_markup_parse_context_get_element (context), "mark") == 0) + { + MarkData *mark = data->marks->data; + + g_string_append_len (mark->markup, text, text_len); + } +} + +static const GMarkupParser marks_parser = + { + marks_start_element, + NULL, + marks_text, + }; + + +static gboolean +gtk_scale_buildable_custom_tag_start (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + GMarkupParser *parser, + gpointer *data) +{ + MarksSubparserData *parser_data; + + if (child) + return FALSE; + + if (strcmp (tagname, "marks") == 0) + { + parser_data = g_slice_new0 (MarksSubparserData); + parser_data->scale = GTK_SCALE (buildable); + parser_data->marks = NULL; + + *parser = marks_parser; + *data = parser_data; + return TRUE; + } + + return parent_buildable_iface->custom_tag_start (buildable, builder, child, + tagname, parser, data); +} + +static void +gtk_scale_buildable_custom_finished (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + gpointer user_data) +{ + GtkScale *scale = GTK_SCALE (buildable); + MarksSubparserData *marks_data; + GtkWidget *toplevel; + + if (strcmp (tagname, "marks") == 0) + { + GSList *m; + gchar *markup; + + marks_data = (MarksSubparserData *)user_data; + + for (m = marks_data->marks; m; m = m->next) + { + MarkData *mdata = m->data; + + if (mdata->translatable && mdata->markup->len) + markup = _gtk_builder_parser_translate (gtk_builder_get_translation_domain (builder), + mdata->context, + mdata->markup->str); + else + markup = mdata->markup->str; + + gtk_scale_add_mark (scale, mdata->value, mdata->position, markup); + + mark_data_free (mdata); + } + + g_slist_free (marks_data->marks); + g_slice_free (MarksSubparserData, marks_data); + } +} #define __GTK_SCALE_C__ #include "gtkaliasdef.c"