this is... the beginning of accessibility supportin elm. it's direct
authorCarsten Haitzler <raster@rasterman.com>
Fri, 19 Aug 2011 11:07:42 +0000 (11:07 +0000)
committerCarsten Haitzler <raster@rasterman.com>
Fri, 19 Aug 2011 11:07:42 +0000 (11:07 +0000)
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.

SVN revision: 62585

configure.ac
data/themes/widgets/label.edc
src/lib/Makefile.am
src/lib/elm_access.c [new file with mode: 0644]
src/lib/elm_button.c
src/lib/elm_widget.h
src/modules/Makefile.am
src/modules/access_output/Makefile.am [new file with mode: 0644]
src/modules/access_output/mod.c [new file with mode: 0644]

index a768c0f..dae927a 100644 (file)
@@ -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
index 681d2d2..cc190a8 100644 (file)
@@ -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;
index 9b6fb2e..5a52be5 100644 (file)
@@ -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 (file)
index 0000000..d441d63
--- /dev/null
@@ -0,0 +1,245 @@
+#include <Elementary.h>
+#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)
+//{
+//}
index 1a4ae1d..aba0869 100644 (file)
@@ -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;
 }
 
index 5d17bb9..9706e49 100644 (file)
@@ -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
 {
index b00ad7e..9354af6 100644 (file)
@@ -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 (file)
index 0000000..0cf2187
--- /dev/null
@@ -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 (file)
index 0000000..79e9799
--- /dev/null
@@ -0,0 +1,116 @@
+#include <Elementary.h>
+#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;
+}