elm_image: Add smart cbs for async open
authorJean-Philippe Andre <jp.andre@samsung.com>
Thu, 22 Sep 2016 06:24:40 +0000 (15:24 +0900)
committerJean-Philippe Andre <jp.andre@samsung.com>
Fri, 23 Sep 2016 02:28:14 +0000 (11:28 +0900)
This adds a few callbacks to inform applications about
async open and preload:
- load,open
- load,ready
- load,error
- load,cancel

This patch adds a new EAPI as well: elm_image_async_open_set.
This is the only way to use async file open with the legacy APIs
and should be easily matched with whatever EO API we end up using
(be it based on promises or events).

Alter the test cases for make check as they were using the
unstable EO API which I just removed.

Thanks @arosis for the original patch. And sorry for the huge
delay in merging this.

See also: https://phab.enlightenment.org/D4215

@feature

src/bin/elementary/test.c
src/bin/elementary/test_image.c
src/lib/efl/interfaces/efl_file.eo
src/lib/elementary/efl_ui_image.c
src/lib/elementary/efl_ui_image.eo
src/lib/elementary/elm_image.h
src/lib/elementary/elm_image_legacy.h
src/tests/elementary/elm_test_image.c

index a1d7a70..2c3d476 100644 (file)
@@ -258,6 +258,7 @@ void test_dayselector(void *data, Evas_Object *obj, void *event_info);
 void test_image(void *data, Evas_Object *obj, void *event_info);
 void test_remote_image(void *data, Evas_Object *obj, void *event_info);
 void test_click_image(void *data, Evas_Object *obj, void *event_info);
+void test_load_image(void *data, Evas_Object *obj, void *event_info);
 void test_external_button(void *data, Evas_Object *obj, void *event_info);
 void test_external_slider(void *data, Evas_Object *obj, void *event_info);
 void test_external_scroller(void *data, Evas_Object *obj, void *event_info);
@@ -632,6 +633,7 @@ add_tests:
    ADD_TEST(NULL, "Images", "Image", test_image);
    ADD_TEST(NULL, "Images", "Image Remote", test_remote_image);
    ADD_TEST(NULL, "Images", "Image Click", test_click_image);
+   ADD_TEST(NULL, "Images", "Image Async Load", test_load_image);
    ADD_TEST(NULL, "Images", "Slideshow", test_slideshow);
    ADD_TEST(NULL, "Images", "Video", test_video);
 
index 528c7af..fc6bdf1 100644 (file)
@@ -308,3 +308,153 @@ test_click_image(void *data EINA_UNUSED, Evas_Object *obj  EINA_UNUSED, void *ev
    evas_object_resize(win, 320, 480);
    evas_object_show(win);
 }
