Update codes for TIZEN 2.0
[apps/home/memo.git] / src / memo_autolink.c
1 /*
2 *
3 * Copyright 2012  Samsung Electronics Co., Ltd
4 *
5 * Licensed under the Flora License, Version 1.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *    http://www.tizenopensource.org/license
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18
19 #include <Elementary.h>
20 #include <dlog.h>
21 #include <gravel.h>
22 #include <extended-elm.h>
23 #include <assert.h>
24 #include <glib.h>
25 #include <memo_string.h>
26 #include <aul.h>
27 #include <appsvc.h>
28 #include <contacts-ug.h>
29 #include <memo_autolink.h>
30 #include <memo_ug.h>
31 #include "memo_log.h"
32
33 static void _autolink_popup_response_cb(void *data, Evas_Object *obj, void *event_info);
34
35 /* BEGIN CONVERTE BETWEEN enum anchor_t AND string  */
36
37 static char *_autolink_anchor_type_to_string(enum anchor_t type)
38 {
39     switch (type) {
40     case ANCHOR_EMAIL:
41         return "email";
42     case ANCHOR_PHONE:
43         return "phone_num";
44     case ANCHOR_URL:
45         return "url";
46     default:
47         return "";
48     }
49 }
50
51 /* END CONVERTE BETWEEN enum anchor_t AND string  */
52
53 /* BEGIN POPUP CALLBACK */
54
55 static void _autolink_send_email_selected_cb(void *data, Evas_Object *obj, void *event_info)
56 {
57         struct autolink_data_t *ald = (struct autolink_data_t *)data;
58         service_h service = NULL;
59         service_create(&service);
60         service_add_extra_data(service, "RUN_TYPE", "5");
61         service_add_extra_data(service, "TO", ald->info);
62
63         ug_launch_common(service, UG_NAME_EMAIL);
64         _autolink_popup_response_cb(ald, ald->popup, NULL);
65         }
66
67 static void _autolink_send_message_selected_cb(void *data, Evas_Object *obj, void *event_info)
68 {
69     struct autolink_data_t *ald = (struct autolink_data_t *)data;
70
71     ug_launch_common_var(UG_NAME_MESSAGE, "TO", ald->info, NULL);
72     _autolink_popup_response_cb(ald, ald->popup, NULL);
73 }
74
75 static void _autolink_vioce_call_selected_cb(void *data, Evas_Object *obj, void *event_info)
76 {
77         struct autolink_data_t *ald = (struct autolink_data_t *)data;
78         bundle *bd = bundle_create();
79         char telnum[255] = {0,};
80         
81         appsvc_set_operation(bd, APPSVC_OPERATION_CALL);
82         snprintf(telnum, sizeof(telnum), "tel:%s", ald->info);
83         appsvc_set_uri(bd, telnum);
84         appsvc_run_service(bd, 0, NULL, NULL);
85
86         bundle_free(bd);
87         _autolink_popup_response_cb(ald, ald->popup, NULL);
88 }
89
90 static void _autolink_video_call_selected_cb(void *data, Evas_Object *obj, void *event_info)
91 {
92         struct autolink_data_t *ald = (struct autolink_data_t *)data;
93         service_h service = NULL;
94         service_create(&service);
95         service_add_extra_data(service, "KEY_CALL_TYPE", "mo");
96         service_add_extra_data(service, "KEY_CALL_HANDLE", "1");
97         service_add_extra_data(service, "KEY_CALLING_PARTY_NUMBER", ald->info);
98         service_add_extra_data(service, "KEY_CLI_CAUSE", "-1");
99         service_add_extra_data(service, "KEY_FORWARDED", "-1");
100         service_set_package(service, AUL_NAME_VEDIO_CALL);
101         service_send_launch_request(service, NULL, NULL);
102
103         service_destroy(service);
104         _autolink_popup_response_cb(ald, ald->popup, NULL);
105 }
106
107 static void _autolink_email_add_to_contact_selected_cb(void *data, Evas_Object *obj,
108                                void *event_info)
109 {
110         struct autolink_data_t *ald = (struct autolink_data_t *)data;
111
112         char buf[10];
113         snprintf(buf, sizeof(buf), "%d", CT_UG_REQUEST_ADD_WITH_EMAIL);
114
115         service_h service = NULL;
116         service_create(&service);
117         service_add_extra_data(service, CT_UG_BUNDLE_TYPE, buf);
118         service_add_extra_data(service, CT_UG_BUNDLE_EMAIL, ald->info);
119
120         ug_launch_common(service, UG_CONTACTS_DETAILS);
121         _autolink_popup_response_cb(ald, ald->popup, NULL);
122 }
123
124 static void _autolink_phone_add_to_contact_selected_cb(void *data, Evas_Object *obj,
125                                void *event_info)
126 {
127         struct autolink_data_t *ald = (struct autolink_data_t *)data;
128
129         char buf[10];
130         snprintf(buf, sizeof(buf), "%d", CT_UG_REQUEST_ADD_WITH_NUM);
131
132         service_h service = NULL;
133         service_create(&service);
134         service_add_extra_data(service, CT_UG_BUNDLE_TYPE, buf);
135         service_add_extra_data(service, CT_UG_BUNDLE_NUM, ald->info);
136
137         ug_launch_common(service, UG_CONTACTS_DETAILS);
138         _autolink_popup_response_cb(ald, ald->popup, NULL);
139 }
140
141 /* END POPUP CALLBACK */
142
143 static struct anchor_popup_item_t g_email_list[] = {
144     {"IDS_MEMO_OPT_SEND_EMAIL", _autolink_send_email_selected_cb, "memo"},
145     {"IDS_MEMO_BODY_ADD_TO_CONTACT", _autolink_email_add_to_contact_selected_cb, "memo"},
146 };
147
148 static struct anchor_popup_item_t g_phone_list[] = {
149     {"IDS_COM_BODY_VOICE_CALL", _autolink_vioce_call_selected_cb, "sys_string"},
150     {"IDS_COM_BODY_SEND_MESSAGE", _autolink_send_message_selected_cb, "sys_string"},
151     {"IDS_COM_BODY_VIDEO_CALL", _autolink_video_call_selected_cb, "sys_string"},
152     {"IDS_MEMO_BODY_ADD_TO_CONTACT", _autolink_phone_add_to_contact_selected_cb, "memo"},
153 };
154
155 static char *_autolink_gl_label_get(void *data, Evas_Object *obj, const char *part)
156 {
157     char *label = (char *)data;
158     if (strcmp(part, "elm.text") == 0) {
159         return strdup(label);
160     }
161     return NULL;
162 }
163
164 static void _autolink_popup_response_cb(void *data, Evas_Object *obj, void *event_info)
165 {
166     PFUNC_ENTER;
167
168     assert(data != NULL);
169     struct autolink_data_t *ald = (struct autolink_data_t *)data;
170     if (ald->popup != NULL) {
171         evas_object_del(ald->popup);
172     }
173
174     assert(ald->info != NULL);
175     free(ald->info);
176     free(ald);
177
178     PFUNC_LEAVE;
179 }
180
181 void _autolink_popup_show(char *title, struct autolink_data_t *ald)
182 {
183     struct anchor_popup_item_t *list = NULL;
184     int count = 0;
185     Evas_Object *box = NULL;
186
187     switch (ald->type) {
188     case ANCHOR_EMAIL:
189         list = g_email_list;
190         count = ARRAY_SIZE(g_email_list);
191         break;
192     case ANCHOR_PHONE:
193         list = g_phone_list;
194         count = ARRAY_SIZE(g_phone_list);
195         break;
196     default:
197         break;
198     }
199
200     Evas_Object *popup = elm_popup_add(ald->win_main);
201     ald->popup = popup;
202     elm_object_style_set(popup, "min_menustyle");
203     elm_object_part_text_set(popup, "title,text", title);
204     Evas_Object *btn1 = elm_button_add(popup);
205     elm_object_text_set(btn1, MEMO_I18N_CLOSE);
206     elm_object_part_content_set(popup, "button1", btn1);
207     evas_object_smart_callback_add(btn1, "clicked", _autolink_popup_response_cb, ald);
208     evas_object_size_hint_weight_set(popup, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
209
210     static Elm_Genlist_Item_Class itc;
211     memset(&itc, 0, sizeof(Elm_Genlist_Item_Class));
212     itc.item_style = "1text";
213     itc.func.text_get = _autolink_gl_label_get;
214
215     Evas_Object *genlist = elm_genlist_add(popup);
216     evas_object_smart_callback_add(genlist, "selected", NULL, NULL);
217     evas_object_size_hint_weight_set(genlist, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
218     evas_object_size_hint_align_set(genlist, EVAS_HINT_FILL, EVAS_HINT_FILL);
219
220     int i = 0;
221     for (i = 0; i < count; ++i) {
222         elm_genlist_item_append(genlist, &itc, dgettext(list[i].domain, list[i].label),
223                     NULL, ELM_GENLIST_ITEM_NONE, list[i].response, ald);
224     }
225
226     evas_object_show(genlist);
227     box = elm_box_add(popup);
228     evas_object_size_hint_min_set(box, 0, (count>3 ? 3 : count)*71);
229     elm_box_pack_end(box, genlist);
230     evas_object_show(box);
231     elm_object_content_set(popup, box);
232     evas_object_show(popup);
233 }
234
235 void autolink_anchor_clicked_cb(void *data, Evas_Object *obj, void *event_info)
236 {
237     Elm_Entry_Anchor_Info *ei = (Elm_Entry_Anchor_Info *)event_info;
238     autolink_data *ald = SMALLOC(autolink_data);
239     RETIF(ald == NULL);
240
241     int info_len = strlen(ei->name) + 1;
242     ald->info = calloc(1, info_len);
243     if (ald->info == NULL) {
244         SFREE(ald);
245         return;
246     }
247
248     ald->win_main = (Evas_Object *)data;
249     /* retrieve anchor info, eg `url|www  phone_num|8888 email|abc@k.com` */
250     switch (ei->name[0]) {
251     case 'u':
252         ald->type = ANCHOR_URL;
253         break;
254     case 'p':
255         ald->type = ANCHOR_PHONE;
256         break;
257     case 'e':
258         ald->type = ANCHOR_EMAIL;
259         break;
260     default :
261         ald->type = NOT_ANCHOR;
262         break;
263     }
264     const char *start = ei->name + strlen(_autolink_anchor_type_to_string(ald->type)) +1;
265     const char *end = strchr(ei->name, ' ');
266     strncpy(ald->info, start, end - start);
267     LOGD("Anchor clicked (%s)\n", ald->info);
268
269     if ((ald->type == ANCHOR_EMAIL) || (ald->type == ANCHOR_PHONE)){
270         _autolink_popup_show(ald->info, ald);
271         return;
272     }
273
274     if (ald->type == ANCHOR_URL) {
275         aul_open_content(ald->info);    /* launch browser */
276     }
277     SFREE(ald->info);
278     SFREE(ald);
279 }
280
281 static Eina_Bool str_is_begin_with(const char *str, const char *prefix)
282 {
283     if (strlen(str) < strlen(prefix)) {
284         return EINA_FALSE;
285     }
286
287     int i = 0;
288     for (i = 0; i < strlen(prefix); ++i) {
289         if (str[i] != prefix[i]) {
290             return EINA_FALSE;
291         }
292     }
293
294     return EINA_TRUE;
295 }
296
297 /* TYPE CHECKER BEGIN */
298 /*
299  * [Checker Function Specification]
300  * These function get target type from beginning of string.
301  * The only parameter str is the string that want to be checked.
302  * Let's assume the returned value is r. There are 3 cases:
303  *     r > 0 : From beginning to r of str match the target type.
304  *     r = 0 : Str doesn't have target type, no longer check.
305  *     r < 0 : It doesn't match from beginning, but from -r position maybe matched.
306  *             It will be checked next time.
307  */
308 static int _autolink_url_checker(const char* str)
309 {
310     char *prefix[] = {"http://", "www.", "wap."};
311
312     int i = 0;
313     for (i = 0; i < ARRAY_SIZE(prefix); ++i) {
314         if (str_is_begin_with(str, prefix[i])) { /* Begin with specified prefix */
315             int j = 0;
316             while(str[j] != '\0' && !isspace(str[j])) {
317                 if (str[j] == '<') { /* tag begin */
318                     break;
319                 }
320                 j++;
321             }
322
323             if(j == strlen(prefix[i])) { /* only with prefix alone */
324                 break;
325             } else {
326                 return j;
327             }
328         }
329     }
330
331     /* not matched, get next position */
332 #if 0 /* Need correct */
333     int next = 0;
334     char *p = NULL;
335     for (i = 0; i < ARRAY_SIZE(prefix); ++i) {
336         p = strstr(str, prefix[i]);
337         if (p != NULL) {
338             if (next == 0) {
339                 next = p - str;
340             } else if (next > p - str) {
341                 next = p - str;
342             }
343         }
344     }
345 #endif
346
347     return -1;
348 }
349
350 static int _autolink_email_checker(const char* str)
351 {
352     char *p = NULL;
353     p = strchr(str, '@');
354
355     if (NULL == p) {
356         return 0;
357     } else if (p == str) { /* '@' at beginning */
358         return -1;
359     }
360
361     char next = p[1];
362     if (next == '@') { /* consecutive '@' */
363         return -(p + 2 - str);
364     } else if (next == '.') { /* "@." */
365         return -(p + 2 - str);
366     } else if (next == '\0') { /* '@' at end of string */
367         return -(p + 1 - str);
368     } else if (isspace(next) || next == '<') { /* '@' at end of word */
369         return -(p + 2 - str);
370     }
371
372     /* check if valid before @ */
373     char *q = p;
374     while (q >= str) {
375         if (isspace(*q)) {
376             return -(q + 1 - str);
377         }
378
379         if (*q == '>' || *q == '/') { /* tag end and other illega character */
380             return -(q + 1 - str);
381         }
382
383         if (*q == '.' && ( q == str || *(q - 1) == '.')) {
384             return -(q + 1 - str);
385         }
386
387         q--;
388     }
389
390     /* get the end position of email */
391     q = p + 1;
392     while (*q != '\0' && !isspace(*q)) {
393         if (*q == '.' && *(q + 1) == '.') {
394             break;
395         }
396
397         if (*q == '<') { /* tag begin */
398             break;
399         }
400
401         q++;
402     }
403     return q - str;
404 }
405
406 static int _autolink_phone_checker(const char *str)
407 {
408     /* search beginning */
409     int i = 0;
410     for (i = 0; str[i] != '\0'; ++i) {
411         /* digit, '+', '*' and '#' at the beginning of phone is valid. */
412         if (isdigit(str[i]) || str[i] == '+' || str[i] == '*' || str[i] == '#') {
413             break;
414         }
415     }
416
417     if (i > 0) {
418         return -i;
419     }
420
421     for (i = 1; str[i] != '\0'; ++i) {
422         if (isdigit(str[i]) || str[i] == '*' || str[i] == '#') {
423             /* NOP */
424         } else if (str[i] == '-') {
425             if (str[i - 1] == '-' || str[i - 1] == ' ') { /* "--" or " -" */
426                 i -= 1;
427                 break;
428             }
429         } else if (str[i] == ' ') {
430             if (str[i - 1] == '-') { /* "- " */
431                 i -= 1;
432                 break;
433             } else if (str[i - 1] == ' ' && isdigit(str[i + 1])) { /* consecutive ' ' */
434                 int j = 0;
435                 while (isdigit(str[i + 1 + j])) { ++j; }
436                 if (j > 5) {
437                     while (str[i] == ' ') { i--; }
438                     return i + 1;
439                 }
440             }
441         } else { /* other character */
442             break;
443         }
444     }
445
446     int len = i;
447     if (len < 3) {
448         return -len;
449     } else {
450         return len;
451     }
452 }
453
454 /* TYEP CHECKER END */
455
456 static void _autolink_box_anchor(GString *text, char *type, char *content)
457 {
458     char *buf = NULL;
459     if (strcmp(type, "phone_num") == 0) { /* remove '-' and ' ' in phone num. */
460         buf = (char *)malloc(strlen(content) + 1);
461         RETIF(buf == NULL);
462         int i = 0, j = 0;
463         while (content[i] != '\0') {
464             if (content[i] == '-' || content[i] == ' ') {
465                 ++i;
466             } else {
467                 buf[j] = content[i];
468                 ++i;
469                 ++j;
470             }
471         }
472         buf[j] = '\0';
473     } else {
474         buf = content;
475     }
476
477     g_string_append_printf(text,
478         "<color=#72b1f2FF><a href=%s|%s underline=on underline_color=#72b1f2FF>%s</a></color>",
479         type, buf, content);
480
481     if (buf != content) {
482         free(buf);
483     }
484 }
485
486 GString *autolink_add_anchor(const char *content)
487 {
488     MEMO_FUN_BEG();
489     assert(content != NULL);
490
491     const char *p = content;
492     char *gt = NULL;
493     GString *text = g_string_new("");
494     GString *buf = g_string_new("");
495
496     struct checker_t {
497         int (*check)(const char* str);
498         char *type;
499         Eina_Bool invoke;
500     } checkers[] = {
501         {_autolink_url_checker, "url", EINA_TRUE},
502         {_autolink_email_checker, "email", EINA_TRUE},
503         {_autolink_phone_checker, "phone_num", EINA_TRUE},
504     };
505
506     while (*p != '\0') {
507
508         if (p[0] == '<') {    /* tag begin */
509             gt = strchr(p, '>');    /* search tag end */
510             assert(gt != NULL);    /* assert '<' and '>' are matched */
511             g_string_append_len(text, p, gt - p + 1);
512             p = gt + 1;
513         }
514
515         int i = 0;
516         int r = 0;
517         int next_pos = -1;
518         for (i = 0; i < ARRAY_SIZE(checkers); ++i) {
519             if (checkers[i].invoke == EINA_TRUE) {
520                 r = checkers[i].check(p);
521                 if (r == 0) { /* no longer check */
522                     checkers[i].invoke = EINA_FALSE;
523                 } else if (r > 0) { /* Get */
524                     g_string_assign(buf, "");    /* reset buffer */
525                     g_string_append_len(buf, p, r);
526                     _autolink_box_anchor(text, checkers[i].type, buf->str);
527                     next_pos = r;
528                     break;
529                 } else { /* r < 0 */
530                     if (next_pos <= 0 || next_pos > -r) {
531                         next_pos = -r;
532                     }
533                 }
534             }
535         }
536
537         if (next_pos < 0) {
538             g_string_append(text, p);
539             break;
540         } else if ( i == ARRAY_SIZE(checkers)) {
541             g_string_append_len(text, p, next_pos);
542         }
543
544         p += next_pos;
545     }
546
547     g_string_free(buf, TRUE);
548     printf("\t\t********** %s\n", text->str);
549
550     MEMO_FUN_END();
551     return text;
552 }