PageRenderTime 389ms CodeModel.GetById 81ms app.highlight 192ms RepoModel.GetById 110ms app.codeStats 0ms

/src/ftk_entry.c

http://ftk.googlecode.com/
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}