PageRenderTime 115ms CodeModel.GetById 7ms app.highlight 74ms RepoModel.GetById 26ms app.codeStats 0ms

/src/ftk_text_view.c

http://ftk.googlecode.com/
C | 826 lines | 667 code | 128 blank | 31 comment | 101 complexity | 8a92bd3b8b3788ad9ae8b63d395ca218 MD5 | raw file
  1/*
  2 * File: ftk_text_view.c    
  3 * Author:  Li XianJing <xianjimli@hotmail.com>
  4 * Brief:   multi 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-12-31 Li XianJing <xianjimli@hotmail.com> created
 29 * 2010-03-14 Li XianJing <xianjimli@hotmail.com> support input method.
 30 */
 31
 32#include "ftk_log.h"
 33#include "ftk_util.h"
 34#include "ftk_window.h"
 35#include "ftk_globals.h"
 36#include "ftk_text_view.h"
 37#include "ftk_text_buffer.h"
 38#include "ftk_source_timer.h"
 39#include "ftk_input_method_preeditor.h"
 40
 41typedef struct _TextViewPrivInfo
 42{
 43	int   caret;
 44	int   caret_x;
 45	int   caret_y;
 46	int   caret_at_line;
 47	int   caret_visible;
 48	FtkSource* caret_timer;
 49	
 50	int   visible_start_line;
 51	int   visible_end_line;
 52
 53	int   readonly;
 54	int   selected_start;
 55	int   selected_end;
 56	int   v_margin;
 57	int   total_lines;
 58	int   visible_lines;
 59	FtkTextBuffer* text_buffer;
 60
 61	int lines_offset_nr;
 62	unsigned short* lines_offset;
 63
 64    int noborder;
 65}PrivInfo;
 66
 67#define TEXT_VIEW_H_MARGIN    4
 68#define TEXT_VIEW_V_MARGIN    1
 69#define TEXT_VIEW_TOP_MARGIN  3
 70
 71#define TB_TEXT        priv->text_buffer->buffer
 72#define TB_LENGTH (int)(priv->text_buffer->length)
 73#define HAS_TEXT(priv) (priv->text_buffer != NULL && TB_LENGTH > 0) 
 74
 75static Ret ftk_text_view_on_paint_caret(FtkWidget* thiz);
 76
 77static Ret ftk_text_view_recalc_caret_at_line(FtkWidget* thiz)
 78{
 79	int i = 0;
 80	DECL_PRIV0(thiz, priv);
 81
 82	priv->caret = priv->caret < 0 ? 0 : priv->caret;
 83	priv->caret = priv->caret >  TB_LENGTH ? TB_LENGTH : priv->caret;
 84
 85	for(i = 0; i < priv->total_lines; i++)
 86	{
 87		if(priv->caret >= priv->lines_offset[i] && priv->caret < priv->lines_offset[i+1])
 88		{
 89			priv->caret_at_line = i;
 90			break;
 91		}
 92	}
 93
 94	if(i == priv->total_lines)
 95	{
 96		priv->caret_at_line = priv->total_lines - 1;
 97	}
 98
 99	return RET_OK;
100}
101
102static Ret ftk_text_view_update_caret(FtkWidget* thiz)
103{
104	DECL_PRIV0(thiz, priv);
105	
106	ftk_text_view_recalc_caret_at_line(thiz);
107
108	if((priv->caret_at_line) < priv->visible_start_line)
109	{
110		priv->visible_start_line = priv->caret_at_line;
111		priv->visible_end_line = FTK_MIN((priv->visible_start_line + priv->visible_lines), priv->total_lines);
112	}
113	else if((priv->caret_at_line) >= priv->visible_end_line)
114	{
115		priv->visible_end_line = priv->caret_at_line + 1;
116		priv->visible_start_line = FTK_MAX(0, (priv->visible_end_line - priv->visible_lines));
117	}
118
119	priv->visible_start_line = FTK_MAX(0, priv->visible_start_line);
120
121	ftk_widget_invalidate(thiz);
122
123	return RET_OK;
124}
125
126static Ret ftk_text_view_move_caret(FtkWidget* thiz, int offset)
127{
128	int caret = 0;
129	DECL_PRIV0(thiz, priv);
130	
131	if(!HAS_TEXT(priv))
132	{
133		priv->caret = 0;
134		return RET_OK;
135	}
136
137	caret = priv->caret;
138	priv->caret_visible = 0;
139	ftk_text_view_on_paint_caret(thiz);
140	priv->caret += ftk_text_buffer_chars_bytes(priv->text_buffer, priv->caret, offset);
141	ftk_logd("%s: %d->%d\n", __func__, caret, priv->caret);
142	
143	return ftk_text_view_update_caret(thiz);
144}
145
146static Ret ftk_text_view_set_caret(FtkWidget* thiz, int caret)
147{
148	DECL_PRIV0(thiz, priv);
149	
150	if(!HAS_TEXT(priv))
151	{
152		priv->caret = 0;
153		return RET_OK;
154	}
155
156	priv->caret_visible = 0;
157	ftk_text_view_on_paint_caret(thiz);
158	priv->caret = caret;
159
160	return ftk_text_view_update_caret(thiz);
161}
162
163static Ret ftk_text_view_calc_lines(FtkWidget* thiz)
164{
165	int font_height = 0;
166	DECL_PRIV0(thiz, priv);
167	int height = ftk_widget_height(thiz);
168	font_height = ftk_widget_get_font_size(thiz);
169
170	priv->visible_lines = (height - 2 * TEXT_VIEW_TOP_MARGIN) / (font_height + TEXT_VIEW_V_MARGIN);
171	priv->v_margin = ((height - 2 * TEXT_VIEW_TOP_MARGIN) % (font_height + TEXT_VIEW_V_MARGIN))/2;
172
173	return RET_OK;
174}
175
176
177static int ftk_text_view_get_chars_nr_in_line(FtkWidget* thiz, int line)
178{
179	DECL_PRIV0(thiz, priv);
180
181	if((line + 1 ) < priv->total_lines)
182	{
183		return priv->lines_offset[line + 1] - priv->lines_offset[line];
184	}
185	else
186	{
187		return strlen(TB_TEXT + priv->lines_offset[line]);
188	}
189}
190
191static Ret ftk_text_view_get_offset_by_pointer(FtkWidget* thiz, int px, int py)
192{
193	int len = 0;
194	int caret = 0;
195	int start = 0;
196	int line_no = 0;
197	int delta_h = 0;
198	int font_height = 0;
199	DECL_PRIV0(thiz, priv);
200	FtkTextLine line = {0};
201	FtkTextLayout* text_layout = NULL;
202	FTK_BEGIN_PAINT(x, y, width, height, canvas);
203	text_layout = ftk_default_text_layout();
204	
205	if(!HAS_TEXT(priv))
206	{
207		return RET_OK;
208	}
209
210	(void)height;
211	ftk_canvas_set_gc(canvas, ftk_widget_get_gc(thiz));
212	font_height = ftk_widget_get_font_size(thiz);
213	
214	delta_h = py - y - TEXT_VIEW_V_MARGIN - TEXT_VIEW_TOP_MARGIN;
215	line_no = priv->visible_start_line + delta_h/(font_height + TEXT_VIEW_V_MARGIN);
216	line_no = line_no < priv->visible_end_line ? line_no : priv->visible_end_line - 1;
217
218	start = priv->lines_offset[line_no];
219
220	width = px - x - TEXT_VIEW_H_MARGIN + 1;
221	len = ftk_text_view_get_chars_nr_in_line(thiz, line_no);
222	ftk_text_layout_init(text_layout, TB_TEXT + start, len, canvas, width); 
223	if(ftk_text_layout_get_visual_line(text_layout, &line) == RET_OK)
224	{
225		caret = start + line.len;
226		ftk_text_view_set_caret(thiz, caret);
227	}
228
229	ftk_logd("%s: line_no=%d priv->total_lines=%d caret=%d priv->caret=%d caret_at_line=%d\n", 
230		__func__, line_no, priv->total_lines, caret, priv->caret, priv->caret_at_line);
231
232	return RET_OK;
233}
234
235static Ret ftk_text_view_extend_lines_offset(FtkWidget* thiz, int nr)
236{
237	int i = 0;
238	int lines_offset_nr = 0;
239	unsigned short* lines_offset = NULL;
240	DECL_PRIV0(thiz, priv);
241
242	if(nr < priv->lines_offset_nr)
243	{
244		return RET_OK;
245	}
246
247	lines_offset_nr = priv->lines_offset_nr + ((priv->lines_offset_nr + 10) >> 1);
248	lines_offset = (short unsigned int*)FTK_REALLOC(priv->lines_offset, sizeof(priv->lines_offset[0]) * lines_offset_nr);
249	if(lines_offset != NULL)
250	{
251		for(i = priv->lines_offset_nr; i < lines_offset_nr; i++)
252		{
253			lines_offset[i] = 0;
254		}
255		priv->lines_offset = lines_offset;
256		priv->lines_offset_nr = lines_offset_nr;
257
258		ftk_logd("%s: lines_offset_nr=%d\n", __func__, priv->lines_offset_nr);
259	}
260
261	return lines_offset != NULL ? RET_OK : RET_FAIL;
262}
263
264static Ret ftk_text_view_relayout(FtkWidget* thiz, int start_line)
265{
266	int i = 0;
267	int start_offset = 0;
268	FtkTextLine line = {0};
269	DECL_PRIV0(thiz, priv);
270	const char* text = TB_TEXT;
271	int width = ftk_widget_width(thiz) - 2 * TEXT_VIEW_H_MARGIN;
272	FtkTextLayout* text_layout = ftk_default_text_layout();
273	return_val_if_fail(thiz != NULL && text != NULL, RET_FAIL);
274
275	start_line = start_line < 0 ? 0 : start_line;
276	start_offset = priv->lines_offset[start_line];
277	ftk_text_layout_init(text_layout, text+start_offset, -1, ftk_widget_canvas(thiz), width);
278	ftk_text_layout_set_wrap_mode(text_layout, ftk_widget_get_wrap_mode(thiz));
279	ftk_logd("%s: start_line=%d\n", __func__, start_line);
280	for(i = start_line ; ftk_text_view_extend_lines_offset(thiz, i + 1) == RET_OK; i++)
281	{
282		if(ftk_text_layout_get_visual_line(text_layout, &line) != RET_OK) break;
283		priv->lines_offset[i] = line.pos_v2l[0] + start_offset;
284	}
285
286	priv->total_lines = i;
287	ftk_text_view_calc_lines(thiz);
288	ftk_text_view_recalc_caret_at_line(thiz);
289	priv->visible_end_line = FTK_MIN(priv->total_lines, priv->visible_start_line + priv->visible_lines);
290	ftk_text_view_update_caret(thiz);
291
292	return RET_OK;
293}
294
295static Ret ftk_text_view_handle_mouse_evevnt(FtkWidget* thiz, FtkEvent* event)
296{
297	return ftk_text_view_get_offset_by_pointer(thiz, event->u.mouse.x, event->u.mouse.y);
298}
299
300static Ret ftk_text_view_input_str(FtkWidget* thiz, const char* str)
301{
302	int count = 0;
303	DECL_PRIV0(thiz, priv);
304	return_val_if_fail(thiz != NULL && str != NULL, RET_FAIL);
305
306	if(priv->readonly) return RET_FAIL;
307
308	count = utf8_count_char(str, strlen(str));
309	ftk_text_buffer_insert(priv->text_buffer, priv->caret, str, -1);
310	ftk_text_view_relayout(thiz, FTK_MIN(priv->visible_start_line,priv->caret_at_line));
311	ftk_text_view_move_caret(thiz, count);	
312
313	return RET_OK;
314}
315
316static Ret ftk_text_view_input_char(FtkWidget* thiz, char c)
317{
318	char str[2] = {0};
319
320	str[0] = c;
321	return ftk_text_view_input_str(thiz, str);
322}
323
324/*should be static ??*/
325//static Ret ftk_text_view_v_move_caret(FtkWidget* thiz, int offset)
326Ret ftk_text_view_v_move_caret(FtkWidget* thiz, int offset)
327{
328	int caret = 0;
329	int start = 0;
330	int width = 0;
331	Ret ret = RET_OK;
332	FtkTextLine line = {0};
333	DECL_PRIV0(thiz, priv);
334	FtkCanvas* canvas = ftk_widget_canvas(thiz);
335	FtkTextLayout* text_layout = ftk_default_text_layout();
336
337	if(offset < 0)
338	{
339		ret = priv->caret_at_line > 0 ? RET_REMOVE: RET_OK;
340	}
341	else
342	{
343		ret = (priv->caret_at_line + 1) < priv->total_lines ? RET_REMOVE: RET_OK;
344	}
345
346	if(ret == RET_OK)
347	{
348		return ret;
349	}
350
351	priv->caret_at_line += offset;
352	priv->caret_at_line = priv->caret_at_line < 0 ? 0 : priv->caret_at_line;
353	priv->caret_at_line = priv->caret_at_line >= priv->total_lines ? priv->total_lines - 1 : priv->caret_at_line;
354
355	start = priv->lines_offset[priv->caret_at_line];
356
357	ftk_canvas_set_gc(canvas, ftk_widget_get_gc(thiz));
358	width = priv->caret_x - TEXT_VIEW_H_MARGIN - FTK_PAINT_X(thiz) + 1;
359	if(width > 0)
360	{
361		ftk_text_layout_init(text_layout, TB_TEXT + start, -1, canvas, width);
362		ftk_text_layout_set_wrap_mode(text_layout, ftk_widget_get_wrap_mode(thiz));
363		if(ftk_text_layout_get_visual_line(text_layout, &line) == RET_OK)
364		{
365			if(line.len > 0)
366			{
367				caret = line.pos_v2l[line.len - 1] + start + 1;	
368			}
369			else
370			{
371				caret = start;
372			}
373		}
374	}
375	else
376	{
377		caret = start;
378	}
379
380	ftk_logd("%s: caret=%d line.len=%d priv->caret_at_line=%d\n",
381		__func__, caret, line.len, priv->caret_at_line);
382
383	ftk_text_view_set_caret(thiz, caret);
384
385	return ret;
386}
387
388static Ret ftk_text_view_up_caret(FtkWidget* thiz)
389{
390	return ftk_text_view_v_move_caret(thiz, -1);
391}
392
393static Ret ftk_text_view_down_caret(FtkWidget* thiz)
394{
395	return ftk_text_view_v_move_caret(thiz, 1);
396}
397
398static Ret ftk_text_view_handle_key_event(FtkWidget* thiz, FtkEvent* event)
399{
400	Ret ret = RET_OK;
401	DECL_PRIV0(thiz, priv);
402
403	switch(event->u.key.code)
404	{
405		case FTK_KEY_CHOOSE_IME:
406		{
407			if(priv->readonly) break;
408			ftk_input_method_manager_focus_out(ftk_default_input_method_manager(), thiz);
409			ftk_input_method_chooser();
410			ftk_input_method_manager_focus_in(ftk_default_input_method_manager(), thiz);
411
412			break;
413		}
414		case FTK_KEY_HOME:
415		{
416			ftk_text_view_set_caret(thiz, 0);
417			break;
418		}
419		case FTK_KEY_END:
420		{
421			ftk_text_view_set_caret(thiz, TB_LENGTH);
422			break;
423		}
424		case FTK_KEY_LEFT:
425		{
426			ftk_text_view_move_caret(thiz, -1);
427			ret = RET_REMOVE;
428			break;
429		}
430		case FTK_KEY_RIGHT:
431		{
432			ftk_text_view_move_caret(thiz, 1);
433			ret = RET_REMOVE;
434			break;
435		}
436		case FTK_KEY_UP:
437		{
438			ret = ftk_text_view_up_caret(thiz);	
439			break;
440		}
441		case FTK_KEY_DOWN:
442		{
443			ret = ftk_text_view_down_caret(thiz);	
444			break;
445		}
446		case FTK_KEY_DELETE:
447		{
448			if(priv->readonly) break;
449			ftk_text_buffer_delete_chars(priv->text_buffer, priv->caret, 1);
450			ftk_text_view_relayout(thiz, priv->caret_at_line);
451			ftk_text_view_move_caret(thiz, 0);
452			break;
453		}
454		case FTK_KEY_BACKSPACE:
455		{
456			int caret = priv->caret;
457			if(priv->readonly) break;
458			ftk_text_view_move_caret(thiz, -1);
459			if(ftk_text_buffer_delete_chars(priv->text_buffer, caret, -1) == RET_OK)
460			{
461				ftk_text_view_relayout(thiz, priv->caret_at_line-1);
462			}
463			break;
464		}
465		default:
466		{
467			if(priv->readonly) break;
468			if((event->u.key.code < 0xff && isprint(event->u.key.code)) 
469				|| event->u.key.code == FTK_KEY_ENTER)
470			{
471				if(event->u.key.code == FTK_KEY_ENTER)
472				{
473					ftk_text_view_input_char(thiz, '\n');
474				}
475				else
476				{
477					ftk_text_view_input_char(thiz, event->u.key.code);
478				}
479				ftk_text_view_relayout(thiz, priv->caret_at_line);
480			}
481			break;
482		}
483	}
484
485	return ret;
486}
487
488static Ret ftk_text_view_on_event(FtkWidget* thiz, FtkEvent* event)
489{
490	Ret ret = RET_OK;
491	DECL_PRIV0(thiz, priv);
492	return_val_if_fail(thiz != NULL && event != NULL, RET_FAIL);
493
494	switch(event->type)
495	{
496		case FTK_EVT_FOCUS_IN:
497		{
498			if(!priv->readonly)
499			{
500				ftk_input_method_manager_focus_in(ftk_default_input_method_manager(), thiz);
501				ftk_input_method_manager_set_current_type(ftk_default_input_method_manager(), FTK_INPUT_NORMAL);
502			}
503			ftk_source_ref(priv->caret_timer);
504			ftk_main_loop_add_source(ftk_default_main_loop(), priv->caret_timer);
505			break;
506		}
507		case FTK_EVT_FOCUS_OUT:
508		{
509			if(!priv->readonly)
510			{
511				ftk_input_method_manager_focus_out(ftk_default_input_method_manager(), thiz);
512			}
513			ftk_main_loop_remove_source(ftk_default_main_loop(), priv->caret_timer);
514			break;
515		}
516		case FTK_EVT_KEY_DOWN:
517		case FTK_EVT_KEY_UP:
518		{
519			if(event->type == FTK_EVT_KEY_DOWN)
520			{
521				ret = ftk_text_view_handle_key_event(thiz, event);
522			}
523			else
524			{
525				ret = event->u.key.code == FTK_KEY_LEFT || event->u.key.code == FTK_KEY_RIGHT 
526					|| event->u.key.code == FTK_KEY_UP || event->u.key.code == FTK_KEY_DOWN
527					? RET_REMOVE : RET_OK;
528			}
529			break;
530		}
531		case FTK_EVT_MOUSE_UP:
532		{
533			ret = ftk_text_view_handle_mouse_evevnt(thiz, event);
534			break;
535		}
536		case FTK_EVT_IM_PREEDIT:
537		{
538			FtkPoint caret_pos = {0};
539			caret_pos.x = priv->caret_x;
540			caret_pos.y = priv->caret_y;
541			ftk_im_show_preeditor(thiz, &caret_pos, (FtkCommitInfo*)event->u.extra);
542			break;
543		}
544		case FTK_EVT_IM_COMMIT:
545		{
546			ftk_text_view_input_str(thiz, (const char*)event->u.extra);
547			ftk_input_method_manager_focus_ack_commit(ftk_default_input_method_manager());
548			break;
549		}
550		case FTK_EVT_MOUSE_LONG_PRESS:
551		{
552			if(priv->readonly) break;
553
554			ftk_input_method_manager_focus_out(ftk_default_input_method_manager(), thiz);
555			ftk_input_method_chooser();
556			ftk_input_method_manager_focus_in(ftk_default_input_method_manager(), thiz);
557
558			break;
559		}
560		case FTK_EVT_SET_TEXT:
561		{
562			ftk_text_view_set_text(thiz, (const char*)event->u.extra, -1);
563			ret = RET_REMOVE;
564
565			break;
566		}
567		case FTK_EVT_GET_TEXT:
568		{
569			event->u.extra = (void*)ftk_text_view_get_text(thiz);
570			ret = RET_REMOVE;
571
572			break;
573		}
574		default:break;
575	}
576
577	return ret;
578}
579
580static Ret ftk_text_view_on_paint_caret(FtkWidget* thiz)
581{
582	DECL_PRIV0(thiz, priv);
583	return_val_if_fail(thiz != NULL, RET_FAIL);
584	
585	if(!ftk_window_is_mapped(ftk_widget_toplevel(thiz)))
586	{
587		return RET_OK;
588	}
589
590	if(ftk_widget_is_focused(thiz))
591	{
592		FtkGc gc = {0};
593		int font_height = 0;
594		FTK_BEGIN_PAINT(x, y, width, height, canvas);
595		ftk_canvas_set_gc(canvas, ftk_widget_get_gc(thiz));
596		font_height = ftk_widget_get_font_size(thiz);
597		(void)x;(void)y;(void)width;(void)height;	
598		
599		gc.mask = FTK_GC_FG;
600		gc.fg = priv->caret_visible ? ftk_widget_get_gc(thiz)->fg : ftk_widget_get_gc(thiz)->bg;
601		ftk_canvas_reset_gc(canvas, &gc);
602		ftk_canvas_draw_vline(canvas, priv->caret_x, priv->caret_y, font_height);
603		priv->caret_visible = !priv->caret_visible;
604		FTK_END_PAINT();
605		ftk_logd("caret(%d %d)\n", priv->caret_x, priv->caret_y);
606	}
607
608	return RET_OK;
609}
610
611static Ret ftk_text_view_paint_border(FtkWidget* thiz, FtkCanvas* canvas, int x, int y, int width, int height)
612{
613	FtkGc gc = {0};
614	gc.mask = FTK_GC_FG;
615	gc.fg = ftk_theme_get_border_color(ftk_default_theme(), FTK_TEXT_VIEW, ftk_widget_state(thiz));
616	ftk_canvas_set_gc(canvas, &gc);
617	
618	ftk_canvas_draw_vline(canvas, x, y + 2, height - 4);
619	ftk_canvas_draw_vline(canvas, x+width-1, y + 2, height - 4);
620	ftk_canvas_draw_hline(canvas, x + 2, y, width-4);
621	ftk_canvas_draw_hline(canvas, x + 1, y + height - 1, width-2);
622
623	if(ftk_widget_state(thiz) == FTK_WIDGET_NORMAL)
624	{
625		gc.fg.r += 0x60;
626		gc.fg.g += 0x60;
627		gc.fg.b += 0x60;
628		ftk_canvas_set_gc(canvas, &gc);
629	}
630	ftk_canvas_draw_hline(canvas, x + 1, y + 1, width-2);
631	ftk_canvas_draw_vline(canvas, x + 1, y + 1, height - 2);
632	ftk_canvas_draw_vline(canvas, x + width -2, y + 1, height - 2);
633	ftk_canvas_draw_hline(canvas, x + 2, y + height - 2, width-4);
634
635	return RET_OK;
636}
637
638static Ret ftk_text_view_on_paint(FtkWidget* thiz)
639{
640	int dx = 0;
641	int dy = 0;
642	int font_height = 0;
643	DECL_PRIV0(thiz, priv);
644	FTK_BEGIN_PAINT(x, y, width, height, canvas);
645
646    if (!priv->noborder)
647	    ftk_text_view_paint_border(thiz, canvas, x, y, width, height);
648
649	if(priv->total_lines <= 0 && TB_LENGTH > 0)
650	{
651		ftk_text_view_relayout(thiz, 0);
652	}
653
654	ftk_text_view_recalc_caret_at_line(thiz);
655	ftk_canvas_set_gc(canvas, ftk_widget_get_gc(thiz));
656	if(priv->text_buffer != NULL && TB_LENGTH > 0)
657	{
658		int i = 0;
659		int start = 0;
660		FtkTextLine line = {0};
661		FtkTextLayout* text_layout = ftk_default_text_layout();
662
663		font_height = ftk_widget_get_font_size(thiz);
664		dy = y + priv->v_margin + TEXT_VIEW_TOP_MARGIN;
665		dx = x + TEXT_VIEW_H_MARGIN;
666		width = width - 2 * TEXT_VIEW_H_MARGIN;
667
668		start = priv->lines_offset[priv->visible_start_line];
669		ftk_text_layout_init(text_layout, TB_TEXT + start, -1, canvas, width);
670		ftk_text_layout_set_wrap_mode(text_layout, ftk_widget_get_wrap_mode(thiz));
671
672		for(i = priv->visible_start_line; i < priv->visible_end_line; i++)
673		{
674			start = priv->lines_offset[i];
675			if(priv->caret_at_line == i)
676			{
677				priv->caret_y = dy + TEXT_VIEW_V_MARGIN;
678				priv->caret_x = dx + ftk_canvas_get_str_extent(canvas, TB_TEXT + start, priv->caret - start) - 1;
679			}
680			
681			dy += TEXT_VIEW_V_MARGIN;
682			if(ftk_text_layout_get_visual_line(text_layout, &line) == RET_OK && line.len > 0)
683			{
684				ftk_canvas_draw_string(canvas, dx, dy+font_height/2, line.text, line.len, 1);
685			}
686			dy += font_height;
687		}
688	}
689	else
690	{
691		priv->caret_x = TEXT_VIEW_H_MARGIN;
692		priv->caret_y = TEXT_VIEW_V_MARGIN;
693	}
694
695	ftk_text_view_on_paint_caret(thiz);
696
697	FTK_END_PAINT();
698}
699
700static void ftk_text_view_destroy(FtkWidget* thiz)
701{
702	if(thiz != NULL)
703	{
704		DECL_PRIV0(thiz, priv);
705	
706		if(ftk_widget_is_focused(thiz))
707		{
708			ftk_input_method_manager_focus_out(ftk_default_input_method_manager(), thiz);
709		}
710		ftk_source_disable(priv->caret_timer);
711		ftk_main_loop_remove_source(ftk_default_main_loop(), priv->caret_timer);
712		ftk_source_unref(priv->caret_timer);
713		ftk_text_buffer_destroy(priv->text_buffer);
714		FTK_FREE(priv);
715	}
716
717	return;
718}
719
720FtkWidget* ftk_text_view_create(FtkWidget* parent, int x, int y, int width, int height)
721{
722	FtkWidget* thiz = (FtkWidget*)FTK_ZALLOC(sizeof(FtkWidget));
723	return_val_if_fail(thiz != NULL, NULL);
724
725	thiz->priv_subclass[0] = (PrivInfo*)FTK_ZALLOC(sizeof(PrivInfo));
726	if(thiz->priv_subclass[0] != NULL)
727	{
728		int min_height = 0;
729		DECL_PRIV0(thiz, priv);
730
731		thiz->on_event = ftk_text_view_on_event;
732		thiz->on_paint = ftk_text_view_on_paint;
733		thiz->destroy  = ftk_text_view_destroy;
734
735		min_height = ftk_font_desc_get_size(ftk_default_font()) + TEXT_VIEW_V_MARGIN * 2;
736		height = height < min_height ? min_height : height;
737		ftk_widget_init(thiz, FTK_TEXT_VIEW, 0, x, y, width, height, 0);
738
739		ftk_text_view_extend_lines_offset(thiz, 16);
740		priv->caret_timer = ftk_source_timer_create(500, (FtkTimer)ftk_text_view_on_paint_caret, thiz);
741		priv->text_buffer = ftk_text_buffer_create(128);
742		ftk_widget_append_child(parent, thiz);
743		ftk_widget_set_wrap_mode(thiz, FTK_WRAP_WORD);
744	}
745	else
746	{
747		FTK_FREE(thiz);
748	}
749
750	return thiz;
751}
752
753Ret ftk_text_view_set_noborder(FtkWidget* thiz, int b)
754{
755	DECL_PRIV0(thiz, priv);
756	return_val_if_fail(thiz != NULL, RET_FAIL);
757
758	priv->noborder = b;
759
760	return RET_OK;
761}
762
763int ftk_text_view_get_total_lines(FtkWidget* thiz)
764{
765	DECL_PRIV0(thiz, priv);
766	return_val_if_fail(thiz != NULL, RET_OK);
767
768	return priv->total_lines;
769}
770
771Ret ftk_text_view_set_text(FtkWidget* thiz, const char* text, int len)
772{
773	DECL_PRIV0(thiz, priv);
774	return_val_if_fail(thiz != NULL && text != NULL, RET_FAIL);
775
776	ftk_text_buffer_delete(priv->text_buffer, 0, TB_LENGTH);
777	
778	return ftk_text_view_insert_text(thiz, 0, text, len);
779}
780
781static int ftk_text_view_pos_to_line(FtkWidget* thiz, size_t pos)
782{
783	int i = 0;
784	DECL_PRIV0(thiz, priv);
785
786	for(i = 0; i < priv->total_lines; i++)
787	{
788		if(priv->lines_offset[i] >= pos)
789		{
790			break;
791		}
792	}
793
794	return i;
795}
796
797Ret ftk_text_view_insert_text(FtkWidget* thiz, size_t pos, const char* text, int len)
798{
799	DECL_PRIV0(thiz, priv);
800	return_val_if_fail(thiz != NULL && text != NULL, RET_FAIL);
801
802	pos = pos < TB_LENGTH ? pos : TB_LENGTH;
803	ftk_text_buffer_insert(priv->text_buffer, pos, text, len);
804	ftk_text_view_relayout(thiz, ftk_text_view_pos_to_line(thiz, pos));
805	ftk_text_view_set_caret(thiz, pos + strlen(text));
806
807	return RET_OK;
808}
809
810const char* ftk_text_view_get_text(FtkWidget* thiz)
811{
812	DECL_PRIV0(thiz, priv);
813	return_val_if_fail(thiz != NULL, NULL);
814
815	return TB_TEXT;
816}
817
818Ret ftk_text_view_set_readonly(FtkWidget* thiz, int readonly)
819{
820	DECL_PRIV0(thiz, priv);
821	return_val_if_fail(priv != NULL, RET_FAIL);
822
823	priv->readonly = readonly;
824
825	return RET_OK;
826}