+
+#define STATUS_SET(obj, fmt) do { \
+   elm_object_text_set(obj, fmt); \
+   fprintf(stderr, "%s\n", fmt); fflush(stderr); \
+   } while (0)
+
+static void
+_img_load_open_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
+{
+   Evas_Object *status_text = data;
+
+   STATUS_SET(status_text, "Async file open done.");
+}
+
+static void
+_img_load_ready_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
+{
+   Evas_Object *status_text = data;
+
+   STATUS_SET(status_text, "Image is ready to show.");
+}
+
+static void
+_img_load_error_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
+{
+   Evas_Object *status_text = data;
+
+   STATUS_SET(status_text, "Async file load failed.");
+}
+
+static void
+_img_load_cancel_cb(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
+{
+   Evas_Object *status_text = data;
+
+   STATUS_SET(status_text, "Async file open has been cancelled.");
+}
+
+static void
+_create_image(Evas_Object *data, Eina_Bool async, Eina_Bool preload)
+{
+   Evas_Object *win = data;
+   Evas_Object *im, *status_text;
+   Evas_Object *box = evas_object_data_get(win, "box");
+   char buf[PATH_MAX] = {0};
+
+   im = elm_image_add(win);
+   elm_image_async_open_set(im, async);
+   elm_image_preload_disabled_set(im, preload);
+
+   evas_object_size_hint_weight_set(im, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+   evas_object_size_hint_align_set(im, EVAS_HINT_FILL, EVAS_HINT_FILL);
+   evas_object_data_set(win, "im", im);
+   elm_box_pack_start(box, im);
+   evas_object_show(im);
+
+   status_text = evas_object_data_get(win, "phld");
+   if (!status_text)
+     {
+        status_text = elm_label_add(win);
+        evas_object_size_hint_weight_set(status_text, EVAS_HINT_EXPAND, 0);
+        evas_object_size_hint_align_set(status_text, EVAS_HINT_FILL, EVAS_HINT_FILL);
+        evas_object_data_set(win, "phld", status_text);
+        elm_box_pack_after(box, status_text, im);
+        evas_object_show(status_text);
+     }
+
+   evas_object_smart_callback_add(im, "load,open", _img_load_open_cb, status_text);
+   evas_object_smart_callback_add(im, "load,ready", _img_load_ready_cb, status_text);
+   evas_object_smart_callback_add(im, "load,error", _img_load_error_cb, status_text);
+   evas_object_smart_callback_add(im, "load,cancel", _img_load_cancel_cb, status_text);
+
+   STATUS_SET(status_text, "Loading image...");
+   snprintf(buf, sizeof(buf) - 1, "%s/images/insanely_huge_test_image.jpg", elm_app_data_dir_get());
+   elm_image_file_set(im, buf, NULL);
+}
+
+static void
+_bt_clicked(void *data, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
+{
+   Evas_Object *win = data;
+   Evas_Object *im = evas_object_data_get(win, "im");
+   Evas_Object *chk1 = evas_object_data_get(win, "chk1");
+   Evas_Object *chk2 = evas_object_data_get(win, "chk2");
+   Eina_Bool async = elm_check_state_get(chk1);
+   Eina_Bool preload = elm_check_state_get(chk2);
+
+   evas_object_del(im);
+   _create_image(win, async, preload);
+}
+
+void
+test_load_image(void *data EINA_UNUSED, Evas_Object *obj  EINA_UNUSED, void *event_info EINA_UNUSED)
+{
+   Evas_Object *win, *box, *hbox, *label, *chk1, *chk2, *bt;
+
+   win = elm_win_util_standard_add("image test", "Image Test");
+   elm_win_autodel_set(win, EINA_TRUE);
+
+   box = elm_box_add(win);
+   elm_box_align_set(box, 0.5, 1.0);
+   evas_object_size_hint_weight_set(box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+   elm_win_resize_object_add(win, box);
+   evas_object_show(box);
+   evas_object_data_set(win, "box", box);
+
+   _create_image(win, EINA_FALSE, EINA_FALSE);
+
+   hbox = elm_box_add(win);
+   elm_box_horizontal_set(hbox, EINA_TRUE);
+   elm_box_align_set(hbox, 0, 0.5);
+   evas_object_size_hint_weight_set(hbox, EVAS_HINT_EXPAND, 0.0);
+   evas_object_size_hint_align_set(hbox, EVAS_HINT_FILL, 0.0);
+   {
+      label = elm_label_add(win);
+      elm_object_text_set(label, "Async load options:");
+      evas_object_size_hint_weight_set(label, 0.0, 0.0);
+      evas_object_size_hint_align_set(label, EVAS_HINT_FILL, 0.5);
+      elm_box_pack_end(hbox, label);
+      evas_object_show(label);
+
+      chk1 = elm_check_add(hbox);
+      elm_object_text_set(chk1, "Async file open");
+      evas_object_size_hint_weight_set(chk1, 0.0, 0.0);
+      evas_object_size_hint_align_set(chk1, EVAS_HINT_FILL, 0.5);
+      elm_box_pack_end(hbox, chk1);
+      evas_object_data_set(win, "chk1", chk1);
+      evas_object_show(chk1);
+
+      chk2 = elm_check_add(hbox);
+      elm_object_text_set(chk2, "Disable preload");
+      evas_object_size_hint_weight_set(chk2, 0.0, 0.0);
+      evas_object_size_hint_align_set(chk2, EVAS_HINT_FILL, 0.5);
+      elm_box_pack_end(hbox, chk2);
+      evas_object_data_set(win, "chk2", chk2);
+      evas_object_show(chk2);
+   }
+   evas_object_show(hbox);
+   elm_box_pack_end(box, hbox);
+
+   bt = elm_button_add(win);
+   evas_object_size_hint_align_set(bt, 0.5, 0.0);
+   elm_object_text_set(bt, "Image Reload");
+   evas_object_smart_callback_add(bt, "clicked", _bt_clicked, win);
+   elm_box_pack_end(box, bt);
+   evas_object_show(bt);
+
+   evas_object_resize(win, 320, 480);
+   evas_object_show(win);
+}
index 63f9e9b..41cf5df 100644 (file)
@@ -79,24 +79,6 @@ interface Efl.File {
                                  $null, otherwise.]]
          }
       }
-      @property async {
-         set {
-            [[If true, file open will happen asynchronously allowing for better
-              performance in some situations. The file will be opened from a
-              different thread. Classes implementing async open might then block
-              and wait when querying information from the file (eg. image size).
-
-              Only a few objects implement this feature, and this flag may
-              be ignored by EFL. In that case, get() will always return false.]]
-         }
-         get {
-            [[Retrieves the asynchronous open flag, which will be true only if
-              enabled and supported by the object.]]
-         }
-         values {
-            async: bool; [[Flag for asynchronous open.]]
-         }
-      }
       save @const {
          [[Save the given image object's contents to an (image) file.
 
@@ -119,16 +101,5 @@ interface Efl.File {
                                        none).]]
          }
       }
