1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 /*
3 * st-icon.c: icon widget
4 *
5 * Copyright 2009, 2010 Intel Corporation.
6 * Copyright 2010 Red Hat, Inc.
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms and conditions of the GNU Lesser General Public License,
10 * version 2.1, as published by the Free Software Foundation.
11 *
12 * This program is distributed in the hope it will be useful, but WITHOUT ANY
13 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 /**
22 * SECTION:st-icon
23 * @short_description: a simple styled icon actor
24 *
25 * #StIcon is a simple styled texture actor that displays an image from
26 * a stylesheet.
27 */
28
29 #include "st-enum-types.h"
30 #include "st-icon.h"
31 #include "st-texture-cache.h"
32 #include "st-private.h"
33
34 enum
35 {
36 PROP_0,
37
38 PROP_GICON,
39 PROP_ICON_NAME,
40 PROP_ICON_SIZE
41 };
42
43 G_DEFINE_TYPE (StIcon, st_icon, ST_TYPE_WIDGET)
44
45 #define ST_ICON_GET_PRIVATE(obj) \
46 (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_ICON, StIconPrivate))
47
48 struct _StIconPrivate
49 {
50 ClutterActor *icon_texture;
51 ClutterActor *pending_texture;
52 guint opacity_handler_id;
53
54 GIcon *gicon;
55 gint prop_icon_size; /* icon size set as property */
56 gint theme_icon_size; /* icon size from theme node */
57 gint icon_size; /* icon size we are using */
58
59 CoglHandle shadow_material;
60 float shadow_width;
61 float shadow_height;
62 StShadow *shadow_spec;
63 };
64
65 static void st_icon_update (StIcon *icon);
66 static gboolean st_icon_update_icon_size (StIcon *icon);
67
68 #define DEFAULT_ICON_SIZE 48
69
70 static void
71 st_icon_set_property (GObject *gobject,
72 guint prop_id,
73 const GValue *value,
74 GParamSpec *pspec)
75 {
76 StIcon *icon = ST_ICON (gobject);
77
78 switch (prop_id)
79 {
80 case PROP_GICON:
81 st_icon_set_gicon (icon, g_value_get_object (value));
82 break;
83
84 case PROP_ICON_NAME:
85 st_icon_set_icon_name (icon, g_value_get_string (value));
86 break;
87
88 case PROP_ICON_SIZE:
89 st_icon_set_icon_size (icon, g_value_get_int (value));
90 break;
91
92 default:
93 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
94 break;
95 }
96 }
97
98 static void
99 st_icon_get_property (GObject *gobject,
100 guint prop_id,
101 GValue *value,
102 GParamSpec *pspec)
103 {
104 StIcon *icon = ST_ICON (gobject);
105
106 switch (prop_id)
107 {
108 case PROP_GICON:
109 g_value_set_object (value, icon->priv->gicon);
110 break;
111
112 case PROP_ICON_NAME:
113 g_value_set_string (value, st_icon_get_icon_name (icon));
114 break;
115
116 case PROP_ICON_SIZE:
117 g_value_set_int (value, st_icon_get_icon_size (icon));
118 break;
119
120 default:
121 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
122 break;
123 }
124 }
125
126 static void
127 st_icon_dispose (GObject *gobject)
128 {
129 StIconPrivate *priv = ST_ICON (gobject)->priv;
130
131 if (priv->icon_texture)
132 {
133 clutter_actor_destroy (priv->icon_texture);
134 priv->icon_texture = NULL;
135 }
136
137 if (priv->pending_texture)
138 {
139 clutter_actor_destroy (priv->pending_texture);
140 g_object_unref (priv->pending_texture);
141 priv->pending_texture = NULL;
142 }
143
144 if (priv->gicon)
145 {
146 g_object_unref (priv->gicon);
147 priv->gicon = NULL;
148 }
149
150 if (priv->shadow_material)
151 {
152 cogl_handle_unref (priv->shadow_material);
153 priv->shadow_material = COGL_INVALID_HANDLE;
154 }
155
156 if (priv->shadow_spec)
157 {
158 st_shadow_unref (priv->shadow_spec);
159 priv->shadow_spec = NULL;
160 }
161
162 G_OBJECT_CLASS (st_icon_parent_class)->dispose (gobject);
163 }
164
165 static void
166 st_icon_get_preferred_height (ClutterActor *actor,
167 gfloat for_width,
168 gfloat *min_height_p,
169 gfloat *nat_height_p)
170 {
171 StIconPrivate *priv = ST_ICON (actor)->priv;
172 StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
173
174 if (min_height_p)
175 *min_height_p = priv->icon_size;
176
177 if (nat_height_p)
178 *nat_height_p = priv->icon_size;
179
180 st_theme_node_adjust_preferred_height (theme_node, min_height_p, nat_height_p);
181 }
182
183 static void
184 st_icon_get_preferred_width (ClutterActor *actor,
185 gfloat for_height,
186 gfloat *min_width_p,
187 gfloat *nat_width_p)
188 {
189 StIconPrivate *priv = ST_ICON (actor)->priv;
190 StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
191
192 if (min_width_p)
193 *min_width_p = priv->icon_size;
194
195 if (nat_width_p)
196 *nat_width_p = priv->icon_size;
197
198 st_theme_node_adjust_preferred_width (theme_node, min_width_p, nat_width_p);
199 }
200
201 static void
202 st_icon_allocate (ClutterActor *actor,
203 const ClutterActorBox *box,
204 ClutterAllocationFlags flags)
205 {
206 StIconPrivate *priv = ST_ICON (actor)->priv;
207 StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor));
208
209 clutter_actor_set_allocation (actor, box, flags);
210
211 if (priv->icon_texture)
212 {
213 ClutterActorBox content_box;
214
215 st_theme_node_get_content_box (theme_node, box, &content_box);
216
217 /* Center the texture in the allocation; scaling up the icon from the size
218 * we loaded it at is just a bad idea and probably accidental. Main downside
219 * of doing this is that it may not be obvious that they have to turn off
220 * fill to align the icon non-centered in the parent container.
221 *
222 * We don't use clutter_actor_allocate_align_fill() for a bit of efficiency
223 * and because we expect to get rid of the child actor in favor of a
224 * CoglTexture in the future.
225 */
226 content_box.x1 = (int)(0.5 + content_box.x1 + (content_box.x2 - content_box.x1 - priv->icon_size) / 2.);
227 content_box.x2 = content_box.x1 + priv->icon_size;
228 content_box.y1 = (int)(0.5 + content_box.y1 + (content_box.y2 - content_box.y1 - priv->icon_size) / 2.);
229 content_box.y2 = content_box.y1 + priv->icon_size;
230
231 clutter_actor_allocate (priv->icon_texture, &content_box, flags);
232 }
233 }
234
235 static void
236 st_icon_paint (ClutterActor *actor)
237 {
238 StIconPrivate *priv = ST_ICON (actor)->priv;
239
240 st_widget_paint_background (ST_WIDGET (actor));
241
242 if (priv->icon_texture)
243 {
244 if (priv->shadow_material)
245 {
246 ClutterActorBox allocation;
247 float width, height;
248
249 clutter_actor_get_allocation_box (priv->icon_texture, &allocation);
250 clutter_actor_box_get_size (&allocation, &width, &height);
251
252 allocation.x1 = (width - priv->shadow_width) / 2;
253 allocation.y1 = (height - priv->shadow_height) / 2;
254 allocation.x2 = allocation.x1 + priv->shadow_width;
255 allocation.y2 = allocation.y1 + priv->shadow_height;
256
257 _st_paint_shadow_with_opacity (priv->shadow_spec,
258 priv->shadow_material,
259 &allocation,
260 clutter_actor_get_paint_opacity (priv->icon_texture));
261 }
262
263 clutter_actor_paint (priv->icon_texture);
264 }
265 }
266
267 static void
268 st_icon_style_changed (StWidget *widget)
269 {
270 StIcon *self = ST_ICON (widget);
271 StThemeNode *theme_node = st_widget_get_theme_node (widget);
272 StIconPrivate *priv = self->priv;
273
274 if (priv->shadow_spec)
275 {
276 st_shadow_unref (priv->shadow_spec);
277 priv->shadow_spec = NULL;
278 }
279
280 if (priv->shadow_material)
281 {
282 cogl_handle_unref (priv->shadow_material);
283 priv->shadow_material = COGL_INVALID_HANDLE;
284 }
285
286 priv->shadow_spec = st_theme_node_get_shadow (theme_node, "icon-shadow");
287
288 if (priv->shadow_spec && priv->shadow_spec->inset)
289 {
290 g_warning ("The icon-shadow property does not support inset shadows");
291 st_shadow_unref (priv->shadow_spec);
292 priv->shadow_spec = NULL;
293 }
294
295 priv->theme_icon_size = (int)(0.5 + st_theme_node_get_length (theme_node, "icon-size"));
296 st_icon_update_icon_size (self);
297 st_icon_update (self);
298 }
299
300 static void
301 st_icon_class_init (StIconClass *klass)
302 {
303 GParamSpec *pspec;
304
305 GObjectClass *object_class = G_OBJECT_CLASS (klass);
306 ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
307 StWidgetClass *widget_class = ST_WIDGET_CLASS (klass);
308
309 g_type_class_add_private (klass, sizeof (StIconPrivate));
310
311 object_class->get_property = st_icon_get_property;
312 object_class->set_property = st_icon_set_property;
313 object_class->dispose = st_icon_dispose;
314
315 actor_class->get_preferred_height = st_icon_get_preferred_height;
316 actor_class->get_preferred_width = st_icon_get_preferred_width;
317 actor_class->allocate = st_icon_allocate;
318 actor_class->paint = st_icon_paint;
319
320 widget_class->style_changed = st_icon_style_changed;
321
322 pspec = g_param_spec_object ("gicon",
323 "GIcon",
324 "The GIcon shown by this icon actor",
325 G_TYPE_ICON,
326 ST_PARAM_READWRITE);
327 g_object_class_install_property (object_class, PROP_GICON, pspec);
328
329 pspec = g_param_spec_string ("icon-name",
330 "Icon name",
331 "An icon name",
332 NULL, ST_PARAM_READWRITE | G_PARAM_DEPRECATED);
333 g_object_class_install_property (object_class, PROP_ICON_NAME, pspec);
334
335 pspec = g_param_spec_int ("icon-size",
336 "Icon size",
337 "The size if the icon, if positive. Otherwise the size will be derived from the current style",
338 -1, G_MAXINT, -1,
339 ST_PARAM_READWRITE);
340 g_object_class_install_property (object_class, PROP_ICON_SIZE, pspec);
341 }
342
343 static void
344 st_icon_init (StIcon *self)
345 {
346 self->priv = ST_ICON_GET_PRIVATE (self);
347
348 self->priv->icon_size = DEFAULT_ICON_SIZE;
349 self->priv->prop_icon_size = -1;
350
351 self->priv->shadow_material = COGL_INVALID_HANDLE;
352 self->priv->shadow_width = -1;
353 self->priv->shadow_height = -1;
354 }
355
356 static void
357 st_icon_update_shadow_material (StIcon *icon)
358 {
359 StIconPrivate *priv = icon->priv;
360
361 if (priv->shadow_material)
362 {
363 cogl_handle_unref (priv->shadow_material);
364 priv->shadow_material = COGL_INVALID_HANDLE;
365 }
366
367 if (priv->shadow_spec)
368 {
369 CoglHandle material;
370 gint width, height;
371
372 clutter_texture_get_base_size (CLUTTER_TEXTURE (priv->icon_texture),
'clutter_texture_get_base_size' is deprecated (declared at /usr/include/clutter-1.0/clutter/deprecated/clutter-texture.h:69)
(emitted by gcc)
373 &width, &height);
374
375 material = _st_create_shadow_material_from_actor (priv->shadow_spec,
376 priv->icon_texture);
377 priv->shadow_material = material;
378 priv->shadow_width = width;
379 priv->shadow_height = height;
380 }
381 }
382
383 static void
384 on_pixbuf_changed (ClutterTexture *texture,
385 StIcon *icon)
386 {
387 st_icon_update_shadow_material (icon);
388 }
389
390 static void
391 st_icon_finish_update (StIcon *icon)
392 {
393 StIconPrivate *priv = icon->priv;
394
395 if (priv->icon_texture)
396 {
397 clutter_actor_destroy (priv->icon_texture);
398 priv->icon_texture = NULL;
399 }
400
401 if (priv->pending_texture)
402 {
403 priv->icon_texture = priv->pending_texture;
404 priv->pending_texture = NULL;
405 clutter_actor_add_child (CLUTTER_ACTOR (icon), priv->icon_texture);
406
407 /* Remove the temporary ref we added */
408 g_object_unref (priv->icon_texture);
409
410 st_icon_update_shadow_material (icon);
411
412 /* "pixbuf-change" is actually a misnomer for "texture-changed" */
413 g_signal_connect (priv->icon_texture, "pixbuf-change",
414 G_CALLBACK (on_pixbuf_changed), icon);
415 }
416 }
417
418 static void
419 opacity_changed_cb (GObject *object,
420 GParamSpec *pspec,
421 gpointer user_data)
422 {
423 StIcon *icon = user_data;
424 StIconPrivate *priv = icon->priv;
425
426 g_signal_handler_disconnect (priv->pending_texture, priv->opacity_handler_id);
427 priv->opacity_handler_id = 0;
428
429 st_icon_finish_update (icon);
430 }
431
432 static void
433 st_icon_update (StIcon *icon)
434 {
435 StIconPrivate *priv = icon->priv;
436 StThemeNode *theme_node;
437 StTextureCache *cache;
438
439 if (priv->pending_texture)
440 {
441 clutter_actor_destroy (priv->pending_texture);
442 g_object_unref (priv->pending_texture);
443 priv->pending_texture = NULL;
444 priv->opacity_handler_id = 0;
445 }
446
447 theme_node = st_widget_peek_theme_node (ST_WIDGET (icon));
448 if (theme_node == NULL)
449 return;
450
451 cache = st_texture_cache_get_default ();
452 if (priv->gicon)
453 {
454 priv->pending_texture = st_texture_cache_load_gicon (cache,
455 theme_node,
456 priv->gicon,
457 priv->icon_size);
458 }
459
460 if (priv->pending_texture)
461 {
462 g_object_ref_sink (priv->pending_texture);
463
464 if (clutter_actor_get_opacity (priv->pending_texture) != 0 || priv->icon_texture == NULL)
465 {
466 /* This icon is ready for showing, or nothing else is already showing */
467 st_icon_finish_update (icon);
468 }
469 else
470 {
471 /* Will be shown when fully loaded */
472 priv->opacity_handler_id = g_signal_connect (priv->pending_texture, "notify::opacity", G_CALLBACK (opacity_changed_cb), icon);
473 }
474 }
475 else if (priv->icon_texture)
476 {
477 clutter_actor_destroy (priv->icon_texture);
478 priv->icon_texture = NULL;
479 }
480 }
481
482 static gboolean
483 st_icon_update_icon_size (StIcon *icon)
484 {
485 StIconPrivate *priv = icon->priv;
486 int new_size;
487
488 if (priv->prop_icon_size > 0)
489 new_size = priv->prop_icon_size;
490 else if (priv->theme_icon_size > 0)
491 new_size = priv->theme_icon_size;
492 else
493 new_size = DEFAULT_ICON_SIZE;
494
495 if (new_size != priv->icon_size)
496 {
497 clutter_actor_queue_relayout (CLUTTER_ACTOR (icon));
498 priv->icon_size = new_size;
499 return TRUE;
500 }
501 else
502 return FALSE;
503 }
504
505 /**
506 * st_icon_new:
507 *
508 * Create a newly allocated #StIcon
509 *
510 * Returns: A newly allocated #StIcon
511 */
512 ClutterActor *
513 st_icon_new (void)
514 {
515 return g_object_new (ST_TYPE_ICON, NULL);
516 }
517
518 const gchar *
519 st_icon_get_icon_name (StIcon *icon)
520 {
521 StIconPrivate *priv;
522
523 g_return_val_if_fail (ST_IS_ICON (icon), NULL);
524
525 priv = icon->priv;
526
527 if (priv->gicon && G_IS_THEMED_ICON (priv->gicon))
528 return g_themed_icon_get_names (G_THEMED_ICON (priv->gicon)) [0];
529 else
530 return NULL;
531 }
532
533 void
534 st_icon_set_icon_name (StIcon *icon,
535 const gchar *icon_name)
536 {
537 StIconPrivate *priv;
538
539 g_return_if_fail (ST_IS_ICON (icon));
540
541 priv = icon->priv;
542
543 if (priv->gicon)
544 g_object_unref (priv->gicon);
545
546 if (icon_name)
547 priv->gicon = g_themed_icon_new_with_default_fallbacks (icon_name);
548 else
549 priv->gicon = NULL;
550
551 g_object_notify (G_OBJECT (icon), "gicon");
552 g_object_notify (G_OBJECT (icon), "icon-name");
553
554 st_icon_update (icon);
555 }
556
557 /**
558 * st_icon_get_gicon:
559 * @icon: an icon
560 *
561 * Return value: (transfer none): the override GIcon, if set, or NULL
562 */
563 GIcon *
564 st_icon_get_gicon (StIcon *icon)
565 {
566 g_return_val_if_fail (ST_IS_ICON (icon), NULL);
567
568 return icon->priv->gicon;
569 }
570
571 /**
572 * st_icon_set_gicon:
573 * @icon: an icon
574 * @gicon: (allow-none): a #GIcon to override :icon-name
575 */
576 void
577 st_icon_set_gicon (StIcon *icon, GIcon *gicon)
578 {
579 g_return_if_fail (ST_IS_ICON (icon));
580 g_return_if_fail (gicon == NULL || G_IS_ICON (gicon));
581
582 if (icon->priv->gicon == gicon) /* do nothing */
583 return;
584
585 if (icon->priv->gicon)
586 {
587 g_object_unref (icon->priv->gicon);
588 icon->priv->gicon = NULL;
589 }
590
591 if (gicon)
592 icon->priv->gicon = g_object_ref (gicon);
593
594 g_object_notify (G_OBJECT (icon), "gicon");
595
596 st_icon_update (icon);
597 }
598
599 /**
600 * st_icon_get_icon_size:
601 * @icon: an icon
602 *
603 * Gets the size explicit size on the icon. This is not necesariily
604 * the size that the icon will actually be displayed at.
605 *
606 * Return value: the size explicitly set, or -1 if no size has been set
607 */
608 gint
609 st_icon_get_icon_size (StIcon *icon)
610 {
611 g_return_val_if_fail (ST_IS_ICON (icon), -1);
612
613 return icon->priv->prop_icon_size;
614 }
615
616 /**
617 * st_icon_set_icon_size:
618 * @icon: an icon
619 * @size: if positive, the new size, otherwise the size will be
620 * derived from the current style
621 *
622 * Sets an explicit size for the icon.
623 */
624 void
625 st_icon_set_icon_size (StIcon *icon,
626 gint size)
627 {
628 StIconPrivate *priv;
629
630 g_return_if_fail (ST_IS_ICON (icon));
631
632 priv = icon->priv;
633 if (priv->prop_icon_size != size)
634 {
635 priv->prop_icon_size = size;
636 if (st_icon_update_icon_size (icon))
637 st_icon_update (icon);
638 g_object_notify (G_OBJECT (icon), "icon-size");
639 }
640 }