elm_image: Add smart cbs for async open

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
This commit is contained in:
Jean-Philippe Andre 2016-09-22 15:24:40 +09:00
parent 76a2bd9312
commit 63088b583c
8 changed files with 246 additions and 78 deletions

View 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);

View 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);
}

View 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]]
}
}

View 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)
{

View 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;

View File

@ -37,6 +37,13 @@
* 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

View 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"

View 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);