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
84enum
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
95struct _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
107struct _GtkPictureClass
108{
109 GtkWidgetClass parent_class;
110};
111
112static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
113
114G_DEFINE_TYPE (GtkPicture, gtk_picture, GTK_TYPE_WIDGET)
115
116static void
117gtk_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
161static GtkSizeRequestMode
162gtk_picture_get_request_mode (GtkWidget *widget)
163{
164 return GTK_SIZE_REQUEST_HEIGHT_FOR_WIDTH;
165}
166
167static void
168gtk_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
226static void
227gtk_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
262static void
263gtk_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
298static void
299gtk_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
311static void
312gtk_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
392static void
393gtk_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 */
406GtkWidget*
407gtk_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 */
423GtkWidget*
424gtk_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 */
446GtkWidget*
447gtk_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 */
482GtkWidget*
483gtk_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 */
503GtkWidget*
504gtk_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 */
533GtkWidget *
534gtk_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 */
573void
574gtk_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 */
612GFile *
613gtk_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 */
629void
630gtk_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 */
658void
659gtk_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 */
700void
701gtk_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
720static void
721gtk_picture_paintable_invalidate_contents (GdkPaintable *paintable,
722 GtkPicture *self)
723{
724 gtk_widget_queue_draw (GTK_WIDGET (self));
725}
726
727static void
728gtk_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 */
745void
746gtk_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 */
811GdkPaintable *
812gtk_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 */
833void
834gtk_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 */
857gboolean
858gtk_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 */
881void
882gtk_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 */
905gboolean
906gtk_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 */
926void
927gtk_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 */
955const char *
956gtk_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

source code of gtk/gtk/gtkpicture.c