-      async_wait @const {
-         [[Block and wait until all asynchronous operations are completed. Unless
-           the async flag was set on this object, this method has no effect.]]
-
-         return: bool;[[$false if an error occurred, else $true]]
-      }
-   }
-   events {
-      /* FIXME - remove Efl.File events: async,{opened,error} */
-      async,opened; [[The file was successfully opened asynchronously]]
-      async,error; [[Error occurred in asynchronous file operation]]
    }
 }
index bb8ef95..1d02c89 100644 (file)
@@ -26,6 +26,10 @@ static const char SIG_DOWNLOAD_START[] = "download,start";
 static const char SIG_DOWNLOAD_PROGRESS[] = "download,progress";
 static const char SIG_DOWNLOAD_DONE[] = "download,done";
 static const char SIG_DOWNLOAD_ERROR[] = "download,error";
+static const char SIG_LOAD_OPEN[] = "load,open";
+static const char SIG_LOAD_READY[] = "load,ready";
+static const char SIG_LOAD_ERROR[] = "load,error";
+static const char SIG_LOAD_CANCEL[] = "load,cancel";
 static const Evas_Smart_Cb_Description _smart_callbacks[] = {
    {SIG_DND, ""},
    {SIG_CLICKED, ""},
@@ -33,6 +37,10 @@ static const Evas_Smart_Cb_Description _smart_callbacks[] = {
    {SIG_DOWNLOAD_PROGRESS, ""},
    {SIG_DOWNLOAD_DONE, ""},
    {SIG_DOWNLOAD_ERROR, ""},
+   {SIG_LOAD_OPEN, "Triggered when the file has been opened (image size is known)"},
+   {SIG_LOAD_READY, "Triggered when the image file is ready for display"},
+   {SIG_LOAD_ERROR, "Triggered whenener an I/O or decoding error occured"},
+   {SIG_LOAD_CANCEL, "Triggered whenener async I/O was cancelled"},
    {NULL, NULL}
 };
 
@@ -61,9 +69,14 @@ _on_image_preloaded(void *data,
                     void *event EINA_UNUSED)
 {
    Efl_Ui_Image_Data *sd = data;
+   Evas_Load_Error err;
+
    sd->preload_status = EFL_UI_IMAGE_PRELOADED;
    if (sd->show) evas_object_show(obj);
    ELM_SAFE_FREE(sd->prev_img, evas_object_del);
+   err = evas_object_image_load_error_get(obj);
+   if (!err) evas_object_smart_callback_call(sd->self, SIG_LOAD_READY, NULL);
+   else evas_object_smart_callback_call(sd->self, SIG_LOAD_ERROR, NULL);
 }
 
 static void
@@ -322,6 +335,7 @@ _efl_ui_image_async_open_cancel(void *data, Ecore_Thread *thread)
         EFL_UI_IMAGE_DATA_GET(todo->obj, sd);
         if (sd)
           {
+             evas_object_smart_callback_call(todo->obj, SIG_LOAD_CANCEL, NULL);
              if (thread == sd->async.th) _async_clear(sd);
           }
      }
