1/* GTK - The GIMP Toolkit
2 * Copyright (C) 2011 Red Hat, Inc.
3 *
4 * Authors: Cosimo Cecchi <cosimoc@gnome.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "config.h"
21
22#include "gtkiconhelperprivate.h"
23
24#include <math.h>
25
26#include "gtkcssenumvalueprivate.h"
27#include "gtkcssnumbervalueprivate.h"
28#include "gtkcssstyleprivate.h"
29#include "gtkcsstransientnodeprivate.h"
30#include "gtkiconthemeprivate.h"
31#include "gtkrendericonprivate.h"
32#include "gtkscalerprivate.h"
33#include "gtksnapshot.h"
34#include "gtkwidgetprivate.h"
35#include "gdk/gdkprofilerprivate.h"
36
37struct _GtkIconHelper
38{
39 GObject parent_instance;
40
41 GtkImageDefinition *def;
42
43 int pixel_size;
44
45 guint use_fallback : 1;
46 guint texture_is_symbolic : 1;
47
48 GtkWidget *owner;
49 GtkCssNode *node;
50 GdkPaintable *paintable;
51};
52
53static GtkIconLookupFlags
54get_icon_lookup_flags (GtkIconHelper *self,
55 GtkCssStyle *style)
56{
57 GtkIconLookupFlags flags;
58 GtkCssIconStyle icon_style;
59
60 flags = 0;
61
62 icon_style = _gtk_css_icon_style_value_get (value: style->icon->icon_style);
63
64 switch (icon_style)
65 {
66 case GTK_CSS_ICON_STYLE_REGULAR:
67 flags |= GTK_ICON_LOOKUP_FORCE_REGULAR;
68 break;
69 case GTK_CSS_ICON_STYLE_SYMBOLIC:
70 flags |= GTK_ICON_LOOKUP_FORCE_SYMBOLIC;
71 break;
72 case GTK_CSS_ICON_STYLE_REQUESTED:
73 break;
74 default:
75 g_assert_not_reached ();
76 return 0;
77 }
78
79 return flags;
80}
81
82static GdkPaintable *
83ensure_paintable_for_gicon (GtkIconHelper *self,
84 GtkCssStyle *style,
85 GtkTextDirection dir,
86 int scale,
87 gboolean preload,
88 GIcon *gicon,
89 gboolean *symbolic)
90{
91 GtkIconTheme *icon_theme;
92 int width, height;
93 GtkIconPaintable *icon;
94 GtkIconLookupFlags flags;
95
96 icon_theme = gtk_icon_theme_get_for_display (display: gtk_widget_get_display (widget: self->owner));
97 flags = get_icon_lookup_flags (self, style);
98 if (preload)
99 flags |= GTK_ICON_LOOKUP_PRELOAD;
100
101 width = height = gtk_icon_helper_get_size (self);
102
103 icon = gtk_icon_theme_lookup_by_gicon (self: icon_theme,
104 icon: gicon,
105 MIN (width, height),
106 scale: dir,
107 direction: scale, flags);
108
109 *symbolic = gtk_icon_paintable_is_symbolic (self: icon);
110 return GDK_PAINTABLE (ptr: icon);
111}
112
113static GdkPaintable *
114gtk_icon_helper_load_paintable (GtkIconHelper *self,
115 gboolean preload,
116 gboolean *out_symbolic)
117{
118 GdkPaintable *paintable;
119 GIcon *gicon;
120 gboolean symbolic;
121
122 switch (gtk_image_definition_get_storage_type (def: self->def))
123 {
124 case GTK_IMAGE_PAINTABLE:
125 paintable = g_object_ref (gtk_image_definition_get_paintable (self->def));
126 symbolic = FALSE;
127 break;
128
129 case GTK_IMAGE_ICON_NAME:
130 if (self->use_fallback)
131 gicon = g_themed_icon_new_with_default_fallbacks (iconname: gtk_image_definition_get_icon_name (def: self->def));
132 else
133 gicon = g_themed_icon_new (iconname: gtk_image_definition_get_icon_name (def: self->def));
134 paintable = ensure_paintable_for_gicon (self,
135 style: gtk_css_node_get_style (cssnode: self->node),
136 dir: gtk_widget_get_scale_factor (widget: self->owner),
137 scale: gtk_widget_get_direction (widget: self->owner),
138 preload,
139 gicon,
140 symbolic: &symbolic);
141 g_object_unref (object: gicon);
142 break;
143
144 case GTK_IMAGE_GICON:
145 paintable = ensure_paintable_for_gicon (self,
146 style: gtk_css_node_get_style (cssnode: self->node),
147 dir: gtk_widget_get_scale_factor (widget: self->owner),
148 scale: gtk_widget_get_direction (widget: self->owner),
149 preload,
150 gicon: gtk_image_definition_get_gicon (def: self->def),
151 symbolic: &symbolic);
152 break;
153
154 case GTK_IMAGE_EMPTY:
155 default:
156 paintable = NULL;
157 symbolic = FALSE;
158 break;
159 }
160
161 *out_symbolic = symbolic;
162
163 return paintable;
164}
165
166static void
167gtk_icon_helper_ensure_paintable (GtkIconHelper *self, gboolean preload)
168{
169 gboolean symbolic;
170
171 if (self->paintable)
172 return;
173
174 self->paintable = gtk_icon_helper_load_paintable (self, preload, out_symbolic: &symbolic);
175 self->texture_is_symbolic = symbolic;
176}
177
178static void
179gtk_icon_helper_paintable_snapshot (GdkPaintable *paintable,
180 GdkSnapshot *snapshot,
181 double width,
182 double height)
183{
184 GtkIconHelper *self = GTK_ICON_HELPER (ptr: paintable);
185 GtkCssStyle *style;
186
187 style = gtk_css_node_get_style (cssnode: self->node);
188
189 gtk_icon_helper_ensure_paintable (self, FALSE);
190 if (self->paintable == NULL)
191 return;
192
193 switch (gtk_image_definition_get_storage_type (def: self->def))
194 {
195 case GTK_IMAGE_ICON_NAME:
196 case GTK_IMAGE_GICON:
197 {
198 double x, y, w, h;
199
200 /* Never scale up icons. */
201 w = gdk_paintable_get_intrinsic_width (paintable: self->paintable);
202 h = gdk_paintable_get_intrinsic_height (paintable: self->paintable);
203 w = MIN (w, width);
204 h = MIN (h, height);
205 x = (width - w) / 2;
206 y = (height - h) / 2;
207
208 if (w == 0 || h == 0)
209 return;
210
211 if (x != 0 || y != 0)
212 {
213 gtk_snapshot_save (snapshot);
214 gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT (x, y));
215 gtk_css_style_snapshot_icon_paintable (style,
216 snapshot,
217 paintable: self->paintable,
218 width: w, height: h);
219 gtk_snapshot_restore (snapshot);
220 }
221 else
222 {
223 gtk_css_style_snapshot_icon_paintable (style,
224 snapshot,
225 paintable: self->paintable,
226 width: w, height: h);
227
228 }
229
230 }
231 break;
232
233 case GTK_IMAGE_EMPTY:
234 break;
235
236 case GTK_IMAGE_PAINTABLE:
237 default:
238 {
239 double image_ratio = (double) width / height;
240 double ratio;
241 double x, y, w, h;
242
243 if (self->paintable == NULL)
244 break;
245
246 ratio = gdk_paintable_get_intrinsic_aspect_ratio (paintable: self->paintable);
247 if (ratio == 0)
248 {
249 w = width;
250 h = height;
251 }
252 else if (ratio > image_ratio)
253 {
254 w = width;
255 h = width / ratio;
256 }
257 else
258 {
259 w = height * ratio;
260 h = height;
261 }
262
263 x = floor (x: width - ceil (x: w)) / 2;
264 y = floor (x: height - ceil (x: h)) / 2;
265
266 if (x != 0 || y != 0)
267 {
268 gtk_snapshot_save (snapshot);
269 gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT (x, y));
270 gtk_css_style_snapshot_icon_paintable (style,
271 snapshot,
272 paintable: self->paintable,
273 width: w, height: h);
274 gtk_snapshot_restore (snapshot);
275 }
276 else
277 {
278 gtk_css_style_snapshot_icon_paintable (style,
279 snapshot,
280 paintable: self->paintable,
281 width: w, height: h);
282 }
283 }
284 break;
285 }
286}
287
288static GdkPaintable *
289gtk_icon_helper_paintable_get_current_image (GdkPaintable *paintable)
290{
291 GtkIconHelper *self = GTK_ICON_HELPER (ptr: paintable);
292
293 gtk_icon_helper_ensure_paintable (self, FALSE);
294 if (self->paintable == NULL)
295 return NULL;
296
297 return gdk_paintable_get_current_image (paintable: self->paintable);
298}
299
300static int
301gtk_icon_helper_paintable_get_intrinsic_width (GdkPaintable *paintable)
302{
303 GtkIconHelper *self = GTK_ICON_HELPER (ptr: paintable);
304
305 return gtk_icon_helper_get_size (self);
306}
307
308static int
309gtk_icon_helper_paintable_get_intrinsic_height (GdkPaintable *paintable)
310{
311 GtkIconHelper *self = GTK_ICON_HELPER (ptr: paintable);
312
313 return gtk_icon_helper_get_size (self);
314}
315
316static double gtk_icon_helper_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
317{
318 return 1.0;
319};
320
321static void
322gtk_icon_helper_paintable_init (GdkPaintableInterface *iface)
323{
324 iface->snapshot = gtk_icon_helper_paintable_snapshot;
325 iface->get_current_image = gtk_icon_helper_paintable_get_current_image;
326 iface->get_intrinsic_width = gtk_icon_helper_paintable_get_intrinsic_width;
327 iface->get_intrinsic_height = gtk_icon_helper_paintable_get_intrinsic_height;
328 iface->get_intrinsic_aspect_ratio = gtk_icon_helper_paintable_get_intrinsic_aspect_ratio;
329}
330
331G_DEFINE_TYPE_WITH_CODE (GtkIconHelper, gtk_icon_helper, G_TYPE_OBJECT,
332 G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
333 gtk_icon_helper_paintable_init))
334
335void
336gtk_icon_helper_invalidate (GtkIconHelper *self)
337{
338 g_clear_object (&self->paintable);
339 self->texture_is_symbolic = FALSE;
340
341 if (!GTK_IS_CSS_TRANSIENT_NODE (self->node))
342 gtk_widget_queue_resize (widget: self->owner);
343}
344
345void
346gtk_icon_helper_invalidate_for_change (GtkIconHelper *self,
347 GtkCssStyleChange *change)
348{
349 if (change == NULL ||
350 gtk_css_style_change_affects (change, affects: GTK_CSS_AFFECTS_ICON_TEXTURE |
351 GTK_CSS_AFFECTS_ICON_SIZE))
352 {
353 /* Avoid the queue_resize in gtk_icon_helper_invalidate */
354 g_clear_object (&self->paintable);
355 self->texture_is_symbolic = FALSE;
356 gtk_widget_queue_draw (widget: self->owner);
357 }
358
359 if (change == NULL ||
360 gtk_css_style_change_affects (change, affects: GTK_CSS_AFFECTS_ICON_SIZE))
361 {
362 gtk_widget_queue_resize (widget: self->owner);
363 }
364 else if (gtk_css_style_change_affects (change, affects: GTK_CSS_AFFECTS_ICON_REDRAW) ||
365 (self->texture_is_symbolic &&
366 gtk_css_style_change_affects (change, affects: GTK_CSS_AFFECTS_ICON_REDRAW_SYMBOLIC)))
367 {
368 gtk_widget_queue_draw (widget: self->owner);
369 }
370
371 /* The css size is valid now, preload */
372 gtk_icon_helper_ensure_paintable (self, TRUE);
373}
374
375static void
376gtk_icon_helper_take_definition (GtkIconHelper *self,
377 GtkImageDefinition *def)
378{
379 _gtk_icon_helper_clear (self);
380
381 if (def == NULL)
382 return;
383
384 gtk_image_definition_unref (def: self->def);
385 self->def = def;
386
387 gtk_icon_helper_invalidate (self);
388}
389
390void
391_gtk_icon_helper_clear (GtkIconHelper *self)
392{
393 g_clear_object (&self->paintable);
394 self->texture_is_symbolic = FALSE;
395
396 if (gtk_image_definition_get_storage_type (def: self->def) != GTK_IMAGE_EMPTY)
397 {
398 gtk_image_definition_unref (def: self->def);
399 self->def = gtk_image_definition_new_empty ();
400 gtk_icon_helper_invalidate (self);
401 }
402}
403
404static void
405gtk_icon_helper_finalize (GObject *object)
406{
407 GtkIconHelper *self = GTK_ICON_HELPER (ptr: object);
408
409 _gtk_icon_helper_clear (self);
410 g_signal_handlers_disconnect_by_func (self->owner, G_CALLBACK (gtk_icon_helper_invalidate), self);
411 gtk_image_definition_unref (def: self->def);
412
413 G_OBJECT_CLASS (gtk_icon_helper_parent_class)->finalize (object);
414}
415
416void
417gtk_icon_helper_class_init (GtkIconHelperClass *klass)
418{
419 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
420
421 gobject_class->finalize = gtk_icon_helper_finalize;
422}
423
424void
425gtk_icon_helper_init (GtkIconHelper *self)
426{
427 self->def = gtk_image_definition_new_empty ();
428}
429
430GtkIconHelper *
431gtk_icon_helper_new (GtkCssNode *css_node,
432 GtkWidget *owner)
433{
434 GtkIconHelper *self;
435
436 self = g_object_new (GTK_TYPE_ICON_HELPER, NULL);
437
438 self->pixel_size = -1;
439 self->texture_is_symbolic = FALSE;
440
441 self->node = css_node;
442 self->owner = owner;
443 g_signal_connect_swapped (owner, "direction-changed", G_CALLBACK (gtk_icon_helper_invalidate), self);
444 g_signal_connect_swapped (owner, "notify::scale-factor", G_CALLBACK (gtk_icon_helper_invalidate), self);
445
446 return self;
447}
448
449int
450gtk_icon_helper_get_size (GtkIconHelper *self)
451{
452 GtkCssStyle *style;
453
454 if (self->pixel_size != -1)
455 return self->pixel_size;
456
457 style = gtk_css_node_get_style (cssnode: self->node);
458 return _gtk_css_number_value_get (number: style->icon->icon_size, one_hundred_percent: 100);
459}
460
461void
462_gtk_icon_helper_set_definition (GtkIconHelper *self,
463 GtkImageDefinition *def)
464{
465 if (def)
466 gtk_icon_helper_take_definition (self, def: gtk_image_definition_ref (def));
467 else
468 _gtk_icon_helper_clear (self);
469}
470
471void
472_gtk_icon_helper_set_gicon (GtkIconHelper *self,
473 GIcon *gicon)
474{
475 gtk_icon_helper_take_definition (self, def: gtk_image_definition_new_gicon (gicon));
476}
477
478void
479_gtk_icon_helper_set_icon_name (GtkIconHelper *self,
480 const char *icon_name)
481{
482 gtk_icon_helper_take_definition (self, def: gtk_image_definition_new_icon_name (icon_name));
483}
484
485void
486_gtk_icon_helper_set_paintable (GtkIconHelper *self,
487 GdkPaintable *paintable)
488{
489 gtk_icon_helper_take_definition (self, def: gtk_image_definition_new_paintable (paintable));
490}
491
492gboolean
493_gtk_icon_helper_set_pixel_size (GtkIconHelper *self,
494 int pixel_size)
495{
496 if (self->pixel_size != pixel_size)
497 {
498 self->pixel_size = pixel_size;
499 gtk_icon_helper_invalidate (self);
500 return TRUE;
501 }
502 return FALSE;
503}
504
505gboolean
506_gtk_icon_helper_set_use_fallback (GtkIconHelper *self,
507 gboolean use_fallback)
508{
509 if (self->use_fallback != use_fallback)
510 {
511 self->use_fallback = use_fallback;
512 gtk_icon_helper_invalidate (self);
513 return TRUE;
514 }
515 return FALSE;
516}
517
518GtkImageType
519_gtk_icon_helper_get_storage_type (GtkIconHelper *self)
520{
521 return gtk_image_definition_get_storage_type (def: self->def);
522}
523
524gboolean
525_gtk_icon_helper_get_use_fallback (GtkIconHelper *self)
526{
527 return self->use_fallback;
528}
529
530int
531_gtk_icon_helper_get_pixel_size (GtkIconHelper *self)
532{
533 return self->pixel_size;
534}
535
536GtkImageDefinition *
537gtk_icon_helper_get_definition (GtkIconHelper *self)
538{
539 return self->def;
540}
541
542GIcon *
543_gtk_icon_helper_peek_gicon (GtkIconHelper *self)
544{
545 return gtk_image_definition_get_gicon (def: self->def);
546}
547
548GdkPaintable *
549_gtk_icon_helper_peek_paintable (GtkIconHelper *self)
550{
551 return gtk_image_definition_get_paintable (def: self->def);
552}
553
554const char *
555_gtk_icon_helper_get_icon_name (GtkIconHelper *self)
556{
557 return gtk_image_definition_get_icon_name (def: self->def);
558}
559
560gboolean
561_gtk_icon_helper_get_is_empty (GtkIconHelper *self)
562{
563 return gtk_image_definition_get_storage_type (def: self->def) == GTK_IMAGE_EMPTY;
564}
565
566void
567gtk_icon_size_set_style_classes (GtkCssNode *cssnode,
568 GtkIconSize icon_size)
569{
570 struct {
571 GtkIconSize icon_size;
572 const char *class_name;
573 } class_names[] = {
574 { GTK_ICON_SIZE_NORMAL, "normal-icons" },
575 { GTK_ICON_SIZE_LARGE, "large-icons" }
576 };
577 guint i;
578
579 for (i = 0; i < G_N_ELEMENTS (class_names); i++)
580 {
581 if (icon_size == class_names[i].icon_size)
582 gtk_css_node_add_class (cssnode, style_class: g_quark_from_static_string (string: class_names[i].class_name));
583 else
584 gtk_css_node_remove_class (cssnode, style_class: g_quark_from_static_string (string: class_names[i].class_name));
585 }
586}
587

source code of gtk/gtk/gtkiconhelper.c