Merge branch 'elmdnd'
Conflicts: trunk/TMP/st/elementary/.gitignore SVN revision: 51935
This commit is contained in:
parent
31ed630554
commit
0d492dcdaf
|
@ -1,5 +1,5 @@
|
|||
*.o
|
||||
*.lo
|
||||
*.o
|
||||
*.la
|
||||
.libs
|
||||
.deps
|
||||
|
@ -30,3 +30,14 @@ elementary_testql
|
|||
/ltmain.sh
|
||||
/missing
|
||||
/stamp-h1
|
||||
core
|
||||
cscope.out
|
||||
doc/elementary.dox
|
||||
elementary.spec
|
||||
m4/libtool.m4
|
||||
m4/ltoptions.m4
|
||||
m4/ltsugar.m4
|
||||
m4/ltversion.m4
|
||||
m4/lt~obsolete.m4
|
||||
src/lib/Elementary.h
|
||||
tags
|
||||
|
|
|
@ -80,7 +80,8 @@ test_multi.c \
|
|||
test_floating.c \
|
||||
test_launcher.c \
|
||||
test_anim.c \
|
||||
test_calendar.c
|
||||
test_calendar.c \
|
||||
test_drag.c
|
||||
|
||||
elementary_test_LDADD = $(top_builddir)/src/lib/libelementary.la @ELEMENTARY_EWEATHER_LIBS@
|
||||
elementary_test_LDFLAGS =
|
||||
|
|
|
@ -81,6 +81,9 @@ void test_launcher2(void *data, Evas_Object *obj, void *event_info);
|
|||
void test_launcher3(void *data, Evas_Object *obj, void *event_info);
|
||||
void test_anim(void *data, Evas_Object *obj, void *event_info);
|
||||
|
||||
void test_drag_source(void *data, Evas_Object *obj, void *event_info);
|
||||
void test_drag_dest(void *data, Evas_Object *obj, void *event_info);
|
||||
void test_drag_genlist(void *data, Evas_Object *obj, void *event_info);
|
||||
|
||||
struct elm_test
|
||||
{
|
||||
|
@ -202,6 +205,10 @@ my_win_main(void)
|
|||
|
||||
tests = NULL;
|
||||
#define ADD_TEST(name_, cb_) elm_test_add(&tests, name_, cb_)
|
||||
ADD_TEST("Drag Source", test_drag_source);
|
||||
ADD_TEST("Drag Destination", test_drag_dest);
|
||||
ADD_TEST("Drag GenList", test_drag_genlist);
|
||||
|
||||
ADD_TEST("Bg Plain", test_bg_plain);
|
||||
ADD_TEST("Bg Image", test_bg_image);
|
||||
ADD_TEST("Icon Transparent", test_icon);
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
#include <stdbool.h>
|
||||
|
||||
#include <Ecore_X.h>
|
||||
#include <Elementary.h>
|
||||
#include "elm_priv.h"
|
||||
/* nash: I have NFI what this does: Just copying the other tests */
|
||||
#ifndef ELM_LIB_QUICKLAUNCH
|
||||
|
||||
|
||||
#define IM "/home/nash/work/samsung/autopaste/images/"
|
||||
static const char *images[] = {
|
||||
IM "cow.jpg",
|
||||
IM "img_3104.jpg",
|
||||
IM"xmas.jpg",
|
||||
IM"river.jpg"
|
||||
};
|
||||
#define N_IMAGES 4
|
||||
|
||||
|
||||
void
|
||||
test_drag_source(void *data, Evas_Object *obj, void *eventinfo){
|
||||
Evas_Object *win, *bg, *bx, *ph, *ctrls;
|
||||
int i;
|
||||
|
||||
win = elm_win_add(NULL, "drag", ELM_WIN_BASIC);
|
||||
elm_win_title_set(win, "Drag Source");
|
||||
elm_win_autodel_set(win, 1);
|
||||
|
||||
bg = elm_bg_add(win);
|
||||
evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND,EVAS_HINT_EXPAND);
|
||||
elm_win_resize_object_add(win, bg);
|
||||
evas_object_show(bg);
|
||||
|
||||
bx = elm_box_add(win);
|
||||
evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND,EVAS_HINT_EXPAND);
|
||||
elm_win_resize_object_add(win, bx);
|
||||
evas_object_show(bx);
|
||||
|
||||
ctrls = elm_box_add(win);
|
||||
elm_box_horizontal_set(ctrls, true);
|
||||
evas_object_size_hint_weight_set(ctrls, EVAS_HINT_EXPAND, 0);
|
||||
evas_object_size_hint_align_set(ctrls, EVAS_HINT_FILL, EVAS_HINT_FILL);
|
||||
elm_box_pack_end(bx, ctrls);
|
||||
evas_object_show(ctrls);
|
||||
|
||||
|
||||
for (i = 0 ; i < N_IMAGES ; i ++){
|
||||
ph = elm_photo_add(win);
|
||||
elm_photo_file_set(ph, images[i]);
|
||||
evas_object_size_hint_weight_set(ph, EVAS_HINT_EXPAND,
|
||||
EVAS_HINT_EXPAND);
|
||||
evas_object_size_hint_align_set(ph, EVAS_HINT_FILL,
|
||||
EVAS_HINT_FILL);
|
||||
elm_photo_size_set(ph, 80);
|
||||
elm_box_pack_end(ctrls, ph);
|
||||
evas_object_show(ph);
|
||||
// evas_object_data_set(ph,"URI",images[i]);
|
||||
// evas_object_smart_callback_add(ph, "clicked",
|
||||
// on_select_image, ph);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
evas_object_size_hint_min_set(bg, 468, 160);
|
||||
evas_object_resize(win, 320, 320);
|
||||
|
||||
evas_object_show(win);
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_dnd_enter(void *data, int etype, void *ev){
|
||||
printf("enter\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_dnd_leave(void *data, int etype, void *ev){
|
||||
printf("leave\n");
|
||||
return true;
|
||||
}
|
||||
static Eina_Bool
|
||||
_dnd_position(void *data, int etype, void *ev){
|
||||
struct _Ecore_X_Event_Xdnd_Position *pos;
|
||||
Ecore_X_Rectangle rect;
|
||||
|
||||
pos = ev;
|
||||
|
||||
printf("position: %3d,%3d, Action: %s\n",
|
||||
pos->position.x,pos->position.y,
|
||||
ecore_x_atom_name_get(pos->action));
|
||||
|
||||
/* Need to send a status back */
|
||||
rect.x = pos->position.x - 5;
|
||||
rect.y = pos->position.y - 5;
|
||||
rect.width = 10;
|
||||
rect.height = 10;
|
||||
ecore_x_dnd_send_status(true, false, rect, pos->action);
|
||||
|
||||
return true;
|
||||
}
|
||||
static Eina_Bool
|
||||
_dnd_status(void *data, int etype, void *ev){
|
||||
printf("status\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
Evas_Object *en;
|
||||
static Eina_Bool
|
||||
_dnd_drop(void *data, int etype, void *ev){
|
||||
struct _Ecore_X_Event_Xdnd_Drop *drop;
|
||||
bool rv;
|
||||
|
||||
drop = ev;
|
||||
printf("Drop: %3d,%3d, Action: %s\n",
|
||||
drop->position.x,drop->position.y,
|
||||
ecore_x_atom_name_get(drop->action));
|
||||
|
||||
rv = elm_selection_get(ELM_SEL_XDND, ELM_SEL_MARKUP, en);
|
||||
if (rv != true){
|
||||
printf("Selection set fail\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
static Eina_Bool
|
||||
_dnd_finish(void *data, int etype, void *ev){
|
||||
printf("finish\n");
|
||||
return true;
|
||||
}
|
||||
void
|
||||
test_drag_dest(void *data, Evas_Object *obj, void *event){
|
||||
Evas_Object *win, *bg, *bx;
|
||||
Ecore_Evas *ee;
|
||||
Ecore_X_Window xwin;
|
||||
|
||||
win = elm_win_add(NULL, "drag", ELM_WIN_BASIC);
|
||||
elm_win_title_set(win, "Drag Destination");
|
||||
elm_win_autodel_set(win, 1);
|
||||
|
||||
bg = elm_bg_add(win);
|
||||
evas_object_size_hint_weight_set(bg, EVAS_HINT_EXPAND,EVAS_HINT_EXPAND);
|
||||
elm_win_resize_object_add(win, bg);
|
||||
evas_object_show(bg);
|
||||
|
||||
evas_object_size_hint_min_set(bg, 160, 160);
|
||||
evas_object_resize(win, 480, 150);
|
||||
|
||||
bx = elm_box_add(win);
|
||||
evas_object_size_hint_weight_set(bx, EVAS_HINT_EXPAND,EVAS_HINT_EXPAND);
|
||||
elm_win_resize_object_add(win, bx);
|
||||
evas_object_show(bx);
|
||||
|
||||
en = elm_entry_add(win);
|
||||
elm_entry_line_wrap_set(en, 0);
|
||||
elm_entry_entry_set(en,"Your mum!");
|
||||
evas_object_size_hint_weight_set(en, EVAS_HINT_EXPAND,EVAS_HINT_EXPAND);
|
||||
evas_object_size_hint_align_set(en, EVAS_HINT_FILL, EVAS_HINT_FILL);
|
||||
elm_box_pack_end(bx, en);
|
||||
evas_object_show(en);
|
||||
|
||||
printf("Adding events\n");
|
||||
ecore_event_handler_add(ECORE_X_EVENT_XDND_ENTER, _dnd_enter, NULL);
|
||||
ecore_event_handler_add(ECORE_X_EVENT_XDND_POSITION, _dnd_position, NULL);
|
||||
ecore_event_handler_add(ECORE_X_EVENT_XDND_STATUS, _dnd_status, NULL);
|
||||
ecore_event_handler_add(ECORE_X_EVENT_XDND_LEAVE, _dnd_leave, NULL);
|
||||
ecore_event_handler_add(ECORE_X_EVENT_XDND_DROP, _dnd_drop, NULL);
|
||||
ecore_event_handler_add(ECORE_X_EVENT_XDND_FINISHED, _dnd_finish, NULL);
|
||||
|
||||
printf("Enabled DND\n");
|
||||
ee = ecore_evas_ecore_evas_get(evas_object_evas_get(win));
|
||||
xwin = (Ecore_X_Window)ecore_evas_window_get(ee);
|
||||
|
||||
ecore_x_dnd_aware_set(xwin, true);
|
||||
|
||||
evas_object_show(win);
|
||||
}
|
||||
|
||||
void
|
||||
test_drag_genlist(void *data, Evas_Object *obj, void *event){
|
||||
printf("No genlist yet\n");
|
||||
}
|
||||
|
||||
|
||||
#endif
|
|
@ -579,7 +579,7 @@ my_ent_bt_pas(void *data, Evas_Object *obj, void *event_info)
|
|||
void
|
||||
test_entry3(void *data, Evas_Object *obj, void *event_info)
|
||||
{
|
||||
Evas_Object *win, *bg, *bx, *bx2, *bt, *en, *en_p, *sp;
|
||||
Evas_Object *win, *bg, *bx, *bx2, *bt, *en;
|
||||
|
||||
win = elm_win_add(NULL, "entry3", ELM_WIN_BASIC);
|
||||
elm_win_title_set(win, "Entry 3");
|
||||
|
@ -1206,7 +1206,7 @@ test_entry4(void *data, Evas_Object *obj, void *event_info)
|
|||
void
|
||||
test_entry5(void *data, Evas_Object *obj, void *event_info)
|
||||
{
|
||||
Evas_Object *win, *bg, *bx, *bx2, *bt, *en, *en_p, *sp;
|
||||
Evas_Object *win, *bg, *bx, *bx2, *bt, *en;
|
||||
|
||||
win = elm_win_add(NULL, "entry5", ELM_WIN_BASIC);
|
||||
elm_win_title_set(win, "Entry 5");
|
||||
|
|
|
@ -123,7 +123,7 @@ test_gengrid(void *data, Evas_Object *obj, void *event_info)
|
|||
{
|
||||
Evas_Object *win, *bg, *grid;
|
||||
static Testitem ti[144];
|
||||
int i, j, n;
|
||||
int i, n;
|
||||
char buf[PATH_MAX];
|
||||
const char *img[9] =
|
||||
{
|
||||
|
|
|
@ -105,7 +105,7 @@ static Eina_Bool external_notify_param_set(void *data __UNUSED__,
|
|||
&& param->type == EDJE_EXTERNAL_PARAM_TYPE_CHOICE)
|
||||
{
|
||||
Elm_Notify_Orient set = _orient_get(param->s);
|
||||
if (set == ELM_NOTIFY_ORIENT_LAST) return;
|
||||
if (set == ELM_NOTIFY_ORIENT_LAST) return EINA_FALSE;
|
||||
elm_notify_orient_set(obj, set);
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include <stdbool.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <stdio.h> // debug
|
||||
|
||||
|
@ -26,6 +27,7 @@
|
|||
enum {
|
||||
CNP_ATOM_TARGETS = 0,
|
||||
CNP_ATOM_text_uri,
|
||||
CNP_ATOM_text_urilist,
|
||||
CNP_ATOM_image_png,
|
||||
CNP_ATOM_XELM,
|
||||
CNP_ATOM_text_html_utf8,
|
||||
|
@ -118,6 +120,8 @@ static int notify_handler_png(struct _elm_cnp_selection *sel,
|
|||
static int notify_handler_uri(struct _elm_cnp_selection *sel,
|
||||
Ecore_X_Event_Selection_Notify *notify);
|
||||
|
||||
static struct pasteimage *pasteimage_alloc(const char *file);
|
||||
static bool pasteimage_append(struct pasteimage *pi, Evas_Object *entry);
|
||||
|
||||
static struct {
|
||||
const char *name;
|
||||
|
@ -153,9 +157,17 @@ static struct {
|
|||
notify_handler_uri,
|
||||
0
|
||||
},
|
||||
[CNP_ATOM_text_urilist] = {
|
||||
"text/uri-list",
|
||||
ELM_SEL_IMAGE | ELM_SEL_MARKUP,
|
||||
uri_converter,
|
||||
NULL,
|
||||
notify_handler_uri,
|
||||
0
|
||||
},
|
||||
[CNP_ATOM_image_png] = {
|
||||
"image/png",
|
||||
ELM_SEL_IMAGE | ELM_SEL_IMAGE,
|
||||
ELM_SEL_IMAGE,
|
||||
png_converter,
|
||||
NULL,
|
||||
notify_handler_png,
|
||||
|
@ -240,9 +252,24 @@ static struct _elm_cnp_selection selections[ELM_SEL_MAX] = {
|
|||
.set = ecore_x_selection_clipboard_set,
|
||||
.clear = ecore_x_selection_clipboard_clear,
|
||||
.request = ecore_x_selection_clipboard_request,
|
||||
}
|
||||
},
|
||||
ARRAYINIT(ELM_SEL_XDND) {
|
||||
.debug = "XDnD",
|
||||
.ecore_sel = ECORE_X_SELECTION_XDND,
|
||||
.request = ecore_x_selection_xdnd_request,
|
||||
},
|
||||
};
|
||||
|
||||
/* Data for DND in progress */
|
||||
/* FIXME: BEtter name */
|
||||
struct {
|
||||
int ntypes;
|
||||
const char **types;
|
||||
unsigned int textreq: 1;
|
||||
struct pasteimage *pi;
|
||||
int x,y;
|
||||
} savedtypes = { 0, NULL, 0, NULL, 0, 0 };
|
||||
|
||||
static int _elm_cnp_init_count = 0;
|
||||
/* Gah... who left this out of XAtoms.h */
|
||||
static Ecore_X_Atom clipboard_atom;
|
||||
|
@ -250,6 +277,8 @@ static Ecore_X_Atom clipboard_atom;
|
|||
Eina_List *pastedimages;
|
||||
#endif
|
||||
|
||||
/* Stringshared, so I can just compare pointers later */
|
||||
static const char *text_uri;
|
||||
|
||||
Eina_Bool
|
||||
elm_selection_set(enum _elm_sel_type selection, Evas_Object *widget,
|
||||
|
@ -319,7 +348,7 @@ elm_selection_get(enum _elm_sel_type selection, enum _elm_sel_format format,
|
|||
|
||||
sel->requestformat = format;
|
||||
sel->requestwidget = widget;
|
||||
sel->request(elm_win_xwindow_get(top), ECORE_X_SELECTION_TARGET_UTF8_STRING);
|
||||
sel->request(elm_win_xwindow_get(top), ECORE_X_SELECTION_TARGET_TARGETS);
|
||||
|
||||
return EINA_TRUE;
|
||||
#else
|
||||
|
@ -345,7 +374,9 @@ _elm_cnp_init(void){
|
|||
|
||||
ecore_event_handler_add(ECORE_X_EVENT_SELECTION_CLEAR, selection_clear,NULL);
|
||||
ecore_event_handler_add(ECORE_X_EVENT_SELECTION_NOTIFY,selection_notify,NULL);
|
||||
return EINA_TRUE;
|
||||
|
||||
text_uri = eina_stringshare_add("text/uri-list");
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
|
@ -433,6 +464,9 @@ selection_notify(void *udata __UNUSED__, int type __UNUSED__, void *event){
|
|||
case ECORE_X_SELECTION_SECONDARY:
|
||||
sel = selections + ELM_SEL_SECONDARY;
|
||||
break;
|
||||
case ECORE_X_SELECTION_XDND:
|
||||
sel = selections + ELM_SEL_XDND;
|
||||
break;
|
||||
default:
|
||||
return ECORE_CALLBACK_PASS_ON;
|
||||
}
|
||||
|
@ -486,11 +520,12 @@ targets_converter(char *target __UNUSED__, void *data, int size __UNUSED__,
|
|||
}
|
||||
|
||||
static int
|
||||
png_converter(char *target __UNUSED__, void *data __UNUSED__, int size __UNUSED__,
|
||||
png_converter(char *target __UNUSED__, void *data, int size,
|
||||
void **data_ret __UNUSED__, int *size_ret __UNUSED__,
|
||||
Ecore_X_Atom *ttype __UNUSED__, int *typesize __UNUSED__)
|
||||
{
|
||||
return 1;
|
||||
cnp_debug("Png converter called\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -582,7 +617,9 @@ notify_handler_text(struct _elm_cnp_selection *sel,
|
|||
char *str;
|
||||
|
||||
data = notify->data;
|
||||
cnp_debug("Notify handler text %d %d %p\n",data->format,data->length,data->data);
|
||||
str = mark_up((char*)data->data, NULL);
|
||||
cnp_debug("String is %s (from %s)\n",str,data->data);
|
||||
elm_entry_entry_insert(sel->requestwidget, str);
|
||||
free(str);
|
||||
|
||||
|
@ -598,30 +635,60 @@ notify_handler_uri(struct _elm_cnp_selection *sel,
|
|||
Ecore_X_Event_Selection_Notify *notify)
|
||||
{
|
||||
Ecore_X_Selection_Data *data;
|
||||
Ecore_X_Selection_Data_Files *files;
|
||||
struct pasteimage *pi;
|
||||
char entrytag[100];
|
||||
char *p;
|
||||
|
||||
data = notify->data;
|
||||
p = (char *)data->data;
|
||||
printf("data->format is %d %p %p\n",data->format,notify,data);
|
||||
if (data->content == ECORE_X_SELECTION_CONTENT_FILES)
|
||||
{
|
||||
cnp_debug("got a files list\n");
|
||||
files = notify->data;
|
||||
if (files->num_files > 1)
|
||||
{
|
||||
/* Don't handle many items */
|
||||
cnp_debug("more then one file: Bailing\n");
|
||||
return 0;
|
||||
}
|
||||
p = files->files[0];
|
||||
}
|
||||
else
|
||||
p = (char *)data->data;
|
||||
if (!p)
|
||||
{
|
||||
cnp_debug("Couldn't find a file\n");
|
||||
return 0;
|
||||
}
|
||||
cnp_debug("Got %s\n",p);
|
||||
if (strncmp(p,"file://",7) != 0){
|
||||
cnp_debug("Doesn't start with ;file; %s\n",p);
|
||||
if (strncmp(p,"file://",7) != 0)
|
||||
{
|
||||
cnp_debug("Doesn't start with ;file; %s\n",p);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
p += strlen("file://");
|
||||
|
||||
pi = calloc(1,sizeof(struct pasteimage));
|
||||
snprintf(entrytag, sizeof(entrytag), "pasteimage-%p",pi);
|
||||
pi->tag = strdup(entrytag);
|
||||
pi->file = strndup(p,data->length - strlen("file://"));
|
||||
if (!strstr(p,"png"))
|
||||
{
|
||||
/* FIXME: Better test: Load it in evasw & see is probably best */
|
||||
cnp_debug("No png, ignoring\n");
|
||||
if (savedtypes.textreq) savedtypes.textreq = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
elm_entry_item_provider_append(sel->requestwidget, image_provider, pi);
|
||||
pastedimages = eina_list_append(pastedimages, pi);
|
||||
pi = pasteimage_alloc(p);
|
||||
|
||||
snprintf(entrytag, sizeof(entrytag), "<item absize=240x180 href=%s>",pi->tag);
|
||||
elm_entry_entry_insert(sel->requestwidget, entrytag);
|
||||
if (savedtypes.textreq)
|
||||
{
|
||||
savedtypes.textreq = 0;
|
||||
savedtypes.pi = pi;
|
||||
}
|
||||
else
|
||||
{
|
||||
pasteimage_append(pi, sel->requestwidget);
|
||||
savedtypes.pi = NULL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -630,7 +697,64 @@ static int
|
|||
notify_handler_png(struct _elm_cnp_selection *sel __UNUSED__,
|
||||
Ecore_X_Event_Selection_Notify *notify __UNUSED__)
|
||||
{
|
||||
Ecore_X_Selection_Data *data;
|
||||
char *fname,*tmppath;
|
||||
struct pasteimage *pi;
|
||||
int fd,len;
|
||||
void *map;
|
||||
|
||||
cnp_debug("got a png!\n");
|
||||
data = notify->data;
|
||||
|
||||
cnp_debug("Size if %d\n",data->length);
|
||||
|
||||
/* generate tmp name */
|
||||
tmppath = getenv("TMP");
|
||||
if (!tmppath) tmppath = P_tmpdir;
|
||||
if (!tmppath) tmppath = "/tmp";
|
||||
len = snprintf(NULL,0,"%s/%sXXXXXX",tmppath, "elmcnpimage-");
|
||||
if (len < 0) return 1;
|
||||
len ++;
|
||||
fname = malloc(len);
|
||||
if (!fname) return 1;
|
||||
len = snprintf(fname,len,"%s/%sXXXXXX",tmppath, "elmcnpimage-");
|
||||
|
||||
fd = mkstemp(fname);
|
||||
if (fd < 0)
|
||||
{
|
||||
free(fname);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ftruncate(fd, data->length))
|
||||
{
|
||||
perror("ftruncate");
|
||||
unlink(fname);
|
||||
free(fname);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
map = mmap(NULL,data->length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
|
||||
if (map == MAP_FAILED)
|
||||
{
|
||||
perror("mmap");
|
||||
unlink(fname);
|
||||
free(fname);
|
||||
close(fd);
|
||||
}
|
||||
memcpy(map, data->data, data->length);
|
||||
munmap(map,data->length);
|
||||
close(fd);
|
||||
|
||||
/* FIXME: add clean up function */
|
||||
// on_exit: file name + pid
|
||||
// need pid, as forked children inheret list :-(
|
||||
|
||||
/* FIXME: Add to paste image data to clean up */
|
||||
|
||||
pi = pasteimage_alloc(fname);
|
||||
pasteimage_append(pi, sel->requestwidget);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -642,11 +766,9 @@ text_converter(char *target __UNUSED__, void *data, int size __UNUSED__,
|
|||
{
|
||||
struct _elm_cnp_selection *sel;
|
||||
|
||||
cnp_debug("text converter\n");
|
||||
sel = selections + *(int *)data;
|
||||
if (!sel->active)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
if (!sel->active) return 1;
|
||||
|
||||
if (sel->format == ELM_SEL_MARKUP){
|
||||
*data_ret = remove_tags(sel->selbuf, size_ret);
|
||||
|
@ -662,8 +784,8 @@ text_converter(char *target __UNUSED__, void *data, int size __UNUSED__,
|
|||
}
|
||||
|
||||
static int
|
||||
edje_converter(char *target __UNUSED__, void *data, int size __UNUSED__,
|
||||
void **data_ret, int *size_ret, Ecore_X_Atom *ttype __UNUSED__,
|
||||
edje_converter(char *target __UNUSED__, void *data, int size __UNUSED__,
|
||||
void **data_ret, int *size_ret, Ecore_X_Atom *ttype __UNUSED__,
|
||||
int *typesize __UNUSED__)
|
||||
{
|
||||
struct _elm_cnp_selection *sel;
|
||||
|
@ -677,8 +799,8 @@ edje_converter(char *target __UNUSED__, void *data, int size __UNUSED__,
|
|||
|
||||
|
||||
static int
|
||||
html_converter(char *target __UNUSED__, void *data, int size __UNUSED__,
|
||||
void **data_ret, int *size_ret, Ecore_X_Atom *ttype __UNUSED__,
|
||||
html_converter(char *target __UNUSED__, void *data, int size __UNUSED__,
|
||||
void **data_ret, int *size_ret, Ecore_X_Atom *ttype __UNUSED__,
|
||||
int *typesize __UNUSED__)
|
||||
{
|
||||
struct _elm_cnp_selection *sel;
|
||||
|
@ -691,8 +813,8 @@ html_converter(char *target __UNUSED__, void *data, int size __UNUSED__,
|
|||
}
|
||||
|
||||
static int
|
||||
uri_converter(char *target __UNUSED__, void *data, int size __UNUSED__,
|
||||
void **data_ret, int *size_ret, Ecore_X_Atom *ttype __UNUSED__,
|
||||
uri_converter(char *target __UNUSED__, void *data, int size __UNUSED__,
|
||||
void **data_ret, int *size_ret, Ecore_X_Atom *ttype __UNUSED__,
|
||||
int *typesize __UNUSED__)
|
||||
{
|
||||
struct _elm_cnp_selection *sel;
|
||||
|
@ -734,6 +856,56 @@ image_provider(void *images __UNUSED__, Evas_Object *entry, const char *item)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static struct pasteimage *
|
||||
pasteimage_alloc(const char *file)
|
||||
{
|
||||
struct pasteimage *pi;
|
||||
int len;
|
||||
char *buf;
|
||||
|
||||
pi = calloc(1,sizeof(struct pasteimage));
|
||||
if (!pi) return NULL;
|
||||
|
||||
len = snprintf(NULL, 0, "pasteimage-%p",pi);
|
||||
len ++;
|
||||
buf = malloc(len);
|
||||
if (!buf)
|
||||
{
|
||||
free(pi);
|
||||
return NULL;
|
||||
}
|
||||
snprintf(buf, len, "pasteimage-%p",pi);
|
||||
pi->tag = buf;
|
||||
|
||||
if (file)
|
||||
{
|
||||
if (strstr(file,"file://")) file += strlen("file://");
|
||||
pi->file = strdup(file);
|
||||
}
|
||||
|
||||
return pi;
|
||||
}
|
||||
|
||||
static bool
|
||||
pasteimage_append(struct pasteimage *pi, Evas_Object *entry)
|
||||
{
|
||||
char entrytag[100];
|
||||
|
||||
if (!pi) return false;
|
||||
if (!entry) return false;
|
||||
|
||||
/* FIXME: Need to do this per widget */
|
||||
if (!pastedimages)
|
||||
elm_entry_item_provider_append(entry, image_provider, NULL);
|
||||
|
||||
pastedimages = eina_list_append(pastedimages, pi);
|
||||
snprintf(entrytag, sizeof(entrytag),"<item absize=240x180 href=%s>",pi->tag);
|
||||
elm_entry_entry_insert(entry, entrytag);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void
|
||||
entry_deleted(void *images __UNUSED__, Evas *e __UNUSED__, Evas_Object *entry, void *unused __UNUSED__)
|
||||
{
|
||||
|
@ -836,4 +1008,302 @@ mark_up(const char *start, int *lenp){
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Drag & Drop functions
|
||||
*/
|
||||
struct dropable {
|
||||
Evas_Object *obj;
|
||||
/* FIXME: Cache window */
|
||||
int types; /* FIXME */
|
||||
elm_drop_cb dropcb;
|
||||
void *cbdata;
|
||||
};
|
||||
/* FIXME: Way too many globals */
|
||||
Eina_List *drops = NULL;
|
||||
Evas_Object *en;
|
||||
Ecore_Event_Handler *handler_pos, *handler_drop, *handler_enter;
|
||||
|
||||
struct dropable *cur;
|
||||
|
||||
static Eina_Bool
|
||||
_dnd_enter(void *data, int etype, void *ev)
|
||||
{
|
||||
Ecore_X_Event_Xdnd_Enter *enter = ev;
|
||||
int i;
|
||||
|
||||
/* Skip it */
|
||||
if (enter->num_types == 0 || enter->types == NULL) return true;
|
||||
|
||||
cnp_debug("Types\n");
|
||||
savedtypes.ntypes = enter->num_types;
|
||||
if (savedtypes.types) free(savedtypes.types);
|
||||
savedtypes.types = malloc(sizeof(char *) * enter->num_types);
|
||||
for (i = 0 ; i < enter->num_types ; i ++)
|
||||
{
|
||||
savedtypes.types[i] = eina_stringshare_add(enter->types[i]);
|
||||
cnp_debug("Type is %s %p %p\n",enter->types[i],
|
||||
savedtypes.types[i],text_uri);
|
||||
if (savedtypes.types[i] == text_uri)
|
||||
{
|
||||
/* Request it, so we know what it is */
|
||||
cnp_debug("Sending uri request\n");
|
||||
savedtypes.textreq = 1;
|
||||
savedtypes.pi = NULL; /* FIXME: Free? */
|
||||
ecore_x_selection_xdnd_request(enter->win, text_uri);
|
||||
}
|
||||
}
|
||||
|
||||
/* FIXME: Find an object and make it current */
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
_dnd_drop(void *data, int etype, void *ev)
|
||||
{
|
||||
struct _Ecore_X_Event_Xdnd_Drop *drop;
|
||||
struct dropable *dropable;
|
||||
Eina_List *l;
|
||||
Evas_Object *elmwin;
|
||||
Ecore_X_Window xwin;
|
||||
Elm_Drop_Data ddata;
|
||||
int x,y,w,h;
|
||||
|
||||
int i,j;
|
||||
|
||||
drop = ev;
|
||||
|
||||
// check we still have something to drop
|
||||
if (!drops) return true;
|
||||
|
||||
/* Find any widget in our window; then work out geometry rel to our window */
|
||||
for (l = drops ; l ; l = l->next)
|
||||
{
|
||||
dropable = l->data;
|
||||
xwin = (Ecore_X_Window)ecore_evas_window_get(ecore_evas_ecore_evas_get(
|
||||
evas_object_evas_get(
|
||||
dropable->obj)));
|
||||
if (xwin == drop->win)
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
/* didn't find a window */
|
||||
if (l == NULL)
|
||||
return true;
|
||||
|
||||
|
||||
/* Calculate real (widget relative) position */
|
||||
// - window position
|
||||
// - widget position
|
||||
elmwin = elm_object_top_widget_get(dropable->obj);
|
||||
elm_win_screen_position_get(elmwin, &x, &y);
|
||||
savedtypes.x = drop->position.x - x;
|
||||
savedtypes.y = drop->position.y - y;
|
||||
|
||||
printf("Drop position is %d,%d\n",savedtypes.x,savedtypes.y);
|
||||
|
||||
for ( ; l ; l = l->next)
|
||||
{
|
||||
dropable = l->data;
|
||||
evas_object_geometry_get(dropable->obj, &x, &y, &w, &h);
|
||||
if (savedtypes.x >= x && savedtypes.y >= y &&
|
||||
savedtypes.x < x + w && savedtypes.y < y + h)
|
||||
break; /* found! */
|
||||
}
|
||||
|
||||
if (!l) /* didn't find one */
|
||||
return true;
|
||||
|
||||
evas_object_geometry_get(dropable->obj, &x, &y, NULL, NULL);
|
||||
savedtypes.x -= x;
|
||||
savedtypes.y -= y;
|
||||
|
||||
/* Find our type from the previous list */
|
||||
for (i = 0 ; i < CNP_N_ATOMS ; i ++)
|
||||
{
|
||||
for (j = 0 ; j < savedtypes.ntypes ; j ++)
|
||||
{
|
||||
if (strcmp(savedtypes.types[j], atoms[i].name) == 0)
|
||||
{
|
||||
goto found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cnp_debug("Didn't find a target\n");
|
||||
return true;
|
||||
|
||||
found:
|
||||
printf("Found a target we'd like: %s\n",atoms[i].name);
|
||||
cnp_debug("0x%x\n",xwin);
|
||||
|
||||
if (i == CNP_ATOM_text_urilist)
|
||||
{
|
||||
printf("We found a URI...\n");
|
||||
if (savedtypes.pi)
|
||||
{
|
||||
char entrytag[100];
|
||||
/* FIXME: Paste immediately */
|
||||
cnp_debug("paste immediately\n");
|
||||
// pasteimage_append(savedtypes.pi, dropable->obj);
|
||||
|
||||
ddata.x = savedtypes.x;
|
||||
ddata.y = savedtypes.y;
|
||||
ddata.format = ELM_SEL_IMAGE;
|
||||
/* FIXME: Need to do this per widget */
|
||||
if (!pastedimages)
|
||||
elm_entry_item_provider_append(dropable->obj, image_provider, NULL);
|
||||
pastedimages = eina_list_append(pastedimages, savedtypes.pi);
|
||||
snprintf(entrytag, sizeof(entrytag),"<item absize=240x180 href=%s>",savedtypes.pi->tag);
|
||||
ddata.data = entrytag;
|
||||
cnp_debug("Insert %s\n",(char *)ddata.data);
|
||||
dropable->dropcb(dropable->cbdata, dropable->obj, &ddata);
|
||||
ecore_x_dnd_send_finished();
|
||||
return true;
|
||||
}
|
||||
else if (savedtypes.textreq)
|
||||
{
|
||||
/* Already asked: Pretend we asked now, and paste imediately when
|
||||
* it comes in */
|
||||
savedtypes.textreq = 0;
|
||||
ecore_x_dnd_send_finished();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
selections[ELM_SEL_XDND].requestwidget = dropable->obj;
|
||||
selections[ELM_SEL_XDND].requestformat = ELM_SEL_MARKUP;
|
||||
selections[ELM_SEL_XDND].active = true;
|
||||
|
||||
ecore_x_selection_xdnd_request(xwin, atoms[i].name);
|
||||
|
||||
return true;
|
||||
}
|
||||
static Eina_Bool
|
||||
_dnd_position(void *data, int etype, void *ev){
|
||||
struct _Ecore_X_Event_Xdnd_Position *pos;
|
||||
Ecore_X_Rectangle rect;
|
||||
|
||||
pos = ev;
|
||||
|
||||
printf("position: %3d,%3d, Action: %s\n",
|
||||
pos->position.x,pos->position.y,
|
||||
ecore_x_atom_name_get(pos->action));
|
||||
|
||||
/* Need to send a status back */
|
||||
rect.x = pos->position.x - 5;
|
||||
rect.y = pos->position.y - 5;
|
||||
rect.width = 10;
|
||||
rect.height = 10;
|
||||
ecore_x_dnd_send_status(true, false, rect, pos->action);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a widget as drop target.
|
||||
*/
|
||||
Eina_Bool
|
||||
elm_drop_target_add(Evas_Object *obj, enum _elm_sel_type format,
|
||||
elm_drop_cb dropcb, void *cbdata)
|
||||
{
|
||||
struct dropable *drop;
|
||||
Ecore_X_Window xwin;
|
||||
int first;
|
||||
|
||||
printf("Adding %p as a drop target\n",obj);
|
||||
if (!obj) return false;
|
||||
if (!_elm_cnp_init_count) _elm_cnp_init();
|
||||
|
||||
|
||||
/* Is this the first? */
|
||||
first = (drops == NULL) ? 1 : 0;
|
||||
printf("Is first: %d\n",first);
|
||||
|
||||
drop = calloc(1,sizeof(struct dropable));
|
||||
if (!drop) return false;
|
||||
drop->dropcb = dropcb;
|
||||
drop->cbdata = cbdata;
|
||||
|
||||
/* FIXME: Check it's not already there */
|
||||
|
||||
/* FIXME: Check for eina's deranged error method */
|
||||
drops = eina_list_append(drops, drop);
|
||||
|
||||
if (!drops/* || or other error */)
|
||||
{
|
||||
free(drop);
|
||||
return false;
|
||||
}
|
||||
|
||||
drop->obj = obj;
|
||||
/* Something for now */
|
||||
drop->types = 1;
|
||||
|
||||
evas_object_event_callback_add(obj, EVAS_CALLBACK_DEL,
|
||||
/* I love C and varargs */
|
||||
(Evas_Object_Event_Cb)elm_drop_target_del,
|
||||
obj);
|
||||
/* FIXME: Handle resizes */
|
||||
|
||||
/* If not the first: We're done */
|
||||
if (!first) return true;
|
||||
|
||||
printf("Enabling DND\n");
|
||||
xwin = (Ecore_X_Window)ecore_evas_window_get(ecore_evas_ecore_evas_get(
|
||||
evas_object_evas_get(obj)));
|
||||
ecore_x_dnd_aware_set(xwin, true);
|
||||
|
||||
printf("Adding drop target calls\n");
|
||||
handler_enter = ecore_event_handler_add(ECORE_X_EVENT_XDND_ENTER,
|
||||
_dnd_enter, NULL);
|
||||
handler_pos = ecore_event_handler_add(ECORE_X_EVENT_XDND_POSITION, _dnd_position, NULL);
|
||||
handler_drop = ecore_event_handler_add(ECORE_X_EVENT_XDND_DROP, _dnd_drop, NULL);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Eina_Bool
|
||||
elm_drop_target_del(Evas_Object *obj){
|
||||
struct dropable *drop,*del;
|
||||
Eina_List *item,*tmp;
|
||||
Ecore_X_Window xwin;
|
||||
|
||||
del = NULL;
|
||||
EINA_LIST_FOREACH_SAFE(drops, item, tmp, drop)
|
||||
{
|
||||
if (drop->obj == obj)
|
||||
{
|
||||
drops = eina_list_remove_list(drops, tmp);
|
||||
del = drop;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!del) return false;
|
||||
|
||||
evas_object_event_callback_del(obj, EVAS_CALLBACK_FREE,
|
||||
(Evas_Object_Event_Cb)elm_drop_target_del);
|
||||
free(drop);
|
||||
|
||||
/* If still drops there: All fine.. continue */
|
||||
if (drops != NULL) return true;
|
||||
|
||||
printf("Disabling DND\n");
|
||||
xwin = (Ecore_X_Window)ecore_evas_window_get(ecore_evas_ecore_evas_get(
|
||||
evas_object_evas_get(obj)));
|
||||
ecore_x_dnd_aware_set(xwin, false);
|
||||
|
||||
printf("Adding drop target calls\n");
|
||||
ecore_event_handler_del(handler_pos);
|
||||
ecore_event_handler_del(handler_drop);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* vim:set ts=8 sw=3 sts=3 expandtab cino=>5n-2f0^-2{2(0W1st0 :*/
|
||||
|
||||
#endif
|
||||
|
|
|
@ -121,6 +121,7 @@ struct _Widget_Data
|
|||
Eina_Bool deferred_cur : 1;
|
||||
Eina_Bool disabled : 1;
|
||||
Eina_Bool context_menu : 1;
|
||||
Eina_Bool drag_selection_asked : 1;
|
||||
};
|
||||
|
||||
struct _Elm_Entry_Context_Menu_Item
|
||||
|
@ -141,6 +142,8 @@ struct _Elm_Entry_Item_Provider
|
|||
};
|
||||
|
||||
static const char *widtype = NULL;
|
||||
|
||||
static Eina_Bool _drag_drop_cb(void *data, Evas_Object *obj, Elm_Drop_Data *);
|
||||
static void _del_hook(Evas_Object *obj);
|
||||
static void _theme_hook(Evas_Object *obj);
|
||||
static void _disable_hook(Evas_Object *obj);
|
||||
|
@ -1185,7 +1188,8 @@ _event_selection_notify(void *data, int type __UNUSED__, void *event)
|
|||
Widget_Data *wd = elm_widget_data_get(data);
|
||||
Ecore_X_Event_Selection_Notify *ev = event;
|
||||
if (!wd) return ECORE_CALLBACK_PASS_ON;
|
||||
if (!wd->selection_asked) return ECORE_CALLBACK_PASS_ON;
|
||||
if (!wd->selection_asked && !wd->drag_selection_asked)
|
||||
return ECORE_CALLBACK_PASS_ON;
|
||||
|
||||
if ((ev->selection == ECORE_X_SELECTION_CLIPBOARD) ||
|
||||
(ev->selection == ECORE_X_SELECTION_PRIMARY))
|
||||
|
@ -1208,6 +1212,30 @@ _event_selection_notify(void *data, int type __UNUSED__, void *event)
|
|||
}
|
||||
wd->selection_asked = EINA_FALSE;
|
||||
}
|
||||
else if (ev->selection == ECORE_X_SELECTION_XDND)
|
||||
{
|
||||
Ecore_X_Selection_Data_Text *text_data;
|
||||
|
||||
text_data = ev->data;
|
||||
if (text_data->data.content == ECORE_X_SELECTION_CONTENT_TEXT)
|
||||
{
|
||||
if (text_data->text)
|
||||
{
|
||||
char *txt = _text_to_mkup(text_data->text);
|
||||
|
||||
if (txt)
|
||||
{
|
||||
/* Massive FIXME: this should be at the drag point */
|
||||
elm_entry_entry_insert(data, txt);
|
||||
free(txt);
|
||||
}
|
||||
}
|
||||
}
|
||||
wd->drag_selection_asked = EINA_FALSE;
|
||||
|
||||
ecore_x_dnd_send_finished();
|
||||
|
||||
}
|
||||
return ECORE_CALLBACK_PASS_ON;
|
||||
}
|
||||
|
||||
|
@ -1227,6 +1255,30 @@ _event_selection_clear(void *data __UNUSED__, int type __UNUSED__, void *event _
|
|||
return 1;*/
|
||||
return ECORE_CALLBACK_PASS_ON;
|
||||
}
|
||||
|
||||
|
||||
static Eina_Bool
|
||||
_drag_drop_cb(void *data, Evas_Object *obj, Elm_Drop_Data *drop)
|
||||
{
|
||||
Widget_Data *wd;
|
||||
Eina_Bool rv;
|
||||
|
||||
wd = elm_widget_data_get(obj);
|
||||
|
||||
if (!wd) return EINA_FALSE;
|
||||
printf("Inserting at (%d,%d) %s\n",drop->x,drop->y,(char*)drop->data);
|
||||
|
||||
edje_object_part_text_cursor_copy(wd->ent, "elm.text",
|
||||
EDJE_CURSOR_MAIN,/*->*/EDJE_CURSOR_USER);
|
||||
rv = edje_object_part_text_cursor_coord_set(wd->ent,"elm.text",
|
||||
EDJE_CURSOR_MAIN,drop->x,drop->y);
|
||||
if (!rv) printf("Warning: Failed to position cursor: paste anyway\n");
|
||||
elm_entry_entry_insert(obj, drop->data);
|
||||
edje_object_part_text_cursor_copy(wd->ent, "elm.text",
|
||||
EDJE_CURSOR_USER,/*->*/EDJE_CURSOR_MAIN);
|
||||
|
||||
return EINA_TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
static Evas_Object *
|
||||
|
@ -1347,6 +1399,8 @@ elm_entry_add(Evas_Object *parent)
|
|||
ecore_event_handler_add(ECORE_X_EVENT_SELECTION_CLEAR,
|
||||
_event_selection_clear, obj);
|
||||
}
|
||||
|
||||
elm_drop_target_add(obj, ELM_SEL_IMAGE | ELM_SEL_MARKUP,_drag_drop_cb, NULL);
|
||||
#endif
|
||||
|
||||
entries = eina_list_prepend(entries, obj);
|
||||
|
@ -1628,6 +1682,12 @@ elm_entry_editable_set(Evas_Object *obj, Eina_Bool editable)
|
|||
elm_entry_entry_set(obj, t);
|
||||
eina_stringshare_del(t);
|
||||
_sizing_eval(obj);
|
||||
|
||||
if (editable)
|
||||
elm_drop_target_add(obj, ELM_SEL_IMAGE | ELM_SEL_MARKUP,
|
||||
_drag_drop_cb, NULL);
|
||||
else
|
||||
elm_drop_target_del(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2167,3 +2227,7 @@ elm_entry_utf8_to_markup(const char *s)
|
|||
if (!ss) ss = strdup("");
|
||||
return ss;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* vim:set ts=8 sw=3 sts=3 expandtab cino=>5n-2f0^-2{2(0W1st0 :*/
|
||||
|
|
|
@ -109,6 +109,7 @@ enum _elm_sel_type {
|
|||
ELM_SEL_PRIMARY,
|
||||
ELM_SEL_SECONDARY,
|
||||
ELM_SEL_CLIPBOARD,
|
||||
ELM_SEL_XDND,
|
||||
|
||||
ELM_SEL_MAX,
|
||||
};
|
||||
|
@ -218,9 +219,24 @@ void _elm_config_init(void);
|
|||
void _elm_config_sub_init(void);
|
||||
void _elm_config_shutdown(void);
|
||||
|
||||
/* FIXME: nash formatiing */
|
||||
typedef struct Elm_Drop_Data {
|
||||
int x,y;
|
||||
|
||||
enum _elm_sel_format format;
|
||||
void *data;
|
||||
int len;
|
||||
} Elm_Drop_Data;
|
||||
|
||||
typedef Eina_Bool (*elm_drop_cb)(void *, Evas_Object *, Elm_Drop_Data *data);
|
||||
|
||||
Eina_Bool elm_selection_set(enum _elm_sel_type selection, Evas_Object *widget, enum _elm_sel_format format, const char *buf);
|
||||
Eina_Bool elm_selection_clear(enum _elm_sel_type selection, Evas_Object *widget);
|
||||
Eina_Bool elm_selection_get(enum _elm_sel_type selection, enum _elm_sel_format format, Evas_Object *widget);
|
||||
/* FIXME: Need a typedef for the callback */
|
||||
Eina_Bool elm_drop_target_add(Evas_Object *widget,
|
||||
enum _elm_sel_type, elm_drop_cb, void *);
|
||||
Eina_Bool elm_drop_target_del(Evas_Object *widget);
|
||||
|
||||
Eina_Bool _elm_dangerous_call_check(const char *call);
|
||||
|
||||
|
|
Loading…
Reference in New Issue