Drag & Drop: add overlapping feature.
authorDaniel Zaoui <daniel.zaoui@samsung.com>
Sun, 13 Oct 2013 03:56:04 +0000 (06:56 +0300)
committerDaniel Zaoui <daniel.zaoui@samsung.com>
Mon, 21 Oct 2013 16:04:42 +0000 (19:04 +0300)
This feature is essential if two overlapping widgets can receive drop
information.
Until now, if two widgets (e.g background and button) were added as drop
targets, pointing to common coordinates would have given priority to the
first inserted as drop target.
Now, it will determine which widget is supposed to receive this drop
target by using the same mechanism as used for mouse move, i.e by
walking on the objects tree whose pointer passes through.

A test has been added (Overlapping DnD) to show how this feature can be
used. You can drop in bg, box and button.

src/bin/test.c
src/bin/test_dnd.c
src/lib/elm_cnp.c

index 88a22ec..e6c3707 100644 (file)
@@ -228,6 +228,7 @@ void test_web_mobile(void *data, Evas_Object *obj, void *event_info);
 void test_dnd_genlist_default_anim(void *data, Evas_Object *obj, void *event_info);
 void test_dnd_genlist_user_anim(void *data, Evas_Object *obj, void *event_info);
 void test_dnd_genlist_gengrid(void *data, Evas_Object *obj, void *event_info);
+void test_dnd_overlapping(void *data, Evas_Object *obj, void *event_info);
 void test_task_switcher(void *data, Evas_Object *obj, void *event_info);
 void test_application_server_message(void *data, Evas_Object *obj, void *event_info);
 void test_application_server_phone(void *data, Evas_Object *obj, void *event_info);
@@ -791,6 +792,7 @@ add_tests:
    ADD_TEST(NULL, "Drag & Drop", "Genlist DnD Dflt Anim", test_dnd_genlist_default_anim);
    ADD_TEST(NULL, "Drag & Drop", "Genlist DnD User Anim", test_dnd_genlist_user_anim);
    ADD_TEST(NULL, "Drag & Drop", "Genlist-Gengrid DnD", test_dnd_genlist_gengrid);
+   ADD_TEST(NULL, "Drag & Drop", "Overlapping DnD", test_dnd_overlapping);
 
    //------------------------------//
    ADD_TEST(NULL, "Miscellaneous", "Copy And Paste", test_cnp);
index c17d8de..443970d 100644 (file)
@@ -89,7 +89,7 @@ _gl_item_getcb(Evas_Object *obj, Evas_Coord x, Evas_Coord y, int *xposret EINA_U
    gli = elm_genlist_at_xy_item_get(obj, x, y, yposret);
    if (gli)
      printf("over <%s>, gli=<%p> yposret %i\n",
-           elm_object_item_part_text_get(gli, "elm.text"), gli, *yposret);
+           (char *)elm_object_item_data_get(gli), gli, *yposret);
    else
      printf("over none, yposret %i\n", *yposret);
    return gli;
@@ -103,7 +103,7 @@ _grid_item_getcb(Evas_Object *obj, Evas_Coord x, Evas_Coord y, int *xposret, int
    item = elm_gengrid_at_xy_item_get(obj, x, y, xposret, yposret);
    if (item)
      printf("over <%s>, item=<%p> xposret %i yposret %i\n",
-           elm_object_item_part_text_get(item, "elm.text"), item, *xposret, *yposret);
+           (char *)elm_object_item_data_get(item), item, *xposret, *yposret);
    else
      printf("over none, xposret %i yposret %i\n", *xposret, *yposret);
    return item;
@@ -490,7 +490,7 @@ _gl_get_drag_data(Evas_Object *obj, Elm_Object_Item *it, Eina_List **items)
 
         EINA_LIST_FOREACH(*items, l, it)
           {
-             t = elm_object_item_part_text_get(it, "elm.text");
+             t = (char *)elm_object_item_data_get(it);
              if (t)
                len += strlen(t);
           }
