From: raster Date: Fri, 19 Aug 2011 11:07:42 +0000 (+0000) Subject: this is... the beginning of accessibility supportin elm. it's direct X-Git-Tag: REL_F_I9500_20120323_1~17^2~1927 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=b7f898e2465de324d5af5981a0a8c064cc3c47f2;p=framework%2Fuifw%2Felementary.git this is... the beginning of accessibility supportin elm. it's direct as in elm manages it itself - all it needs is a module to send text to. one is provided here that just execs espeak and handles a stream of things for it to say. this is only a start and is still being fleshed out. git-svn-id: svn+ssh://svn.enlightenment.org/var/svn/e/trunk/elementary@62585 7cbeb6ba-43b4-40fd-8cce-4c39aea84d33 --- diff --git a/configure.ac b/configure.ac index a768c0f..dae927a 100644 --- a/configure.ac +++ b/configure.ac @@ -582,6 +582,7 @@ src/lib/Makefile src/lib/Elementary.h src/bin/Makefile src/modules/Makefile +src/modules/access_output/Makefile src/modules/test_entry/Makefile src/modules/test_map/Makefile src/edje_externals/Makefile diff --git a/data/themes/widgets/label.edc b/data/themes/widgets/label.edc index 681d2d2..cc190a8 100644 --- a/data/themes/widgets/label.edc +++ b/data/themes/widgets/label.edc @@ -30,7 +30,6 @@ group { name: "elm/label/base/default"; } part { name: "elm.text"; type: TEXTBLOCK; - mouse_events: 0; scale: 1; clip_to: "label.text.clip"; description { state: "default" 0.0; @@ -70,7 +69,6 @@ group { name: "elm/label/base/marker"; } part { name: "elm.text"; type: TEXTBLOCK; - mouse_events: 0; scale: 1; description { state: "default" 0.0; text { @@ -161,7 +159,6 @@ group { name: "elm/label/base/slide_long"; } part { name: "elm.text"; type: TEXTBLOCK; - mouse_events: 0; scale: 1; clip_to: "label.text.clip"; description { state: "default" 0.0; @@ -287,7 +284,6 @@ group { name: "elm/label/base/slide_short"; } part { name: "elm.text"; type: TEXTBLOCK; - mouse_events: 0; scale: 1; clip_to: "label.text.clip"; description { state: "default" 0.0; @@ -424,7 +420,6 @@ group { name: "elm/label/base/slide_bounce"; } part { name: "elm.text"; type: TEXTBLOCK; - mouse_events: 0; scale: 1; clip_to: "label.text.clip"; description { state: "default" 0.0; diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 9b6fb2e..5a52be5 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -51,6 +51,7 @@ elc_hoversel.c \ elc_naviframe.c \ elc_player.c \ elc_scrolled_entry.c \ +elm_access.c \ elm_actionslider.c \ elm_bg.c \ elm_box.c \ diff --git a/src/lib/elm_access.c b/src/lib/elm_access.c new file mode 100644 index 0000000..d441d63 --- /dev/null +++ b/src/lib/elm_access.c @@ -0,0 +1,245 @@ +#include +#include "elm_priv.h" + +typedef struct _Mod_Api Mod_Api; + +struct _Mod_Api +{ + void (*out_read) (const char *txt); + void (*out_read_done) (void); + void (*out_cancel) (void); + void (*out_done_callback_set) (void (*func) (void *data), const void *data); +}; + +static int initted = 0; +static Elm_Module *mod = NULL; +static Mod_Api *mapi = NULL; + +static void +_access_init(void) +{ + Elm_Module *m; + initted++; + if (initted > 1) return; + if (!(m = _elm_module_find_as("access/api"))) return; + mod = m; + m->api = malloc(sizeof(Mod_Api)); + if (!m->api) return; + m->init_func(m); + ((Mod_Api *)(m->api) )->out_read = // called to read out some text + _elm_module_symbol_get(m, "out_read"); + ((Mod_Api *)(m->api) )->out_read_done = // called to set a done marker so when it is reached the done callback is called + _elm_module_symbol_get(m, "out_read_done"); + ((Mod_Api *)(m->api) )->out_cancel = // called to read out some text + _elm_module_symbol_get(m, "out_cancel"); + ((Mod_Api *)(m->api) )->out_done_callback_set = // called when last read done + _elm_module_symbol_get(m, "out_done_callback_set"); + mapi = m->api; +} + +static Elm_Access_Item * +_access_add_set(Elm_Access_Info *ac, int type) +{ + Elm_Access_Item *ai; + Eina_List *l; + + if (!ac) return NULL; + EINA_LIST_FOREACH(ac->items, l, ai) + { + if (ai->type == type) + { + if (!ai->func) + { + if (ai->data) eina_stringshare_del(ai->data); + } + ai->func = NULL; + ai->data = NULL; + return ai; + } + } + ai = calloc(1, sizeof(Elm_Access_Item)); + ai->type = type; + ac->items = eina_list_prepend(ac->items, ai); + return ai; +} +static Eina_Bool +_access_obj_over_timeout_cb(void *data) +{ + Elm_Access_Info *ac = evas_object_data_get(data, "_elm_access"); + if (!ac) return EINA_FALSE; + _elm_access_read(ac, ELM_ACCESS_CANCEL, data, NULL); + _elm_access_read(ac, ELM_ACCESS_TYPE, data, NULL); + _elm_access_read(ac, ELM_ACCESS_INFO, data, NULL); + _elm_access_read(ac, ELM_ACCESS_STATE, data, NULL); + _elm_access_read(ac, ELM_ACCESS_DONE, data, NULL); + ac->delay_timer = NULL; + return EINA_FALSE; +} + +static void +_access_obj_mouse_in_cb(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__) +{ + Elm_Access_Info *ac = evas_object_data_get(data, "_elm_access"); + if (!ac) return; + + if (ac->delay_timer) + { + ecore_timer_del(ac->delay_timer); + ac->delay_timer = NULL; + } + ac->delay_timer = ecore_timer_add(0.2, _access_obj_over_timeout_cb, data); +} + +static void +_access_obj_mouse_out_cb(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info __UNUSED__) +{ + Elm_Access_Info *ac = evas_object_data_get(data, "_elm_access"); + if (!ac) return; + if (ac->delay_timer) + { + ecore_timer_del(ac->delay_timer); + ac->delay_timer = NULL; + } +} + +static void +_access_obj_del_cb(void *data, Evas *e __UNUSED__, Evas_Object *obj, void *event_info __UNUSED__) +{ + Elm_Access_Info *ac; + + evas_object_event_callback_del_full(obj, EVAS_CALLBACK_MOUSE_IN, + _access_obj_mouse_in_cb, data); + evas_object_event_callback_del_full(obj, EVAS_CALLBACK_MOUSE_OUT, + _access_obj_mouse_out_cb, data); + evas_object_event_callback_del_full(obj, EVAS_CALLBACK_DEL, + _access_obj_del_cb, data); + ac = evas_object_data_get(data, "_elm_access"); + evas_object_data_del(data, "_elm_access"); + if (ac) + { + _elm_access_clear(ac); + free(ac); + } +} + +static void +_access_read_done(void *data __UNUSED__) +{ + printf("read done\n"); +} + +//-------------------------------------------------------------------------// + +EAPI void +_elm_access_clear(Elm_Access_Info *ac) +{ + Elm_Access_Item *ai; + + if (!ac) return; + if (ac->delay_timer) + { + ecore_timer_del(ac->delay_timer); + ac->delay_timer = NULL; + } + EINA_LIST_FREE(ac->items, ai) + { + if (!ai->func) + { + if (ai->data) eina_stringshare_del(ai->data); + } + free(ai); + } +} + +EAPI void +_elm_access_text_set(Elm_Access_Info *ac, int type, const char *text) +{ + Elm_Access_Item *ai = _access_add_set(ac, type); + if (!ai) return; + ai->data = eina_stringshare_add(text); +} + +EAPI void +_elm_access_callback_set(Elm_Access_Info *ac, int type, Elm_Access_Content_Cb func, const void *data) +{ + Elm_Access_Item *ai = _access_add_set(ac, type); + if (!ai) return; + ai->func = func; + ai->data = data; +} + +EAPI char * +_elm_access_text_get(Elm_Access_Info *ac, int type, Evas_Object *obj, Elm_Widget_Item *item) +{ + Elm_Access_Item *ai; + Eina_List *l; + + if (!ac) return NULL; + EINA_LIST_FOREACH(ac->items, l, ai) + { + if (ai->type == type) + { + if (ai->func) return ai->func(ai->data, obj, item); + else if (ai->data) return strdup(ai->data); + return NULL; + } + } + return NULL; +} + +EAPI void +_elm_access_read(Elm_Access_Info *ac, int type, Evas_Object *obj, Elm_Widget_Item *item) +{ + char *txt = _elm_access_text_get(ac, type, obj, item); + + _access_init(); + if (mapi) + { + if (mapi->out_done_callback_set) + mapi->out_done_callback_set(_access_read_done, NULL); + if (type == ELM_ACCESS_DONE) + { + if (mapi->out_read_done) mapi->out_read_done(); + } + else if (type == ELM_ACCESS_CANCEL) + { + if (mapi->out_cancel) mapi->out_cancel(); + } + else + { + if (txt) + { + if (mapi->out_read) mapi->out_read(txt); + if (mapi->out_read) mapi->out_read(".\n"); + } + } + } + if (txt) free(txt); +} + +EAPI Elm_Access_Info * +_elm_access_object_get(Evas_Object *obj) +{ + return evas_object_data_get(obj, "_elm_access"); +} + +EAPI void +_elm_access_object_register(Evas_Object *obj, Evas_Object *hoverobj) +{ + Elm_Access_Info *ac; + + evas_object_event_callback_add(hoverobj, EVAS_CALLBACK_MOUSE_IN, + _access_obj_mouse_in_cb, obj); + evas_object_event_callback_add(hoverobj, EVAS_CALLBACK_MOUSE_OUT, + _access_obj_mouse_out_cb, obj); + evas_object_event_callback_add(hoverobj, EVAS_CALLBACK_DEL, + _access_obj_del_cb, obj); + ac = calloc(1, sizeof(Elm_Access_Info)); + evas_object_data_set(obj, "_elm_access", ac); +} + +// XXX special version for items +//EAPI void +//_elm_access_item_hover_register(Elm_Widget_Item *item, Evas_Object *hoverobj) +//{ +//} diff --git a/src/lib/elm_button.c b/src/lib/elm_button.c index 1a4ae1d..aba0869 100644 --- a/src/lib/elm_button.c +++ b/src/lib/elm_button.c @@ -316,6 +316,14 @@ _elm_button_label_get(const Evas_Object *obj, const char *item) return wd->label; } +static char * +_access_info_cb(const void *data, Evas_Object *obj, Elm_Widget_Item *item) +{ + const char *txt = _elm_button_label_get(obj, NULL); + if (txt) return strdup(txt); + return txt; +} + EAPI Evas_Object * elm_button_add(Evas_Object *parent) { @@ -359,6 +367,12 @@ elm_button_add(Evas_Object *parent) // TODO: convert Elementary to subclassing of Evas_Smart_Class // TODO: and save some bytes, making descriptions per-class and not instance! evas_object_smart_callbacks_descriptions_set(obj, _signals); + + _elm_access_object_register(obj, wd->btn); + _elm_access_text_set(_elm_access_object_get(obj), + ELM_ACCESS_TYPE, E_("Button")); + _elm_access_callback_set(_elm_access_object_get(obj), + ELM_ACCESS_INFO, _access_info_cb, obj); return obj; } diff --git a/src/lib/elm_widget.h b/src/lib/elm_widget.h index 5d17bb9..9706e49 100644 --- a/src/lib/elm_widget.h +++ b/src/lib/elm_widget.h @@ -192,11 +192,45 @@ typedef struct _Elm_Tooltip Elm_Tooltip; typedef struct _Elm_Cursor Elm_Cursor; typedef struct _Elm_Widget_Item Elm_Widget_Item; /**< base structure for all widget items that are not Elm_Widget themselves */ -typedef void (*Elm_Widget_On_Text_Set_Cb)(void *, const char *part, const char *text); -typedef void (*Elm_Widget_On_Content_Set_Cb)(void *, const char *part, Evas_Object *content); -typedef const char *(*Elm_Widget_On_Text_Get_Cb)(const void *, const char *part); -typedef Evas_Object *(*Elm_Widget_On_Content_Get_Cb)(const void *, const char *part); -typedef Evas_Object *(*Elm_Widget_On_Content_Unset_Cb)(const void *, const char *part); +typedef struct _Elm_Access_Info Elm_Access_Info; /**< accessibility information to be able to set and get from the access API */ +typedef struct _Elm_Access_Item Elm_Access_Item; /**< accessibility info item */ + +typedef void (*Elm_Widget_On_Text_Set_Cb)(void *data, const char *part, const char *text); +typedef void (*Elm_Widget_On_Content_Set_Cb)(void *data, const char *part, Evas_Object *content); +typedef const char *(*Elm_Widget_On_Text_Get_Cb)(const void *data, const char *part); +typedef Evas_Object *(*Elm_Widget_On_Content_Get_Cb)(const void *data, const char *part); +typedef Evas_Object *(*Elm_Widget_On_Content_Unset_Cb)(const void *data, const char *part); + +#define ELM_ACCESS_TYPE 0 // when reading out widget or item this is read first +#define ELM_ACCESS_INFO 1 // next read is info - this is normally label +#define ELM_ACCESS_STATE 2 // if there is a state (eg checkbox) then read state out +#define ELM_ACCESS_CONTENT 3 // read ful content - eg all of the label, not a shortened version + +#define ELM_ACCESS_DONE -1 // sentence done - send done event here +#define ELM_ACCESS_CANCEL -2 // stop reading immediately + +typedef char *(*Elm_Access_Content_Cb)(const void *data, Evas_Object *obj, Elm_Widget_Item *item); + +struct _Elm_Access_Item +{ + int type; + const void *data; + Elm_Access_Content_Cb func; +}; + +struct _Elm_Access_Info +{ + Eina_List *items; + Ecore_Timer *delay_timer; +}; + +EAPI void _elm_access_clear(Elm_Access_Info *ac); +EAPI void _elm_access_text_set(Elm_Access_Info *ac, int type, const char *text); +EAPI void _elm_access_callback_set(Elm_Access_Info *ac, int type, Elm_Access_Content_Cb func, const void *data); +EAPI char *_elm_access_text_get(Elm_Access_Info *ac, int type, Evas_Object *obj, Elm_Widget_Item *item); +EAPI void _elm_access_read(Elm_Access_Info *ac, int type, Evas_Object *obj, Elm_Widget_Item *item); +EAPI Elm_Access_Info *_elm_access_object_get(Evas_Object *obj); +EAPI void _elm_access_object_register(Evas_Object *obj, Evas_Object *hoverobj); struct _Elm_Widget_Item { diff --git a/src/modules/Makefile.am b/src/modules/Makefile.am index b00ad7e..9354af6 100644 --- a/src/modules/Makefile.am +++ b/src/modules/Makefile.am @@ -3,4 +3,5 @@ MAINTAINERCLEANFILES = Makefile.in SUBDIRS = \ test_entry \ -test_map +test_map \ +access_output diff --git a/src/modules/access_output/Makefile.am b/src/modules/access_output/Makefile.am new file mode 100644 index 0000000..0cf2187 --- /dev/null +++ b/src/modules/access_output/Makefile.am @@ -0,0 +1,33 @@ + +MAINTAINERCLEANFILES = Makefile.in + +AM_CPPFLAGS = \ +-I. \ +-I$(top_builddir) \ +-I$(top_srcdir) \ +-I$(top_srcdir)/src/lib \ +-I$(top_builddir)/src/lib \ +-DPACKAGE_DATA_DIR=\"$(datadir)/$(PACKAGE)\" \ +-DPACKAGE_LIB_DIR=\"$(libdir)\" \ +@ELEMENTARY_CFLAGS@ \ +@ELEMENTARY_X_CFLAGS@ \ +@ELEMENTARY_FB_CFLAGS@ \ +@ELEMENTARY_WIN32_CFLAGS@ \ +@ELEMENTARY_WINCE_CFLAGS@ \ +@ELEMENTARY_EDBUS_CFLAGS@ \ +@ELEMENTARY_EFREET_CFLAGS@ \ +@ELEMENTARY_ETHUMB_CFLAGS@ \ +@ELEMENTARY_EMAP_CFLAGS@ + +if ELEMENTARY_WINDOWS_BUILD +AM_CPPFLAGS += -DELEMENTARY_BUILD +endif + +pkgdir = $(libdir)/elementary/modules/access_output/$(MODULE_ARCH) +pkg_LTLIBRARIES = module.la + +module_la_SOURCES = mod.c + +module_la_LIBADD = $(top_builddir)/src/lib/libelementary.la +module_la_LDFLAGS = -no-undefined @lt_enable_auto_import@ -module -avoid-version +module_la_LIBTOOLFLAGS = --tag=disable-static diff --git a/src/modules/access_output/mod.c b/src/modules/access_output/mod.c new file mode 100644 index 0000000..79e9799 --- /dev/null +++ b/src/modules/access_output/mod.c @@ -0,0 +1,116 @@ +#include +#ifdef HAVE_CONFIG_H +# include "elementary_config.h" +#endif + +/* to enable this module +export ELM_MODULES="access_output>access/api" + */ + +static void (*cb_func) (void *data); +static void *cb_data; +static Ecore_Exe *espeak = NULL; +static Ecore_Event_Handler *exe_exit_handler = NULL; +static char *tmpf = NULL; +static int tmpfd = -1; + +static Eina_Bool +_exe_del(void *data __UNUSED__, int type __UNUSED__, void *event) +{ + Ecore_Exe_Event_Del *ev = event; + + if ((espeak) && (ev->exe == espeak)) + { + if (tmpf) + { + unlink(tmpf); + free(tmpf); + tmpf = NULL; + close(tmpfd); + } + espeak = NULL; + if (cb_func) cb_func(cb_data); + } + return ECORE_CALLBACK_RENEW; +} + +// module api funcs needed +EAPI int +elm_modapi_init(void *m __UNUSED__) +{ + exe_exit_handler = + ecore_event_handler_add(ECORE_EXE_EVENT_DEL, + _exe_del, NULL); + return 1; // succeed always +} + +EAPI int +elm_modapi_shutdown(void *m __UNUSED__) +{ + if (exe_exit_handler) + { + ecore_event_handler_del(exe_exit_handler); + exe_exit_handler = NULL; + } + return 1; // succeed always +} + +// module fucns for the specific module type +EAPI void +out_read(const char *txt) +{ + if (!tmpf) + { + char buf[PATH_MAX]; + + snprintf(buf, sizeof(buf), "/tmp/.elm-speak-XXXXXX"); + tmpfd = mkstemp(buf); + if (tmpfd >= 0) tmpf = strdup(buf); + else return; + } + if (write(tmpfd, txt, strlen(txt)) < 0) perror("write to tmpfile (espeak)"); +} + +EAPI void +out_read_done(void) +{ + char buf[PATH_MAX]; + + if (espeak) + { + ecore_exe_interrupt(espeak); + espeak = NULL; + } + if (tmpf) + { + close(tmpfd); + snprintf(buf, sizeof(buf), "espeak -m -a 20 -f %s", tmpf); + espeak = ecore_exe_pipe_run(buf, + ECORE_EXE_NOT_LEADER, + NULL); + } +} + +EAPI void +out_cancel(void) +{ + if (espeak) + { + ecore_exe_interrupt(espeak); + espeak = NULL; + } + if (tmpf) + { + unlink(tmpf); + free(tmpf); + tmpf = NULL; + close(tmpfd); + } +} + +EAPI void +out_done_callback_set(void (*func) (void *data), const void *data) +{ + cb_func = func; + cb_data = (void *)data; +}