/src/ftk_entry.c
C | 660 lines | 530 code | 99 blank | 31 comment | 87 complexity | d7a583fcebb1485752c03c56c7a7d06e MD5 | raw file
1/* 2 * File: ftk_entry.c 3 * Author: Li XianJing <xianjimli@hotmail.com> 4 * Brief: single line editor 5 * 6 * Copyright (c) 2009 - 2010 Li XianJing <xianjimli@hotmail.com> 7 * 8 * Licensed under the Academic Free License version 2.1 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; either version 2 of the License, or 13 * (at your option) any later version. 14 * 15 * This program is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with this program; if not, write to the Free Software 22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23 */ 24 25/* 26 * History: 27 * ================================================================ 28 * 2009-11-02 Li XianJing <xianjimli@hotmail.com> created 29 * 2012-5-22 woodysu@gmail.com modified 30 */ 31 32#include "ftk_util.h" 33#include "ftk_log.h" 34#include "ftk_entry.h" 35#include "ftk_globals.h" 36#include "ftk_window.h" 37#include "ftk_text_buffer.h" 38#include "ftk_source_timer.h" 39#include "ftk_input_method_preeditor.h" 40 41typedef struct _EntryPrivInfo 42{ 43 int caret; 44 int caret_visible; 45 int visible_start; 46 int visible_end; 47 int selected_end; 48 int selected_start; 49 int input_type; 50 int readonly; 51 int noborder; 52 FtkPoint caret_pos; 53 FtkSource* caret_timer; 54 FtkTextBuffer* text_buffer; 55 56 char* tips; 57}PrivInfo; 58 59#define FTK_ENTRY_H_MARGIN 4 60#define FTK_ENTRY_V_MARGIN 8 61 62#define TB_TEXT priv->text_buffer->buffer 63#define TB_LENGTH (int)(priv->text_buffer->length) 64#define HAS_TEXT(priv) (priv != NULL && priv->text_buffer != NULL && TB_LENGTH > 0) 65 66static Ret ftk_entry_on_paint_caret(FtkWidget* thiz); 67static Ret ftk_entry_update_caret_pos(FtkWidget* thiz); 68static Ret ftk_entry_compute_visible_range(FtkWidget* thiz); 69static Ret ftk_entry_set_text_internal(FtkWidget* thiz, const char* text); 70 71static Ret ftk_entry_move_caret(FtkWidget* thiz, int offset) 72{ 73 DECL_PRIV0(thiz, priv); 74 priv->caret_visible = 0; 75 ftk_entry_update_caret_pos(thiz); 76 ftk_entry_on_paint_caret(thiz); 77 78 if(!HAS_TEXT(priv)) 79 { 80 priv->caret = 0; 81 ftk_widget_invalidate(thiz); 82 return RET_OK; 83 } 84 85 priv->caret += ftk_text_buffer_chars_bytes(priv->text_buffer, priv->caret, offset); 86 priv->caret = priv->caret < 0 ? 0 : priv->caret; 87 priv->caret = priv->caret > TB_LENGTH ? TB_LENGTH : priv->caret; 88 89 if((priv->caret) < priv->visible_start) 90 { 91 priv->visible_start = priv->caret; 92 priv->visible_end = -1; 93 } 94 else if((priv->caret) > priv->visible_end) 95 { 96 priv->visible_end = priv->caret; 97 priv->visible_start = -1; 98 } 99 100 ftk_widget_invalidate(thiz); 101 102 return RET_OK; 103} 104 105static Ret ftk_entry_get_offset_by_pointer(FtkWidget* thiz, int x) 106{ 107 DECL_PRIV0(thiz, priv); 108 FtkTextLine line = {0}; 109 FtkTextLayout* text_layout = ftk_default_text_layout(); 110 int width = x - FTK_PAINT_X(thiz) - FTK_ENTRY_H_MARGIN + 1; 111 FtkCanvas* canvas = NULL; 112 return_val_if_fail(width >= 0, RET_FAIL); 113 114 if(!HAS_TEXT(priv)) 115 { 116 return RET_OK; 117 } 118 119 canvas = ftk_widget_canvas(thiz); 120 ftk_canvas_reset_gc(canvas, ftk_widget_get_gc(thiz)); 121 122 ftk_text_layout_init(text_layout, TB_TEXT + priv->visible_start, 123 priv->visible_end - priv->visible_start, canvas, width); 124 125 if(ftk_text_layout_get_visual_line(text_layout, &line) == RET_OK) 126 { 127 if(line.len > 0) 128 { 129 priv->caret = priv->visible_start + line.pos_v2l[line.len-1] + 1; 130 } 131 else 132 { 133 priv->caret = priv->visible_start; 134 } 135 } 136 137 ftk_logd("%s: start=%d caret=%d line.len=%d x=%d width=%d\n", 138 __func__, priv->visible_start, priv->caret, line.len, x, width); 139 140 return ftk_entry_move_caret(thiz, 0); 141} 142 143static Ret ftk_entry_handle_mouse_evevnt(FtkWidget* thiz, FtkEvent* event) 144{ 145 return ftk_entry_get_offset_by_pointer(thiz, event->u.mouse.x); 146} 147 148static Ret ftk_entry_input_str(FtkWidget* thiz, const char* str) 149{ 150 int count = 0; 151 DECL_PRIV0(thiz, priv); 152 return_val_if_fail(thiz != NULL && str != NULL, RET_FAIL); 153 154 count = utf8_count_char(str, strlen(str)); 155 ftk_text_buffer_insert(priv->text_buffer, priv->caret, str, -1); 156 ftk_entry_move_caret(thiz, count); 157 158 if(priv->visible_start >= 0) 159 { 160 priv->visible_end = -1; 161 } 162 163 return RET_OK; 164} 165 166static Ret ftk_entry_input_char(FtkWidget* thiz, char c) 167{ 168 char str[2] = {0}; 169 str[0] = c; 170 str[1] = '\0'; 171 172 return ftk_entry_input_str(thiz, str); 173} 174 175static Ret ftk_entry_handle_key_event(FtkWidget* thiz, FtkEvent* event) 176{ 177 Ret ret = RET_OK; 178 DECL_PRIV0(thiz, priv); 179 180 switch(event->u.key.code) 181 { 182 case FTK_KEY_CHOOSE_IME: 183 { 184 if(priv->readonly) break; 185 ftk_input_method_manager_focus_out(ftk_default_input_method_manager(), thiz); 186 ftk_input_method_chooser(); 187 ftk_input_method_manager_focus_in(ftk_default_input_method_manager(), thiz); 188 189 break; 190 } 191 case FTK_KEY_HOME: 192 { 193 ftk_entry_move_caret(thiz, -priv->caret); 194 break; 195 } 196 case FTK_KEY_END: 197 { 198 ftk_entry_move_caret(thiz, TB_LENGTH-priv->caret); 199 break; 200 } 201 case FTK_KEY_LEFT: 202 { 203 ftk_entry_move_caret(thiz, -1); 204 ret = RET_REMOVE; 205 break; 206 } 207 case FTK_KEY_RIGHT: 208 { 209 ftk_entry_move_caret(thiz, 1); 210 ret = RET_REMOVE; 211 break; 212 } 213 case FTK_KEY_UP: 214 case FTK_KEY_DOWN:break; 215 case FTK_KEY_DELETE: 216 { 217 if(priv->readonly) break; 218 ftk_text_buffer_delete_chars(priv->text_buffer, priv->caret, 1); 219 ftk_entry_move_caret(thiz, 0); 220 break; 221 } 222 case FTK_KEY_BACKSPACE: 223 { 224 int caret = priv->caret; 225 if(priv->readonly) break; 226 ftk_entry_move_caret(thiz, -1); 227 if(ftk_text_buffer_delete_chars(priv->text_buffer, caret, -1) == RET_OK) 228 { 229 } 230 231 /*FIXME*/ 232 if(priv->visible_start > 0) 233 { 234 if(priv->caret == priv->visible_start) 235 { 236 ftk_entry_move_caret(thiz, -1); 237 ftk_entry_move_caret(thiz, 1); 238 } 239 } 240 break; 241 } 242 case FTK_KEY_CLR: 243 { 244 if(priv->readonly) break; 245 ftk_entry_set_text_internal(thiz, ""); 246 break; 247 } 248 default: 249 { 250 if(priv->readonly) break; 251 if(event->u.key.code < 0xff && isprint(event->u.key.code)) 252 { 253 ftk_entry_input_char(thiz, event->u.key.code); 254 } 255 break; 256 } 257 } 258 259 return ret; 260} 261 262static Ret ftk_entry_on_event(FtkWidget* thiz, FtkEvent* event) 263{ 264 Ret ret = RET_OK; 265 DECL_PRIV0(thiz, priv); 266 return_val_if_fail(thiz != NULL && event != NULL, RET_FAIL); 267 268 switch(event->type) 269 { 270 case FTK_EVT_FOCUS_IN: 271 { 272 if(!priv->readonly) 273 { 274 ftk_input_method_manager_focus_in(ftk_default_input_method_manager(), thiz); 275 ftk_input_method_manager_set_current_type(ftk_default_input_method_manager(), (FtkInputType)priv->input_type); 276 } 277 ftk_source_ref(priv->caret_timer); 278 ftk_source_timer_reset(priv->caret_timer); 279 ftk_main_loop_add_source(ftk_default_main_loop(), priv->caret_timer); 280 break; 281 } 282 case FTK_EVT_FOCUS_OUT: 283 { 284 if(!priv->readonly) 285 { 286 ftk_input_method_manager_focus_out(ftk_default_input_method_manager(), thiz); 287 } 288 ftk_main_loop_remove_source(ftk_default_main_loop(), priv->caret_timer); 289 break; 290 } 291 case FTK_EVT_KEY_DOWN: 292 case FTK_EVT_KEY_UP: 293 { 294 if(event->type == FTK_EVT_KEY_DOWN) 295 { 296 ret = ftk_entry_handle_key_event(thiz, event); 297 } 298 else 299 { 300 ret = event->u.key.code == FTK_KEY_LEFT || event->u.key.code == FTK_KEY_RIGHT 301 ? RET_REMOVE : RET_OK; 302 } 303 break; 304 } 305 case FTK_EVT_MOUSE_UP: 306 { 307 ret = ftk_entry_handle_mouse_evevnt(thiz, event); 308 break; 309 } 310 case FTK_EVT_IM_PREEDIT: 311 { 312 ftk_im_show_preeditor(thiz, &(priv->caret_pos), (FtkCommitInfo*)event->u.extra); 313 break; 314 } 315 case FTK_EVT_IM_COMMIT: 316 { 317 ftk_entry_input_str(thiz, (const char*)event->u.extra); 318 ftk_input_method_manager_focus_ack_commit(ftk_default_input_method_manager()); 319 break; 320 } 321 case FTK_EVT_MOUSE_LONG_PRESS: 322 { 323 if(priv->readonly) break; 324 ftk_input_method_manager_focus_out(ftk_default_input_method_manager(), thiz); 325 ftk_input_method_chooser(); 326 ftk_input_method_manager_focus_in(ftk_default_input_method_manager(), thiz); 327 328 break; 329 } 330 case FTK_EVT_SET_TEXT: 331 { 332 ftk_entry_set_text_internal(thiz, (const char*)event->u.extra); 333 ret = RET_REMOVE; 334 335 break; 336 } 337 case FTK_EVT_GET_TEXT: 338 { 339 event->u.extra = (void*)ftk_entry_get_text(thiz); 340 ret = RET_REMOVE; 341 342 break; 343 } 344 default:break; 345 } 346 347 return ret; 348} 349 350static Ret ftk_entry_update_caret_pos(FtkWidget* thiz) 351{ 352 FtkGc gc = {0}; 353 DECL_PRIV0(thiz, priv); 354 FTK_BEGIN_PAINT(x, y, width, height, canvas); 355 (void)height; 356 return_val_if_fail(thiz != NULL, RET_FAIL); 357 358 if(priv->caret < priv->visible_start || priv->caret > priv->visible_end 359 || priv->visible_start < 0 || priv->visible_end < 0) 360 { 361 return RET_FAIL; 362 } 363 364 (void)width; 365 gc.mask = FTK_GC_FG; 366 if(ftk_widget_is_focused(thiz)) 367 { 368 int extent = 0; 369 gc.fg = priv->caret_visible ? ftk_widget_get_gc(thiz)->fg : ftk_widget_get_gc(thiz)->bg; 370 priv->caret_visible = !priv->caret_visible; 371 372 if(HAS_TEXT(priv)) 373 { 374 ftk_canvas_set_gc(canvas, ftk_widget_get_gc(thiz)); 375 extent = ftk_canvas_get_str_extent(canvas, TB_TEXT+priv->visible_start, 376 priv->caret - priv->visible_start); 377 378 //ftk_logd("%s: %d len=%d\n", __func__, extent, priv->caret - priv->visible_start); 379 } 380 381 ftk_canvas_reset_gc(canvas, &gc); 382 x += extent + FTK_ENTRY_H_MARGIN - 1; 383 y += FTK_ENTRY_V_MARGIN; 384 priv->caret_pos.x = x; 385 priv->caret_pos.y = y; 386 } 387 388 return RET_OK; 389} 390 391static Ret ftk_entry_on_paint_caret(FtkWidget* thiz) 392{ 393 FtkGc gc = {0}; 394 DECL_PRIV0(thiz, priv); 395 FTK_BEGIN_PAINT(x, y, width, height, canvas); 396 return_val_if_fail(thiz != NULL, RET_FAIL); 397 398 if(!ftk_window_is_mapped(ftk_widget_toplevel(thiz))) 399 { 400 return RET_OK; 401 } 402 403 if(priv->caret < priv->visible_start || priv->caret > priv->visible_end 404 || priv->visible_start < 0 || priv->visible_end < 0) 405 { 406 return RET_FAIL; 407 } 408 409 (void)width; 410 gc.mask = FTK_GC_FG; 411 if(ftk_widget_is_focused(thiz)) 412 { 413 priv->caret_visible = !priv->caret_visible; 414 gc.fg = priv->caret_visible ? ftk_widget_get_gc(thiz)->fg : ftk_widget_get_gc(thiz)->bg; 415 416 x = priv->caret_pos.x; 417 y = priv->caret_pos.y; 418 ftk_canvas_reset_gc(canvas, &gc); 419 ftk_canvas_draw_vline(canvas, x, y, height - (3 * FTK_ENTRY_V_MARGIN)/2 ); 420 FTK_END_PAINT(); 421 } 422 423 return RET_OK; 424} 425 426static Ret ftk_entry_on_paint(FtkWidget* thiz) 427{ 428 FtkGc gc = {0}; 429 DECL_PRIV0(thiz, priv); 430 int font_height = 0; 431 FTK_BEGIN_PAINT(x, y, width, height, canvas); 432 433 if (!priv->noborder) { 434 gc.mask = FTK_GC_FG; 435 gc.fg = ftk_theme_get_border_color(ftk_default_theme(), FTK_ENTRY, ftk_widget_state(thiz)); 436 ftk_canvas_set_gc(canvas, &gc); 437 ftk_canvas_draw_vline(canvas, x, y + 2, height - 4); 438 ftk_canvas_draw_vline(canvas, x+width-1, y + 2, height - 4); 439 ftk_canvas_draw_hline(canvas, x + 2, y, width-4); 440 ftk_canvas_draw_hline(canvas, x + 1, y + height - 1, width-2); 441 442 if(ftk_widget_state(thiz) == FTK_WIDGET_NORMAL) 443 { 444 gc.fg.r += 0x60; 445 gc.fg.g += 0x60; 446 gc.fg.b += 0x60; 447 ftk_canvas_set_gc(canvas, &gc); 448 } 449 ftk_canvas_draw_hline(canvas, x + 1, y + 1, width-2); 450 ftk_canvas_draw_vline(canvas, x + 1, y + 1, height - 2); 451 ftk_canvas_draw_vline(canvas, x + width -2, y + 1, height - 2); 452 ftk_canvas_draw_hline(canvas, x + 2, y + height - 2, width-4); 453 } 454 455 ftk_canvas_set_gc(canvas, ftk_widget_get_gc(thiz)); 456 if(HAS_TEXT(priv) || priv->tips != NULL) 457 { 458 FtkTextLine line = {0}; 459 FtkTextLayout* text_layout = ftk_default_text_layout(); 460 int width = ftk_widget_width(thiz) - 2 * FTK_ENTRY_H_MARGIN; 461 462 if(HAS_TEXT(priv)) 463 { 464 ftk_entry_compute_visible_range(thiz); 465 ftk_text_layout_init(text_layout, TB_TEXT + priv->visible_start, 466 priv->visible_end - priv->visible_start, canvas, width); 467 } 468 else 469 { 470 FtkColor fg = ftk_theme_get_fg_color(ftk_default_theme(), FTK_ENTRY, ftk_widget_state(thiz)); 471 FtkColor bg = ftk_theme_get_bg_color(ftk_default_theme(), FTK_ENTRY, ftk_widget_state(thiz)); 472 473 gc.fg.r = (fg.r + bg.r) >> 1; 474 gc.fg.g = (fg.r + bg.g) >> 1; 475 gc.fg.b = (fg.r + bg.b) >> 1; 476 ftk_canvas_set_gc(canvas, &gc); 477 478 ftk_text_layout_init(text_layout, priv->tips, -1, canvas, width); 479 } 480 font_height = ftk_widget_get_font_size(thiz); 481 x += FTK_ENTRY_H_MARGIN; 482 y += font_height + FTK_ENTRY_V_MARGIN; 483 484 if(ftk_text_layout_get_visual_line(text_layout, &line) == RET_OK) 485 { 486 ftk_canvas_draw_string(canvas, x + line.xoffset, y, line.text, line.len, 0); 487 } 488 } 489 490 ftk_entry_update_caret_pos(thiz); 491 ftk_entry_on_paint_caret(thiz); 492 493 FTK_END_PAINT(); 494} 495 496static void ftk_entry_destroy(FtkWidget* thiz) 497{ 498 if(thiz != NULL) 499 { 500 DECL_PRIV0(thiz, priv); 501 502 if(ftk_widget_is_focused(thiz)) 503 { 504 ftk_input_method_manager_focus_out(ftk_default_input_method_manager(), thiz); 505 } 506 507 FTK_FREE(priv->tips); 508 ftk_source_disable(priv->caret_timer); 509 ftk_main_loop_remove_source(ftk_default_main_loop(), priv->caret_timer); 510 ftk_source_unref(priv->caret_timer); 511 ftk_text_buffer_destroy(priv->text_buffer); 512 FTK_FREE(priv); 513 } 514 515 return; 516} 517 518FtkWidget* ftk_entry_create(FtkWidget* parent, int x, int y, int width, int height) 519{ 520 FtkWidget* thiz = (FtkWidget*)FTK_ZALLOC(sizeof(FtkWidget)); 521 return_val_if_fail(thiz != NULL, NULL); 522 523 thiz->priv_subclass[0] = (PrivInfo*)FTK_ZALLOC(sizeof(PrivInfo)); 524 if(thiz->priv_subclass[0] != NULL) 525 { 526 DECL_PRIV0(thiz, priv); 527 528 thiz->on_event = ftk_entry_on_event; 529 thiz->on_paint = ftk_entry_on_paint; 530 thiz->destroy = ftk_entry_destroy; 531 532 height = ftk_font_desc_get_size(ftk_default_font()) + FTK_ENTRY_V_MARGIN * 2; 533 ftk_widget_init(thiz, FTK_ENTRY, 0, x, y, width, height, FTK_ATTR_TRANSPARENT|FTK_ATTR_BG_FOUR_CORNER); 534 535 priv->input_type = FTK_INPUT_NORMAL; 536 priv->caret_timer = ftk_source_timer_create(500, (FtkTimer)ftk_entry_on_paint_caret, thiz); 537 priv->text_buffer = ftk_text_buffer_create(128); 538 ftk_widget_append_child(parent, thiz); 539 } 540 else 541 { 542 FTK_FREE(thiz); 543 } 544 545 return thiz; 546} 547 548Ret ftk_entry_set_noborder(FtkWidget* thiz, int b) 549{ 550 DECL_PRIV0(thiz, priv); 551 return_val_if_fail(thiz != NULL, RET_FAIL); 552 553 priv->noborder = b; 554 555 return RET_OK; 556} 557 558 559static Ret ftk_entry_compute_visible_range(FtkWidget* thiz) 560{ 561 DECL_PRIV0(thiz, priv); 562 int width = ftk_widget_width(thiz); 563 FtkCanvas* canvas = ftk_widget_canvas(thiz); 564 const char* other_side = NULL; 565 return_val_if_fail(thiz != NULL && priv->text_buffer != NULL, RET_FAIL); 566 567 if(priv->visible_start < 0 || priv->visible_end < 0) 568 { 569 other_side = ftk_canvas_calc_str_visible_range(canvas, TB_TEXT, 570 priv->visible_start, priv->visible_end, width - 2 * FTK_ENTRY_H_MARGIN, NULL); 571 572 if(priv->visible_start < 0) 573 { 574 priv->visible_start = other_side - TB_TEXT; 575 } 576 else if(priv->visible_end < 0) 577 { 578 priv->visible_end = other_side - TB_TEXT; 579 } 580 } 581 582 return RET_OK; 583} 584 585static Ret ftk_entry_set_text_internal(FtkWidget* thiz, const char* text) 586{ 587 DECL_PRIV0(thiz, priv); 588 return_val_if_fail(thiz != NULL && text != NULL, RET_FAIL); 589 590 ftk_text_buffer_delete(priv->text_buffer, 0, TB_LENGTH); 591 ftk_text_buffer_insert(priv->text_buffer, 0, text, -1); 592 593 priv->visible_start = -1; 594 priv->visible_end = TB_LENGTH; 595 priv->caret = priv->visible_end; 596 ftk_widget_invalidate(thiz); 597 598 return RET_OK; 599} 600 601Ret ftk_entry_set_tips(FtkWidget* thiz, const char* tips) 602{ 603 DECL_PRIV0(thiz, priv); 604 return_val_if_fail(thiz != NULL, RET_FAIL); 605 606 FTK_FREE(priv->tips); 607 priv->tips = FTK_STRDUP(tips); 608 609 return RET_OK; 610} 611 612Ret ftk_entry_set_input_type(FtkWidget* thiz, FtkInputType type) 613{ 614 DECL_PRIV0(thiz, priv); 615 return_val_if_fail(thiz != NULL, RET_FAIL); 616 617 priv->input_type = type; 618 619 return RET_OK; 620} 621 622Ret ftk_entry_insert_text(FtkWidget* thiz, size_t pos, const char* text) 623{ 624 DECL_PRIV0(thiz, priv); 625 return_val_if_fail(thiz != NULL && text != NULL, RET_FAIL); 626 627 pos = pos < TB_LENGTH ? pos : TB_LENGTH; 628 ftk_text_buffer_insert(priv->text_buffer, pos, text, -1); 629 priv->visible_start = -1; 630 priv->visible_end = TB_LENGTH; 631 priv->caret = priv->visible_end; 632 ftk_widget_invalidate(thiz); 633 634 return RET_OK; 635} 636 637const char* ftk_entry_get_text(FtkWidget* thiz) 638{ 639 DECL_PRIV0(thiz, priv); 640 return_val_if_fail(thiz != NULL, NULL); 641 642 return TB_TEXT; 643} 644 645Ret ftk_entry_set_text(FtkWidget* thiz, const char* text) 646{ 647 ftk_widget_set_text(thiz, text); 648 649 return RET_OK; 650} 651 652Ret ftk_entry_set_readonly(FtkWidget* thiz, int readonly) 653{ 654 DECL_PRIV0(thiz, priv); 655 return_val_if_fail(thiz != NULL, RET_FAIL); 656 657 priv->readonly = readonly; 658 659 return RET_OK; 660}