1 | /* |
2 | * Copyright © 2018 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 "gtkpicture.h" |
23 | |
24 | #include "gtkcssnodeprivate.h" |
25 | #include "gtkcssnumbervalueprivate.h" |
26 | #include "gtkcssstyleprivate.h" |
27 | #include "gtkintl.h" |
28 | #include "gtkprivate.h" |
29 | #include "gtksnapshot.h" |
30 | #include "gtkwidgetprivate.h" |
31 | #include "gdkpixbufutilsprivate.h" |
32 | |
33 | /** |
34 | * GtkPicture: |
35 | * |
36 | * The `GtkPicture` widget displays a `GdkPaintable`. |
37 | * |
38 | * ![An example GtkPicture](picture.png) |
39 | * |
40 | * Many convenience functions are provided to make pictures simple to use. |
41 | * For example, if you want to load an image from a file, and then display |
42 | * it, there’s a convenience function to do this: |
43 | * |
44 | * ```c |
45 | * GtkWidget *widget = gtk_picture_new_for_filename ("myfile.png"); |
46 | * ``` |
47 | * |
48 | * If the file isn’t loaded successfully, the picture will contain a |
49 | * “broken image” icon similar to that used in many web browsers. |
50 | * If you want to handle errors in loading the file yourself, |
51 | * for example by displaying an error message, then load the image with |
52 | * [ctor@Gdk.Texture.new_from_file], then create the `GtkPicture` with |
53 | * [ctor@Gtk.Picture.new_for_paintable]. |
54 | * |
55 | * Sometimes an application will want to avoid depending on external data |
56 | * files, such as image files. See the documentation of `GResource` for details. |
57 | * In this case, [ctor@Gtk.Picture.new_for_resource] and |
58 | * [method@Gtk.Picture.set_resource] should be used. |
59 | * |
60 | * `GtkPicture` displays an image at its natural size. See [class@Gtk.Image] |
61 | * if you want to display a fixed-size image, such as an icon. |
62 | * |
63 | * ## Sizing the paintable |
64 | * |
65 | * You can influence how the paintable is displayed inside the `GtkPicture`. |
66 | * By turning off [property@Gtk.Picture:keep-aspect-ratio] you can allow the |
67 | * paintable to get stretched. [property@Gtk.Picture:can-shrink] can be unset |
68 | * to make sure that paintables are never made smaller than their ideal size - |
69 | * but be careful if you do not know the size of the paintable in use (like |
70 | * when displaying user-loaded images). This can easily cause the picture to |
71 | * grow larger than the screen. And [property@GtkWidget:halign] and |
72 | * [property@GtkWidget:valign] can be used to make sure the paintable doesn't |
73 | * fill all available space but is instead displayed at its original size. |
74 | * |
75 | * ## CSS nodes |
76 | * |
77 | * `GtkPicture` has a single CSS node with the name `picture`. |
78 | * |
79 | * ## Accessibility |
80 | * |
81 | * `GtkPicture` uses the `GTK_ACCESSIBLE_ROLE_IMG` role. |
82 | */ |
83 | |
84 | enum |
85 | { |
86 | PROP_0, |
87 | PROP_PAINTABLE, |
88 | PROP_FILE, |
89 | PROP_ALTERNATIVE_TEXT, |
90 | PROP_KEEP_ASPECT_RATIO, |
91 | PROP_CAN_SHRINK, |
92 | NUM_PROPERTIES |
93 | }; |
94 | |
95 | struct _GtkPicture |
96 | { |
97 | GtkWidget parent_instance; |
98 | |
99 | GdkPaintable *paintable; |
100 | GFile *file; |
101 | |
102 | char *alternative_text; |
103 | guint keep_aspect_ratio : 1; |
104 | guint can_shrink : 1; |
105 | }; |
106 | |
107 | struct _GtkPictureClass |
108 | { |
109 | GtkWidgetClass parent_class; |
110 | }; |
111 | |
112 | static GParamSpec *properties[NUM_PROPERTIES] = { NULL, }; |
113 | |
114 | G_DEFINE_TYPE (GtkPicture, gtk_picture, GTK_TYPE_WIDGET) |
115 | |
116 | static void |
117 | gtk_picture_snapshot (GtkWidget *widget, |
118 | GtkSnapshot *snapshot) |
119 | { |
120 | GtkPicture *self = GTK_PICTURE (ptr: widget); |
121 | double ratio; |
122 | int x, y, width, height; |
123 | double w, h; |
124 | |
125 | if (self->paintable == NULL) |
126 | return; |
127 | |
128 | width = gtk_widget_get_width (widget); |
129 | height = gtk_widget_get_height (widget); |
130 | ratio = gdk_paintable_get_intrinsic_aspect_ratio (paintable: self->paintable); |
131 | |
132 | if (!self->keep_aspect_ratio || ratio == 0) |
133 | { |
134 | gdk_paintable_snapshot (paintable: self->paintable, snapshot, width, height); |
135 | } |
136 | else |
137 | { |
138 | double picture_ratio = (double) width / height; |
139 | |
140 | if (ratio > picture_ratio) |
141 | { |
142 | w = width; |
143 | h = width / ratio; |
144 | } |
145 | else |
146 | { |
147 | w = height * ratio; |
148 | h = height; |
149 | } |
150 | |
151 | x = (width - ceil (x: w)) / 2; |
152 | y = floor(x: height - ceil (x: h)) / 2; |
153 | |
154 | gtk_snapshot_save (snapshot); |
155 | gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT (x, y)); |
156 | gdk_paintable_snapshot (paintable: self->paintable, snapshot, width: w, height: h); |
157 | gtk_snapshot_restore (snapshot); |
158 | } |
159 | } |
160 | |
161 | static GtkSizeRequestMode |
162 | gtk_picture_get_request_mode (GtkWidget *widget) |
163 | { |
164 | return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH; |
165 | } |
166 | |
167 | static void |
168 | gtk_picture_measure (GtkWidget *widget, |
169 | GtkOrientation orientation, |
170 | int for_size, |
171 | int *minimum, |
172 | int *natural, |
173 | int *minimum_baseline, |
174 | int *natural_baseline) |
175 | { |
176 | GtkPicture *self = GTK_PICTURE (ptr: widget); |
177 | GtkCssStyle *style; |
178 | double min_width, min_height, nat_width, nat_height; |
179 | double default_size; |
180 | |
181 | /* for_size = 0 below is treated as -1, but we want to return zeros. */ |
182 | if (self->paintable == NULL || for_size == 0) |
183 | { |
184 | *minimum = 0; |
185 | *natural = 0; |
186 | return; |
187 | } |
188 | |
189 | style = gtk_css_node_get_style (cssnode: gtk_widget_get_css_node (widget)); |
190 | default_size = _gtk_css_number_value_get (number: style->icon->icon_size, one_hundred_percent: 100); |
191 | |
192 | if (self->can_shrink) |
193 | { |
194 | min_width = min_height = 0; |
195 | } |
196 | else |
197 | { |
198 | gdk_paintable_compute_concrete_size (paintable: self->paintable, |
199 | specified_width: 0, specified_height: 0, |
200 | default_width: default_size, default_height: default_size, |
201 | concrete_width: &min_width, concrete_height: &min_height); |
202 | } |
203 | |
204 | if (orientation == GTK_ORIENTATION_HORIZONTAL) |
205 | { |
206 | gdk_paintable_compute_concrete_size (paintable: self->paintable, |
207 | specified_width: 0, |
208 | specified_height: for_size < 0 ? 0 : for_size, |
209 | default_width: default_size, default_height: default_size, |
210 | concrete_width: &nat_width, concrete_height: &nat_height); |
211 | *minimum = ceil (x: min_width); |
212 | *natural = ceil (x: nat_width); |
213 | } |
214 | else |
215 | { |
216 | gdk_paintable_compute_concrete_size (paintable: self->paintable, |
217 | specified_width: for_size < 0 ? 0 : for_size, |
218 | specified_height: 0, |
219 | default_width: default_size, default_height: default_size, |
220 | concrete_width: &nat_width, concrete_height: &nat_height); |
221 | *minimum = ceil (x: min_height); |
222 | *natural = ceil (x: nat_height); |
223 | } |
224 | } |
225 | |
226 | static void |
227 | gtk_picture_set_property (GObject *object, |
228 | guint prop_id, |
229 | const GValue *value, |
230 | GParamSpec *pspec) |
231 | { |
232 | GtkPicture *self = GTK_PICTURE (ptr: object); |
233 | |
234 | switch (prop_id) |
235 | { |
236 | case PROP_PAINTABLE: |
237 | gtk_picture_set_paintable (self, paintable: g_value_get_object (value)); |
238 | break; |
239 | |
240 | case PROP_FILE: |
241 | gtk_picture_set_file (self, file: g_value_get_object (value)); |
242 | break; |
243 | |
244 | case PROP_ALTERNATIVE_TEXT: |
245 | gtk_picture_set_alternative_text (self, alternative_text: g_value_get_string (value)); |
246 | break; |
247 | |
248 | case PROP_KEEP_ASPECT_RATIO: |
249 | gtk_picture_set_keep_aspect_ratio (self, keep_aspect_ratio: g_value_get_boolean (value)); |
250 | break; |
251 | |
252 | case PROP_CAN_SHRINK: |
253 | gtk_picture_set_can_shrink (self, can_shrink: g_value_get_boolean (value)); |
254 | break; |
255 | |
256 | default: |
257 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
258 | break; |
259 | } |
260 | } |
261 | |
262 | static void |
263 | gtk_picture_get_property (GObject *object, |
264 | guint prop_id, |
265 | GValue *value, |
266 | GParamSpec *pspec) |
267 | { |
268 | GtkPicture *self = GTK_PICTURE (ptr: object); |
269 | |
270 | switch (prop_id) |
271 | { |
272 | case PROP_PAINTABLE: |
273 | g_value_set_object (value, v_object: self->paintable); |
274 | break; |
275 | |
276 | case PROP_FILE: |
277 | g_value_set_object (value, v_object: self->file); |
278 | break; |
279 | |
280 | case PROP_ALTERNATIVE_TEXT: |
281 | g_value_set_string (value, v_string: self->alternative_text); |
282 | break; |
283 | |
284 | case PROP_KEEP_ASPECT_RATIO: |
285 | g_value_set_boolean (value, v_boolean: self->keep_aspect_ratio); |
286 | break; |
287 | |
288 | case PROP_CAN_SHRINK: |
289 | g_value_set_boolean (value, v_boolean: self->can_shrink); |
290 | break; |
291 | |
292 | default: |
293 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
294 | break; |
295 | } |
296 | } |
297 | |
298 | static void |
299 | gtk_picture_dispose (GObject *object) |
300 | { |
301 | GtkPicture *self = GTK_PICTURE (ptr: object); |
302 | |
303 | gtk_picture_set_paintable (self, NULL); |
304 | |
305 | g_clear_object (&self->file); |
306 | g_clear_pointer (&self->alternative_text, g_free); |
307 | |
308 | G_OBJECT_CLASS (gtk_picture_parent_class)->dispose (object); |
309 | }; |
310 | |
311 | static void |
312 | gtk_picture_class_init (GtkPictureClass *class) |
313 | { |
314 | GObjectClass *gobject_class = G_OBJECT_CLASS (class); |
315 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); |
316 | |
317 | gobject_class->set_property = gtk_picture_set_property; |
318 | gobject_class->get_property = gtk_picture_get_property; |
319 | gobject_class->dispose = gtk_picture_dispose; |
320 | |
321 | widget_class->snapshot = gtk_picture_snapshot; |
322 | widget_class->get_request_mode = gtk_picture_get_request_mode; |
323 | widget_class->measure = gtk_picture_measure; |
324 | |
325 | /** |
326 | * GtkPicture:paintable: (attributes org.gtk.Property.get=gtk_picture_get_paintable org.gtk.Property.set=gtk_picture_set_paintable) |
327 | * |
328 | * The `GdkPaintable` to be displayed by this `GtkPicture`. |
329 | */ |
330 | properties[PROP_PAINTABLE] = |
331 | g_param_spec_object (name: "paintable" , |
332 | P_("Paintable" ), |
333 | P_("The GdkPaintable to display" ), |
334 | GDK_TYPE_PAINTABLE, |
335 | GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); |
336 | |
337 | /** |
338 | * GtkPicture:file: (attributes org.gtk.Property.get=gtk_picture_get_file org.gtk.Property.set=gtk_picture_set_file) |
339 | * |
340 | * The `GFile` that is displayed or %NULL if none. |
341 | */ |
342 | properties[PROP_FILE] = |
343 | g_param_spec_object (name: "file" , |
344 | P_("File" ), |
345 | P_("File to load and display" ), |
346 | G_TYPE_FILE, |
347 | GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); |
348 | |
349 | /** |
350 | * GtkPicture:alternative-text: (attributes org.gtk.Property.get=gtk_picture_get_alternative_text org.gtk.Property.set=gtk_picture_set_alternative_text) |
351 | * |
352 | * The alternative textual description for the picture. |
353 | */ |
354 | properties[PROP_ALTERNATIVE_TEXT] = |
355 | g_param_spec_string (name: "alternative-text" , |
356 | P_("Alternative text" ), |
357 | P_("The alternative textual description" ), |
358 | NULL, |
359 | GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); |
360 | |
361 | /** |
362 | * GtkPicture:keep-aspect-ratio: (attributes org.gtk.Property.get=gtk_picture_get_keep_aspect_ratio org.gtk.Property.set=gtk_picture_set_keep_aspect_ratio) |
363 | * |
364 | * Whether the GtkPicture will render its contents trying to preserve the aspect |
365 | * ratio. |
366 | */ |
367 | properties[PROP_KEEP_ASPECT_RATIO] = |
368 | g_param_spec_boolean (name: "keep-aspect-ratio" , |
369 | P_("Keep aspect ratio" ), |
370 | P_("Render contents respecting the aspect ratio" ), |
371 | TRUE, |
372 | GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); |
373 | |
374 | /** |
375 | * GtkPicture:can-shrink: (attributes org.gtk.Property.get=gtk_picture_get_can_shrink org.gtk.Property.set=gtk_picture_set_can_shrink) |
376 | * |
377 | * If the `GtkPicture` can be made smaller than the natural size of its contents. |
378 | */ |
379 | properties[PROP_CAN_SHRINK] = |
380 | g_param_spec_boolean (name: "can-shrink" , |
381 | P_("Can shrink" ), |
382 | P_("Allow self to be smaller than contents" ), |
383 | TRUE, |
384 | GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); |
385 | |
386 | g_object_class_install_properties (oclass: gobject_class, n_pspecs: NUM_PROPERTIES, pspecs: properties); |
387 | |
388 | gtk_widget_class_set_css_name (widget_class, I_("picture" )); |
389 | gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_IMG); |
390 | } |
391 | |
392 | static void |
393 | gtk_picture_init (GtkPicture *self) |
394 | { |
395 | self->can_shrink = TRUE; |
396 | self->keep_aspect_ratio = TRUE; |
397 | } |
398 | |
399 | /** |
400 | * gtk_picture_new: |
401 | * |
402 | * Creates a new empty `GtkPicture` widget. |
403 | * |
404 | * Returns: a newly created `GtkPicture` widget. |
405 | */ |
406 | GtkWidget* |
407 | gtk_picture_new (void) |
408 | { |
409 | return g_object_new (GTK_TYPE_PICTURE, NULL); |
410 | } |
411 | |
412 | /** |
413 | * gtk_picture_new_for_paintable: |
414 | * @paintable: (nullable): a `GdkPaintable` |
415 | * |
416 | * Creates a new `GtkPicture` displaying @paintable. |
417 | * |
418 | * The `GtkPicture` will track changes to the @paintable and update |
419 | * its size and contents in response to it. |
420 | * |
421 | * Returns: a new `GtkPicture` |
422 | */ |
423 | GtkWidget* |
424 | gtk_picture_new_for_paintable (GdkPaintable *paintable) |
425 | { |
426 | g_return_val_if_fail (paintable == NULL || GDK_IS_PAINTABLE (paintable), NULL); |
427 | |
428 | return g_object_new (GTK_TYPE_PICTURE, |
429 | first_property_name: "paintable" , paintable, |
430 | NULL); |
431 | } |
432 | |
433 | /** |
434 | * gtk_picture_new_for_pixbuf: |
435 | * @pixbuf: (nullable): a `GdkPixbuf` |
436 | * |
437 | * Creates a new `GtkPicture` displaying @pixbuf. |
438 | * |
439 | * This is a utility function that calls [ctor@Gtk.Picture.new_for_paintable], |
440 | * See that function for details. |
441 | * |
442 | * The pixbuf must not be modified after passing it to this function. |
443 | * |
444 | * Returns: a new `GtkPicture` |
445 | */ |
446 | GtkWidget* |
447 | gtk_picture_new_for_pixbuf (GdkPixbuf *pixbuf) |
448 | { |
449 | GtkWidget *result; |
450 | GdkPaintable *paintable; |
451 | |
452 | g_return_val_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf), NULL); |
453 | |
454 | if (pixbuf) |
455 | paintable = GDK_PAINTABLE (ptr: gdk_texture_new_for_pixbuf (pixbuf)); |
456 | else |
457 | paintable = NULL; |
458 | |
459 | result = gtk_picture_new_for_paintable (paintable); |
460 | |
461 | if (paintable) |
462 | g_object_unref (object: paintable); |
463 | |
464 | return result; |
465 | } |
466 | |
467 | /** |
468 | * gtk_picture_new_for_file: |
469 | * @file: (nullable): a `GFile` |
470 | * |
471 | * Creates a new `GtkPicture` displaying the given @file. |
472 | * |
473 | * If the file isn’t found or can’t be loaded, the resulting |
474 | * `GtkPicture` is empty. |
475 | * |
476 | * If you need to detect failures to load the file, use |
477 | * [ctor@Gdk.Texture.new_from_file] to load the file yourself, |
478 | * then create the `GtkPicture` from the texture. |
479 | * |
480 | * Returns: a new `GtkPicture` |
481 | */ |
482 | GtkWidget* |
483 | gtk_picture_new_for_file (GFile *file) |
484 | { |
485 | g_return_val_if_fail (file == NULL || G_IS_FILE (file), NULL); |
486 | |
487 | return g_object_new (GTK_TYPE_PICTURE, |
488 | first_property_name: "file" , file, |
489 | NULL); |
490 | } |
491 | |
492 | /** |
493 | * gtk_picture_new_for_filename: |
494 | * @filename: (type filename) (nullable): a filename |
495 | * |
496 | * Creates a new `GtkPicture` displaying the file @filename. |
497 | * |
498 | * This is a utility function that calls [ctor@Gtk.Picture.new_for_file]. |
499 | * See that function for details. |
500 | * |
501 | * Returns: a new `GtkPicture` |
502 | */ |
503 | GtkWidget* |
504 | gtk_picture_new_for_filename (const char *filename) |
505 | { |
506 | GtkWidget *result; |
507 | GFile *file; |
508 | |
509 | if (filename) |
510 | file = g_file_new_for_path (path: filename); |
511 | else |
512 | file = NULL; |
513 | |
514 | result = gtk_picture_new_for_file (file); |
515 | |
516 | if (file) |
517 | g_object_unref (object: file); |
518 | |
519 | return result; |
520 | } |
521 | |
522 | /** |
523 | * gtk_picture_new_for_resource: |
524 | * @resource_path: (nullable): resource path to play back |
525 | * |
526 | * Creates a new `GtkPicture` displaying the resource at @resource_path. |
527 | * |
528 | * This is a utility function that calls [ctor@Gtk.Picture.new_for_file]. |
529 | * See that function for details. |
530 | * |
531 | * Returns: a new `GtkPicture` |
532 | */ |
533 | GtkWidget * |
534 | gtk_picture_new_for_resource (const char *resource_path) |
535 | { |
536 | GtkWidget *result; |
537 | GFile *file; |
538 | |
539 | if (resource_path) |
540 | { |
541 | char *uri, *escaped; |
542 | |
543 | escaped = g_uri_escape_string (unescaped: resource_path, |
544 | G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE); |
545 | uri = g_strconcat (string1: "resource://" , escaped, NULL); |
546 | g_free (mem: escaped); |
547 | |
548 | file = g_file_new_for_uri (uri); |
549 | g_free (mem: uri); |
550 | } |
551 | else |
552 | { |
553 | file = NULL; |
554 | } |
555 | |
556 | result = gtk_picture_new_for_file (file); |
557 | |
558 | if (file) |
559 | g_object_unref (object: file); |
560 | |
561 | return result; |
562 | } |
563 | |
564 | /** |
565 | * gtk_picture_set_file: (attributes org.gtk.Method.set_property=file) |
566 | * @self: a `GtkPicture` |
567 | * @file: (nullable): a `GFile` |
568 | * |
569 | * Makes @self load and display @file. |
570 | * |
571 | * See [ctor@Gtk.Picture.new_for_file] for details. |
572 | */ |
573 | void |
574 | gtk_picture_set_file (GtkPicture *self, |
575 | GFile *file) |
576 | { |
577 | GdkPaintable *paintable; |
578 | |
579 | g_return_if_fail (GTK_IS_PICTURE (self)); |
580 | g_return_if_fail (file == NULL || G_IS_FILE (file)); |
581 | |
582 | if (self->file == file) |
583 | return; |
584 | |
585 | g_object_freeze_notify (G_OBJECT (self)); |
586 | |
587 | g_set_object (&self->file, file); |
588 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_FILE]); |
589 | |
590 | if (file) |
591 | paintable = gdk_paintable_new_from_file_scaled (file, scale_factor: gtk_widget_get_scale_factor (GTK_WIDGET (self))); |
592 | else |
593 | paintable = NULL; |
594 | |
595 | gtk_picture_set_paintable (self, paintable); |
596 | g_clear_object (&paintable); |
597 | |
598 | g_object_thaw_notify (G_OBJECT (self)); |
599 | } |
600 | |
601 | /** |
602 | * gtk_picture_get_file: (attributes org.gtk.Method.get_property=file) |
603 | * @self: a `GtkPicture` |
604 | * |
605 | * Gets the `GFile` currently displayed if @self is displaying a file. |
606 | * |
607 | * If @self is not displaying a file, for example when |
608 | * [method@Gtk.Picture.set_paintable] was used, then %NULL is returned. |
609 | * |
610 | * Returns: (nullable) (transfer none): The `GFile` displayed by @self. |
611 | */ |
612 | GFile * |
613 | gtk_picture_get_file (GtkPicture *self) |
614 | { |
615 | g_return_val_if_fail (GTK_IS_PICTURE (self), FALSE); |
616 | |
617 | return self->file; |
618 | } |
619 | |
620 | /** |
621 | * gtk_picture_set_filename: |
622 | * @self: a `GtkPicture` |
623 | * @filename: (type filename) (nullable): the filename to play |
624 | * |
625 | * Makes @self load and display the given @filename. |
626 | * |
627 | * This is a utility function that calls [method@Gtk.Picture.set_file]. |
628 | */ |
629 | void |
630 | gtk_picture_set_filename (GtkPicture *self, |
631 | const char *filename) |
632 | { |
633 | GFile *file; |
634 | |
635 | g_return_if_fail (GTK_IS_PICTURE (self)); |
636 | |
637 | if (filename) |
638 | file = g_file_new_for_path (path: filename); |
639 | else |
640 | file = NULL; |
641 | |
642 | gtk_picture_set_file (self, file); |
643 | |
644 | if (file) |
645 | g_object_unref (object: file); |
646 | } |
647 | |
648 | /** |
649 | * gtk_picture_set_resource: |
650 | * @self: a `GtkPicture` |
651 | * @resource_path: (nullable): the resource to set |
652 | * |
653 | * Makes @self load and display the resource at the given |
654 | * @resource_path. |
655 | * |
656 | * This is a utility function that calls [method@Gtk.Picture.set_file]. |
657 | */ |
658 | void |
659 | gtk_picture_set_resource (GtkPicture *self, |
660 | const char *resource_path) |
661 | { |
662 | GFile *file; |
663 | |
664 | g_return_if_fail (GTK_IS_PICTURE (self)); |
665 | |
666 | if (resource_path) |
667 | { |
668 | char *uri, *escaped; |
669 | |
670 | escaped = g_uri_escape_string (unescaped: resource_path, |
671 | G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE); |
672 | uri = g_strconcat (string1: "resource://" , escaped, NULL); |
673 | g_free (mem: escaped); |
674 | |
675 | file = g_file_new_for_uri (uri); |
676 | g_free (mem: uri); |
677 | } |
678 | else |
679 | { |
680 | file = NULL; |
681 | } |
682 | |
683 | gtk_picture_set_file (self, file); |
684 | |
685 | if (file) |
686 | g_object_unref (object: file); |
687 | } |
688 | |
689 | /** |
690 | * gtk_picture_set_pixbuf: |
691 | * @self: a `GtkPicture` |
692 | * @pixbuf: (nullable): a `GdkPixbuf` |
693 | * |
694 | * Sets a `GtkPicture` to show a `GdkPixbuf`. |
695 | * |
696 | * See [ctor@Gtk.Picture.new_for_pixbuf] for details. |
697 | * |
698 | * This is a utility function that calls [method@Gtk.Picture.set_paintable]. |
699 | */ |
700 | void |
701 | gtk_picture_set_pixbuf (GtkPicture *self, |
702 | GdkPixbuf *pixbuf) |
703 | { |
704 | GdkTexture *texture; |
705 | |
706 | g_return_if_fail (GTK_IS_PICTURE (self)); |
707 | g_return_if_fail (pixbuf == NULL || GDK_IS_PIXBUF (pixbuf)); |
708 | |
709 | if (pixbuf) |
710 | texture = gdk_texture_new_for_pixbuf (pixbuf); |
711 | else |
712 | texture = NULL; |
713 | |
714 | gtk_picture_set_paintable (self, paintable: GDK_PAINTABLE (ptr: texture)); |
715 | |
716 | if (texture) |
717 | g_object_unref (object: texture); |
718 | } |
719 | |
720 | static void |
721 | gtk_picture_paintable_invalidate_contents (GdkPaintable *paintable, |
722 | GtkPicture *self) |
723 | { |
724 | gtk_widget_queue_draw (GTK_WIDGET (self)); |
725 | } |
726 | |
727 | static void |
728 | gtk_picture_paintable_invalidate_size (GdkPaintable *paintable, |
729 | GtkPicture *self) |
730 | { |
731 | gtk_widget_queue_resize (GTK_WIDGET (self)); |
732 | } |
733 | |
734 | /** |
735 | * gtk_picture_set_paintable: (attributes org.gtk.Method.set_property=paintable) |
736 | * @self: a `GtkPicture` |
737 | * @paintable: (nullable): a `GdkPaintable` |
738 | * |
739 | * Makes @self display the given @paintable. |
740 | * |
741 | * If @paintable is %NULL, nothing will be displayed. |
742 | * |
743 | * See [ctor@Gtk.Picture.new_for_paintable] for details. |
744 | */ |
745 | void |
746 | gtk_picture_set_paintable (GtkPicture *self, |
747 | GdkPaintable *paintable) |
748 | { |
749 | g_return_if_fail (GTK_IS_PICTURE (self)); |
750 | g_return_if_fail (paintable == NULL || GDK_IS_PAINTABLE (paintable)); |
751 | |
752 | if (self->paintable == paintable) |
753 | return; |
754 | |
755 | g_object_freeze_notify (G_OBJECT (self)); |
756 | |
757 | if (paintable) |
758 | g_object_ref (paintable); |
759 | |
760 | if (self->paintable) |
761 | { |
762 | const guint flags = gdk_paintable_get_flags (paintable: self->paintable); |
763 | |
764 | if ((flags & GDK_PAINTABLE_STATIC_CONTENTS) == 0) |
765 | g_signal_handlers_disconnect_by_func (self->paintable, |
766 | gtk_picture_paintable_invalidate_contents, |
767 | self); |
768 | |
769 | if ((flags & GDK_PAINTABLE_STATIC_SIZE) == 0) |
770 | g_signal_handlers_disconnect_by_func (self->paintable, |
771 | gtk_picture_paintable_invalidate_size, |
772 | self); |
773 | |
774 | g_object_unref (object: self->paintable); |
775 | } |
776 | |
777 | self->paintable = paintable; |
778 | |
779 | if (paintable) |
780 | { |
781 | const guint flags = gdk_paintable_get_flags (paintable); |
782 | |
783 | if ((flags & GDK_PAINTABLE_STATIC_CONTENTS) == 0) |
784 | g_signal_connect (paintable, |
785 | "invalidate-contents" , |
786 | G_CALLBACK (gtk_picture_paintable_invalidate_contents), |
787 | self); |
788 | |
789 | if ((flags & GDK_PAINTABLE_STATIC_SIZE) == 0) |
790 | g_signal_connect (paintable, |
791 | "invalidate-size" , |
792 | G_CALLBACK (gtk_picture_paintable_invalidate_size), |
793 | self); |
794 | } |
795 | |
796 | gtk_widget_queue_resize (GTK_WIDGET (self)); |
797 | |
798 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_PAINTABLE]); |
799 | |
800 | g_object_thaw_notify (G_OBJECT (self)); |
801 | } |
802 | |
803 | /** |
804 | * gtk_picture_get_paintable: (attributes org.gtk.Method.get_property=paintable) |
805 | * @self: a `GtkPicture` |
806 | * |
807 | * Gets the `GdkPaintable` being displayed by the `GtkPicture`. |
808 | * |
809 | * Returns: (nullable) (transfer none): the displayed paintable |
810 | */ |
811 | GdkPaintable * |
812 | gtk_picture_get_paintable (GtkPicture *self) |
813 | { |
814 | g_return_val_if_fail (GTK_IS_PICTURE (self), NULL); |
815 | |
816 | return self->paintable; |
817 | } |
818 | |
819 | /** |
820 | * gtk_picture_set_keep_aspect_ratio: (attributes org.gtk.Method.set_property=keep-aspect-ratio) |
821 | * @self: a `GtkPicture` |
822 | * @keep_aspect_ratio: whether to keep aspect ratio |
823 | * |
824 | * If set to %TRUE, the @self will render its contents according to |
825 | * their aspect ratio. |
826 | * |
827 | * That means that empty space may show up at the top/bottom or |
828 | * left/right of @self. |
829 | * |
830 | * If set to %FALSE or if the contents provide no aspect ratio, |
831 | * the contents will be stretched over the picture's whole area. |
832 | */ |
833 | void |
834 | gtk_picture_set_keep_aspect_ratio (GtkPicture *self, |
835 | gboolean keep_aspect_ratio) |
836 | { |
837 | g_return_if_fail (GTK_IS_PICTURE (self)); |
838 | |
839 | if (self->keep_aspect_ratio == keep_aspect_ratio) |
840 | return; |
841 | |
842 | self->keep_aspect_ratio = keep_aspect_ratio; |
843 | |
844 | gtk_widget_queue_draw (GTK_WIDGET (self)); |
845 | |
846 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_KEEP_ASPECT_RATIO]); |
847 | } |
848 | |
849 | /** |
850 | * gtk_picture_get_keep_aspect_ratio: (attributes org.gtk.Method.get_property=keep-aspect-ratio) |
851 | * @self: a `GtkPicture` |
852 | * |
853 | * Returns whether the `GtkPicture` preserves its contents aspect ratio. |
854 | * |
855 | * Returns: %TRUE if the self tries to keep the contents' aspect ratio |
856 | */ |
857 | gboolean |
858 | gtk_picture_get_keep_aspect_ratio (GtkPicture *self) |
859 | { |
860 | g_return_val_if_fail (GTK_IS_PICTURE (self), TRUE); |
861 | |
862 | return self->keep_aspect_ratio; |
863 | } |
864 | |
865 | /** |
866 | * gtk_picture_set_can_shrink: (attributes org.gtk.Method.set_property=can-shrink) |
867 | * @self: a `GtkPicture` |
868 | * @can_shrink: if @self can be made smaller than its contents |
869 | * |
870 | * If set to %TRUE, the @self can be made smaller than its contents. |
871 | * |
872 | * The contents will then be scaled down when rendering. |
873 | * |
874 | * If you want to still force a minimum size manually, consider using |
875 | * [method@Gtk.Widget.set_size_request]. |
876 | * |
877 | * Also of note is that a similar function for growing does not exist |
878 | * because the grow behavior can be controlled via |
879 | * [method@Gtk.Widget.set_halign] and [method@Gtk.Widget.set_valign]. |
880 | */ |
881 | void |
882 | gtk_picture_set_can_shrink (GtkPicture *self, |
883 | gboolean can_shrink) |
884 | { |
885 | g_return_if_fail (GTK_IS_PICTURE (self)); |
886 | |
887 | if (self->can_shrink == can_shrink) |
888 | return; |
889 | |
890 | self->can_shrink = can_shrink; |
891 | |
892 | gtk_widget_queue_resize (GTK_WIDGET (self)); |
893 | |
894 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_CAN_SHRINK]); |
895 | } |
896 | |
897 | /** |
898 | * gtk_picture_get_can_shrink: (attributes org.gtk.Method.get_property=can-shrink) |
899 | * @self: a `GtkPicture` |
900 | * |
901 | * Returns whether the `GtkPicture` respects its contents size. |
902 | * |
903 | * Returns: %TRUE if the picture can be made smaller than its contents |
904 | */ |
905 | gboolean |
906 | gtk_picture_get_can_shrink (GtkPicture *self) |
907 | { |
908 | g_return_val_if_fail (GTK_IS_PICTURE (self), FALSE); |
909 | |
910 | return self->can_shrink; |
911 | } |
912 | |
913 | /** |
914 | * gtk_picture_set_alternative_text: (attributes org.gtk.Method.set_property=alternative-text) |
915 | * @self: a `GtkPicture` |
916 | * @alternative_text: (nullable): a textual description of the contents |
917 | * |
918 | * Sets an alternative textual description for the picture contents. |
919 | * |
920 | * It is equivalent to the "alt" attribute for images on websites. |
921 | * |
922 | * This text will be made available to accessibility tools. |
923 | * |
924 | * If the picture cannot be described textually, set this property to %NULL. |
925 | */ |
926 | void |
927 | gtk_picture_set_alternative_text (GtkPicture *self, |
928 | const char *alternative_text) |
929 | { |
930 | g_return_if_fail (GTK_IS_PICTURE (self)); |
931 | |
932 | if (g_strcmp0 (str1: self->alternative_text, str2: alternative_text) == 0) |
933 | return; |
934 | |
935 | g_free (mem: self->alternative_text); |
936 | self->alternative_text = g_strdup (str: alternative_text); |
937 | |
938 | gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: self), |
939 | first_property: GTK_ACCESSIBLE_PROPERTY_DESCRIPTION, alternative_text, |
940 | -1); |
941 | |
942 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_ALTERNATIVE_TEXT]); |
943 | } |
944 | |
945 | /** |
946 | * gtk_picture_get_alternative_text: (attributes org.gtk.Method.get_property=alternative-text) |
947 | * @self: a `GtkPicture` |
948 | * |
949 | * Gets the alternative textual description of the picture. |
950 | * |
951 | * The returned string will be %NULL if the picture cannot be described textually. |
952 | * |
953 | * Returns: (nullable) (transfer none): the alternative textual description of @self. |
954 | */ |
955 | const char * |
956 | gtk_picture_get_alternative_text (GtkPicture *self) |
957 | { |
958 | g_return_val_if_fail (GTK_IS_PICTURE (self), NULL); |
959 | |
960 | return self->alternative_text; |
961 | } |
962 | |
963 | |