@@ -361,19 +375,8 @@ _efl_ui_image_async_open_done(void *data, Ecore_Thread *thread)
                          ok = _efl_ui_image_smart_internal_file_set
                            (sd->self, sd, file, f, key);
                     }
-                  if (ok)
-                    {
-                       // TODO: Implement Efl.File async,opened event_info type
-                       efl_event_callback_legacy_call
-                         (sd->self, EFL_FILE_EVENT_ASYNC_OPENED, NULL);
-                       _efl_ui_image_internal_sizing_eval(sd->self, sd);
-                    }
-                  else
-                    {
-                       // keep file,key around for file_get
-                       efl_event_callback_legacy_call
-                         (sd->self, EFL_FILE_EVENT_ASYNC_ERROR, NULL);
-                    }
+                  if (ok) evas_object_smart_callback_call(sd->self, SIG_LOAD_OPEN, NULL);
+                  else evas_object_smart_callback_call(sd->self, SIG_LOAD_ERROR, NULL);
                }
           }
      }
@@ -1038,6 +1041,8 @@ _efl_ui_image_efl_file_file_get(Eo *obj EINA_UNUSED, Efl_Ui_Image_Data *sd, cons
    evas_object_image_file_get(sd->img, file, key);
 }
 
+#if 0
+// Kept for reference: wait for async open to complete - probably unused.
 static Eina_Bool
 _efl_ui_image_efl_file_async_wait(const Eo *obj EINA_UNUSED, Efl_Ui_Image_Data *pd)
 {
@@ -1050,21 +1055,24 @@ _efl_ui_image_efl_file_async_wait(const Eo *obj EINA_UNUSED, Efl_Ui_Image_Data *
      }
    return ok;
 }
+#endif
 
-EOLIAN static void
-_efl_ui_image_efl_file_async_set(Eo *obj EINA_UNUSED, Efl_Ui_Image_Data *pd, Eina_Bool async)
+/* Legacy style async API. While legacy only, this is new from 1.19.
+ * Tizen has used elm_image_async_open_set() internally for a while, despite
+ * EFL upstream not exposing a proper async API. */
+
+EAPI void
+elm_image_async_open_set(Eo *obj, Eina_Bool async)
 {
+   Efl_Ui_Image_Data *pd;
+
+   EINA_SAFETY_ON_FALSE_RETURN(efl_isa(obj, MY_CLASS));
+   pd = efl_data_scope_get(obj, MY_CLASS);
    if (pd->async_enable == async) return;
    pd->async_enable = async;
    if (!async) _async_cancel(pd);
 }
 
