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 | |
37 | struct _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 | |
53 | static GtkIconLookupFlags |
54 | get_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 | |
82 | static GdkPaintable * |
83 | ensure_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 | |
113 | static GdkPaintable * |
114 | gtk_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 | |
166 | static void |
167 | gtk_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 | |
178 | static void |
179 | gtk_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 | |
288 | static GdkPaintable * |
289 | gtk_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 | |
300 | static int |
301 | gtk_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 | |
308 | static int |
309 | gtk_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 | |
316 | static double gtk_icon_helper_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable) |
317 | { |
318 | return 1.0; |
319 | }; |
320 | |
321 | static void |
322 | gtk_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 | |
331 | G_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 | |
335 | void |
336 | gtk_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 | |
345 | void |
346 | gtk_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 | |
375 | static void |
376 | gtk_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 | |
390 | void |
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 | |
404 | static void |
405 | gtk_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 | |
416 | void |
417 | gtk_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 | |
424 | void |
425 | gtk_icon_helper_init (GtkIconHelper *self) |
426 | { |
427 | self->def = gtk_image_definition_new_empty (); |
428 | } |
429 | |
430 | GtkIconHelper * |
431 | gtk_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 | |
449 | int |
450 | gtk_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 | |
461 | void |
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 | |
471 | void |
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 | |
478 | void |
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 | |
485 | void |
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 | |
492 | gboolean |
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 | |
505 | gboolean |
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 | |
518 | GtkImageType |
519 | _gtk_icon_helper_get_storage_type (GtkIconHelper *self) |
520 | { |
521 | return gtk_image_definition_get_storage_type (def: self->def); |
522 | } |
523 | |
524 | gboolean |
525 | _gtk_icon_helper_get_use_fallback (GtkIconHelper *self) |
526 | { |
527 | return self->use_fallback; |
528 | } |
529 | |
530 | int |
531 | _gtk_icon_helper_get_pixel_size (GtkIconHelper *self) |
532 | { |
533 | return self->pixel_size; |
534 | } |
535 | |
536 | GtkImageDefinition * |
537 | gtk_icon_helper_get_definition (GtkIconHelper *self) |
538 | { |
539 | return self->def; |
540 | } |
541 | |
542 | GIcon * |
543 | _gtk_icon_helper_peek_gicon (GtkIconHelper *self) |
544 | { |
545 | return gtk_image_definition_get_gicon (def: self->def); |
546 | } |
547 | |
548 | GdkPaintable * |
549 | _gtk_icon_helper_peek_paintable (GtkIconHelper *self) |
550 | { |
551 | return gtk_image_definition_get_paintable (def: self->def); |
552 | } |
553 | |
554 | const char * |
555 | _gtk_icon_helper_get_icon_name (GtkIconHelper *self) |
556 | { |
557 | return gtk_image_definition_get_icon_name (def: self->def); |
558 | } |
559 | |
560 | gboolean |
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 | |
566 | void |
567 | gtk_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 | |