svn update: 51469 (latest:51480)
[framework/uifw/elementary.git] / src / lib / elm_thumb.c
1 #include <Elementary.h>
2 #include "elm_priv.h"
3
4 /**
5  * @defgroup Thumb Thumb
6  *
7  * A thumb object is used for displaying the thumbnail of an image or video.
8  * You must have compiled Elementary with Ethumb_Client support and the DBus
9  * service must be present and auto-activated in order to have thumbnails to
10  * be generated.
11  *
12  * Signals that you can add callbacks for are:
13  *
14  * clicked - This is called when a user has clicked the thumb without dragging
15  * around.
16  *
17  * clicked,double - This is called when a user has double-clicked the thumb.
18  *
19  * press - This is called when a user has pressed down the thumb.
20  *
21  * generate,start - The thumbnail generation started.
22  *
23  * generate,stop - The generation process stopped.
24  *
25  * generate,error - The generation failed.
26  *
27  * load,error - The thumbnail image loading failed.
28  */
29
30 typedef struct _Widget_Data Widget_Data;
31
32 struct _Widget_Data
33 {
34    Evas_Object *self;
35    Evas_Object *frame;
36    Evas_Object *view;
37    const char *file;
38    const char *key;
39    struct
40      {
41         int id;
42         const char *file;
43         const char *key;
44      } thumb;
45    Ecore_Event_Handler *eeh;
46    Elm_Thumb_Animation_Setting anim_setting;
47    Eina_Bool on_hold : 1;
48    Eina_Bool is_video : 1;
49    Eina_Bool was_video : 1;
50 };
51
52 static const char *widtype = NULL;
53
54 static const char SIG_CLICKED[] = "clicked";
55 static const char SIG_CLICKED_DOUBLE[] = "clicked,double";
56 static const char SIG_GENERATE_ERROR[] = "generate,error";
57 static const char SIG_GENERATE_START[] = "generate,start";
58 static const char SIG_GENERATE_STOP[] = "generate,stop";
59 static const char SIG_LOAD_ERROR[] = "load,error";
60 static const char SIG_PRESS[]= "press";
61 static const Evas_Smart_Cb_Description _signals[] = {
62   {SIG_CLICKED, ""},
63   {SIG_CLICKED_DOUBLE, ""},
64   {SIG_GENERATE_ERROR, ""},
65   {SIG_GENERATE_START, ""},
66   {SIG_GENERATE_STOP, ""},
67   {SIG_LOAD_ERROR, ""},
68   {SIG_PRESS, ""},
69   {NULL, NULL}
70 };
71
72 static const char EDJE_SIGNAL_GENERATE_START[] = "elm,thumb,generate,start";
73 static const char EDJE_SIGNAL_GENERATE_STOP[] = "elm,thumb,generate,stop";
74 static const char EDJE_SIGNAL_GENERATE_ERROR[] = "elm,thumb,generate,error";
75 static const char EDJE_SIGNAL_LOAD_ERROR[] = "elm,thumb,load,error";
76 static const char EDJE_SIGNAL_PULSE_START[] = "elm,state,pulse,start";
77 static const char EDJE_SIGNAL_PULSE_STOP[] = "elm,state,pulse,stop";
78
79 #ifdef HAVE_ELEMENTARY_ETHUMB
80 Ethumb_Client *_elm_ethumb_client = NULL;
81 #endif
82 Eina_Bool _elm_ethumb_connected = EINA_FALSE;
83
84 EAPI int ELM_ECORE_EVENT_ETHUMB_CONNECT = 0;
85
86 static void
87 _del_hook(Evas_Object *obj)
88 {
89    Widget_Data *wd = elm_widget_data_get(obj);
90
91 #ifdef HAVE_ELEMENTARY_ETHUMB
92    if (wd->thumb.id >= 0)
93      ethumb_client_generate_cancel(_elm_ethumb_client, wd->thumb.id,
94                                    NULL, NULL, NULL);
95 #endif
96
97    eina_stringshare_del(wd->file);
98    eina_stringshare_del(wd->key);
99    if (wd->eeh) ecore_event_handler_del(wd->eeh);
100    free(wd);
101 }
102
103 static void
104 _theme_hook(Evas_Object *obj)
105 {
106    Widget_Data *wd = elm_widget_data_get(obj);
107    _elm_theme_object_set(obj, wd->frame, "thumb", "base", elm_widget_style_get(obj));
108 }
109
110 #ifdef HAVE_ELEMENTARY_ETHUMB
111 static void
112 _mouse_down_cb(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info)
113 {
114    Widget_Data *wd = data;
115    Evas_Event_Mouse_Down *ev = event_info;
116    if (ev->button != 1)
117      return;
118    if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD)
119      wd->on_hold = EINA_TRUE;
120    else
121      wd->on_hold = EINA_FALSE;
122    if (ev->flags & EVAS_BUTTON_DOUBLE_CLICK)
123      evas_object_smart_callback_call(wd->self, SIG_CLICKED_DOUBLE, NULL);
124    else
125      evas_object_smart_callback_call(wd->self, SIG_PRESS, NULL);
126 }
127
128 static void
129 _mouse_up_cb(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info)
130 {
131
132    Widget_Data *wd = data;
133    Evas_Event_Mouse_Up *ev = event_info;
134    if (ev->button != 1)
135      return;
136    if (ev->event_flags & EVAS_EVENT_FLAG_ON_HOLD)
137      wd->on_hold = EINA_TRUE;
138    else
139      wd->on_hold = EINA_FALSE;
140    if (!wd->on_hold)
141      evas_object_smart_callback_call(wd->self, SIG_CLICKED, NULL);
142    wd->on_hold = EINA_FALSE;
143 }
144
145 static void
146 _finished_thumb(Widget_Data *wd, const char *thumb_path, const char *thumb_key)
147 {
148    Eina_Bool new_view = EINA_FALSE;
149    int r;
150    Evas_Coord mw, mh;
151    Evas *evas;
152
153    evas = evas_object_evas_get(wd->self);
154    if ((wd->view) && (wd->is_video ^ wd->was_video))
155      {
156         evas_object_del(wd->view);
157         wd->view = NULL;
158      }
159    wd->was_video = wd->is_video;
160
161    if ((wd->is_video) &&
162        (ethumb_client_format_get(_elm_ethumb_client) == ETHUMB_THUMB_EET))
163      {
164         if (!wd->view)
165           {
166              wd->view = edje_object_add(evas);
167              if (!wd->view)
168                {
169                   ERR("could not create edje object");
170                   goto err;
171                }
172              new_view = EINA_TRUE;
173           }
174
175         if (!edje_object_file_set(wd->view, thumb_path, "movie/thumb"))
176           {
177              ERR("could not set file=%s key=%s for %s", thumb_path, thumb_key,
178                  wd->file);
179              goto view_err;
180           }
181      }
182    else
183      {
184         if (!wd->view)
185           {
186              wd->view = evas_object_image_filled_add(evas);
187              if (!wd->view)
188                {
189                   ERR("could not create image object");
190                   goto err;
191                }
192              new_view = EINA_TRUE;
193           }
194
195         evas_object_image_file_set(wd->view, thumb_path, thumb_key);
196         r = evas_object_image_load_error_get(wd->view);
197         if (r != EVAS_LOAD_ERROR_NONE)
198           {
199              ERR("%s: %s", thumb_path, evas_load_error_str(r));
200              goto view_err;
201           }
202      }
203
204    if (new_view) elm_widget_sub_object_add(wd->self, wd->view);
205    edje_object_part_swallow(wd->frame, "elm.swallow.content", wd->view);
206    edje_object_size_min_get(wd->frame, &mw, &mh);
207    edje_object_size_min_restricted_calc(wd->frame, &mw, &mh, mw, mh);
208    evas_object_size_hint_min_set(wd->self, mw, mh);
209    eina_stringshare_replace(&(wd->thumb.file), thumb_path);
210    eina_stringshare_replace(&(wd->thumb.key), thumb_key);
211    edje_object_signal_emit(wd->frame, EDJE_SIGNAL_GENERATE_STOP, "elm");
212    evas_object_smart_callback_call(wd->self, SIG_GENERATE_STOP, NULL);
213    return;
214
215 view_err:
216    evas_object_del(wd->view);
217    wd->view = NULL;
218 err:
219    edje_object_signal_emit(wd->frame, EDJE_SIGNAL_LOAD_ERROR, "elm");
220    evas_object_smart_callback_call(wd->self, SIG_LOAD_ERROR, NULL);
221 }
222
223 static void
224 _finished_thumb_cb(void *data, Ethumb_Client *c __UNUSED__, int id, const char *file, const char *key, const char *thumb_path, const char *thumb_key, Eina_Bool success)
225 {
226    Widget_Data *wd = data;
227
228    EINA_SAFETY_ON_FALSE_RETURN(wd->thumb.id == id);
229    wd->thumb.id = -1;
230
231    edje_object_signal_emit(wd->frame, EDJE_SIGNAL_PULSE_STOP, "elm");
232
233    if (success)
234      {
235         _finished_thumb(wd, thumb_path, thumb_key);
236         return;
237      }
238
239    ERR("could not generate thumbnail for %s (key: %s)", file, key ? key : "");
240    edje_object_signal_emit(wd->frame, EDJE_SIGNAL_GENERATE_ERROR, "elm");
241    evas_object_smart_callback_call(wd->self, SIG_GENERATE_ERROR, NULL);
242 }
243
244 static void
245 _thumb_apply(Widget_Data *wd)
246 {
247    ethumb_client_file_set(_elm_ethumb_client, wd->file, wd->key);
248    if (ethumb_client_thumb_exists(_elm_ethumb_client))
249      {
250         const char *thumb_path, *thumb_key;
251         wd->thumb.id = -1;
252         ethumb_client_thumb_path_get(_elm_ethumb_client, &thumb_path,
253                                      &thumb_key);
254         _finished_thumb(wd, thumb_path, thumb_key);
255         return;
256      }
257    else if ((wd->thumb.id = ethumb_client_generate
258              (_elm_ethumb_client, _finished_thumb_cb, wd, NULL)) != -1)
259      {
260         edje_object_signal_emit(wd->frame, EDJE_SIGNAL_PULSE_START,
261                                 "elm");
262         edje_object_signal_emit(wd->frame, EDJE_SIGNAL_GENERATE_START,
263                                 "elm");
264         evas_object_smart_callback_call(wd->self, SIG_GENERATE_START, NULL);
265      }
266    else
267      {
268         wd->thumb.id = -1;
269         edje_object_signal_emit(wd->frame, EDJE_SIGNAL_GENERATE_ERROR,
270                                 "elm");
271         evas_object_smart_callback_call(wd->self, SIG_GENERATE_ERROR, NULL);
272      }
273 }
274
275 static Eina_Bool
276 _thumb_apply_cb(void *data, int type __UNUSED__, void *ev __UNUSED__)
277 {
278    _thumb_apply(data);
279    return ECORE_CALLBACK_RENEW;
280 }
281
282 static void
283 _thumb_show(Widget_Data *wd)
284 {
285    evas_object_show(wd->frame);
286
287    if (elm_thumb_ethumb_client_connected())
288      {
289         _thumb_apply(wd);
290         return;
291      }
292
293    if (!wd->eeh)
294      wd->eeh = ecore_event_handler_add(ELM_ECORE_EVENT_ETHUMB_CONNECT,
295                                        _thumb_apply_cb, wd);
296 }
297
298 static void
299 _thumb_show_cb(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
300 {
301    _thumb_show(data);
302 }
303
304 static void
305 _thumb_hide_cb(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__)
306 {
307    Widget_Data *wd = data;
308
309    evas_object_hide(wd->frame);
310
311    if (wd->thumb.id >= 0)
312      {
313         ethumb_client_generate_cancel
314           (_elm_ethumb_client, wd->thumb.id, NULL, NULL, NULL);
315         wd->thumb.id = -1;
316
317         edje_object_signal_emit(wd->frame, EDJE_SIGNAL_GENERATE_STOP, "elm");
318         evas_object_smart_callback_call(wd->self, SIG_GENERATE_STOP, NULL);
319      }
320
321    if (wd->eeh)
322      {
323         ecore_event_handler_del(wd->eeh);
324         wd->eeh = NULL;
325      }
326 }
327
328 #endif
329
330 #ifdef ELM_ETHUMB
331 static Eina_Bool _elm_need_ethumb = EINA_FALSE;
332
333 static void _on_die_cb(void *, Ethumb_Client *);
334
335 static void
336 _connect_cb(void *data __UNUSED__, Ethumb_Client *c, Eina_Bool success)
337 {
338    if (success)
339      {
340         ethumb_client_on_server_die_callback_set(c, _on_die_cb, NULL, NULL);
341         _elm_ethumb_connected = EINA_TRUE;
342         ecore_event_add(ELM_ECORE_EVENT_ETHUMB_CONNECT, NULL, NULL, NULL);
343      }
344    else
345      _elm_ethumb_client = NULL;
346 }
347
348 static void
349 _on_die_cb(void *data __UNUSED__, Ethumb_Client *c __UNUSED__)
350 {
351    ethumb_client_disconnect(_elm_ethumb_client);
352    _elm_ethumb_client = NULL;
353    _elm_ethumb_connected = EINA_FALSE;
354    _elm_ethumb_client = ethumb_client_connect(_connect_cb, NULL, NULL);
355 }
356 #endif
357
358 void
359 _elm_unneed_ethumb(void)
360 {
361 #ifdef ELM_ETHUMB
362    if (_elm_need_ethumb)
363      {
364         _elm_need_ethumb = 0;
365         ethumb_client_disconnect(_elm_ethumb_client);
366         _elm_ethumb_client = NULL;
367         ethumb_client_shutdown();
368         ELM_ECORE_EVENT_ETHUMB_CONNECT = 0;
369      }
370 #endif
371 }
372
373 /**
374  * This must be called before any other function that handle with
375  * elm_thumb objects or ethumb_client instances.
376  *
377  * @ingroup Thumb
378  */
379 EAPI void
380 elm_need_ethumb(void)
381 {
382 #ifdef ELM_ETHUMB
383    if (_elm_need_ethumb)
384      return;
385    ELM_ECORE_EVENT_ETHUMB_CONNECT = ecore_event_type_new();
386    _elm_need_ethumb = 1;
387    ethumb_client_init();
388    _elm_ethumb_client = ethumb_client_connect(_connect_cb, NULL, NULL);
389 #endif
390 }
391
392 /**
393  * Add a new thumb object to the parent.
394  *
395  * @param parent The parent object.
396  * @return The new object or NULL if it cannot be created.
397  *
398  * @see elm_thumb_file_set()
399  * @see elm_thumb_ethumb_client_get()
400  *
401  * @ingroup Thumb
402  */
403 EAPI Evas_Object *
404 elm_thumb_add(Evas_Object *parent)
405 {
406    Evas *evas;
407    Widget_Data *wd;
408    Evas_Object *obj;
409    Evas_Coord minw, minh;
410
411    wd = ELM_NEW(Widget_Data);
412    evas = evas_object_evas_get(parent);
413    obj = elm_widget_add(evas);
414    ELM_SET_WIDTYPE(widtype, "thumb");
415    elm_widget_type_set(obj, "thumb");
416    elm_widget_sub_object_add(parent, obj);
417    elm_widget_data_set(obj, wd);
418    elm_widget_del_hook_set(obj, _del_hook);
419    elm_widget_theme_hook_set(obj, _theme_hook);
420
421    wd->frame = edje_object_add(evas);
422    _elm_theme_object_set(obj, wd->frame, "thumb", "base", "default");
423    elm_widget_resize_object_set(obj, wd->frame);
424
425    edje_object_size_min_calc(obj, &minw, &minh);
426    evas_object_size_hint_min_set(obj, minw, minh);
427
428    wd->self = obj;
429    wd->view = NULL;
430    wd->file = NULL;
431    wd->key = NULL;
432    wd->eeh = NULL;
433    wd->thumb.id = -1;
434    wd->on_hold = EINA_FALSE;
435    wd->is_video = EINA_FALSE;
436    wd->was_video = EINA_FALSE;
437
438 #ifdef HAVE_ELEMENTARY_ETHUMB
439    evas_object_event_callback_add(obj, EVAS_CALLBACK_MOUSE_DOWN,
440                                   _mouse_down_cb, wd);
441    evas_object_event_callback_add(obj, EVAS_CALLBACK_MOUSE_UP,
442                                   _mouse_up_cb, wd);
443    evas_object_event_callback_add(obj, EVAS_CALLBACK_SHOW,
444                                   _thumb_show_cb, wd);
445    evas_object_event_callback_add(obj, EVAS_CALLBACK_HIDE,
446                                   _thumb_hide_cb, wd);
447 #endif
448
449    // TODO: convert Elementary to subclassing of Evas_Smart_Class
450    // TODO: and save some bytes, making descriptions per-class and not instance!
451    evas_object_smart_callbacks_descriptions_set(obj, _signals);
452    return obj;
453 }
454
455 /**
456  * Set the file that will be used as thumbnail.
457  *
458  * The file can be an image or a video (in that case, acceptable extensions are:
459  * avi, mp4, ogv, mov, mpg and wmv). To start the video animation, use the
460  * function elm_thumb_animate().
461  *
462  * @param obj The thumb object.
463  * @param file The path to file that will be used as thumb.
464  * @param key The key used in case of an EET file.
465  *
466  * @see elm_thumb_file_get()
467  * @see elm_thumb_animate()
468  *
469  * @ingroup Thumb
470  */
471 EAPI void
472 elm_thumb_file_set(Evas_Object *obj, const char *file, const char *key)
473 {
474    ELM_CHECK_WIDTYPE(obj, widtype);
475    Eina_Bool file_replaced, key_replaced;
476    Widget_Data *wd = elm_widget_data_get(obj);
477
478    file_replaced = eina_stringshare_replace(&(wd->file), file);
479    key_replaced = eina_stringshare_replace(&(wd->key), key);
480
481    if (file_replaced)
482      {
483         int prefix_size;
484         const char **ext, *ptr;
485         static const char *extensions[] = { ".avi", ".mp4", ".ogv", ".mov",
486                                             ".mpg", ".wmv", NULL };
487
488         prefix_size = eina_stringshare_strlen(wd->file) - 4;
489         if (prefix_size >= 0)
490           {
491              ptr = wd->file + prefix_size;
492              wd->is_video = EINA_FALSE;
493              for (ext = extensions; *ext; ext++)
494                if (!strcasecmp(ptr, *ext))
495                  {
496                     wd->is_video = EINA_TRUE;
497                     break;
498                  }
499           }
500      }
501
502    eina_stringshare_replace(&(wd->thumb.file), NULL);
503    eina_stringshare_replace(&(wd->thumb.key), NULL);
504
505 #ifdef HAVE_ELEMENTARY_ETHUMB
506    if ((file_replaced || key_replaced) && evas_object_visible_get(obj))
507      _thumb_show(wd);
508 #endif
509 }
510
511 /**
512  * Get the image or video path and key used to generate the thumbnail.
513  *
514  * @param obj The thumb object.
515  * @param file Pointer to filename.
516  * @param key Pointer to key.
517  *
518  * @see elm_thumb_file_set()
519  * @see elm_thumb_path_get()
520  * @see elm_thumb_animate()
521  *
522  * @ingroup Thumb
523  */
524 EAPI void
525 elm_thumb_file_get(const Evas_Object *obj, const char **file, const char **key)
526 {
527    ELM_CHECK_WIDTYPE(obj, widtype);
528    Widget_Data *wd = elm_widget_data_get(obj);
529    if (file)
530      *file = wd->file;
531    if (key)
532      *key = wd->key;
533 }
534
535 /**
536  * Get the path and key to the image or video generated by ethumb.
537  *
538  * One just need to make sure that the thumbnail was generated before getting
539  * its path; otherwise, the path will be NULL. One way to do that is by asking
540  * for the path when/after the "generate,stop" smart callback is called.
541  *
542  * @param obj The thumb object.
543  * @param file Pointer to thumb path.
544  * @param key Pointer to thumb key.
545  *
546  * @see elm_thumb_file_get()
547  *
548  * @ingroup Thumb
549  */
550 EAPI void
551 elm_thumb_path_get(const Evas_Object *obj, const char **file, const char **key)
552 {
553    ELM_CHECK_WIDTYPE(obj, widtype);
554    Widget_Data *wd = elm_widget_data_get(obj);
555    if (file)
556      *file = wd->thumb.file;
557    if (key)
558      *key = wd->thumb.key;
559 }
560
561 /**
562  * Set the animation state for the thumb object. If its content is an animated
563  * video, you may start/stop the animation or tell it to play continuously and
564  * looping.
565  *
566  * @param obj The thumb object.
567  * @param setting The animation setting.
568  *
569  * @see elm_thumb_file_set()
570  *
571  * @ingroup Thumb
572  */
573 EAPI void
574 elm_thumb_animate_set(Evas_Object *obj, Elm_Thumb_Animation_Setting setting)
575 {
576    ELM_CHECK_WIDTYPE(obj, widtype);
577    Widget_Data *wd = elm_widget_data_get(obj);
578
579    if (setting < ELM_THUMB_ANIMATION_START ||
580        setting >= ELM_THUMB_ANIMATION_LAST)
581      {
582         return;
583      }
584
585    wd->anim_setting = setting;
586    if (setting == ELM_THUMB_ANIMATION_LOOP)
587      edje_object_signal_emit(wd->view, "animate_loop", "");
588    else if (setting == ELM_THUMB_ANIMATION_START)
589      edje_object_signal_emit(wd->view, "animate", "");
590    else if (setting == ELM_THUMB_ANIMATION_STOP)
591      edje_object_signal_emit(wd->view, "animate_stop", "");
592 }
593
594 /**
595  * Get the animation state for the thumb object.
596  *
597  * @param obj The thumb object.
598  * @return getting The animation setting or @c ELM_THUMB_ANIMATION_LAST,
599  * on errors.
600  *
601  * @see elm_thumb_file_get()
602  *
603  * @ingroup Thumb
604  */
605 EAPI Elm_Thumb_Animation_Setting
606 elm_thumb_animate_get(const Evas_Object *obj)
607 {
608    ELM_CHECK_WIDTYPE(obj, widtype) ELM_THUMB_ANIMATION_LAST;
609    Widget_Data *wd = elm_widget_data_get(obj);
610
611    return wd->anim_setting;
612 }
613
614 /**
615  * Get the ethumb_client handle so custom configuration can be made.
616  * This must be called before the objects are created to be sure no object is
617  * visible and no generation started.
618  *
619  * @return Ethumb_Client instance or NULL.
620  *
621  * Example of usage:
622  *
623  * @code
624  * #include <Elementary.h>
625  * #ifndef ELM_LIB_QUICKLAUNCH
626  * EAPI int
627  * elm_main(int argc, char **argv)
628  * {
629  *    Ethumb_Client *client;
630  *
631  *    elm_need_ethumb();
632  *
633  *    // ... your code
634  *
635  *    client = elm_thumb_ethumb_client_get();
636  *    if (!client)
637  *      {
638  *         ERR("could not get ethumb_client");
639  *         return 1;
640  *      }
641  *    ethumb_client_size_set(client, 100, 100);
642  *    ethumb_client_crop_align_set(client, 0.5, 0.5);
643  *    // ... your code
644  *
645  *    // Create elm_thumb objects here
646  *
647  *    elm_run();
648  *    elm_shutdown();
649  *    return 0;
650  * }
651  * #endif
652  * ELM_MAIN()
653  * @endcode
654  *
655  * @ingroup Thumb
656  */
657 #ifdef ELM_ETHUMB
658 EAPI Ethumb_Client *
659 elm_thumb_ethumb_client_get(void)
660 {
661    return _elm_ethumb_client;
662 }
663 #else
664 EAPI void *
665 elm_thumb_ethumb_client_get(void)
666 {
667    return NULL;
668 }
669 #endif
670
671 /**
672  * Get the ethumb_client connection state.
673  *
674  * @return EINA_TRUE if the client is connected to the server or
675  *         EINA_FALSE otherwise.
676  */
677 EAPI Eina_Bool
678 elm_thumb_ethumb_client_connected(void)
679 {
680    return _elm_ethumb_connected;
681 }