3 * Copyright 2012 Samsung Electronics Co., Ltd
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
9 * http://www.tizenopensource.org/license
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.
21 #include <appcore-efl.h>
22 #include <Elementary.h>
25 #include <extended-elm.h>
26 #include <supplement.h>
27 #include <memo-assist.h>
28 #include <memo_string.h>
30 typedef struct __doodle_editor_t {
33 Memo_Component_Callback cb;
36 char buf[MEMO_BUFFER_SIZE]; /* for temporarily usage only */
38 Eina_Bool erase; /* erase mode */
39 Eina_Bool comment; /* edit comment mode */
40 Eina_Bool doodle_init;
41 Eina_Bool stroke_down; /* see comment in _on_doodle_move_cb */
46 Evas_Object *body_main;
49 Evas_Object *btn_size;
51 Evas_Object *btn_color;
53 Evas_Object *edit_field;
57 Ecore_IMF_Context *imf_context;
60 static void _stroke_size_selector_del(doodle_editor_t *de);
61 static void _stroke_color_selector_del(doodle_editor_t *de);
63 static void _save_record(doodle_editor_t *de)
66 if (de->record->id != -1) {
67 memo_mod_data(de->record);
69 de->record->content = memo_get_doodle_title();
70 memo_add_data(de->record);
72 /* save doodle to file */
73 snprintf(de->buf, MEMO_BUFFER_SIZE, DOODLEDIR "/%d.png", (int)de->record->id);
74 evas_object_image_save(de->doodle, de->buf, NULL, NULL);
78 static void _on_save_yes(void *data, Evas_Object *obj, void *event_info)
80 doodle_editor_t *de = (doodle_editor_t *)data;
82 de->cb(de->data, "save", NULL);
85 static void _on_save_no(void *data, Evas_Object *obj, void *event_info)
87 doodle_editor_t *de = (doodle_editor_t *)data;
88 de->cb(de->data, "cancel", NULL);
91 static Eina_Bool _launch_yes_no_popup(void *data)
93 doodle_editor_t *de = (doodle_editor_t *)data;
94 memo_create_yes_no_popup(de->win, MEMO_I18N_SAVE_MEMO, _on_save_yes, _on_save_no, de);
98 static Eina_Bool _comment_savable(doodle_editor_t *de)
100 const char *text = elm_entry_entry_get(de->entry);
101 Eina_Bool savable = EINA_TRUE;
103 if (strcmp(text, "") == 0) {
104 savable = EINA_FALSE;
105 } else if (de->record->comment != NULL) { /* check if same as init string */
106 if (strcmp(de->record->comment, text)==0) {
107 savable = EINA_FALSE;
113 static void _stroke_size_selector_callback(void *data, const char *msg, void *event)
115 doodle_editor_t *de = (doodle_editor_t *)data;
116 if (strcmp(msg, "layout") == 0) {
117 elm_object_part_content_set(de->body_main, "elm.swallow.tool", (Evas_Object *)event);
118 } else if (strcmp(msg, "change") == 0) {
119 de->stroke_size = (int)event;
120 } else if (strcmp(msg, "flick,down") == 0) {
121 _stroke_size_selector_del(de);
122 edje_object_signal_emit(elm_layout_edje_get(de->body_main), "expand", "elm");
126 static void _stroke_size_selector_del(doodle_editor_t *de)
128 if (de->h_ss != NULL) {
129 elm_object_part_content_unset(de->body_main, "elm.swallow.tool");
130 memo_del_pencil_size_selector(de->h_ss);
132 memo_tool_btn_focus_set(de->btn_size, EINA_FALSE);
136 static void _on_stroke_size_btn_up(void *data, Evas *e, Evas_Object *evas_obj, void *event_info)
138 doodle_editor_t *de = (doodle_editor_t *)data;
139 if (de->h_ss != NULL) { /* stroke size selector already open */
142 if (de->h_cs != NULL) { /* destroy stroke color selector */
143 _stroke_color_selector_del(de);
145 if (de->erase) { /* deselect eraser */
146 de->erase = EINA_FALSE;
147 Evas_Object *eo = (Evas_Object *)elm_object_part_content_get(de->toolbar, "elm.swallow.btn3");
148 memo_tool_btn_focus_set(eo, de->erase);
150 /* load stroke size selector */
151 service_h service = NULL;
152 service_create(&service);
153 snprintf(de->buf, MEMO_BUFFER_SIZE, "%d", de->stroke_size);
154 service_add_extra_data(service, "current", de->buf);
155 snprintf(de->buf, MEMO_BUFFER_SIZE, "%d", 0xFFFFFFFF);
156 service_add_extra_data(service, "bg_color", de->buf);
157 de->h_ss = memo_load_pencil_size_selector(de->body_main, service, _stroke_size_selector_callback, de);
158 service_destroy(service);
159 memo_tool_btn_focus_set(de->btn_size, EINA_TRUE);
160 edje_object_signal_emit(elm_layout_edje_get(de->body_main), "abbrev", "elm");
163 void _stroke_color_selector_callback(void *data, const char *msg, void *event)
165 doodle_editor_t *de = (doodle_editor_t *)data;
166 if (strcmp(msg, "layout") == 0) {
167 elm_object_part_content_set(de->body_main, "elm.swallow.tool", (Evas_Object *)event);
168 } else if (strcmp(msg, "change") == 0) {
169 de->stroke_color = (int)event;
170 } else if (strcmp(msg, "flick,down") == 0) {
171 _stroke_color_selector_del(de);
172 edje_object_signal_emit(elm_layout_edje_get(de->body_main), "expand", "elm");
176 static void _stroke_color_selector_del(doodle_editor_t *de)
178 if (de->h_cs != NULL) {
179 elm_object_part_content_unset(de->body_main, "elm.swallow.tool");
180 memo_del_color_selector(de->h_cs);
181 memo_tool_btn_focus_set(de->btn_color, EINA_FALSE);
186 static void _on_stroke_color_btn_up(void *data, Evas *e, Evas_Object *evas_obj, void *event_info)
188 doodle_editor_t *de = (doodle_editor_t *)data;
189 if (de->h_cs!= NULL) { /* stroke color selector already open */
192 if (de->h_ss!= NULL) { /* destroy stroke size selector */
193 _stroke_size_selector_del(de);
195 if (de->erase) { /* deselect eraser */
196 de->erase = EINA_FALSE;
197 Evas_Object *eo = (Evas_Object *)elm_object_part_content_get(de->toolbar, "elm.swallow.btn3");
198 memo_tool_btn_focus_set(eo, de->erase);
200 /* load stroke color selector */
201 service_h service = NULL;
202 service_create(&service);
203 snprintf(de->buf, MEMO_BUFFER_SIZE, "%d", de->stroke_color);
204 service_add_extra_data(service, "color", de->buf);
205 snprintf(de->buf, MEMO_BUFFER_SIZE, "%d", 0xFFFFFFFF);
206 service_add_extra_data(service, "bg_color", de->buf);
207 de->h_cs = memo_load_color_selector(de->body_main, service, _stroke_color_selector_callback, de);
208 service_destroy(service);
209 memo_tool_btn_focus_set(de->btn_color, EINA_TRUE);
210 edje_object_signal_emit(elm_layout_edje_get(de->body_main), "abbrev", "elm");
213 static void _input_panel_event_callback(void *data, Ecore_IMF_Context *ctx, int value)
215 doodle_editor_t *de = (doodle_editor_t *)data;
216 if (value == ECORE_IMF_INPUT_PANEL_STATE_HIDE) {
218 edje_object_signal_emit(elm_layout_edje_get(de->body_main), "expand", "comment");
220 } else if (value == ECORE_IMF_INPUT_PANEL_STATE_SHOW) {
221 edje_object_signal_emit(elm_layout_edje_get(de->body_main), "abbrev", "comment");
222 if (!de->comment) { /* first shift to comment mode */
223 if (de->record->comment == NULL) { /* remove DUMMY_COMMENT */
224 elm_entry_entry_set(de->entry, "");
226 de->cb(de->data, "savable", (void *)0);
228 de->comment = EINA_TRUE;
229 de->cb(de->data, "title", de->record->comment == NULL ? MEMO_I18N_ADD_COMMENT : MEMO_I18N_EDIT_COMMENT);
233 static void _on_entry_content_change(void *data, Evas_Object *obj, void *event_info)
235 doodle_editor_t *de = (doodle_editor_t *)data;
236 if (elm_object_focus_get(de->edit_field)) {
237 if (elm_entry_is_empty(obj)) {
238 elm_object_signal_emit(de->edit_field, "elm,state,eraser,hide", "elm");
240 elm_object_signal_emit(de->edit_field, "elm,state,eraser,show", "elm");
244 Eina_Bool savable = _comment_savable(de);
245 de->cb(de->data, "savable", (void *)(savable?1:0));
248 // Focused callback will show X marked button and hide guidetext
249 static void _focused_cb(void *data, Evas_Object *obj, void *event_info)
251 if (!elm_entry_is_empty(obj))
252 elm_object_signal_emit(data, "elm,state,eraser,show", "elm");
253 elm_object_signal_emit(data, "elm,state,guidetext,hide", "elm");
256 // Unfocused callback will show guidetext and hide X marked button
257 static void _unfocused_cb(void *data, Evas_Object *obj, void *event_info)
259 if (elm_entry_is_empty(obj))
260 elm_object_signal_emit(data, "elm,state,guidetext,show", "elm");
261 elm_object_signal_emit(data, "elm,state,eraser,hide", "elm");
264 // When X marked button is clicked, empty entry’s contents
265 static void _eraser_clicked_cb(void *data, Evas_Object *obj, const char *emission, const char *source)
267 elm_entry_entry_set(data, "");
270 static void _draw_argb32(int *img, int w, int h, int x, int y, int size, int color)
279 CLIP_RECT_TO_RECT(cx, cy, cw, ch, 0, 0, w, h);
280 for (i = 0; i < ch; i++) {
281 for (j = 0; j < cw; j++) {
282 memcpy(img + (i + cy) * w + (cx + j), &color, 4);
287 static void _on_doodle_down_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
289 doodle_editor_t *de = (doodle_editor_t *)data;
293 if (de->h_ss!= NULL) { /* destroy stroke size selector */
294 _stroke_size_selector_del(de);
296 if (de->h_cs != NULL) { /* destroy stroke color selector */
297 _stroke_color_selector_del(de);
299 edje_object_signal_emit(elm_layout_edje_get(de->body_main), "expand", "elm");
300 de->stroke_down = EINA_TRUE;
301 de->record->has_doodle = 1;
303 de->savable = EINA_TRUE;
304 } else if (de->record->id != -1) { /* edit existing doodle with eraser */
305 de->savable = EINA_TRUE;
307 de->cb(de->data, "savable", (void *)(de->savable?1:0));
310 static void _on_doodle_move_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
312 doodle_editor_t *de = (doodle_editor_t *)data;
313 Evas_Event_Mouse_Move *event = (Evas_Event_Mouse_Move *)event_info;
314 int *img_data = NULL;
315 Evas_Coord x, y, w, h;
324 /* a little mouse move events will be received before mouse down event,
325 if those mouse move event is not filtered,
326 a line (from last up point to current move point) will be draw */
327 if (!de->stroke_down) {
331 color = de->erase ? 0xFFFFFFFF : de->stroke_color;
332 evas_object_geometry_get(de->doodle, &x, &y, &w, &h);
333 img_data = evas_object_image_data_get(de->doodle, 1);
334 gap_x = event->cur.output.x - event->prev.output.x;
335 gap_y = event->cur.output.y - event->prev.output.y;
336 gap_max = MAX(abs(gap_x), abs(gap_y));
338 for (i = 0; i < gap_max; i++) {
339 px = event->prev.output.x + i * gap_x / gap_max;
340 py = event->prev.output.y + i * gap_y / gap_max;
341 /* mouse position in image */
344 _draw_argb32(img_data, w, h, px, py, de->stroke_size, color);
346 evas_object_image_data_update_add(de->doodle, 0, 0, w, h);
349 static void _on_doodle_up_cb(void *data, Evas *e, Evas_Object *obj, void *event_info)
351 doodle_editor_t *de = (doodle_editor_t *)data;
352 de->stroke_down = EINA_FALSE;
355 /* this function must be execute only once
356 * in resize callback we will get the actual size of doodle
357 * we need to transfer origin image data to fit the actual canvas size
359 static void _on_doodle_resize_cb(void *data, Evas_Object *obj, void *event_info)
361 doodle_editor_t *de = (doodle_editor_t *)data;
363 int *img_data = NULL;
365 if (de->doodle_init) { /* reenter control */
368 de->doodle_init = EINA_TRUE;
369 evas_object_geometry_get(de->doodle, NULL, NULL, &w, &h);
370 evas_object_image_size_set(de->doodle, w, h);
371 img_data = evas_object_image_data_get(de->doodle, 1);
372 if (img_data != NULL) { /* set background color as white */
373 memset(img_data, 0xFFFFFFFF, sizeof(int)*w*h);
374 evas_object_image_data_update_add(de->doodle, 0, 0, w, h);
379 static void _on_eraser_up(void *data, Evas *e, Evas_Object *evas_obj, void *event_info)
381 doodle_editor_t *de = (doodle_editor_t *)data;
382 if (de->h_ss!= NULL) { /* destroy stroke size selector */
383 _stroke_size_selector_del(de);
385 if (de->h_cs != NULL) { /* destroy stroke color selector */
386 _stroke_color_selector_del(de);
388 de->erase = !(de->erase);
389 Evas_Object *eo = (Evas_Object *)elm_object_part_content_get(de->toolbar, "elm.swallow.btn3");
390 memo_tool_btn_focus_set(eo, de->erase);
391 edje_object_signal_emit(elm_layout_edje_get(de->body_main), "expand", "elm");
394 static void _on_yes(void *data, Evas_Object *obj, void *event_info)
396 doodle_editor_t *de = (doodle_editor_t *)data;
398 de->cb(de->data, "text", NULL);
401 static void _on_no(void *data, Evas_Object *obj, void *event_info)
403 doodle_editor_t *de = (doodle_editor_t *)data;
404 de->cb(de->data, "text", NULL);
407 static void _on_text_mode(void *data, Evas_Object *obj, void *event_info)
409 doodle_editor_t *de = (doodle_editor_t *)data;
410 if (de->savable) { /* modified, need user confirmation */
411 memo_create_yes_no_popup(de->parent, MEMO_I18N_SAVE_MEMO, _on_yes, _on_no, de);
413 de->cb(de->data, "text", NULL);
417 static void _create_doodle_editor_layout(doodle_editor_t *de, service_h service)
419 de->body_main = elm_layout_create(de->parent, EDJ_FILE, "doodle_editor");
420 de->cb(de->data, "layout", de->body_main);
422 de->cb(de->data, "title", de->record->id==-1 ? MEMO_I18N_NEW_DRAWING : MEMO_I18N_EDIT_DRAWING);
424 memo_time_format(de->buf, MEMO_BUFFER_SIZE, de->record->modi_time);
425 edje_object_part_text_set(elm_layout_edje_get(de->body_main), "elm.text.date", de->buf);
428 de->toolbar = elm_swallowed_layout(de->body_main, "elm.swallow.toolbar", EDJ_FILE, "edit_toolbar");
429 de->btn_size = elm_swallowed_layout(de->toolbar, "elm.swallow.btn1", EDJ_DIR"/black/memo.edj", "tl_stroke_size");
430 evas_object_event_callback_add(de->btn_size, EVAS_CALLBACK_MOUSE_UP, _on_stroke_size_btn_up, de);
431 de->btn_color = elm_swallowed_layout(de->toolbar, "elm.swallow.btn2", EDJ_DIR"/black/memo.edj", "tl_stroke_color");
432 evas_object_event_callback_add(de->btn_color, EVAS_CALLBACK_MOUSE_UP, _on_stroke_color_btn_up, de);
433 Evas_Object *eo = elm_swallowed_layout(de->toolbar, "elm.swallow.btn3", EDJ_DIR"/black/memo.edj", "tl_stroke_eraser");
434 evas_object_event_callback_add(eo, EVAS_CALLBACK_MOUSE_UP, _on_eraser_up, de);
435 if (!service_key_check(service, "toggle", "disable")) {
436 elm_swallowed_button(de->toolbar, "elm.swallow.btn4", MEMO_I18N_TEXT, _on_text_mode, de);
440 Evas *evas = evas_object_evas_get(de->parent);
441 de->doodle = evas_object_image_filled_add(evas);
442 evas_object_image_colorspace_set(de->doodle, EVAS_COLORSPACE_ARGB8888);
443 evas_object_image_alpha_set(de->doodle, 1);
445 if (de->record->has_doodle == 1) {
446 snprintf(de->buf, MEMO_BUFFER_SIZE, DOODLEDIR "/%d.png", (int)de->record->id);
447 evas_object_image_file_set(de->doodle, de->buf, NULL);
449 evas_object_render_flush_hook(de->doodle, _on_doodle_resize_cb, de);
451 evas_object_event_callback_add(de->doodle, EVAS_CALLBACK_MOUSE_DOWN, _on_doodle_down_cb, de);
452 evas_object_event_callback_add(de->doodle, EVAS_CALLBACK_MOUSE_MOVE, _on_doodle_move_cb, de);
453 evas_object_event_callback_add(de->doodle, EVAS_CALLBACK_MOUSE_UP, _on_doodle_up_cb, de);
454 evas_object_show(de->doodle);
455 elm_object_part_content_set(de->body_main, "elm.swallow.doodle", de->doodle);
458 eo = elm_swallowed_layout(de->body_main, "elm.swallow.comment", EDJ_FILE, "doodle_comment");
460 Evas_Object *layout = elm_layout_add(eo);
461 elm_layout_theme_set(layout, "layout", "editfield", "default");
462 elm_object_part_content_set(eo, "elm.swallow.editfield", layout);
463 Evas_Object *ef = elm_entry_add(eo);
464 elm_object_part_content_set(layout, "elm.swallow.content", ef);
465 de->edit_field = layout;
467 elm_object_style_set(ef, "font_color_black");
468 elm_entry_single_line_set(ef, EINA_TRUE);
469 elm_entry_scrollable_set(ef, EINA_TRUE);
470 evas_object_size_hint_weight_set(ef, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
471 evas_object_size_hint_align_set(ef, EVAS_HINT_EXPAND, 0.5);
474 elm_entry_cnp_mode_set(de->entry, ELM_CNP_MODE_PLAINTEXT);
475 de->imf_context = elm_entry_imf_context_get(de->entry);
476 /* if not hide explicitly, seems hide event of sweep down can not be received */
477 ecore_imf_context_input_panel_hide(de->imf_context);
478 elm_entry_entry_set(de->entry, de->record->comment == NULL ? MEMO_I18N_ADD_COMMENT : de->record->comment);
479 ecore_imf_context_input_panel_event_callback_add(de->imf_context, ECORE_IMF_INPUT_PANEL_STATE_EVENT,
480 _input_panel_event_callback, de);
481 evas_object_smart_callback_add(de->entry, "changed", _on_entry_content_change, de);
482 evas_object_smart_callback_add(de->entry, "preedit,changed", _on_entry_content_change, de);
483 evas_object_smart_callback_add(de->entry, "focused", _focused_cb, layout);
484 evas_object_smart_callback_add(de->entry, "unfocused", _unfocused_cb, layout);
485 elm_object_signal_callback_add(layout, "elm,eraser,clicked", "elm", _eraser_clicked_cb, de->entry);
488 de->cb(de->data, "savable", (void *)(de->savable?1:0));
491 void memo_doodle_editor_time_format_update(void *h_de)
493 doodle_editor_t *de = (doodle_editor_t *)h_de;
494 memo_time_format(de->buf, MEMO_BUFFER_SIZE, de->record->modi_time);
495 edje_object_part_text_set(elm_layout_edje_get(de->body_main), "elm.text.date", de->buf);
498 void memo_doodle_editor_cancel(void *h_de)
500 doodle_editor_t *de = (doodle_editor_t *)h_de;
502 de->comment = EINA_FALSE;
503 ecore_imf_context_input_panel_hide(de->imf_context);
504 edje_object_signal_emit(elm_layout_edje_get(de->body_main), "expand", "elm");
505 if (de->record->comment == NULL) {
506 elm_entry_entry_set(de->entry, MEMO_I18N_ADD_COMMENT);
507 } else { /* recover origin comment */
508 elm_entry_entry_set(de->entry, de->record->comment);
510 de->cb(de->data, "title", de->record->id==-1 ? MEMO_I18N_NEW_DRAWING : MEMO_I18N_EDIT_DRAWING);
511 de->cb(de->data, "savable", (void *)(de->savable?1:0));
512 de->cb(de->data, "comment cancel", NULL);
514 if (!de->savable) { /* not modified, return directly */
515 de->cb(de->data, "cancel", NULL);
517 _launch_yes_no_popup(de);
522 void memo_doodle_editor_save(void *h_de)
524 doodle_editor_t *de = (doodle_editor_t *)h_de;
526 de->comment = EINA_FALSE;
527 ecore_imf_context_input_panel_hide(de->imf_context);
528 edje_object_signal_emit(elm_layout_edje_get(de->body_main), "expand", "elm");
529 Eina_Bool savable = _comment_savable(de);
531 de->record->comment = strdup(elm_entry_entry_get(de->entry));
532 if (de->record->id != -1) { /* when edit existing, modification of comment will make record savable */
533 de->savable = EINA_TRUE;
536 de->cb(de->data, "title", de->record->id==-1 ? MEMO_I18N_NEW_DRAWING : MEMO_I18N_EDIT_DRAWING);
537 de->cb(de->data, "savable", (void *)(de->savable?1:0));
538 elm_object_focus_set(de->edit_field, EINA_FALSE); /* when disable done button, focus may reset to edit field */
539 de->cb(de->data, "comment done", NULL);
542 de->cb(de->data, "save", NULL);
546 void *memo_load_doodle_editor(Evas_Object *win, Evas_Object *parent, service_h service, Memo_Component_Callback cb, void *data)
548 doodle_editor_t *de = SMALLOC(doodle_editor_t);
549 RETVIF(de==NULL, NULL);
550 service_dump(service);
553 de->cb = (cb==NULL ? memo_com_dummy_cb : cb); /* make sure cb is not null, no need to check legitimacy of cb when call */
557 const char *s = NULL;
558 service_get_extra_data(service, "index", &s);
560 de->record = memo_create_data();
562 de->record->modi_time = time((time_t *) 0);
563 } else { /* load item */
564 de->record = memo_get_data(atoi(s));
568 de->stroke_color = 0xff000000;
570 _create_doodle_editor_layout(de, service);
574 void memo_destroy_doodle_editor(void *h_de)
576 doodle_editor_t *de = (doodle_editor_t *)h_de;
577 if (de->h_ss!= NULL) { /* destroy stroke size selector */
578 _stroke_size_selector_del(de);
580 if (de->h_cs != NULL) { /* destroy stroke color selector */
581 _stroke_color_selector_del(de);
583 memo_free_data(de->record);
584 evas_object_del(de->body_main);