@@ -500,7 +500,7 @@ _gl_get_drag_data(Evas_Object *obj, Elm_Object_Item *it, Eina_List **items)
 
         EINA_LIST_FOREACH(*items, l, it)
           {
-             t = elm_object_item_part_text_get(it, "elm.text");
+             t = (char *)elm_object_item_data_get(it);
              if (t)
                {
                   strcat((char *) drag_data, "#");
@@ -537,7 +537,7 @@ _grid_get_drag_data(Evas_Object *obj, Elm_Object_Item *it, Eina_List **items)
 
         EINA_LIST_FOREACH(*items, l, it)
           {
-             t = elm_object_item_part_text_get(it, "elm.text");
+             t = (char *)elm_object_item_data_get(it);
              if (t)
                len += strlen(t);
           }
@@ -547,7 +547,7 @@ _grid_get_drag_data(Evas_Object *obj, Elm_Object_Item *it, Eina_List **items)
 
         EINA_LIST_FOREACH(*items, l, it)
           {
-             t = elm_object_item_part_text_get(it, "elm.text");
+             t = (char *)elm_object_item_data_get(it);
              if (t)
                {
                   strcat((char *) drag_data, "#");
@@ -892,3 +892,144 @@ test_dnd_genlist_gengrid(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, v
    evas_object_show(win);
 }
 
+static Eina_Bool _drop_box_button_new_cb(void *data, Evas_Object *obj, Elm_Selection_Data *ev)
+{
+   Evas_Object *win = data;
+   char *p = strchr(ev->data, '#');
+   while(p)
+     {
+        p++;
+        char *p2 = strchr(p, '#');
+        if (p2)
+          {
+             *p2 = '\0';
+             Evas_Object *ic = elm_icon_add(win);
+             elm_image_file_set(ic, p, NULL);
+             evas_object_size_hint_aspect_set(ic, EVAS_ASPECT_CONTROL_VERTICAL, 1, 1);
+             Evas_Object *bt = elm_button_add(win);
+             elm_object_text_set(bt, "Dropped button");
+             elm_object_part_content_set(bt, "icon", ic);
+             elm_box_pack_end(obj, bt);
+             evas_object_show(bt);
+             evas_object_show(ic);
+             p = p2;
+          }
+        else p = NULL;
+     }
+   return EINA_TRUE;
+}
+
+static Eina_Bool _drop_but_icon_change_cb(void *data, Evas_Object *obj, Elm_Selection_Data *ev)
+{
+   Evas_Object *win = data;
+   Evas_Object *ic = elm_icon_add(win);
+   char *p = strchr(ev->data, '#');
+   p++;
+   char *p2 = strchr(p, '#');
+   *p2 = '\0';
+   elm_image_file_set(ic, p, NULL);
+   evas_object_size_hint_aspect_set(ic, EVAS_ASPECT_CONTROL_VERTICAL, 1, 1);
+   evas_object_del(elm_object_part_content_get(obj, "icon"));
+   elm_object_part_content_set(obj, "icon", ic);
+   evas_object_show(ic);
+   return EINA_TRUE;
+}
+
+static Eina_Bool _drop_bg_change_cb(void *data EINA_UNUSED, Evas_Object *obj, Elm_Selection_Data *ev)
+{
+   char *p = strchr(ev->data, '#');
+   p++;
+   char *p2 = strchr(p, '#');
+   *p2 = '\0';
+   elm_bg_file_set(obj, p, NULL);
+   return EINA_TRUE;
+}
+
+void
+test_dnd_overlapping(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
+{
+   char buf[PATH_MAX];
+   Evas_Object *win, *bxx, *bg;
+   int i;
+
+   win = elm_win_util_standard_add("dnd-overlapping", "DnD-Overlapping");
+   elm_win_autodel_set(win, EINA_TRUE);
+
+   bg = elm_bg_add(win);
+   evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+   elm_drop_target_add(bg, ELM_SEL_FORMAT_TARGETS, NULL, NULL, NULL, NULL, NULL, NULL, _drop_bg_change_cb, NULL);
+   elm_win_resize_object_add(win, bg);
+
+   /* And show the background. */
+   evas_object_show(bg);
+   bxx = elm_box_add(win);
+   elm_box_horizontal_set(bxx, EINA_TRUE);
+   evas_object_size_hint_weight_set(bxx, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+   elm_win_resize_object_add(win, bxx);
+   evas_object_show(bxx);
+
+     {
+        Evas_Object *grid = elm_gengrid_add(bxx);
+        evas_object_smart_callback_add(win, "delete,request", _win_del, grid);
+        elm_gengrid_item_size_set(grid,
+              elm_config_scale_get() * 100,
+              elm_config_scale_get() * 100);
+        elm_gengrid_horizontal_set(grid, EINA_FALSE);
+        elm_gengrid_reorder_mode_set(grid, EINA_FALSE);
+        elm_gengrid_multi_select_set(grid, EINA_TRUE); /* We allow multi drag */
+        evas_object_size_hint_weight_set(grid, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+        evas_object_size_hint_align_set(grid, EVAS_HINT_FILL, EVAS_HINT_FILL);
+
+        gic = elm_gengrid_item_class_new();
+        gic->item_style = "default";
+        gic->func.text_get = gl_text_get;
+        gic->func.content_get = gl_content_get;
+
+        elm_drag_item_container_add(grid, ANIM_TIME, DRAG_TIMEOUT,
+              _grid_item_getcb, _grid_data_getcb);
+        for (i = 0; i < 10; i++)
+          {
+             snprintf(buf, sizeof(buf), "%s/images/%s", elm_app_data_dir_get(), img[(i % 9)]);
+             const char *path = eina_stringshare_add(buf);
+             elm_gengrid_item_append(grid, gic, path, NULL, NULL);
+          }
+        elm_box_pack_end(bxx, grid);
+        evas_object_show(grid);
+     }
+
+     {
+        Evas_Object *ic, *bt;
+        Evas_Object *vert_box = elm_box_add(bxx);
+        evas_object_size_hint_weight_set(vert_box, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
+        elm_box_pack_end(bxx, vert_box);
+        evas_object_show(vert_box);
+        elm_drop_target_add(vert_box, ELM_SEL_FORMAT_TARGETS, NULL, NULL, NULL, NULL, NULL, NULL, _drop_box_button_new_cb, win);
+
+        ic = elm_icon_add(win);
+        snprintf(buf, sizeof(buf), "%s/images/logo_small.png", elm_app_data_dir_get());
+        elm_image_file_set(ic, buf, NULL);
+        evas_object_size_hint_aspect_set(ic, EVAS_ASPECT_CONTROL_VERTICAL, 1, 1);
+        bt = elm_button_add(win);
+        elm_object_text_set(bt, "Drop into me to change my icon");
+        elm_drop_target_add(bt, ELM_SEL_FORMAT_TARGETS, NULL, NULL, NULL, NULL, NULL, NULL, _drop_but_icon_change_cb, win);
+        elm_object_part_content_set(bt, "icon", ic);
+        elm_box_pack_end(vert_box, bt);
+        evas_object_show(bt);
+        evas_object_show(ic);
+
+        ic = elm_icon_add(win);
+        snprintf(buf, sizeof(buf), "%s/images/logo_small.png", elm_app_data_dir_get());
+        elm_image_file_set(ic, buf, NULL);
+        evas_object_size_hint_aspect_set(ic, EVAS_ASPECT_CONTROL_VERTICAL, 1, 1);
+        bt = elm_button_add(win);
+        elm_object_text_set(bt, "No action on drop");
+        elm_object_part_content_set(bt, "icon", ic);
+        elm_object_disabled_set(bt, EINA_TRUE);
+        elm_box_pack_end(vert_box, bt);
+        evas_object_show(bt);
+        evas_object_show(ic);
+     }
+   evas_object_resize(win, 680, 800);
+   evas_object_show(win);
+}
+
index 5d02dcc..ee6497e 100644 (file)
@@ -7,9 +7,9 @@
 # include <sys/mman.h>
 #endif
 
-//#define DEBUGON 1
+#define DEBUGON 1
 #ifdef DEBUGON
-# define cnp_debug(x...) fprintf(stderr, __FILE__": " x)
+# define cnp_debug(fmt, args...) fprintf(stderr, __FILE__":%s : " fmt , __FUNCTION__, ##args)
 #else
 # define cnp_debug(x...) do { } while (0)
 #endif
@@ -1136,21 +1136,52 @@ _x11_dropable_find(Ecore_X_Window win)
 static Dropable *
 _x11_dropable_geom_find(Ecore_X_Window win, Evas_Coord px, Evas_Coord py)
 {
-   Eina_List *l;
-   Dropable *dropable;
-   Evas_Coord x, y, w, h;
+   Eina_List *itr, *top_objects_list = NULL;
+   Evas *evas = NULL;
+   Evas_Object *top_obj;
+   Dropable *dropable = NULL;
 
    if (!drops) return NULL;
-   EINA_LIST_FOREACH(drops, l, dropable)
+   /* Find the Evas connected to the window */
+   EINA_LIST_FOREACH(drops, itr, dropable)
      {
         if (_x11_elm_widget_xwin_get(dropable->obj) == win)
           {
-             evas_object_geometry_get(dropable->obj, &x, &y, &w, &h);
-             if ((px >= x) && (py >= y) && (px < (x + w)) && (py < (y + h)))
-               return dropable;
+             evas = evas_object_evas_get(dropable->obj);
+             break;
           }
      }
-   return NULL;
+   if (!evas) return NULL;
+
+   /* We retrieve the (non-smart) objects pointed by (px, py) */
+   top_objects_list = evas_tree_objects_at_xy_get(evas, NULL, px, py);
+   /* We walk on this list from the last because if the list contains more than one
+    * element, all but the last will repeat events. The last one can repeat events
+    * or not. Anyway, this last one is the first that has to be taken into account
+    * for the determination of the drop target.
+    */
+   EINA_LIST_REVERSE_FOREACH(top_objects_list, itr, top_obj)
+     {
+        Evas_Object *object = top_obj;
+        /* We search for the dropable data into the object. If not found, we search into its parent.
+         * For example, if a button is a drop target, the first object will be an (internal) image.
+         * The drop target is attached to the button, i.e to image's parent. That's why we need to
+         * walk on the parents until NULL.
+         * If we find this dropable data, we found our drop target.
+         */
+        while (object)
+          {
+             eo_do(object, eo_base_data_get("__elm_dropable", (void **)&dropable));
+             if (dropable)
+                goto end;
+             else
+                object = evas_object_smart_parent_get(object);
+          }
+     }
+end:
+   eina_list_free(top_objects_list);
+   if (dropable) cnp_debug("Drop target %p of type %s found\n", dropable->obj, eo_class_name_get(eo_class_get(dropable->obj)));
+   return dropable;
 }
 
 static void
@@ -1352,7 +1383,7 @@ _x11_dnd_position(void *data __UNUSED__, int etype __UNUSED__, void *ev)
         else
           {
              ecore_x_dnd_send_status(EINA_FALSE, EINA_FALSE, rect, pos->action);
-             cnp_debug("dnd position not in obj\n");
+             cnp_debug("dnd position (%d, %d) not in obj\n", x, y);
              _x11_dnd_dropable_handle(dropable_old, 0, 0, EINA_FALSE,
                                       act);
              // CCCCCCC: call dnd exit on last obj
@@ -1459,7 +1490,7 @@ found:
                   snprintf(entrytag, len + 1, tagstring, savedtypes.imgfile);
                   ddata.data = entrytag;
                   cnp_debug("Insert %s\n", (char *)ddata.data);
-                  dropable->dropcb(dropable->cbdata, dropable->obj, &ddata);
+                  if (dropable->dropcb) dropable->dropcb(dropable->cbdata, dropable->obj, &ddata);
                   ecore_x_dnd_send_finished();
                   if (savedtypes.imgfile) free(savedtypes.imgfile);
                   savedtypes.imgfile = NULL;
@@ -1810,6 +1841,7 @@ _x11_elm_drop_target_add(Evas_Object *obj, Elm_Sel_Format format,
    /* Create new drop */
    drop = calloc(1, sizeof(Dropable));
    if (!drop) return EINA_FALSE;
+
    /* FIXME: Check for eina's deranged error method */
    drops = eina_list_append(drops, drop);
 
@@ -1829,6 +1861,7 @@ _x11_elm_drop_target_add(Evas_Object *obj, Elm_Sel_Format format,
    drop->types = format;
    drop->obj = obj;
 
+   eo_do(obj, eo_base_data_set("__elm_dropable", drop, NULL));
    evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL,
                                   /* I love C and varargs */
                                   (Evas_Object_Event_Cb)elm_drop_target_del,
@@ -1856,28 +1889,25 @@ _x11_elm_drop_target_add(Evas_Object *obj, Elm_Sel_Format format,
 static Eina_Bool
 _x11_elm_drop_target_del(Evas_Object *obj)
 {
-   Dropable *drop, *del, *dropable;
-   Eina_List *item, *l;
+   Dropable *dropable;
+   Eina_List *l;
    Ecore_X_Window xwin;
    Eina_Bool have_drops = EINA_FALSE;
 
    _x11_elm_cnp_init();
 
-   del = NULL;
-   EINA_LIST_FOREACH(drops, item, drop)
+   eo_do(obj, eo_base_data_get("__elm_dropable", (void **)&dropable));
+   if (dropable)
      {
-        if (drop->obj == obj)
-          {
-             drops = eina_list_remove_list(drops, item);
-             del = drop;
-             break;
-          }
+        drops = eina_list_remove(drops, dropable);
+        eo_do(obj, eo_base_data_del("__elm_dropable"));
+        free(dropable);
+        dropable = NULL;
      }
-   if (!del) return EINA_FALSE;
+   else return EINA_FALSE;
 
    evas_object_event_callback_del(obj, EVAS_CALLBACK_FREE,
                                   (Evas_Object_Event_Cb)elm_drop_target_del);
-   if (del) free(del);
 
    /* TODO BUG: we should handle dnd-aware per window, not just the last that reelased it */
 
@@ -3589,7 +3619,7 @@ _cont_drag_done_cb(void *data, Evas_Object *obj __UNUSED__)
 static Eina_Bool
 _cont_obj_drag_start(void *data)
 {  /* Start a drag-action when timer expires */
-   cnp_debug("%s In\n", __FUNCTION__);
+   cnp_debug("In\n");
    Item_Container_Drag_Info *st = data;
    st->tm = NULL;
    Elm_Drag_User_Info *info = &st->user_info;
@@ -3646,7 +3676,7 @@ _anim_icons_make(Eina_List *icons)
 static Eina_Bool
 _drag_anim_play(void *data, double pos)
 {  /* Impl of the animation of icons, called on frame time */
-   cnp_debug("%s In\n", __FUNCTION__);
+   cnp_debug("In\n");
    Item_Container_Drag_Info *st = data;
    Eina_List *l;
    Anim_Icon *sti;
@@ -3685,7 +3715,7 @@ _drag_anim_play(void *data, double pos)
 static inline Eina_Bool
 _drag_anim_start(void *data)
 {  /* Start default animation */
-   cnp_debug("%s In\n", __FUNCTION__);
+   cnp_debug("In\n");
    Item_Container_Drag_Info *st = data;
 
    st->tm = NULL;
@@ -3707,7 +3737,7 @@ _drag_anim_start(void *data)
 static Eina_Bool
 _cont_obj_anim_start(void *data)
 {  /* Start a drag-action when timer expires */
-   cnp_debug("%s In\n", __FUNCTION__);
+   cnp_debug("In\n");
    Item_Container_Drag_Info *st = data;
    int xposret, yposret;  /* Unused */
    Elm_Object_Item *it = (st->itemgetcb) ?
@@ -3753,7 +3783,7 @@ static void
 _cont_obj_mouse_down(void *data, Evas *e, Evas_Object *obj __UNUSED__, void *event_info)
 {  /* Launch a timer to start dragging */
    Evas_Event_Mouse_Down *ev = event_info;
-   cnp_debug("%s In - event %X\n", __FUNCTION__, ev->event_flags);
+   cnp_debug("In - event %X\n", ev->event_flags);
    if (ev->button != 1)
      return;  /* We only process left-click at the moment */
 
@@ -3778,10 +3808,10 @@ static Eina_Bool elm_drag_item_container_del_internal(Evas_Object *obj, Eina_Boo
 static void
 _cont_obj_mouse_move(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__, void *event_info)
 {  /* Cancel any drag waiting to start on timeout */
-   cnp_debug("%s In\n", __FUNCTION__);
+   cnp_debug("In\n");
    if (((Evas_Event_Mouse_Move *)event_info)->event_flags & EVAS_EVENT_FLAG_ON_HOLD)
      {
-        cnp_debug("%s event on hold - have to cancel DnD\n", __FUNCTION__);
+        cnp_debug("event on hold - have to cancel DnD\n");
         Item_Container_Drag_Info *st = data;
 
         evas_object_event_callback_del_full
@@ -3794,7 +3824,7 @@ _cont_obj_mouse_move(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__
 
         _anim_st_free(st);
      }
-   cnp_debug("%s Out\n", __FUNCTION__);
+   cnp_debug("Out\n");
 }
 
 static void
@@ -3802,7 +3832,7 @@ _cont_obj_mouse_up(void *data, Evas *e __UNUSED__, Evas_Object *obj __UNUSED__,
 {  /* Cancel any drag waiting to start on timeout */
    Item_Container_Drag_Info *st = data;
 
-   cnp_debug("%s In\n", __FUNCTION__);
+   cnp_debug("In\n");
    if (((Evas_Event_Mouse_Up *)event_info)->button != 1)
      return;  /* We only process left-click at the moment */