1 /*
2  * Copyright © 2019 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 #include "config.h"
21 
22 #include "gtkbuilderlistitemfactory.h"
23 
24 #include "gtkbuilder.h"
25 #include "gtkbuilderprivate.h"
26 #include "gtkintl.h"
27 #include "gtklistitemfactoryprivate.h"
28 #include "gtklistitemprivate.h"
29 
30 /**
31  * GtkBuilderListItemFactory:
32  *
33  * `GtkBuilderListItemFactory` is a `GtkListItemFactory` that creates
34  * widgets by instantiating `GtkBuilder` UI templates.
35  *
36  * The templates must be extending `GtkListItem`, and typically use
37  * `GtkExpression`s to obtain data from the items in the model.
38  *
39  * Example:
40  * ```xml
41  *   <interface>
42  *     <template class="GtkListItem">
43  *       <property name="child">
44  *         <object class="GtkLabel">
45  *           <property name="xalign">0</property>
46  *           <binding name="label">
47  *             <lookup name="name" type="SettingsKey">
48  *               <lookup name="item">GtkListItem</lookup>
49  *             </lookup>
50  *           </binding>
51  *         </object>
52  *       </property>
53  *     </template>
54  *   </interface>
55  * ```
56  */
57 
58 struct _GtkBuilderListItemFactory
59 {
60   GtkListItemFactory parent_instance;
61 
62   GtkBuilderScope *scope;
63   GBytes *bytes;
64   GBytes *data;
65   char *resource;
66 };
67 
68 struct _GtkBuilderListItemFactoryClass
69 {
70   GtkListItemFactoryClass parent_class;
71 };
72 
73 enum {
74   PROP_0,
75   PROP_BYTES,
76   PROP_RESOURCE,
77   PROP_SCOPE,
78 
79   N_PROPS
80 };
81 
82 G_DEFINE_TYPE (GtkBuilderListItemFactory, gtk_builder_list_item_factory, GTK_TYPE_LIST_ITEM_FACTORY)
83 
84 static GParamSpec *properties[N_PROPS] = { NULL, };
85 
86 static void
gtk_builder_list_item_factory_setup(GtkListItemFactory * factory,GtkListItemWidget * widget,GtkListItem * list_item)87 gtk_builder_list_item_factory_setup (GtkListItemFactory *factory,
88                                      GtkListItemWidget  *widget,
89                                      GtkListItem        *list_item)
90 {
91   GtkBuilderListItemFactory *self = GTK_BUILDER_LIST_ITEM_FACTORY (factory);
92   GtkBuilder *builder;
93   GError *error = NULL;
94 
95   GTK_LIST_ITEM_FACTORY_CLASS (gtk_builder_list_item_factory_parent_class)->setup (factory, widget, list_item);
96 
97   builder = gtk_builder_new ();
98 
99   gtk_builder_set_current_object (builder, G_OBJECT (list_item));
100   if (self->scope)
101     gtk_builder_set_scope (builder, self->scope);
102 
103   if (!gtk_builder_extend_with_template (builder, G_OBJECT (list_item), G_OBJECT_TYPE (list_item),
104                                          (const char *)g_bytes_get_data (self->data, NULL),
105                                          g_bytes_get_size (self->data),
106                                          &error))
107     {
108       g_critical ("Error building template for list item: %s", error->message);
109       g_error_free (error);
110 
111       /* This should never happen, if the template XML cannot be built
112        * then it is a critical programming error.
113        */
114       g_object_unref (builder);
115       return;
116     }
117 
118   g_object_unref (builder);
119 }
120 
121 static void
gtk_builder_list_item_factory_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)122 gtk_builder_list_item_factory_get_property (GObject    *object,
123                                             guint       property_id,
124                                             GValue     *value,
125                                             GParamSpec *pspec)
126 {
127   GtkBuilderListItemFactory *self = GTK_BUILDER_LIST_ITEM_FACTORY (object);
128 
129   switch (property_id)
130     {
131     case PROP_BYTES:
132       g_value_set_boxed (value, self->bytes);
133       break;
134 
135     case PROP_RESOURCE:
136       g_value_set_string (value, self->resource);
137       break;
138 
139     case PROP_SCOPE:
140       g_value_set_object (value, self->scope);
141       break;
142 
143     default:
144       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
145       break;
146     }
147 }
148 
149 static gboolean
gtk_builder_list_item_factory_set_bytes(GtkBuilderListItemFactory * self,GBytes * bytes)150 gtk_builder_list_item_factory_set_bytes (GtkBuilderListItemFactory *self,
151                                          GBytes                    *bytes)
152 {
153   if (bytes == NULL)
154     return FALSE;
155 
156   if (self->bytes)
157     {
158       g_critical ("Data for GtkBuilderListItemFactory has already been set.");
159       return FALSE;
160     }
161 
162   self->bytes = g_bytes_ref (bytes);
163 
164   if (!_gtk_buildable_parser_is_precompiled (g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes)))
165     {
166       GError *error = NULL;
167       GBytes *data;
168 
169       data = _gtk_buildable_parser_precompile (g_bytes_get_data (bytes, NULL),
170                                                g_bytes_get_size (bytes),
171                                                &error);
172       if (data == NULL)
173         {
174           g_warning ("Failed to precompile template for GtkBuilderListItemFactory: %s", error->message);
175           g_error_free (error);
176           self->data = g_bytes_ref (bytes);
177         }
178       else
179         {
180           self->data = data;
181         }
182     }
183 
184   return TRUE;
185 }
186 
187 static void
gtk_builder_list_item_factory_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)188 gtk_builder_list_item_factory_set_property (GObject      *object,
189                                             guint         property_id,
190                                             const GValue *value,
191                                             GParamSpec   *pspec)
192 {
193   GtkBuilderListItemFactory *self = GTK_BUILDER_LIST_ITEM_FACTORY (object);
194 
195   switch (property_id)
196     {
197     case PROP_BYTES:
198       gtk_builder_list_item_factory_set_bytes (self, g_value_get_boxed (value));
199       break;
200 
201     case PROP_RESOURCE:
202       {
203         GError *error = NULL;
204         GBytes *bytes;
205         const char *resource;
206 
207         resource = g_value_get_string (value);
208         if (resource == NULL)
209           break;
210 
211         bytes = g_resources_lookup_data (resource, 0, &error);
212         if (bytes)
213           {
214             if (gtk_builder_list_item_factory_set_bytes (self, bytes))
215               self->resource = g_strdup (resource);
216             g_bytes_unref (bytes);
217           }
218         else
219           {
220             g_critical ("Unable to load resource for list item template: %s", error->message);
221             g_error_free (error);
222           }
223       }
224       break;
225 
226     case PROP_SCOPE:
227       self->scope = g_value_dup_object (value);
228       break;
229 
230     default:
231       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
232       break;
233     }
234 }
235 
236 static void
gtk_builder_list_item_factory_finalize(GObject * object)237 gtk_builder_list_item_factory_finalize (GObject *object)
238 {
239   GtkBuilderListItemFactory *self = GTK_BUILDER_LIST_ITEM_FACTORY (object);
240 
241   g_clear_object (&self->scope);
242   g_bytes_unref (self->bytes);
243   g_bytes_unref (self->data);
244   g_free (self->resource);
245 
246   G_OBJECT_CLASS (gtk_builder_list_item_factory_parent_class)->finalize (object);
247 }
248 
249 static void
gtk_builder_list_item_factory_class_init(GtkBuilderListItemFactoryClass * klass)250 gtk_builder_list_item_factory_class_init (GtkBuilderListItemFactoryClass *klass)
251 {
252   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
253   GtkListItemFactoryClass *factory_class = GTK_LIST_ITEM_FACTORY_CLASS (klass);
254 
255   gobject_class->finalize = gtk_builder_list_item_factory_finalize;
256   gobject_class->get_property = gtk_builder_list_item_factory_get_property;
257   gobject_class->set_property = gtk_builder_list_item_factory_set_property;
258 
259   factory_class->setup = gtk_builder_list_item_factory_setup;
260 
261   /**
262    * GtkBuilderListItemFactory:bytes: (attributes org.gtk.Property.get=gtk_builder_list_item_factory_get_bytes)
263    *
264    * `GBytes` containing the UI definition.
265    */
266   properties[PROP_BYTES] =
267     g_param_spec_boxed ("bytes",
268                         P_("Bytes"),
269                         P_("bytes containing the UI definition"),
270                         G_TYPE_BYTES,
271                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
272 
273   /**
274    * GtkBuilderListItemFactory:resource: (attributes org.gtk.Property.get=gtk_builder_list_item_factory_get_resource)
275    *
276    * Path of the resource containing the UI definition.
277    */
278   properties[PROP_RESOURCE] =
279     g_param_spec_string ("resource",
280                          P_("Resource"),
281                          P_("resource containing the UI definition"),
282                          NULL,
283                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
284 
285   /**
286    * GtkBuilderListItemFactory:scope: (attributes org.gtk.Property.get=gtk_builder_list_item_factory_get_scope)
287    *
288    * `GtkBuilderScope` to use when instantiating listitems
289    */
290   properties[PROP_SCOPE] =
291     g_param_spec_object ("scope",
292                          P_("Scope"),
293                          P_("scope to use when instantiating listitems"),
294                          GTK_TYPE_BUILDER_SCOPE,
295                          G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
296 
297   g_object_class_install_properties (gobject_class, N_PROPS, properties);
298 }
299 
300 static void
gtk_builder_list_item_factory_init(GtkBuilderListItemFactory * self)301 gtk_builder_list_item_factory_init (GtkBuilderListItemFactory *self)
302 {
303 }
304 
305 /**
306  * gtk_builder_list_item_factory_new_from_bytes:
307  * @scope: (nullable) (transfer none): A scope to use when instantiating
308  * @bytes: the `GBytes` containing the ui file to instantiate
309  *
310  * Creates a new `GtkBuilderListItemFactory` that instantiates widgets
311  * using @bytes as the data to pass to `GtkBuilder`.
312  *
313  * Returns: a new `GtkBuilderListItemFactory`
314  **/
315 GtkListItemFactory *
gtk_builder_list_item_factory_new_from_bytes(GtkBuilderScope * scope,GBytes * bytes)316 gtk_builder_list_item_factory_new_from_bytes (GtkBuilderScope *scope,
317                                               GBytes          *bytes)
318 {
319   g_return_val_if_fail (bytes != NULL, NULL);
320 
321   return g_object_new (GTK_TYPE_BUILDER_LIST_ITEM_FACTORY,
322                        "bytes", bytes,
323                        "scope", scope,
324                        NULL);
325 }
326 
327 /**
328  * gtk_builder_list_item_factory_new_from_resource:
329  * @scope: (nullable) (transfer none): A scope to use when instantiating
330  * @resource_path: valid path to a resource that contains the data
331  *
332  * Creates a new `GtkBuilderListItemFactory` that instantiates widgets
333  * using data read from the given @resource_path to pass to `GtkBuilder`.
334  *
335  * Returns: a new `GtkBuilderListItemFactory`
336  **/
337 GtkListItemFactory *
gtk_builder_list_item_factory_new_from_resource(GtkBuilderScope * scope,const char * resource_path)338 gtk_builder_list_item_factory_new_from_resource (GtkBuilderScope *scope,
339                                                  const char      *resource_path)
340 {
341   g_return_val_if_fail (scope == NULL || GTK_IS_BUILDER_SCOPE (scope), NULL);
342   g_return_val_if_fail (resource_path != NULL, NULL);
343 
344   return g_object_new (GTK_TYPE_BUILDER_LIST_ITEM_FACTORY,
345                        "resource", resource_path,
346                        "scope", scope,
347                        NULL);
348 }
349 
350 /**
351  * gtk_builder_list_item_factory_get_bytes: (attributes org.gtk.Method.get_property=bytes)
352  * @self: a `GtkBuilderListItemFactory`
353  *
354  * Gets the data used as the `GtkBuilder` UI template for constructing
355  * listitems.
356  *
357  * Returns: (transfer none): The `GtkBuilder` data
358  */
359 GBytes *
gtk_builder_list_item_factory_get_bytes(GtkBuilderListItemFactory * self)360 gtk_builder_list_item_factory_get_bytes (GtkBuilderListItemFactory *self)
361 {
362   g_return_val_if_fail (GTK_IS_BUILDER_LIST_ITEM_FACTORY (self), NULL);
363 
364   return self->bytes;
365 }
366 
367 /**
368  * gtk_builder_list_item_factory_get_resource: (attributes org.gtk.Method.get_property=resource)
369  * @self: a `GtkBuilderListItemFactory`
370  *
371  * If the data references a resource, gets the path of that resource.
372  *
373  * Returns: (transfer none) (nullable): The path to the resource
374  */
375 const char *
gtk_builder_list_item_factory_get_resource(GtkBuilderListItemFactory * self)376 gtk_builder_list_item_factory_get_resource (GtkBuilderListItemFactory *self)
377 {
378   g_return_val_if_fail (GTK_IS_BUILDER_LIST_ITEM_FACTORY (self), NULL);
379 
380   return self->resource;
381 }
382 
383 /**
384  * gtk_builder_list_item_factory_get_scope: (attributes org.gtk.Method.get_property=scope)
385  * @self: a `GtkBuilderListItemFactory`
386  *
387  * Gets the scope used when constructing listitems.
388  *
389  * Returns: (transfer none) (nullable): The scope used when constructing listitems
390  */
391 GtkBuilderScope *
gtk_builder_list_item_factory_get_scope(GtkBuilderListItemFactory * self)392 gtk_builder_list_item_factory_get_scope (GtkBuilderListItemFactory *self)
393 {
394   g_return_val_if_fail (GTK_IS_BUILDER_LIST_ITEM_FACTORY (self), NULL);
395 
396   return self->scope;
397 }
398