-EOLIAN static Eina_Bool
-_efl_ui_image_efl_file_async_get(Eo *obj EINA_UNUSED, Efl_Ui_Image_Data *sd)
-{
-   return sd->async_enable;
-}
-
 EOLIAN static void
 _efl_ui_image_efl_gfx_view_view_size_get(Eo *obj EINA_UNUSED, Efl_Ui_Image_Data *sd, int *w, int *h)
 {
index d84139e..1655768 100644 (file)
@@ -122,9 +122,6 @@ class Efl.Ui.Image (Elm.Widget, Efl.Ui.Clickable, Efl.Ui.Draggable,
       Efl.File.file.set;
       Efl.File.file.get;
       Efl.File.mmap.set;
-      Efl.File.async.set;
-      Efl.File.async.get;
-      Efl.File.async_wait;
       Efl.Gfx.View.view_size.get;
       Efl.Image.Load.load_size.set;
       Efl.Image.Load.load_size.get;
index 51ba0cf..5a86f44 100644 (file)
  *                              data is of type Elm_Image_Progress.
  * @li @c "download,done" - This is called when the download has completed.
  * @li @c "download,error"- This is called when the download has failed.
+ * @li @c "load,open" - Triggered when the file has been opened, if async
+ *                      open is enabled (image size is known). (since 1.19)
+ * @li @c "load,ready" - Triggered when the image file is ready for display,
+ *                       if preload is enabled. (since 1.19)
+ * @li @c "load,error" - Triggered if an async I/O or decoding error occurred,
+ *                       if async open or preload is enabled (since 1.19)
+ * @li @c "load,cancel" - Triggered whenener async I/O was cancelled. (since 1.19)
  *
  * An example of usage for this API follows:
  * @li @ref tutorial_image
index 49f3759..961abdb 100644 (file)
@@ -530,4 +530,17 @@ EAPI void elm_image_aspect_fixed_set(Evas_Object *obj, Eina_Bool fixed);
  */
 EAPI Eina_Bool elm_image_aspect_fixed_get(const Evas_Object *obj);
 
+/**
+ * @brief Enable asynchronous file I/O for elm_image_file_set.
+ *
+ * If @c true, this will make elm_image_file_set() an asynchronous operation.
+ * Use of this function is not recommended and the standard EO-based
+ * asynchronous I/O API should be preferred instead.
+ *
+ * @since 1.19
+ *
+ * @ingroup Elm_Image
+ */
+EAPI void elm_image_async_open_set(Evas_Object *obj, Eina_Bool async);
+
 #include "efl_ui_image.eo.legacy.h"
index 3ae76f7..67f6bc8 100644 (file)
@@ -14,7 +14,7 @@ typedef struct _Test_Data Test_Data;
 struct _Test_Data
 {
    int image_id;
-   Eina_Bool success;
+   int success;
 };
 
 START_TEST (elm_atspi_role_get)
@@ -35,33 +35,37 @@ START_TEST (elm_atspi_role_get)
 END_TEST
 
 static void
-_async_error_cb(void *data, const Efl_Event *event)
+_async_error_cb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
 {
    Test_Data *td = data;
    char path[PATH_MAX];
    sprintf(path, pathfmt, td->image_id);
-   efl_file_set(event->object, path, NULL);
+   elm_image_file_set(obj, path, NULL);
+
+   td->success = 1;
 }
 
 static void
-_async_opened_cb(void *data, const Efl_Event *event)
+_async_opened_cb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
 {
    Test_Data *td = data;
    const char *ff, *kk, *r1, *r2;
    char path[PATH_MAX];
 
    sprintf(path, pathfmt, td->image_id);
-   efl_file_get(event->object, &ff, &kk);
+   elm_image_file_get(obj, &ff, &kk);
    r1 = strrchr(ff, '/');
    r2 = strrchr(path, '/');
-   ck_assert(!strcmp(r1, r2));
+   ck_assert(eina_streq(r1, r2));
    ck_assert(!kk);
 
+   fprintf(stderr, "opened: %s\n", ff);
+
    if (td->image_id < MAX_IMAGE_ID / 2)
      {
         td->image_id++;
         sprintf(path, pathfmt, td->image_id);
-        efl_file_set(event->object, path, NULL);
+        elm_image_file_set(obj, path, NULL);
         return;
      }
    else if (td->image_id < MAX_IMAGE_ID)
@@ -70,13 +74,28 @@ _async_opened_cb(void *data, const Efl_Event *event)
         for (; td->image_id < MAX_IMAGE_ID;)
           {
              sprintf(path, pathfmt, ++td->image_id);
-             efl_file_set(event->object, path, NULL);
+             elm_image_file_set(obj, path, NULL);
           }
         return;
      }
 
-   td->success = 1;
-   ecore_main_loop_quit();
+   if (td->success == 1)
+     td->success = 2;
+}
+
+static void
+_async_ready_cb(void *data, Evas_Object *obj, void *event_info EINA_UNUSED)
+{
+   Test_Data *td = data;
+
+   const char *ff;
+   elm_image_file_get(obj, &ff, 0);
+   fprintf(stderr, "ready : %s\n", ff);
+   if (td->success == 2)
+     {
+        td->success = 3;
+        ecore_main_loop_quit();
+     }
 }
 
 static Eina_Bool
@@ -93,7 +112,7 @@ _timeout_cb(void *data)
 START_TEST (elm_image_async_path)
 {
    Evas_Object *win, *image;
-   Eina_Bool one, two, ok;
+   Eina_Bool ok;
    Test_Data td;
 
    elm_init(1, NULL);
@@ -103,19 +122,18 @@ START_TEST (elm_image_async_path)
    td.image_id = 0;
 
    image = elm_image_add(win);
-   one = efl_file_async_get(image);
-   efl_file_async_set(image, 1);
-   efl_event_callback_add(image, EFL_FILE_EVENT_ASYNC_ERROR, _async_error_cb, &td);
-   efl_event_callback_add(image, EFL_FILE_EVENT_ASYNC_OPENED, _async_opened_cb, &td);
-   ok = efl_file_set(image, invalid, NULL);
-   two = efl_file_async_get(image);
-   ck_assert(!one && two);
+   elm_image_async_open_set(image, 1);
+   evas_object_smart_callback_add(image, "load,error", _async_error_cb, &td);
+   evas_object_smart_callback_add(image, "load,open", _async_opened_cb, &td);
+   evas_object_smart_callback_add(image, "load,ready", _async_ready_cb, &td);
+   evas_object_show(image);
+   ok = elm_image_file_set(image, invalid, NULL);
    ck_assert(ok);
 
    ecore_timer_add(10.0, _timeout_cb, &td);
 
    elm_run();
-   ck_assert(td.success);
+   ck_assert(td.success == 3);
 
    elm_shutdown();
 }
@@ -132,7 +150,7 @@ START_TEST (elm_image_async_mmap)
    elm_init(1, NULL);
    win = elm_win_add(NULL, "image", ELM_WIN_BASIC);
 
-   td.success = 0;
+   td.success = 1; // skip "error" case
    td.image_id = 1;
 
    sprintf(path, pathfmt, td.image_id);
@@ -140,16 +158,18 @@ START_TEST (elm_image_async_mmap)
    ck_assert(f);
 
    image = elm_image_add(win);
-   efl_file_async_set(image, 1);
-   efl_event_callback_add(image, EFL_FILE_EVENT_ASYNC_ERROR, _async_error_cb, &td);
-   efl_event_callback_add(image, EFL_FILE_EVENT_ASYNC_OPENED, _async_opened_cb, &td);
-   ok = efl_file_mmap_set(image, f, NULL);
+   elm_image_async_open_set(image, 1);
+   evas_object_smart_callback_add(image, "load,error", _async_error_cb, &td);
+   evas_object_smart_callback_add(image, "load,open", _async_opened_cb, &td);
+   evas_object_smart_callback_add(image, "load,ready", _async_ready_cb, &td);
+   evas_object_show(image);
+   ok = elm_image_mmap_set(image, f, NULL);
    ck_assert(ok);
 
    ecore_timer_add(10.0, _timeout_cb, &td);
 
    elm_run();
-   ck_assert(td.success);
+   ck_assert(td.success == 3);
 
    eina_file_close(f);