///////////////////////////////////////////////////////////////////////////////
//
//                          IMPORTANT NOTICE
//
// The following open source license statement does not apply to any
// entity in the Exception List published by FMSoft.
//
// For more information, please visit:
//
// https://www.fmsoft.cn/exception-list
//
//////////////////////////////////////////////////////////////////////////////
/*
 *   This file is part of MiniGUI, a mature cross-platform windowing 
 *   and Graphics User Interface (GUI) support system for embedded systems
 *   and smart IoT devices.
 * 
 *   Copyright (C) 2002~2018, Beijing FMSoft Technologies Co., Ltd.
 *   Copyright (C) 1998~2002, WEI Yongming
 * 
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 * 
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 * 
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 *   Or,
 * 
 *   As this program is a library, any link to this program must follow
 *   GNU General Public License version 3 (GPLv3). If you cannot accept
 *   GPLv3, you need to be licensed from FMSoft.
 * 
 *   If you have got a commercial license of this program, please use it
 *   under the terms and conditions of the commercial license.
 * 
 *   For more information about the commercial license, please refer to
 *   <http://www.minigui.com/en/about/licensing-policy/>.
 */
/*
** textedit.c: the new textedit module.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>


#ifdef __MINIGUI_LIB__
#include "common.h"
#include "minigui.h"
#include "gdi.h"
#include "window.h"
#include "object.h"
#elif defined (__MGNCS_LIB__) 
#include <minigui/common.h>
#include <minigui/minigui.h>
#include <minigui/gdi.h>
#include <minigui/window.h>
#include <minigui/ctrl/edit.h>
#include "mgncsconfig.h"
#include "mcommon.h"
#include "mobject.h"
#include "mcomponent.h"
#endif

#if defined(_MGCTRL_TEXTEDIT_USE_NEW_IMPL) || defined(_MGNCS_TEXTEDITOR)

#include "mwidget.h"
#include "mbtree.h"
#include "mtexteditbase.h"
#include "mtextedit.h"
#ifdef __MINIGUI_LIB__
#include "ctrl/ctrlhelper.h"
#include "ctrl/edit.h"
#include "ctrl/textedit.h"

#define NCSS_TE_READONLY  ES_READONLY
#define NCSS_TE_UPPERCASE ES_UPPERCASE
#define NCSS_TE_LOWERCASE ES_LOWERCASE
#define NCSS_TE_NOHIDESEL ES_NOHIDESEL
#define NCSS_TE_AUTOWRAP  ES_AUTOWRAP
#define NCSS_TE_BASELINE  ES_BASELINE
#define NCSS_TE_TITLE     ES_TITLE
#define NCSS_TE_AUTOVSCROLL ES_AUTOVSCROLL
#define NCSS_TE_AUTOHSCROLL ES_AUTOHSCROLL

#define NCSS_EX_UNNEEDIME  0x1

#define NCSN_TE_CONTENTCHANGED  EN_CONTCHANGED
#define NCSN_TE_SETFOCUS        EN_SETFOCUS
#define NCSN_TE_KILLFOCUS       EN_KILLFOCUS
#define NCSN_TE_MAXTEXT         EN_MAXTEXT
#define NCSN_TE_CHANGE          EN_CHANGE
#define NCSN_TE_UPDATE          EN_UPDATE
#define NCSN_TE_SELCHANGED      EN_SELCHANGED
#define NCSN_WIDGET_DBCLICKED   EN_DBLCLK
#define NCSN_WIDGET_CLICKED     EN_CLICKED

#else /* __MINIGUI_LIB__ */
#   include "mdblist.h"
#   include "mitem.h"
#   include "mscroll_widget.h"
#   include "mitem_manager.h"
#   include "mitemview.h"
#   include "mscrollview.h"
#   include "medit.h"
#endif


#define SMART_ASSIGN(lvalue, rvalue)    \
    do {                                \
        if(lvalue != NULL)              \
            *lvalue = rvalue;           \
    } while(0)                          \

static int get_first_char_len(PLOGFONT plogfont, const char* str, int len)
{
    int l;
    if(len <= 0)
        return 0;
    /*[humingming for bug 5344]: don't process like this,
     * on some borad may case some problem */
#if 0
    if(*str > 0) //is ascii code
    {
        return 1;
    }
    //it's a multi-byte character, the return value must > 1
    l = GetFirstMCharLen(plogfont, str, len);
    if(l <= 1)
        return 0;
#endif
    l = GetFirstMCharLen(plogfont, str, len);
    if (l < 0)
        return 1;
    return l;
}


// Enable this marco if support margin decorative
//#define MARGIN_UPDATE 1

static mTextRender g_textRender;
#define MAX_BLOCK 80

//mTextBuffer
static inline char* alloc_str_buffer(char* old_buff, int *plen)
{
    if(*plen <= 0)
        return old_buff;

    *plen = ((*plen + MAX_BLOCK - 1) / MAX_BLOCK ) * MAX_BLOCK;

    if(old_buff)
        return (char*)realloc(old_buff, *plen);
    return (char*)calloc(1, *plen);
}

static inline void free_str_buffer(char* text_buffer)
{
    if(text_buffer)
        free(text_buffer);
}

static inline void set_textbuffer(mTextBuffer* self, const char* title, int len)
{
    if(len <= 0 || !title)
        return;

    if(self->max_size < len){
        self->max_size = len;
        self->buffer = alloc_str_buffer(self->buffer, &self->max_size);
    }

    strncpy(self->buffer, title, len);
    self->buffer[len] = 0;
    self->char_len = len + 1;
}

static void update_observers(mTextBuffer* self, 
        int begin, int delcount, int insertcount)
{
    mTextBufferObserverNode* node = self->observers;
    while(node)
    {
        if(node->observer)
            (node->observer)->_vtable->onTextChanged(INTEFACE_ADJUST(node->observer), begin, delcount, insertcount);
        node = node->next;
    }
}

static void mTextBuffer_construct(mTextBuffer* self, va_list va)
{
    const char* title = NULL;
    int len = 0;

    Class(mObject).construct((mObject*)self, 0);
    ncsParseConstructParams(va, "si", &title, &len);

    if(title)
    {
        if(len <= 0)
            len = strlen(title);
        set_textbuffer(self, title, len);
    }
}

static void mTextBuffer_destroy(mTextBuffer* self)
{
    mTextBufferObserverNode * node;
    free_str_buffer(self->buffer);

    node = self->observers;
    while(node)
    {
        mTextBufferObserverNode* tmp = node;
        node = node->next;
        free(tmp);
    }

    Class(mObject).destroy((mObject*)self);
}

static int mTextBuffer_replace(mTextBuffer* self, int at, 
        int count, const char* str, int str_count)
{
    int i, j;
    if(at < 0 || at > self->char_len)
        at = self->char_len;
    if(str_count <= 0 && str)
        str_count = strlen(str);
    
    if(str_count < 0)
        str_count = 0;

    if(count < 0)
        count = 0;

    if(at + count > self->char_len)
        count = self->char_len - at;

    //prepare the new size
    if(count < str_count)
    {
        //alloc size and reset value
        if(self->max_size <= (self->char_len + str_count - count))
        {
            self->max_size = self->char_len + str_count - count;
            self->buffer = alloc_str_buffer(self->buffer, &self->max_size);
        }
    }
    
    if(self->char_len>(at+count) && str_count != count)
    {
        memmove(self->buffer + at + str_count, self->buffer + at + count, self->char_len - at - count);
    }

    self->char_len += (str_count - count);
    if((self->max_size - self->char_len) > (MAX_BLOCK*2))
    {
        //reduce the memory
        self->max_size = self->char_len + MAX_BLOCK*2;
        self->buffer = alloc_str_buffer(self->buffer, &self->max_size);
    }

    if(str)
    {
        i = at;
        j = 0;
        while(j < str_count)
        {
            if(str[j] == '\r')
                self->buffer[i] = '\n';
            else
                self->buffer[i] = str[j];
            i ++;
            j ++;
        }
    }
    
    //update the observer
    update_observers(self, at, count, str_count);
    
    return str_count;
}

static int mTextBuffer_getText(mTextBuffer* self, 
        int at, char* str_buffer, int str_count)
{
    int len;

    if(!str_buffer || str_count <= 0)
        return 0;

    str_buffer[0] = 0;

    if(at < 0 || at >= self->char_len)
        return 0;

    len = self->char_len - at;
    if(len > str_count)
        len = str_count;

    strncpy(str_buffer, self->buffer + at, len);
    str_buffer[len] = 0;
    return len;

}

static ITextIterator* mTextBuffer_getAt(mTextBuffer* self, int charIndex)
{
    mTextIterator* it = NEWV(mTextIterator, 2, self, charIndex);
    if(!it)
        return NULL;
    return INTERFACE_CAST(ITextIterator, it);
}

static BOOL mTextBuffer_addObserver(mTextBuffer* self, 
        ITextBufferObserver* observer)
{
    mTextBufferObserverNode * node;
    if(!observer)
        return FALSE;

    node = self->observers;
    while(node && node->observer != observer)
        node = node->next;

    if(node)
        return TRUE;

    node = 
        (mTextBufferObserverNode*)calloc(1, sizeof(mTextBufferObserverNode));
    node->next = self->observers;
    node->observer = observer;
    
    self->observers = node;

    return TRUE;
}

static BOOL mTextBuffer_removeObserver(mTextBuffer* self, 
        ITextBufferObserver* observer)
{
    mTextBufferObserverNode * node, *prev;

    if(!self->observers)
        return FALSE;
    
    node = self->observers;
    prev = NULL;
    while(node)
    {
        if(node->observer == observer)
            break;
        prev = node;
        node = node->next;
    }
    
    if(prev == NULL) {
        self->observers = node->next;
    }
    else {
        prev->next = node->next;
    }

    free(node);
    return TRUE;
}

#if 0
static int mTextBuffer_getIndexByPara(mTextBuffer* self, int line, int col)
{
    int i ;
    if(col < 0)
        col = 0;

    if(!self->buffer)
        return 0;

    i = 0;
    while(line > 0 && i < self->char_len)
    {
        if(self->buffer[i] == '\n')
            -- line;
        i ++;
    }

    if(i >= self->char_len)
        return self->char_len;

    return i + col;
}

static int mTextBuffer_getParaByIndex(mTextBuffer* self, int index, int *pcol)
{
    int i;
    int line = 0;
    int col = 0;
    int line_at = 0;

    i = 0;
    if(index > self->char_len)
        index = self->char_len;

    while(i < index)
    {
        if(self->buffer[i] == '\n')
        {
            line ++;
            line_at = i + 1;
        }
        i ++;
    }

    col = i - line_at;
    if(pcol)
        *pcol = col;
    return line;
}
#endif

static int mTextBuffer_getCount(mTextBuffer * self)
{
    return self->char_len;
}

static int findnstr(const char* text, int text_len, 
        const char* str, int str_count)
{
    int i = 0;
    while((i + str_count) < text_len)
    {
        int j;
        if(text[i] != str[0])
        {
            i ++;
            continue;
        }
        j = 1;
        while(j < str_count && text[i+j] == str[j] )
            j ++;
        if(j == str_count)
            return i;

        i += j;

    }
    return -1;
}

static int mTextBuffer_find(mTextBuffer* self, int start, 
        const char* str, int str_count)
{
    int pos = -1;
    if(str == NULL || str_count == 0)
        return -1;

    if(start < 0)
        start = 0;

    if(start >= self->char_len)
        return -1;

    if((INT_PTR)str == TI_LINERETURN || (INT_PTR)str == TI_EOF)
    {
        pos = findnstr(self->buffer + start, self->char_len - start, "\n", 1);
    }
    else
    {
        if(str_count < 0)
            str_count = strlen(str);
        pos = findnstr(self->buffer + start, self->char_len - start, str, str_count);
    }

    if(pos == -1)
        return -1;
    return start + pos;
}

static int mTextBuffer_setCase(mTextBuffer* self, int start, int len, BOOL bupper)
{
    char *str, *strend;
    char (*tocase)(char);
    if(!self || !self->buffer)
        return 0;

    if(start < 0)
        start = 0;

    if(len < 0)
        len = self->char_len - start;
    if(len <= 0)
        return 0;

    if(bupper)
        tocase = (char(*)(char))toupper;
    else
        tocase = (char(*)(char))tolower;

    str = self->buffer + start;
    strend = str + len;
    while(str < strend)
    {
        str[0] = tocase(str[0]);
        str ++;
    }

    return len;
}

BEGIN_MINI_CLASS(mTextBuffer, mObject)
    IMPLEMENT(mTextBuffer, ITextBuffer)
    CLASS_METHOD_MAP(mTextBuffer, construct)
    CLASS_METHOD_MAP(mTextBuffer, destroy)
    CLASS_METHOD_MAP(mTextBuffer, replace)
    CLASS_METHOD_MAP(mTextBuffer, getText)
    CLASS_METHOD_MAP(mTextBuffer, getCount)
    CLASS_METHOD_MAP(mTextBuffer, getAt)
    CLASS_METHOD_MAP(mTextBuffer, addObserver)
    CLASS_METHOD_MAP(mTextBuffer, removeObserver)
    CLASS_METHOD_MAP(mTextBuffer, find)
    CLASS_METHOD_MAP(mTextBuffer, setCase)
END_MINI_CLASS

/////////////////////////////////////////
//mTextIterator
static void mTextIterator_construct(mTextIterator* self, va_list va)
{
    Class(mObject).construct((mObject*)self, 0);
    ncsParseConstructParams(va, "pi", &self->buffer, &self->index); 
}

static void mTextIterator_nextChar(mTextIterator* self)
{
    if(self->buffer && self->index < self->buffer->char_len)
    {
        self->index ++;
    }
}

static void mTextIterator_prevChar(mTextIterator* self)
{
    if(self->buffer && self->index > 0)
    {
        self->index --;
    }
}

static int mTextIterator_index(mTextIterator* self)
{
    return self->index;
}

static int mTextIterator_reset(mTextIterator* self, int charIndex)
{
    if(self->buffer)
    {
        if(charIndex >= 0 && charIndex <= self->buffer->char_len)
        {
            self->index = charIndex;
        }
    }
    return self->index;
}

static BOOL mTextIterator_isEnd(mTextIterator*self)
{
    return (!self->buffer || self->index >= self->buffer->char_len );
}

static BOOL mTextIterator_isHead(mTextIterator* self)
{
    return (!self->buffer || self->index <= 0);
}

static const char* mTextIterator_getMore(mTextIterator* self, 
        int *pTextLen, int *pVisibleTextLen, BOOL bAutoSkip)
{
    const char* str;
    int len;
    int len_limit = -1;
    int left_count;
    if(!self->buffer || !self->buffer->buffer)
    {
        if(pTextLen)
            *pTextLen = 0;
        if(pVisibleTextLen)
            *pVisibleTextLen = 0;
        return NULL;
    }

    str = self->buffer->buffer;

    str += self->index;

    if(pTextLen)
        len_limit = *pTextLen;

    left_count = self->buffer->char_len - self->index;

    if (!pVisibleTextLen) {
        if (len_limit == -1 || len_limit > left_count)
            len = left_count;
        else
            len = len_limit;
    }
    else {
        len = 0;

        while(str[len] && str[len] != '\n' 
                && len != len_limit && len < left_count) {
            len ++;
        }
        
        *pVisibleTextLen = len;
        
        if(str[len] == '\n' && len != len_limit && len < left_count) {
            len ++;
        }
    }

    if(pTextLen)
        *pTextLen = len;

    if(bAutoSkip) {
        self->index += len;
    }
    return str;
}

static int mTextIterator_diff(mTextIterator* self, 
        const ITextIterator* another)
{
    mTextIterator* ti;
    if(!another)
        return 0;

    ti = INTERFACE2OBJECT(mTextIterator,another);
    return self->index - ti->index;
}

static int mTextIterator_inRange(mTextIterator* self, 
        const ITextIterator* left, const ITextIterator* right)
{
    mTextIterator* tileft, *tiright;
    if(left == NULL || right == NULL)
        return TXTITER_RANGE_INVALID;
    
    tileft = INTERFACE2OBJECT(mTextIterator,left);
    tiright = INTERFACE2OBJECT(mTextIterator,right);

    if(!tileft->buffer || !tiright->buffer 
            || tileft->buffer != self->buffer
            || tiright->buffer != self->buffer)
        return TXTITER_RANGE_INVALID;
    
    if(tileft->index > self->index)
        return TXTITER_RANGE_LEFTOUT;
    else if(tileft->index == self->index)
        return TXTITER_RANGE_LEFTEDGE;

    if(tiright->index < self->index)
        return TXTITER_RANGE_RIGHTOUT;
    else if(tiright->index == self->index)
        return TXTITER_RANGE_RIGHTEDGE;

    return TXTITER_RANGE_IN;

}

static void mTextIterator_releaseIterator(mTextIterator* self)
{
    DELETE(self);
}

static ITextIterator* mTextIterator_reference(mTextIterator* self)
{
    return INTERFACE_CAST(ITextIterator,self);
}

static ITextRender* mTextIterator_getTextRender(mTextIterator* self)
{
    return INTERFACE_CAST(ITextRender, &g_textRender);
}

static int mTextIterator_getCharType(mTextIterator* self, int offset)
{
    int idx = self->index + offset;
    if(self->buffer)
    {
        if(!self->buffer->buffer)
            return TI_OUTOFRANGE;
        if(idx < 0)
            idx = 0;
        else if(idx >= self->buffer->char_len)
            idx = self->buffer->char_len - 1;

        if(self->buffer->buffer[idx] == '\n')
            return TI_LINERETURN ;
        if(self->buffer->buffer[idx] == 0)
            return TI_EOF;

        return TI_VISIBILE_CHAR;    
    }
    return TI_OUTOFRANGE;
}

BEGIN_MINI_CLASS(mTextIterator, mObject)
    IMPLEMENT(mTextIterator, ITextIterator)
    CLASS_METHOD_MAP(mTextIterator, construct)
    CLASS_METHOD_MAP(mTextIterator, nextChar)
    CLASS_METHOD_MAP(mTextIterator, prevChar)
    CLASS_METHOD_MAP(mTextIterator, index)
    CLASS_METHOD_MAP(mTextIterator, reset)
    CLASS_METHOD_MAP(mTextIterator, isEnd)
    CLASS_METHOD_MAP(mTextIterator, isHead)
    CLASS_METHOD_MAP(mTextIterator, getMore)
    CLASS_METHOD_MAP(mTextIterator, reference)
    CLASS_METHOD_MAP(mTextIterator, diff)
    CLASS_METHOD_MAP(mTextIterator, inRange)
    CLASS_METHOD_MAP(mTextIterator, releaseIterator)
    CLASS_METHOD_MAP(mTextIterator, getTextRender)
    CLASS_METHOD_MAP(mTextIterator, getCharType)
END_MINI_CLASS

////////////////////////////////////////
//mTextLayoutNode
typedef struct _TextLayoutNodeSearchInfo {
    int type;
    union {
        unsigned int str_index;
        unsigned int text_index;
        struct {
            int x_find;
            int y_find;
        }pos;
    }key;
    unsigned int text_count;
    unsigned int str_count;
    int x_count, y_count;
}mTextLayoutNodeSearchInfo;

typedef struct _TextLayoutNodeDiff{
    int strdiff;
    int textdiff;
    int hdiff;
    int old_width; //the old_width before diff
    int new_width; //the new_width before diff
}mTextLayoutNodeDiff;

enum {
    TLNST_STR_INDEX = 0,
    TLNST_TEXT_INDEX,
    TLNST_TEXT_POS
};

static void mTextLayoutNode_resetKey(mTextLayoutNode* self)
{
    self->text_count = 0;
    self->str_count  = 0;
    self->width = 0;
    self->height = 0;
}

#define TL_ROOT(self)    ((mTextLayoutNode*)((self)->rootNode))
static inline unsigned int textlayout_get_text_count(mTextLayout* self)
{
    return TL_ROOT(self)->text_count;
}
static inline unsigned int textlayout_get_str_count(mTextLayout* self)
{
    return TL_ROOT(self)->str_count;
}

static inline unsigned int textlayout_get_total_height(mTextLayout* self)
{
    return TL_ROOT(self)->height;
}

#undef TL_ROOT

static mCommBTreeNode* _get_last_child_node(mCommBTreeNode* node, BOOL bNotNullKey)
{
    mCommBTreeNode* lastNotNullNode = node;
    if(!node)
        return node;

    while(node && (!bNotNullKey || !_c(node)->isNullKey(node)))
    {
        lastNotNullNode = node;
        node = node->next;
    }

    node = lastNotNullNode;

    if(node->level == 0 || !node->children)
        return node;
    else
        return _get_last_child_node(node->children, bNotNullKey);
}

static inline mTextLayoutNode* textlayout_get_last_node(mTextLayout* self, BOOL bNotNullKey)
{
    return (mTextLayoutNode*)_get_last_child_node((mCommBTreeNode*)self->rootNode, bNotNullKey);
}


static int mTextLayoutNode_compareNode(mTextLayoutNode* self, 
        mTextLayoutNodeSearchInfo* searchInfo)
{
    unsigned int text_count, str_count;
    int y_count;
    if(searchInfo == NULL)
        return BTREE_ERRNO_SNULL;

    text_count = self->text_count + searchInfo->text_count;
    str_count  = self->str_count + searchInfo->str_count;
    y_count    = self->height + searchInfo->y_count;

    if(searchInfo->type == TLNST_STR_INDEX)
    {
        if(searchInfo->key.str_index < str_count)
            return BTREE_ERRNO_SCHILD;
    }
    else if(searchInfo->type == TLNST_TEXT_INDEX)
    {
        if(searchInfo->key.text_index < text_count)
            return BTREE_ERRNO_SCHILD;
    }
    else
    {
        if(searchInfo->key.pos.y_find <= y_count)
            return BTREE_ERRNO_SCHILD;
    }

    searchInfo->text_count = text_count;
    searchInfo->str_count  = str_count;
    searchInfo->y_count    = y_count;
    return BTREE_ERRNO_SNEXT;
}

static void mTextLayoutNode_recompute(mTextLayoutNode* self, 
        mTextLayoutNode* child)
{
    Class(mCommBTreeNode).recompute((mCommBTreeNode*)self, (mCommBTreeNode*)child);

    if(self->level == 0)
        return;

    self->text_count += child->text_count;
    self->str_count  += child->str_count;
    if(self->width < child->width)
        self->width = child->width;

    self->height += child->height;
}

static void mTextLayoutNode_changeKey(mTextLayoutNode* self, 
        mTextLayoutNodeDiff* pdiff, int deltaChild)
{
    mTextLayoutNodeDiff diff;
    if(pdiff == NULL)
    {
        diff.textdiff = self->text_count;
        diff.strdiff  = self->str_count;
        diff.old_width = 0;
        diff.new_width = self->width;
        diff.hdiff = self->height;
        pdiff = &diff;
        
    }
    else if(pdiff == (mTextLayoutNodeDiff*)(-1))
    {
        diff.textdiff = -self->text_count;
        diff.strdiff  = -self->str_count;
        diff.old_width = self->width;
        diff.new_width = 0;
        diff.hdiff = -self->height;
        pdiff = &diff;
    }
    else 
    {
        if(self->level > 0)
        {
            self->text_count += pdiff->textdiff;
            self->str_count  += pdiff->strdiff;
            self->height += pdiff->hdiff;

            if(pdiff->new_width > pdiff->old_width) 
            {   //replace the old max width
                if(pdiff->new_width > self->width)
                    self->width = pdiff->new_width;
            }
            else if(pdiff->new_width < pdiff->old_width 
                    && pdiff->old_width == self->width) 
            {//the old width is the max width, we must refind the new max width.
                int max_width = 0;
                mTextLayoutNode* node = (mTextLayoutNode*)self->children;
                while(node)
                {
                    if(max_width < node->width)
                        max_width = node->width;
                    node = (mTextLayoutNode*)node->next;
                }
                self->width = max_width;
            }
        }
    }

     Class(mCommBTreeNode).changeKey((mCommBTreeNode*)self, pdiff, deltaChild);

}

static BOOL mTextLayoutNode_isNullKey(mTextLayoutNode* self)
{
    return self->str_count == 0;
}

BEGIN_MINI_CLASS(mTextLayoutNode, mCommBTreeNode)
    CLASS_METHOD_MAP(mTextLayoutNode, resetKey)
    CLASS_METHOD_MAP(mTextLayoutNode, compareNode)
    CLASS_METHOD_MAP(mTextLayoutNode, recompute)
    CLASS_METHOD_MAP(mTextLayoutNode, changeKey)
    CLASS_METHOD_MAP(mTextLayoutNode, isNullKey)
END_MINI_CLASS


//////////////////////////////////////////////
//get the text byte length
static inline int textlayout_get_text_byte_length(ITextIterator* txtit, 
        HDC hdc, int text_count)
{
    int str_len = 0;
    int index = (txtit)->_vtable->index(INTEFACE_ADJUST(txtit));
    while(text_count > 0)
    {
        int count = text_count;
        ITextRender* render = (txtit)->_vtable->getTextRender(INTEFACE_ADJUST(txtit));
        int len = (render)->_vtable->getTextByteLen(INTEFACE_ADJUST(render), hdc, txtit, &count);
        str_len += len;
        text_count -= count;
        if(text_count <= 0)
            break;
        (txtit)->_vtable->reset(INTEFACE_ADJUST(txtit), index + str_len);
    }
    //reset at begin
    if((txtit)->_vtable->index(INTEFACE_ADJUST(txtit)) != index)
        (txtit)->_vtable->reset(INTEFACE_ADJUST(txtit), index);

    return str_len;
}

//get the text count by length
static int textlayout_get_text_count_by_length(ITextIterator* txtit, 
        HDC hdc, int str_len, int *pstr_len)
{
    ITextRender* render;
    int          index, count;
    int          old_index;
    int          text_count = 0;

    if (pstr_len)
        *pstr_len = 0;

    if(str_len <= 0)
        return 0;
    
    count = 0;
    old_index = index = (txtit)->_vtable->index(INTEFACE_ADJUST(txtit));
    while(str_len > 0)
    {
        int len = str_len;
        (txtit)->_vtable->reset(INTEFACE_ADJUST(txtit), index);
        render = (txtit)->_vtable->getTextRender(INTEFACE_ADJUST(txtit));
        text_count += (render)->_vtable->getTextCount(INTEFACE_ADJUST(render), hdc, txtit, &len);
        if(len == 0)
            break;

        count += len;
        str_len -= len;
        index += len;
    }

    if (pstr_len)
        *pstr_len = count;
    (txtit)->_vtable->reset(INTEFACE_ADJUST(txtit), old_index);
    return text_count;
}

//get the width of text by str index, return the width
static int textlayout_get_text_width_by_strindex(mTextLayout* self,
        mTextLayoutNode* node,
        HDC hdc, 
        ITextIterator *txtit,
        /*in out*/int *pstr_index,
        /*out*/int *ptext_index)
{
    RECT rcLimit;
    RECT rcBound;
    int  str_index = *pstr_index;
    ITextRender *render;
    int  width = 0;
    int  index;
    int  text_index = 0;

    rcLimit.left = rcLimit.top = 0;
    rcLimit.right = (self->context)->_vtable->getMetrics(INTEFACE_ADJUST(self->context), TEXTMETRICS_MAXWIDTH);
    rcLimit.bottom = node->height;

    while((index = (txtit)->_vtable->index(INTEFACE_ADJUST(txtit))) < str_index && rcLimit.right > 0)
    {
        int len;
        int text_count;
        render = (txtit)->_vtable->getTextRender(INTEFACE_ADJUST(txtit));
        len = (render)->_vtable->calc(INTEFACE_ADJUST(render), hdc, txtit, str_index - index, 
                &text_count, &rcLimit, &rcBound);
        if(len == 0 || RECTW(rcBound) == 0)
            break;
        text_index += text_count;
        (txtit)->_vtable->reset(INTEFACE_ADJUST(txtit), index + len);
        width += RECTW(rcBound);
    }

    *pstr_index = (txtit)->_vtable->index(INTEFACE_ADJUST(txtit));
    if(ptext_index)
        *ptext_index = text_index;
    return width;
}

//get the text index by width, return text_index
static int textlayout_get_text_index_by_width(mTextLayout* self,
        mTextLayoutNode* node,
        /*in out*/int* pwidth,
        HDC hdc /*= 0*/,
        ITextIterator* txtit,
        int *pstr_index)
{
    BOOL         bGetDC = (hdc == 0);
    RECT         rcLimit, rcBound;
    int          str_index = 0;
    int          text_index = 0;
    ITextRender *render;
    int          width = *pwidth;
    int          str_len = node->str_count;

    if(width >= node->width)
    {
        *pwidth = node->width;
        text_index = node->text_count;

        if((txtit)->_vtable->getCharType(INTEFACE_ADJUST(txtit), str_len - 1) == TI_LINERETURN)
        {
            str_len --;
            text_index --;
        }

        if(pstr_index)
            *pstr_index = str_len;
        return text_index;
    }


    rcLimit.left = rcLimit.top = 0;
    rcLimit.bottom = node->height;

    if(bGetDC)
        hdc = (self->context)->_vtable->getDC(INTEFACE_ADJUST(self->context));

    while(width > 0)
    {
        int len;
        int text_count;
        int index = (txtit)->_vtable->index(INTEFACE_ADJUST(txtit));
        rcLimit.right = width;
        render = (txtit)->_vtable->getTextRender(INTEFACE_ADJUST(txtit));
        len = (render)->_vtable->calc(INTEFACE_ADJUST(render), hdc, txtit, str_len, 
                &text_count, &rcLimit, &rcBound);
        if(len >0 && (txtit)->_vtable->getCharType(INTEFACE_ADJUST(txtit), len - 1) == TI_LINERETURN)
        {
            int visibile_len;
            (void)(txtit)->_vtable->getMore(INTEFACE_ADJUST(txtit), &len, &visibile_len, FALSE);
            width -= RECTW(rcBound);
            str_index += visibile_len;
            text_index += text_count;
            break;
        }

        if(len == 0 || RECTW(rcBound) == 0)
            break;

        width      -= RECTW(rcBound);
        str_index  += len;
        text_index += text_count;
        str_len    -= len;
        (txtit)->_vtable->reset(INTEFACE_ADJUST(txtit), index + len);
    }

    if(bGetDC)
        (self->context)->_vtable->releaseDC(INTEFACE_ADJUST(self->context), hdc);

    if(pstr_index)
        *pstr_index = str_index;
    *pwidth -= width;
    return text_index;
}


//////////////////////////////////////////
static inline void append_updaterc(mTextLayout* self, RECT *prcupate)
{
    if(self->dirty_area == NULL)
        self->dirty_area = CreateClipRgn();
    AddClipRect(self->dirty_area, prcupate);
}

static void textlayout_update_selection_ex(mTextLayout* self, 
        int *begin, int *end)
{
    RECT rcUpdate;      
        
    rcUpdate.left = begin[0]; //begin.x
    rcUpdate.top  = begin[1]; //begin.y
    //printf("--- update selection from : begin(%d,%d)-end(%d,%d)\n", begin[0],begin[1],end[0],end[1]);

    //the same line
    if(begin[1] == end[1])
    {
        rcUpdate.right = end[0]; //end.x
        rcUpdate.bottom = rcUpdate.top + begin[2]; //begin.height
        //(self->context)->_vtable->update(INTEFACE_ADJUST(self->context), &rcUpdate);
        append_updaterc(self, &rcUpdate);
    }
    else
    {
        //multiple lines, first update the above line
        rcUpdate.right = (self->context)->_vtable->getMetrics(INTEFACE_ADJUST(self->context), TEXTMETRICS_MAXWIDTH);
        rcUpdate.bottom = rcUpdate.top + begin[2]; //begin.height
        //(self->context)->_vtable->update(INTEFACE_ADJUST(self->context), &rcUpdate);
        append_updaterc(self, &rcUpdate);

        //second: update middle line(s) 
        rcUpdate.top  = begin[1] + begin[2]; //begin.y + begin.height
        if(rcUpdate.top < end[1]/*end.y*/) 
        {
            //int top = rcUpdate.top;
            rcUpdate.left = 0;
            rcUpdate.right = (self->context)->_vtable->getMetrics(INTEFACE_ADJUST(self->context), TEXTMETRICS_MAXWIDTH);
            rcUpdate.bottom = end[1]; //end.y
            //(self->context)->_vtable->update(INTEFACE_ADJUST(self->context), &rcUpdate);
            append_updaterc(self, &rcUpdate);
        }

        rcUpdate.left = 0;
        rcUpdate.right = end[0]; //end.x
        rcUpdate.top = end[1]; //end.y
        rcUpdate.bottom = end[1] + end[2]; //end.y + end.height
        //(self->context)->_vtable->update(INTEFACE_ADJUST(self->context), &rcUpdate);
        append_updaterc(self, &rcUpdate);
    }

    //Don't call update with TRUE. must call it when the scrollbar has changed
    //(self->context)->_vtable->update(INTEFACE_ADJUST(self->context), TRUE);
}

static void textlayout_updateselection(mTextLayout* self)
{
    int *begin; //begin[0] : begin.x, begin[1]: begin.y, begin[2]:begin.height
    int *end;

    if(self->sel_begin == self->cursor)
        return;


    if(self->sel_begin > self->cursor) {
        begin = &self->cursor_x;
        end   = &self->sel_begin_x;
    }
    else {
        begin = &self->sel_begin_x;
        end   = &self->cursor_x;
    }

    if(self->old_height != -1
            && self->old_height != self->new_height)
    {
        int max;
        if(self->old_height > self->new_height)
        {
            max = self->old_height;
        }else{
            max = self->new_height;
        }

        if(begin[1] >= max)
        {
            begin[1] = begin[1] + self->new_height - self->old_height;
            end[1] = end[1] + self->new_height - self->old_height;
        }
        else if(end[1] >= max)
        {
            end[1] = end[1] + self->new_height - self->old_height;
        }
 
    }

    textlayout_update_selection_ex(self, begin, end);
}

static void textlayout_resetsel(mTextLayout* self)
{
    self->sel_begin = self->cursor;
    self->sel_begin_x = self->cursor_x;
    self->sel_begin_y = self->cursor_y; 
    self->sel_begin_height = self->cursor_height;
    self->text_sel_begin = self->text_cursor;
}

static void textlayout_update_new_selection(mTextLayout* self, 
        int new_index, int x_sel, int y_sel, int height)
{
    //get the max update selection
    int sel_info[] = {new_index, x_sel, y_sel, height};
    int *pa, *pc;
    int *pbegin, *pend;
    pa = sel_info;
    pc = &self->sel_begin;

    //not process selection between min and cursor.
    pbegin = *pa < *pc ? pa : pc;
    pend = *pa < *pc ? pc : pa;

    textlayout_update_selection_ex(self, pbegin + 1, pend + 1);
}

#define TEXTLAYOUT_ISEMPTY(self)   \
    (((mTextLayoutNode*)(self)->rootNode)->str_count == 0)

static inline mTextLayoutNode* _get_first_child(mTextLayoutNode*node)
{
    if(node->level == 0 || !node->children)
        return node;
    return _get_first_child((mTextLayoutNode*)node->children);
}

static inline mTextLayoutNode* textlayout_get_first_node(mTextLayout* self)
{
    return _get_first_child((mTextLayoutNode*)self->rootNode);
}

static inline mTextLayoutNode* 
search_textlayout_node_by_textindex(mTextLayout* self, 
        mTextLayoutNodeSearchInfo* searchInfo,
        int index, int search_flags)
{
    memset(searchInfo, 0, sizeof(mTextLayoutNodeSearchInfo));
    searchInfo->type = TLNST_TEXT_INDEX;
    if(TEXTLAYOUT_ISEMPTY(self))
        return textlayout_get_first_node(self);
    else if(index > textlayout_get_text_count(self))
        index = textlayout_get_text_count(self);
    if(index < 0)
        index = 0;
    searchInfo->key.text_index = index;
    return (mTextLayoutNode*)_c(self)->search(self, (void*)searchInfo, search_flags);
}

static inline mTextLayoutNode* 
search_textlayout_node_by_strindex(mTextLayout* self, 
        mTextLayoutNodeSearchInfo* searchInfo,
        int index, int search_flags)
{
    memset(searchInfo, 0, sizeof(mTextLayoutNodeSearchInfo));
    searchInfo->type = TLNST_STR_INDEX;
    if(TEXTLAYOUT_ISEMPTY(self))
        return textlayout_get_first_node(self);
    else if(index > textlayout_get_str_count(self))
        index = textlayout_get_str_count(self);
    searchInfo->key.str_index = index;
    return (mTextLayoutNode*)_c(self)->search(self, (void*)searchInfo, search_flags);
}

static inline mTextLayoutNode* 
search_textlayout_node_by_pos(mTextLayout* self, 
        mTextLayoutNodeSearchInfo* searchInfo,
        int x, int y, int search_flags)
{
    memset(searchInfo, 0, sizeof(mTextLayoutNodeSearchInfo));
    searchInfo->type = TLNST_TEXT_POS;
    searchInfo->key.pos.x_find = x;
    searchInfo->key.pos.y_find = y;
    if(TEXTLAYOUT_ISEMPTY(self))
        return textlayout_get_first_node(self);
    return (mTextLayoutNode*)_c(self)->search(self, (void*)searchInfo, search_flags);
}

static inline int calc_line(ITextIterator* txtit, HDC hdc, 
        int str_len, /*out*/ int *ptext_count, 
        const RECT* prcLimit, RECT *prcBound)
{
    int             text_count;
    ITextRender    *render;
    int             len = 0;

    if(ptext_count)
        *ptext_count = 0;

    if(str_len <= 0)
        str_len = 0x7FFFFFFF;

    memset(prcBound, 0, sizeof(RECT));

    while(len != str_len)
    {
        RECT rcBound;
        int  tlen;
        int  index = (txtit)->_vtable->index(INTEFACE_ADJUST(txtit));
        int  all_len = str_len - len;

        render = (txtit)->_vtable->getTextRender(INTEFACE_ADJUST(txtit));
        (void)(txtit)->_vtable->getMore(INTEFACE_ADJUST(txtit), &all_len, NULL, FALSE);
        tlen = (render)->_vtable->calc(INTEFACE_ADJUST(render), hdc,txtit,  
                str_len - len, &text_count, prcLimit, &rcBound);

        //attempt to output a character
        if(tlen == 0) {
            //get byte length with a character
            int byte_len = textlayout_get_text_byte_length(txtit, hdc, 1);
            tlen = (render)->_vtable->calc(INTEFACE_ADJUST(render), hdc, txtit,
                    byte_len, &text_count, NULL, &rcBound);
        }

        if (tlen == 0)
            break;

        len += tlen;
        prcBound->right += RECTW(rcBound);
        if(prcBound->bottom < RECTH(rcBound))
            prcBound->bottom = RECTH(rcBound);  
        if(ptext_count)
            *ptext_count += text_count;

        (txtit)->_vtable->reset(INTEFACE_ADJUST(txtit), index + tlen);
        if(all_len >= tlen || (txtit)->_vtable->getCharType(INTEFACE_ADJUST(txtit), -1) == TI_LINERETURN)
            break;
    }

    return len;
}

static int calc_node(mTextLayout* self, 
        mTextLayoutNode* node, 
        ITextIterator*  txtit,
        HDC             hdc,
        const RECT     *rcLimit, 
        int             linespace)
{
    RECT rcBound;
    int old_count, old_height, old_len;
    int text_count, str_len = 0x7FFFFFFF;
    mTextLayoutNodeDiff diff;

    old_count = node->text_count;
    old_len   = node->str_count;
    old_height = node->height;

    diff.old_width = node->width;

    str_len = calc_line(txtit, hdc, str_len, &text_count, rcLimit, &rcBound);

    node->text_count = text_count;
    node->str_count  = str_len;

    if(old_len <= 0 && str_len <= 0)
        return -old_len;
    //If node is changed from non-null to null, we should differ its information.
    //important: don't reset the width and height value. 
    //because we would use them to calc the update area.
    node->width = RECTW(rcBound);
    node->height = RECTH(rcBound) + linespace;
    
    diff.textdiff = node->text_count - old_count;
    diff.strdiff  = node->str_count - old_len;
    diff.new_width = node->width;
    diff.hdiff = node->height - old_height;
    if(node->parent) //change parent's key
        _c(node->parent)->changeKey(node->parent, &diff, 0);

    return old_len - str_len;
}

static void textlayout_relayout(mTextLayout* self, int begin, 
        int delCount, int insertCount, BOOL bAutoUpdate)
{
    mTextLayoutNodeSearchInfo searchInfo;
    mTextLayoutNode           *begin_node=NULL, *end_node = NULL;
    int                       begin_index;
    ITextIterator*            txtit;
    mCommBTreeLeafIterator    node_it;
    BOOL                      bHeightChanged = FALSE; /* Very important for unequal height*/
    int maxwidth;
    int linespace;
    int total_height;
    //int old_heights = 0, new_heihts = 0; //TODO total height which is changed of textedit
    HDC hdc;
    RECT rcLimit;
    RECT rcUpdate = {0, 0, 0, 0};
    int count = (self->text_buffer)->_vtable->getCount(INTEFACE_ADJUST(self->text_buffer));
    count -= (insertCount - delCount);

    //1. find the begin node

    //if begin is equal with the character number of old contents, 
    //then we need to use begin-1 to get last valid node.
    begin_node = search_textlayout_node_by_strindex(self, 
                    &searchInfo, begin >= count ? begin - 1: begin, 
                    BTSF_RETLAST_IF_OUTOFRANGE);

    // get the end not
    if(begin_node == NULL)
    {
        end_node = begin_node = textlayout_get_last_node(self, FALSE);
        begin_index = textlayout_get_str_count(self);
    }
    else
    {
        begin_index = searchInfo.str_count;
    }

    //include the height change info
    //if(self->old_height == -1)
    //    self->old_height = searchInfo.y_count;
    self->old_height = self->new_height = searchInfo.y_count;
    //printf("--- textrelayout: old_height=%d\n", self->old_height);

    txtit = (self->text_buffer)->_vtable->getAt(INTEFACE_ADJUST(self->text_buffer), begin_index);

    delCount    += (begin - begin_index);
    insertCount += (begin - begin_index);
  

    linespace = (self->context)->_vtable->getMetrics(INTEFACE_ADJUST(self->context), TEXTMETRICS_LINESPACE);
    maxwidth  = (self->context)->_vtable->getMetrics(INTEFACE_ADJUST(self->context), TEXTMETRICS_MAXWIDTH);
    rcLimit.left = rcLimit.top = 0;
    rcLimit.right = maxwidth;
    rcLimit.bottom = (self->context)->_vtable->getMetrics(INTEFACE_ADJUST(self->context), TEXTMETRICS_MAXHEIGHT);

    rcUpdate.top = searchInfo.y_count;
    rcUpdate.bottom = rcUpdate.top;


    total_height = textlayout_get_total_height(self);

    hdc = (self->context)->_vtable->getDC(INTEFACE_ADJUST(self->context));

    //exclude the area before  begin_index
    if(begin > begin_index)
    {
        //recalc the start left
        RECT rcBound;
        ITextRender *textRender = _I(txtit)->getTextRender(INTEFACE_ADJUST(txtit));
        _I(textRender)->calc(INTEFACE_ADJUST(textRender), hdc, txtit, 
                begin - begin_index, NULL, &rcLimit, &rcBound);
        rcUpdate.left = RECTW(rcBound);
    }

    //(void)INIT_OBJV(mCommBTreeLeafIterator, &node_it, 2, self, begin_node);
    (void)initObjectArgs((mObject*)((void*)(&node_it)), (mObjectClass*)((void*)(&Class(mCommBTreeLeafIterator))), 2, self, begin_node);

    if( end_node == NULL) //get the end node
    {
        int left_del_count = delCount;
        int index = 0;
        mTextLayoutNode* tnode = NULL;
        while(node_it.current)
        {
            tnode = (mTextLayoutNode*)(node_it.current);
            if(tnode->str_count == 0)
                break;
            index += tnode->str_count;
            left_del_count -= tnode->str_count;
            if((txtit)->_vtable->getCharType(INTEFACE_ADJUST(txtit), index) == TI_LINERETURN)
            {
                if(left_del_count == 0) {
                    _c(&node_it)->next(&node_it);
                    tnode = (mTextLayoutNode*)(node_it.current);
                    break;
                }
                else if(left_del_count < 0) {
                    break;
                }
            }
            _c(&node_it)->next(&node_it);
        }
        end_node = tnode;
        if(!end_node)
            end_node = textlayout_get_last_node(self, FALSE);

        if(node_it.current != (mCommBTreeNode*)begin_node)
            (void)initObjectArgs((mObject*)((void*)(&node_it)), (mObjectClass*)((void*)(&Class(mCommBTreeLeafIterator))), 2, self, begin_node);

        delCount -= left_del_count;
        if(left_del_count < 0)
            insertCount -= left_del_count;
    }

    //replace: remove and insert
    while(1)
    {
        int height = 0;
        mTextLayoutNode* tnode = (mTextLayoutNode*)(node_it.current);

        BOOL bEnd = node_it.current == (mCommBTreeNode*)end_node;
        /** TODO:
         *  old_heights += tnode->height;
         */
        if(!bHeightChanged) //if height changed, we must update all area
        {
            rcUpdate.right = tnode->width;
            height = tnode->height;
        }
        if(tnode->str_count > 0)
            self->old_height += tnode->height;
        
        delCount -= tnode->str_count; //old count
        calc_node(self, tnode, txtit, hdc, &rcLimit, linespace);
        insertCount -= tnode->str_count;
        /**TODO:
         *  new_heihts += tnode->height;
         **/
        if(tnode->str_count == 0) {
            //remove all, the height must changed
            rcUpdate.right = maxwidth;
            rcUpdate.bottom = total_height;

            while(node_it.current) {
                if(((mTextLayoutNode*)(node_it.current))->str_count > 0)
                    self->old_height += ((mTextLayoutNode*)(node_it.current))->height;
                _c(&node_it)->remove(&node_it);
            }
            bHeightChanged = TRUE;
            goto END;
        }

        
        if(!bHeightChanged)
        {
            if(height != tnode->height)
            {
                bHeightChanged = TRUE;
                height = self->new_height; //store the start changed of height
            }
            else
            {
            //    if(rcUpdate.right < tnode->width)
            //        rcUpdate.right = tnode->width;
                //use max width
                rcUpdate.right = maxwidth;
                rcUpdate.bottom = rcUpdate.top + height;
                //add region 
                append_updaterc(self, &rcUpdate);
                rcUpdate.left = 0; //new line
                rcUpdate.top = rcUpdate.bottom;
            }
        }

        if(tnode->str_count > 0)
            self->new_height += tnode->height;

        if(delCount < 0)
            insertCount -= delCount;
        else if(insertCount < 0)
            delCount -= insertCount;

        if(bEnd && (delCount == 0 || insertCount == 0))
            break;

        _c(&node_it)->next(&node_it);

        if(delCount <= 0 || insertCount <= 0)
            break;
    }

    if(insertCount > 0)
    {
        //add new lines and set update
        mTextLayoutNode* node = NULL;

        if(node_it.current == NULL)
            node_it.current = (mCommBTreeNode*)textlayout_get_last_node(self, FALSE);

        node = (mTextLayoutNode*)node_it.current;
        if(node->str_count == 0) //a null inline, we should add it
        {
            calc_node(self, node, txtit, hdc, &rcLimit, linespace);
            insertCount -= node->str_count;
            self->new_height += node->height;
        }

        while(insertCount > 0)
        {
            int text_count;
            int str_len ;
            RECT rcBound;
            str_len = calc_line(txtit, hdc, insertCount, 
                    &text_count, &rcLimit, &rcBound);
            if(str_len <= 0)
                break;
            node = (mTextLayoutNode*)_c(self)->newLeaf(self);
            node->str_count = str_len;
            node->text_count = text_count;
            node->width  = RECTW(rcBound);
            node->height = RECTH(rcBound) + linespace;

            _c(&node_it)->insert(&node_it, (mCommBTreeNode*)node);


            insertCount -= str_len;

            //include new height;
            self->new_height += node->height;
        }

        bHeightChanged = TRUE;
    }
    else if(node_it.current)
    {
        //delete the lines update all the area of line

        if (delCount == 0) {
            (txtit)->_vtable->reset(INTEFACE_ADJUST(txtit), textlayout_get_str_count(self));

            //whether delete last null line
            if((txtit)->_vtable->getCharType(INTEFACE_ADJUST(txtit), - 1) != TI_LINERETURN) {
                mTextLayoutNode* node = textlayout_get_last_node(self, FALSE);
                if (node->str_count <= 0) {
                    self->old_height += node->height;
                    _c(self)->deleteNode(self, (mCommBTreeNode*)node);
                }
            }
        }
        if(delCount > 0)
        {
            //need change the height
            bHeightChanged = TRUE;
        }
        while(delCount > 0 && node_it.current)
        {
            mTextLayoutNode* tnode = (mTextLayoutNode*)(node_it.current);
            delCount -= tnode->str_count;
            
            if(tnode->str_count > 0)
                 self->old_height += tnode->height;
            _c(&node_it)->remove(&node_it);

            if(tnode == end_node)
                break;
        }
    }


END:
    //if last node is ended with '\n', we should add a new empty line 
    //to make sure root node's height correct.
    end_node = textlayout_get_last_node(self, FALSE);
    //printf("---- end_node=%p, text count = %d\n", end_node, end_node?end_node->text_count:0);
    if(end_node)
    {
        (txtit)->_vtable->reset(INTEFACE_ADJUST(txtit), textlayout_get_str_count(self));
        //the last line is not null, and end with \n, we must ensure the last line is a null line
        if ((txtit)->_vtable->getCharType(INTEFACE_ADJUST(txtit), - 1) == TI_LINERETURN) {
            if(end_node->text_count > 0) //insert a new null line
            {
                //note:use the above end_node's height
                int height = _I(self->context)->getMetrics(INTEFACE_ADJUST(self->context), TEXTMETRICS_DEFLINEHEIGHT);
                node_it.current = (mCommBTreeNode*)end_node;
                end_node = (mTextLayoutNode*)_c(self)->newLeaf(self);
                end_node->str_count = 0;
                end_node->text_count = 0;
                end_node->width = 0;
                end_node->height = height;
                _c(&node_it)->insert(&node_it, (mCommBTreeNode*)end_node);
            }
            else if(end_node->text_count <= 0 && end_node->height <= 0)
            {
                //make sure the end_node->height > 0
                int height = end_node->height;
                mTextLayoutNodeDiff diff;
                memset(&diff, 0, sizeof(diff));
                end_node->height = _I(self->context)->getMetrics(INTEFACE_ADJUST(self->context), TEXTMETRICS_DEFLINEHEIGHT);
                diff.hdiff = end_node->height - height;
                if(end_node->parent)
                    _c(end_node->parent)->changeKey(end_node->parent, &diff, 0);
            }
            //printf("--- insert null end node = %p, height=%d\n", end_node,height);
            bHeightChanged = TRUE;
        }
    }


    (self->context)->_vtable->releaseDC(INTEFACE_ADJUST(self->context), hdc);
    (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));

    //printf("--- old height=%d, new height=%d\n",self->old_height, self->new_height);
    
    if(bHeightChanged)
    {
        int maxheight = self->new_height;
        if(maxheight < self->old_height)
            maxheight = self->old_height;
        rcUpdate.left = 0;
        rcUpdate.right = maxwidth;
        rcUpdate.bottom = maxheight;
        append_updaterc(self, &rcUpdate);
        //rcUpdate.bottom = rcUpdate.top + (self->context)->_vtable->getMetrics(INTEFACE_ADJUST(self->context), TEXTMETRICS_MAXHEIGHT);
    }
    else
    {
        self->old_height = -1; //no height changed
    }
    (self->context)->_vtable->update(INTEFACE_ADJUST(self->context), bAutoUpdate);

}

static void mTextLayout_reset(mTextLayout* self)
{
    int count = (self->text_buffer)->_vtable->getCount(INTEFACE_ADJUST(self->text_buffer));

    textlayout_relayout(self, 0, count, count, FALSE);

    //recalc cursor and sel_begin position
    self->text_cursor = _c(self)->getCharPos(self, self->cursor, 
            &self->cursor_x, &self->cursor_y, &self->cursor_height, FALSE);

    if (self->sel_begin != self->cursor) {
        self->text_sel_begin = _c(self)->getCharPos(self, self->sel_begin, 
                &self->sel_begin_x, &self->sel_begin_y, 
                &self->sel_begin_height, FALSE);
    }
    else
        textlayout_resetsel(self);

    //(self->context)->_vtable->update(INTEFACE_ADJUST(self->context), &rcUpdate);
    (self->context)->_vtable->updateCaret(INTEFACE_ADJUST(self->context));
}


static void mTextLayout_construct(mTextLayout* self, va_list va)
{
    Class(mCommBTree).construct((mCommBTree*)self, 0);
    
    ncsParseConstructParams(va, "pp", &self->text_buffer, &self->context);

    self->old_height = -1;
}

static void mTextLayout_destroy(mTextLayout* self)
{
    if(self->dirty_area)
    {
        DestroyClipRgn(self->dirty_area);
    }

    Class(mCommBTree).destroy((mCommBTree*)self);
}

#if 0
//get the para pos by text
static int textlayout_get_text_para_pos(mTextLayout* self, int text_index, int *offChar)
{
    mTextLayoutNode* node = textlayout_get_first_node(self);
    mCommBTreeLeafIterator    node_it;
    int text_idx = 0;
    int para_idx = 0;
    if(!node)
        return 0;

    if(text_index <= 0)
    {
        if(offChar)
            *offChar = 0;
        return 0;
    }

    (void)initObjectArgs((mObject*)((void*)(&node_it)), (mObjectClass*)((void*)(&Class(mCommBTreeLeafIterator))), 2, self, node);

    while(node_it.current && (text_idx + node->text_count) <= text_index)
    {
        node = (mTextLayoutNode*)(node_it.current);
        text_idx += node->text_count;
        _c(&node_it)->next(&node_it);
    }


    if(offChar)
    {
        if(text_idx < text_index)
            _c(self)->textCount2Len(self, text_idx, text_index - text_idx, offChar);
        else
            *offChar = 0;
    }
    
    return para_idx;
}
#endif

#define GETCHARINDEX_RET(x, y, height, str_index, text_index)  do { \
    *px = (x);      \
    *py = (y);      \
    if(plineHeight) *plineHeight = (height);    \
    if(pstr_index)  *pstr_index  = (str_index); \
    return (text_index);  }while(0)

static int mTextLayout_getCharIndex(mTextLayout* self, int* px, int* py, 
        int *plineHeight, int *pstr_index)
{
    mTextLayoutNodeSearchInfo searchInfo;
    mTextLayoutNode  *node;
    ITextIterator   *txtit;
    int             text_index=0,str_index=0;
    int             x;
    int             y;
    int             width;

    if(!self->text_buffer || !px || !py)
        return -1;

    if ((self->text_buffer)->_vtable->getCount(INTEFACE_ADJUST(self->text_buffer)) <= 0) {
        *px = 0;
        *py = 0;
        return 0;
    }

    x = *px;
    y = *py;

    node = search_textlayout_node_by_pos(self, &searchInfo, x, y,0);

    if(node == NULL)
    {
        // FALSE can get the last empty node, if have.
        //node = textlayout_get_last_node(self, TRUE);
        node = textlayout_get_last_node(self, FALSE);
        GETCHARINDEX_RET(node->width, 
                textlayout_get_total_height(self) - node->height, 
                node->height,
                textlayout_get_str_count(self),
                textlayout_get_text_count(self));
    }

    if(searchInfo.x_count == x)
    {
        GETCHARINDEX_RET(0, searchInfo.y_count, node->height, 
                searchInfo.str_count, searchInfo.text_count);
    }
    else if(node->width <= x)
    {
        int reduce = 0;
        if(node->str_count > 0)
        {
            txtit = _I(self->text_buffer)->getAt(INTEFACE_ADJUST(self->text_buffer), searchInfo.str_count);
            if(_I(txtit)->getCharType(INTEFACE_ADJUST(txtit), node->str_count -1) == TI_LINERETURN)
                reduce = 1;
            _I(txtit)->releaseIterator(INTEFACE_ADJUST(txtit));
        }
        //printf("-- reduce=%d\n",reduce);

        GETCHARINDEX_RET(node->width, searchInfo.y_count, node->height,
                searchInfo.str_count + node->str_count - reduce , 
                searchInfo.text_count + node->text_count - reduce);
    }

    //recalc the left size
    txtit = (self->text_buffer)->_vtable->getAt(INTEFACE_ADJUST(self->text_buffer), searchInfo.str_count);
    width = x - searchInfo.x_count;
    text_index = textlayout_get_text_index_by_width(self, 
            node,
            &width,
            0,
            txtit,
            &str_index) + searchInfo.text_count;

    GETCHARINDEX_RET(width, 
            searchInfo.y_count, 
            node->height,
            str_index + searchInfo.str_count,
            text_index);
}
#undef GETCHARINDEX_RET

#define GETCHARPOS_RET(x, y, index)  do{ \
    if(px) *px = (x);                     \
    if(py) *py = (y);                     \
    return (index); } while(0)

static int _get_str_width(mTextLayout *self, 
        mTextLayoutNode *node, int str_index, int len)
{
    ITextIterator   * txtit;
    HDC     hdc;
    int     width;

    txtit = (self->text_buffer)->_vtable->getAt(INTEFACE_ADJUST(self->text_buffer), str_index);
    hdc = (self->context)->_vtable->getDC(INTEFACE_ADJUST(self->context));

    str_index += len;
    width = textlayout_get_text_width_by_strindex(self, node, 
                        hdc, txtit, &str_index, NULL);
    (self->context)->_vtable->releaseDC(INTEFACE_ADJUST(self->context), hdc);
    (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));
    return width;
}

static int mTextLayout_getCharPos(mTextLayout* self, int index, 
        int *px, int *py, int *plineHeight, BOOL bAsText)
{
    mTextLayoutNodeSearchInfo searchInfo;
    mTextLayoutNode  *node;
    HDC             hdc;
    int text_index, str_index, width, count;
    ITextIterator   *txtit;

    if(!self->text_buffer)
        return -1;

    if (index < 0)
        index = 0;

    count = bAsText ? textlayout_get_text_count(self) 
                    : textlayout_get_str_count(self);
    if (count <= 0)
        GETCHARPOS_RET(0, 0, 0);

    if (index > count)
        index = count;

    if(bAsText) {
        //only for last text, need to use last index to get correct node.
        text_index = index >= count ? count - 1 : index;
        node = search_textlayout_node_by_textindex(self, &searchInfo, 
                text_index, BTSF_RETLAST_IF_OUTOFRANGE);
        if(!node)
            return -1;

    
        if(plineHeight)
            *plineHeight = node->height;

        if(text_index == searchInfo.text_count) {
            if (index != text_index) {
                //for last text, need add the last index's length and width
                int width, len, str_index;
                _c(self)->textCount2Len(self, text_index, 1, &len);
                str_index = textlayout_get_str_count(self) - len;

                if (len == 1) {
                    BOOL isLnSep;
                    txtit = (self->text_buffer)->_vtable->getAt(INTEFACE_ADJUST(self->text_buffer), str_index);
                    isLnSep = (txtit)->_vtable->getCharType(INTEFACE_ADJUST(txtit), str_index) == TI_LINERETURN;
                    (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));
                    //for null line, should skip to next line's start position.
                    if (isLnSep) {
                        GETCHARPOS_RET(0, searchInfo.y_count + node->height, 
                                searchInfo.str_count + len);
                    }
                }

                width = _get_str_width(self, node, str_index, len);
                GETCHARPOS_RET(width, searchInfo.y_count, searchInfo.str_count + len);
            }
            else {
                GETCHARPOS_RET(0, searchInfo.y_count, searchInfo.str_count);
            }
        }
        text_index = index;
    }
    else
    {
        //only for last char, need to use last index to get correct node.
        str_index = index >= count ? count -1 : index;
        node = search_textlayout_node_by_strindex(self, 
                &searchInfo, str_index, BTSF_RETLAST_IF_OUTOFRANGE);
        if(!node)
            return -1;

        if(plineHeight)
            *plineHeight = node->height;

        if(str_index == searchInfo.str_count) {
            if (index != str_index) {
                BOOL isLnSep;
                txtit = (self->text_buffer)->_vtable->getAt(INTEFACE_ADJUST(self->text_buffer), str_index);
                isLnSep = (txtit)->_vtable->getCharType(INTEFACE_ADJUST(txtit), str_index) == TI_LINERETURN;
                (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));
                //for last char, need add the last index's length and width.
                // and for null line, should skip to next line's start position.
                if(isLnSep) {
                    GETCHARPOS_RET(0, searchInfo.y_count + node->height, 
                            searchInfo.text_count + 1);
                }
                else {
                    GETCHARPOS_RET(_get_str_width(self, node, str_index, 1), 
                        searchInfo.y_count, searchInfo.text_count + 1);
                }
            }
            else {
                GETCHARPOS_RET(0, searchInfo.y_count, searchInfo.text_count);
            }
        }
        if (index != str_index)
            str_index = index;
    }

    txtit = (self->text_buffer)->_vtable->getAt(INTEFACE_ADJUST(self->text_buffer), searchInfo.str_count);

    if((bAsText && text_index >= (searchInfo.text_count + node->text_count))
        || (!bAsText && str_index >= (searchInfo.str_count + node->str_count)))
    {
        int xtmp , ytmp = searchInfo.y_count;
        if((txtit)->_vtable->getCharType(INTEFACE_ADJUST(txtit), node->str_count-1) == TI_LINERETURN)
        {
            xtmp = 0;
            ytmp += node->height;
        }
        else
        {
            xtmp = node->width;
        }

        (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));
        GETCHARPOS_RET(xtmp, ytmp, 
                bAsText?(searchInfo.str_count + node->str_count)
                    :(searchInfo.text_count + node->text_count));
    }

    hdc = (self->context)->_vtable->getDC(INTEFACE_ADJUST(self->context));

    if(bAsText)
        str_index = searchInfo.str_count + 
            textlayout_get_text_byte_length(txtit, hdc, 
                    text_index - searchInfo.text_count);

    width = textlayout_get_text_width_by_strindex(self, 
                    node, hdc, txtit, &str_index, &text_index);
    text_index += searchInfo.text_count;


    (self->context)->_vtable->releaseDC(INTEFACE_ADJUST(self->context), hdc);
    (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));

    GETCHARPOS_RET(width, searchInfo.y_count, bAsText?str_index:text_index);

}
#undef GETCHARPOS_RET

static int mTextLayout_setSelBeginPos(mTextLayout* self, 
        int x, int y, BOOL bUpdate)
{
    int x_sel, y_sel, height;
    int str_index;
    int text_index;
    if(x == self->sel_begin_x && y == self->sel_begin_y)
        return self->sel_begin;

    if(y < 0)
        y = 0;
    else if( y > (int)((mTextLayoutNode*)self->rootNode)->height)
        y = ((mTextLayoutNode*)self->rootNode)->height - 1;

    if(x < 0)
        x = 0;
    else if(x > (self->context)->_vtable->getMetrics(INTEFACE_ADJUST(self->context), TEXTMETRICS_MAXWIDTH))
        x = (self->context)->_vtable->getMetrics(INTEFACE_ADJUST(self->context), TEXTMETRICS_MAXWIDTH);

    x_sel = x;
    y_sel = y;

    text_index = _c(self)->getCharIndex(self, &x_sel, &y_sel, &height, &str_index);

    if(str_index < 0)
        return self->sel_begin;

    //printf("---- x_sel=%d,    y_sel=%d,    str_index=%d, text_index=%d\n", x_sel, y_sel, str_index, text_index);
    //printf("---- cursor_x=%d, cursor_y=%d, cursor=%d,    text_cursor=%d\n", self->cursor_x, self->cursor_y, self->cursor, self->text_cursor); 
    if(bUpdate) 
        textlayout_update_new_selection(self, str_index, x_sel, y_sel, height);

    self->sel_begin        = str_index;
    self->sel_begin_x      = x_sel;
    self->sel_begin_y      = y_sel;
    self->sel_begin_height = height;
    self->text_sel_begin   = text_index;

    (self->context)->_vtable->updateCaret(INTEFACE_ADJUST(self->context));
    return text_index;  
}

static BOOL mTextLayout_setSelBegin(mTextLayout* self, 
        int text_index, BOOL bUpdate)
{
    int x_sel, y_sel;
    int height = 0;
    int str_index;

    if(text_index < 0)
        text_index = 0;
    else {
        int count = textlayout_get_text_count(self); //(self->text_buffer)->_vtable->getCount(INTEFACE_ADJUST(self->text_buffer));
        if(text_index >= count)
            text_index = count;
    }

    if(text_index == self->text_cursor)
    {
        if(bUpdate)
        {
            textlayout_updateselection(self);
        }
        textlayout_resetsel(self);
        return TRUE;
    }
    
    str_index = _c(self)->getCharPos(self, text_index, &x_sel, &y_sel,&height, TRUE);

    if(bUpdate) 
        textlayout_update_new_selection(self, str_index, x_sel, y_sel, height);

    self->sel_begin        = str_index;
    self->sel_begin_x      = x_sel;
    self->sel_begin_y      = y_sel;
    self->sel_begin_height = height;
    self->text_sel_begin   = text_index;
    
    (self->context)->_vtable->updateCaret(INTEFACE_ADJUST(self->context));
    return TRUE;
}

static int mTextLayout_getSelBegin(mTextLayout* self, int* px, int *py,
        int *pHeight)
{
    if(px)
        *px = self->sel_begin_x;
    if(py)
        *py = self->sel_begin_y;

    if (pHeight)
        *pHeight = self->sel_begin_height;

    return self->text_sel_begin;
}

static BOOL mTextLayout_setCursor(mTextLayout* self, int text_index, BOOL isSel)
{
    if (isSel) {
        return mTextLayout_setSelBegin(self, text_index, TRUE);
    }

    if(self->sel_begin != self->cursor) {
        textlayout_updateselection(self);
    }

    if (text_index < 0)
        self->text_cursor = 0;
    else {
        int count = textlayout_get_text_count(self);
        if (text_index >= count)
            self->text_cursor = count;
        else
            self->text_cursor = text_index;
    }
    
    self->cursor = _c(self)->getCharPos(self, self->text_cursor, &self->cursor_x, 
            &self->cursor_y, &self->cursor_height, TRUE);

    //printf("===== cursor_x=%d, cursor_y=%d, cursor=%d,    text_cursor=%d\n", self->cursor_x, self->cursor_y, self->cursor, self->text_cursor); 

    textlayout_resetsel(self);

    (self->context)->_vtable->updateCaret(INTEFACE_ADJUST(self->context));
    return TRUE;
}

static int mTextLayout_setCursorPos(mTextLayout* self, int x, int y, BOOL isSel)
{
    if (isSel) {
        return mTextLayout_setSelBeginPos(self, x, y, TRUE);
    }

    if(x == self->cursor_x && y == self->cursor_y)
        return self->text_cursor;

    if( y > ((mTextLayoutNode*)self->rootNode)->height)
        return self->text_cursor;

    if(self->sel_begin != self->cursor) {
        textlayout_updateselection(self);
    }

    self->cursor_x = x;
    self->cursor_y = y;
    self->text_cursor = _c(self)->getCharIndex(self, &self->cursor_x, 
            &self->cursor_y, &self->cursor_height, &self->cursor);
    //printf("++++ cursor_x=%d, cursor_y=%d, cursor=%d,    text_cursor=%d\n", self->cursor_x, self->cursor_y, self->cursor, self->text_cursor); 
    
    textlayout_resetsel(self);

    (self->context)->_vtable->updateCaret(INTEFACE_ADJUST(self->context));
    return self->text_cursor;
}

static int mTextLayout_getCursor(mTextLayout* self, int *px, int *py, 
        int *pcursorHeight, BOOL isSel)
{
    if (isSel) {
        return mTextLayout_getSelBegin(self, px, py, pcursorHeight);
    }

    if(px)
        *px = self->cursor_x;
    if(py)
        *py = self->cursor_y;
    if(pcursorHeight)
        *pcursorHeight = self->cursor_height;
    return self->text_cursor;
}

static mTextLayoutNode* get_begin_line(mTextLayout* self, 
        int* px, int* py, int *pindex)
{
    mTextLayoutNodeSearchInfo searchInfo;
    mTextLayoutNode* node;
    node = search_textlayout_node_by_pos(self, &searchInfo, *px, *py, 0);
    if(!node)
        return node;
    if(pindex)
        *pindex = searchInfo.str_count;
    
    if(py)
        *py = searchInfo.y_count;
    if(px)
        *px = searchInfo.x_count;
    return node;
}

static int textlayout_drawline(mTextLayout* self,
        HDC hdc, 
        int* px, int y, 
        mTextLayoutNode* node, 
        int *pcount_left, ITextIterator* it, 
        int max_selection, BOOL bsel)
{
    int index = (it)->_vtable->index(INTEFACE_ADJUST(it));  
    int total_count = *pcount_left;
    int index_begin = index;
    int draw_count;
    ITextRender* textRender;
    //RECT rcbg;
    //BOOL bRecalcWidth = FALSE;
    //BOOL drawLeftBg;

    //if is in selection and the whole line is selected, we should draw whole line
    //drawLeftBg = (bsel &&  (total_count == max_selection - index));
    //printf("--- bsel =%d, total_count=%d, max_selection=%d, index=%d\n",bsel, total_count, max_selection, index);

    if(max_selection >0 && total_count > max_selection - index)
    {
        //the left chars from max_selection to (index + total_count) is selected or unslected,
        //so, we cannot draw the whole line, so we must recalc the width
        total_count = max_selection - index;
        //bRecalcWidth = TRUE;
    }

    textRender = (it)->_vtable->getTextRender(INTEFACE_ADJUST(it));
#if 0
    rcbg.left = *px;
    rcbg.bottom = y + node->height;
    rcbg.top  =  y;

    //printf("---- rcbg=%d,%d,%d,%d\n",rcbg.left, rcbg.right, rcbg.top, rcbg.bottom);
    if(rcbg.bottom > rcbg.top)
    {
        rcbg.right = (self->context)->_vtable->getMetrics(INTEFACE_ADJUST(self->context), TEXTMETRICS_MAXWIDTH);
        if(bRecalcWidth && !drawLeftBg) //if drawLeftBg is true, mean we should draw whole line
        {
            RECT rcBounds;
            (textRender)->_vtable->calc(INTEFACE_ADJUST(textRender), hdc, 
                    it, total_count, NULL, &rcbg, &rcBounds);
            rcbg.right = rcbg.left + RECTW(rcBounds);
        }
        //printf("---- draw : rcbg=%d,%d,%d,%d\n",rcbg.left, rcbg.right, rcbg.top, rcbg.bottom);
        (self->context)->_vtable->drawBkgnd(INTEFACE_ADJUST(self->context), hdc, &rcbg, bsel);
    }
#endif

    while(total_count > 0 && !(it)->_vtable->isEnd(INTEFACE_ADJUST(it)))
    {
        int old_index = index;
        int tx = *px, ty = y;
        _I(self->context)->contentPosToDCPos(INTEFACE_ADJUST(self->context), hdc, &tx, &ty);
        *px += (textRender)->_vtable->draw(INTEFACE_ADJUST(textRender), hdc, tx, ty, it, total_count);
        index = (it)->_vtable->index(INTEFACE_ADJUST(it));
        if(index == old_index)
        {
            (it)->_vtable->nextChar(INTEFACE_ADJUST(it)); //goto next charactor and draw it
            index ++;
        }
        total_count -= (index - old_index);
    }
    
    draw_count = index - index_begin;
    *pcount_left -= draw_count;
    return draw_count;
}

//return the x pos of last line
static int textlayout_drawlines(mTextLayout* self, HDC hdc, 
                int x_begin,int x, int *py, 
                mCommBTreeLeafIterator* pnode_it, ITextIterator* it,
                int first_line_left_count, int *plast_line_left_count, 
                int max_selection, int bottom, BOOL bsel)
{
    int left_count = first_line_left_count;
    if (max_selection == 0) {
        *plast_line_left_count = first_line_left_count;
        return x_begin;
    }

    if(!pnode_it->current)
    {
        *plast_line_left_count = 0;
        return x_begin;
    }

    while(*py < bottom && !(it)->_vtable->isEnd(INTEFACE_ADJUST(it)))
    {
        if (left_count == 0 && !_c(pnode_it)->hasNext(pnode_it) 
                && pnode_it->current 
                && ((mTextLayoutNode*)pnode_it->current)->height > 0) {
            //only last node is empty node possiblely, 
            //we should draw it's background.
            textlayout_drawline(self, hdc, &x, *py, 
                    (mTextLayoutNode*)pnode_it->current, 
                    &left_count, it, max_selection, bsel);
        }

        while(left_count > 0 && !(it)->_vtable->isEnd(INTEFACE_ADJUST(it)))
        {
            textlayout_drawline(self, hdc, &x, *py, 
                    (mTextLayoutNode*)pnode_it->current, 
                    &left_count, it, max_selection, bsel);
            if(max_selection > 0 && (it)->_vtable->index(INTEFACE_ADJUST(it)) >= max_selection)
            {
                if(left_count <= 0)
                {
                    *py += ((mTextLayoutNode*)pnode_it->current)->height;
                    _c(pnode_it)->next(pnode_it);
                    if(pnode_it->current)
                        *plast_line_left_count = 
                            ((mTextLayoutNode*)pnode_it->current)->str_count;
                    else
                        *plast_line_left_count = 0;
                    return x_begin;
                }
                *plast_line_left_count = left_count;
                return x;
            }
        }
        *py += ((mTextLayoutNode*)pnode_it->current)->height;
        x = x_begin;
        _c(pnode_it)->next(pnode_it);
        if(!pnode_it->current)
            break;
        left_count = ((mTextLayoutNode*)pnode_it->current)->str_count;
    }

    *plast_line_left_count = left_count;
    return x;
}


static void mTextLayout_draw(mTextLayout* self, 
        HDC hdc, int x, int y, int width, int height)
{
    mTextLayoutNode* node;
    int index;
    ITextIterator * it;
    mCommBTreeLeafIterator  node_it;
    int x_off, y_off;
    int last_left_count;

    x_off = -x;
    y_off = -y;
    
    node = get_begin_line(self, &x, &y, &index);
    if(!node)
        return;

    (void)INIT_OBJV(mCommBTreeLeafIterator, &node_it,2,self, node);

    
    it = (self->text_buffer)->_vtable->getAt(INTEFACE_ADJUST(self->text_buffer), index);

    if(self->sel_begin == self->cursor)
    {
        x += x_off;
        y += y_off;
        (self->context)->_vtable->setupDC(INTEFACE_ADJUST(self->context), hdc);
        textlayout_drawlines(self, hdc, x, x, &y, &node_it, it, 
                node->str_count, &last_left_count, -1, height,FALSE);
    }
    else
    {
        int sel_begin, sel_end;
        int x_sel_begin;
        x += x_off;
        y += y_off;
        if(self->sel_begin < self->cursor)
        {
            sel_begin = self->sel_begin;
            sel_end   = self->cursor;
        }
        else
        {
            sel_begin = self->cursor;
            sel_end   = self->sel_begin;
        }
        //printf("--- sel_begin=%d, sel_end=%d\n", sel_begin, sel_end);

        (self->context)->_vtable->setupDC(INTEFACE_ADJUST(self->context), hdc);
        x_sel_begin = textlayout_drawlines(self, hdc, x, x, &y, &node_it, 
                        it, node->str_count, &last_left_count, 
                        sel_begin, height, FALSE);
        if(y >= height)
            goto END;

        (self->context)->_vtable->beginSelection(INTEFACE_ADJUST(self->context), hdc);
        x_sel_begin = textlayout_drawlines(self, hdc, x, x_sel_begin, &y, 
                &node_it, it, last_left_count, &last_left_count, 
                sel_end, height, TRUE);
        (self->context)->_vtable->endSelection(INTEFACE_ADJUST(self->context), hdc);
        if(y >= height)
            goto END;

        textlayout_drawlines(self, hdc, x, x_sel_begin, &y, &node_it, it, 
                last_left_count, &last_left_count, -1, height,FALSE);

    }

END:
    (it)->_vtable->releaseIterator(INTEFACE_ADJUST(it));
}

static void 
textlayout_te_set_cursor_by_strindex(mTextLayout* self, int str_index)
{
    if(self->sel_begin != self->cursor)
    {
        textlayout_updateselection(self);
    }

    if(str_index < 0)
        self->cursor = 0;
    else {
        int count = textlayout_get_str_count(self);
        if(str_index > count)
            self->cursor = count;
        else
            self->cursor = str_index;
    }

    self->text_cursor = _c(self)->getCharPos(self, self->cursor, 
            &self->cursor_x, &self->cursor_y, &self->cursor_height, FALSE);
    
    textlayout_resetsel(self);

    (self->context)->_vtable->updateCaret(INTEFACE_ADJUST(self->context));
}

static void mTextLayout_onTextChanged(mTextLayout* self, 
        int begin, int delCount, int insertCount)
{
    textlayout_relayout(self, begin, delCount, insertCount, FALSE);
    textlayout_te_set_cursor_by_strindex(self, begin+insertCount);
}

static mCommBTreeNode* mTextLayout_newLeaf(mTextLayout *self)
{
    return (mCommBTreeNode*)NEW(mTextLayoutNode);
}

static mCommBTreeNode* mTextLayout_newNode(mTextLayout *self)
{
    return (mCommBTreeNode*)NEW(mTextLayoutNode);
}

static int mTextLayout_getLineHeight(mTextLayout* self, int textindex)
{
    mTextLayoutNodeSearchInfo searchInfo;
    mTextLayoutNode* node;
    if(textindex < 0)
        textindex = 0;
    else if(textindex > textlayout_get_text_count(self))
        textindex = textlayout_get_text_count(self);
    
    node = search_textlayout_node_by_textindex(self, &searchInfo, 
            textindex, BTSF_RETLAST_IF_OUTOFRANGE);

    if(!node)
        return 0;

    return node->height;
}

static BOOL mTextLayout_getTextBoundSize(mTextLayout* self, int *cx, int *cy)
{
    if(cx)
        *cx = ((mTextLayoutNode*)self->rootNode)->width;
    if(cy)
        *cy = ((mTextLayoutNode*)self->rootNode)->height;
    return TRUE;
}

static int mTextLayout_textCount2Len(mTextLayout* self, int text_index, 
        int text_count, int *plen)
{
    mTextLayoutNodeSearchInfo searchInfo;
    mTextLayoutNode *node;
    HDC hdc;
    int str_index;
    BOOL bcalcIndex = FALSE;
    ITextIterator* txtit;

    if(plen) *plen = 0;

    if(text_index <= 0)
    {
        if(!plen || text_count <= 0)
        {
            return 0;
        }
        text_index = 0;
        str_index = 0;
        goto CALC;
    }
    else
    {
        int count = textlayout_get_text_count(self);
        if(text_index >= count)
        {
            return textlayout_get_str_count(self);
        }
    }

    node = search_textlayout_node_by_textindex(self, 
            &searchInfo, text_index, BTSF_RETLAST_IF_OUTOFRANGE);

    if(!node)
        return -1;

    if(text_index == searchInfo.text_count)
    {
        if(!plen || text_count <= 0)
            return searchInfo.str_count;
        str_index = searchInfo.str_count;
    }

    if(text_index == (searchInfo.text_count + node->text_count))
    {
        if(!plen || text_count <= 0)
            return searchInfo.str_count + node->str_count;
        str_index = searchInfo.str_count + node->str_count;
    }

    if(node->str_count == node->text_count)
    {
        if(!plen 
            ||text_count<=(node->text_count+searchInfo.text_count-text_index))
        {
            if(plen)
                *plen = text_count;
            return searchInfo.str_count + (text_index - searchInfo.text_count);
        }
        str_index = searchInfo.str_count + (text_index - searchInfo.text_count);
    }
    else
    {
        str_index = searchInfo.str_count;
        bcalcIndex = TRUE;
    }

CALC:

    hdc = (self->context)->_vtable->getDC(INTEFACE_ADJUST(self->context));

    txtit = (self->text_buffer)->_vtable->getAt(INTEFACE_ADJUST(self->text_buffer), str_index);

    if(bcalcIndex)
    {
        str_index += textlayout_get_text_byte_length(txtit, hdc, 
                text_index - searchInfo.text_count);
        (txtit)->_vtable->reset(INTEFACE_ADJUST(txtit), str_index);
    }

    if(plen && text_count > 0)
        *plen = textlayout_get_text_byte_length(txtit, hdc, text_count);

    (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));
    (self->context)->_vtable->releaseDC(INTEFACE_ADJUST(self->context), hdc);
    return str_index;
}

static int mTextLayout_textLen2Count(mTextLayout* self, int str_index, 
        int str_len, int *ptext_count, int *poutstr_index, int *poutstr_count)
{
    mTextLayoutNodeSearchInfo searchInfo;
    mTextLayoutNode *node;
    HDC hdc;
    int text_index;
    BOOL bcalcIndex = FALSE;
    ITextIterator* txtit;

    if(ptext_count) *ptext_count = 0;
    if (poutstr_index) *poutstr_index = str_index;
    if (poutstr_count) *poutstr_count = 0;

    if(str_index <= 0)
    {
        if(!(ptext_count || poutstr_count) || str_len <= 0)
            return 0;
        str_index = 0;
        if (poutstr_index) *poutstr_index = 0;
        text_index = 0;
        goto CALC;
    }
    else
    {
        int count = textlayout_get_str_count(self);
        if(str_index >= count) {
            if (poutstr_index) *poutstr_index = count;
            return textlayout_get_text_count(self);
        }
    }

    node = search_textlayout_node_by_strindex(self, &searchInfo, 
                        str_index, BTSF_RETLAST_IF_OUTOFRANGE);

    if(!node)
        return -1;

    if(str_index == searchInfo.str_count)
    {
        if(!(ptext_count || poutstr_count) || str_len <= 0)
            return searchInfo.text_count;
        text_index = searchInfo.text_count;
    }

    if(str_index == (searchInfo.str_count + node->str_count))
    {
        if(!(ptext_count || poutstr_count) || str_len <= 0)
            return searchInfo.text_count + node->text_count;

        text_index = searchInfo.text_count + node->text_count;
    }

    if(node->str_count == node->text_count) {
        //no need output length information or information in this range.
        if(!(ptext_count || poutstr_count) 
            || str_len <= (searchInfo.str_count + node->str_count - str_index ))
        {
            if(ptext_count) *ptext_count = str_len;
            if (poutstr_count) *poutstr_count = str_len;
            return searchInfo.text_count + (str_index - searchInfo.str_count);
        }

        text_index = searchInfo.text_count + (str_index - searchInfo.str_count);
    }
    else {
        text_index = searchInfo.text_count;
        bcalcIndex = TRUE;
    }

CALC:

    hdc = (self->context)->_vtable->getDC(INTEFACE_ADJUST(self->context));
    if(bcalcIndex) {
        int len;
        txtit = (self->text_buffer)->_vtable->getAt(INTEFACE_ADJUST(self->text_buffer), searchInfo.str_count);
        text_index += textlayout_get_text_count_by_length(txtit, hdc, 
                            str_index - searchInfo.str_count, &len);

        if (poutstr_index) *poutstr_index = searchInfo.str_count + len;
    }
    else {
        txtit = (self->text_buffer)->_vtable->getAt(INTEFACE_ADJUST(self->text_buffer), str_index);
    }

    //calc text count
    if((ptext_count || poutstr_count) && str_len > 0) {
        int count = textlayout_get_text_count_by_length(txtit, 
                        hdc, str_len, poutstr_count);
        if (ptext_count) *ptext_count = count;
    }

    (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));
    (self->context)->_vtable->releaseDC(INTEFACE_ADJUST(self->context), hdc);
    return text_index;
}

static void mTextLayout_foreachLine(mTextLayout* self, int x, int y, int maxWidth, int maxHeight, 
        CB_LINE_PROC proc, mObject* proc_owern, void* user_param)
{
    mTextLayoutNode* node;
    mCommBTreeLeafIterator  node_it;
    int  xline, yline;
    int  wline, hline;
    int  bottom;
    int  index = 0;
    int lineNo = 0;
    int cx, cy;
    if(!proc)
        return ;

    xline = x;
    yline = y;

    bottom = y + maxHeight;

    node = get_begin_line(self, &xline, &yline, &index);
    _c(self)->getTextBoundSize(self, &cx, &cy);
    //printf("--- x=%d, y=%d, xline=%d, yline=%d, cx=%d,cy=%d\n",x, y,xline, yline,cx,cy);
    lineNo = _c(self)->getParaByIndex(self, index, NULL, FALSE);
    if(!node)
    {
        node = textlayout_get_last_node(self, FALSE);
    }

    if(!node || node->height <= 0) //
    {
        xline = 0;
        wline = maxWidth;
        hline = _I(self->context)->getMetrics(INTEFACE_ADJUST(self->context), TEXTMETRICS_DEFLINEHEIGHT);
        yline = (y / hline) * hline;
        lineNo = -1; //no lines

        goto DEFAULT_LINE;
    }

    
    (void)initObjectArgs((mObject*)((void*)(&node_it)), (mObjectClass*)((void*)(&Class(mCommBTreeLeafIterator))), 2, self, node);

    xline = 0;

    while(node_it.current && yline < bottom)
    {
        node = (mTextLayoutNode*)(node_it.current);
        proc(proc_owern, xline, yline, node->width,  node->height, lineNo, user_param);
        yline += node->height;
        lineNo ++;
    }

    if(yline >= bottom)
        return;

    index = -1;

    wline = maxWidth;
    hline = _I(self->context)->getMetrics(INTEFACE_ADJUST(self->context), TEXTMETRICS_DEFLINEHEIGHT);

DEFAULT_LINE:

    while( yline < bottom)
    {
        proc(proc_owern, xline, yline, wline, hline, -1, user_param);
        yline += hline;
    }
}

static void mTextLayout_travelNode(mTextLayout* self, CB_NODE_PROC proc, mObject* proc_owern, void* user_param)
{
}

static PLOGFONT mTextLayout_getReleatedFont(mTextLayout* self, int idx, BOOL isText)
{
    return NULL;
}

static int mTextLayout_getParaByIndex(mTextLayout* self, int index, int *pcol, BOOL bText)
{
    int idx = 0;
    int line_idx = 0;
    int para_idx = 0;

    mTextLayoutNode* node = textlayout_get_first_node(self);
    mCommBTreeLeafIterator node_it;
    ITextIterator *txtit;

    if(!node)
        return 0;

    if(index <= 0)
    {
        if(pcol)
            *pcol = 0;
        return 0;
    }

    txtit = _I(self->text_buffer)->getAt(INTEFACE_ADJUST(self->text_buffer), 0);

    (void)initObjectArgs((mObject*)((void*)(&node_it)), (mObjectClass*)((void*)(&Class(mCommBTreeLeafIterator))), 2, self, node);

    if(bText)
    {
        int str_idx = 0;
        while((node = (mTextLayoutNode*)node_it.current) && (idx + node->text_count) < index)
        {
            idx += node->text_count;
            str_idx += node->str_count;
            if(_I(txtit)->getCharType(INTEFACE_ADJUST(txtit), str_idx-1) == TI_LINERETURN)
            {
                para_idx ++;
                line_idx = idx;
            }
            _c(&node_it)->next(&node_it);
        }

        if(node && (idx + node->text_count) == index
                && (_I(txtit)->getCharType(INTEFACE_ADJUST(txtit), str_idx + node->str_count - 1) == TI_LINERETURN))
        {
            idx += node->text_count;
            line_idx = idx;
            para_idx ++;
        }

    }
    else
    {
        while((node = (mTextLayoutNode*)node_it.current) && (idx + node->str_count) < index)
        {
            node = (mTextLayoutNode*)(node_it.current);
            idx += node->str_count;
            if(_I(txtit)->getCharType(INTEFACE_ADJUST(txtit), idx - 1) == TI_LINERETURN)
            {
                para_idx ++;
                line_idx = idx;
            }
            _c(&node_it)->next(&node_it);
        }

        if(node && (idx + node->str_count) == index
                && (_I(txtit)->getCharType(INTEFACE_ADJUST(txtit), idx + node->str_count - 1) == TI_LINERETURN))
        {
            idx += node->str_count;
            line_idx = idx;
            para_idx ++;
        }

    }

    if(pcol)
        *pcol = index - line_idx;

    _I(txtit)->releaseIterator(INTEFACE_ADJUST(txtit));

    return para_idx;
}

/* Internal interface: the index is contain the title context. */
static int mTextLayout_getLineByIndex(mTextLayout* self, int index, int *pcol, BOOL bText)
{
    int idx = 0;
    int line_idx = 0;
    int str_idx = 0;
    int count = 0;

    mTextLayoutNode* node = textlayout_get_first_node(self);
    mCommBTreeLeafIterator node_it;
    ITextIterator *txtit;
    //mTextEditor* obj_texteditor = (mTextEditor*)(INTEFACE_ADJUST(self->context));
    
    if(!node)
        return 0;

    if(index <= 0) 
    {
        if(pcol)
            *pcol = 0;
        return 0;
    }

    txtit = _I(self->text_buffer)->getAt(INTEFACE_ADJUST(self->text_buffer), 0);

    (void)initObjectArgs((mObject*)((void*)(&node_it)), (mObjectClass*)((void*)(&Class(mCommBTreeLeafIterator))), 2, self, node);

    while(1) 
    {
        node = (mTextLayoutNode*)node_it.current;
        if(!node)
            break;

        if(bText)
           count = node->text_count;
        else
           count = node->str_count; 

        if(idx + count >= index)
            break;

        str_idx += node->str_count;
        idx += count;
        line_idx ++;

        _c(&node_it)->next(&node_it);
    }

    if(node && (idx + count) == index
            && (_I(txtit)->getCharType(INTEFACE_ADJUST(txtit), str_idx + node->str_count - 1) == TI_LINERETURN))
    {
        idx += count;
        line_idx ++;
    }

    if(pcol)
        *pcol = index - idx;

    _I(txtit)->releaseIterator(INTEFACE_ADJUST(txtit));
    
    return line_idx;
}

static int mTextLayout_getIndexByPara(mTextLayout* self, int paraNo, int col, BOOL* outOfRange, BOOL bText)
{
    int para_idx = 0, str_idx = 0;
    int idx = 0, count = 0;
    int col_idx = 0, col_para_idx = 0, col_str_idx = 0;

    mTextLayoutNode* node;
    mCommBTreeLeafIterator node_it;
    ITextIterator *txtit;

    if(paraNo <= 0 && col <= 0)
    {
        if (paraNo < 0 || col < 0)
            SMART_ASSIGN(outOfRange, TRUE);
        return 0;
    }

    node = textlayout_get_first_node(self);

    txtit = _I(self->text_buffer)->getAt(INTEFACE_ADJUST(self->text_buffer), 0);

    (void)initObjectArgs((mObject*)((void*)(&node_it)), (mObjectClass*)((void*)(&Class(mCommBTreeLeafIterator))), 2, self, node);

    /* IMPORTANT: make the node has the correct value when we jump out the while loop.
     * more loop once to calc the para length to compare the col and the para length */
    while((node = (mTextLayoutNode*)node_it.current) && col_para_idx < paraNo + 1)
    {
        if (para_idx < paraNo)
        {
            str_idx += node->str_count;
            idx += node->text_count;
            if(_I(txtit)->getCharType(INTEFACE_ADJUST(txtit), str_idx - 1) == TI_LINERETURN)
                para_idx ++;
        }

        col_str_idx += node->str_count;
        col_idx += node->text_count;
        if(_I(txtit)->getCharType(INTEFACE_ADJUST(txtit), col_str_idx - 1) == TI_LINERETURN)
            col_para_idx ++;
        
        _c(&node_it)->next(&node_it);
    }

    if(para_idx < paraNo)
    {
        SMART_ASSIGN(outOfRange, TRUE);
        idx = _I(self->text_buffer)->getCount(INTEFACE_ADJUST(self->text_buffer));
    }
    else
    {
        if (bText)
            count = col_idx - idx;
        else
            count = col_str_idx - str_idx;

        if (col >= count)
        {
            idx = col_idx;
            str_idx = col_str_idx;

            if (col > count)
                SMART_ASSIGN(outOfRange, TRUE);

            // ignore the '\n' 
            if (_I(txtit)->getCharType(INTEFACE_ADJUST(txtit), str_idx - 1) == TI_LINERETURN)
            {
                SMART_ASSIGN(outOfRange, TRUE);
                idx -= 1;
                str_idx -= 1;
            }
        }
        else
        {
            idx += col;
            str_idx += col;
        }
    }

    _I(txtit)->releaseIterator(INTEFACE_ADJUST(txtit));

    if (bText)
        return idx;
    else
        return str_idx;
}

/* Internal interface: the index is contain the title context. */
static int mTextLayout_getIndexByLine(mTextLayout* self, int lineIdx, int col, BOOL* outOfRange, BOOL bText)
{
    int index = 0, str_idx = 0; 
    int i = 0, count = 0;
    BOOL found = FALSE;

    mCommBTreeLeafIterator iter;
    mTextLayoutNode *node = NULL;
    mTextEditor* obj_texteditor = (mTextEditor*)(INTEFACE_ADJUST(self->context));

    (void)INIT_OBJV(mCommBTreeLeafIterator, &iter, 2, self, node);
    node = (mTextLayoutNode*)_c(&iter)->getCurrent(&iter);

    if (lineIdx <= 0 && col <= 0)
    {
        if (lineIdx < 0 || col < 0)
            SMART_ASSIGN(outOfRange, TRUE);
        return 0;
    }

    while (node)
    {
        if (lineIdx == i) 
        {
            found = TRUE;
            break; 
        }

        index += node->text_count;
        str_idx += node->str_count;
        i++;

        node = (mTextLayoutNode*)_c(&iter)->next(&iter);
    }

    if (found) 
    {
        ITextIterator* txtit = _c(obj_texteditor->textBuffer)->getAt(obj_texteditor->textBuffer, 0);

        if (bText)
            count = node->text_count;
        else
            count = node->str_count;

        // the charPos has more than the total char of the line
        if (col >= count)
        {
            index += node->text_count;
            str_idx += node->str_count;

            if (col > count)
                SMART_ASSIGN(outOfRange, TRUE);

            // ignore the '\n' 
            if (_I(txtit)->getCharType(INTEFACE_ADJUST(txtit), str_idx - 1) == TI_LINERETURN)
            {
                SMART_ASSIGN(outOfRange, TRUE);
                index -= 1;
                str_idx -= 1;
            }
        }
        else
        {
            index += col;
            str_idx += col;
        }

        _I(txtit)->releaseIterator(INTEFACE_ADJUST(txtit));
    }
    else // the lineIdx has more than the total line number
    {
        SMART_ASSIGN(outOfRange, TRUE);
        index = _c(obj_texteditor->textBuffer)->getCount(obj_texteditor->textBuffer);
        str_idx = index;
    }

    if (bText)
        return index;
    else
        return str_idx;
}

BEGIN_MINI_CLASS(mTextLayout, mCommBTree)
    IMPLEMENT(mTextLayout, ITextLayout)
    CLASS_METHOD_MAP(mTextLayout, construct)
    CLASS_METHOD_MAP(mTextLayout, destroy)
    CLASS_METHOD_MAP(mTextLayout, getCharIndex)
    CLASS_METHOD_MAP(mTextLayout, getCharPos)
    CLASS_METHOD_MAP(mTextLayout, setCursor)
    CLASS_METHOD_MAP(mTextLayout, getCursor)
    CLASS_METHOD_MAP(mTextLayout, setCursorPos)
    CLASS_METHOD_MAP(mTextLayout, getLineHeight)
    CLASS_METHOD_MAP(mTextLayout, draw)
    CLASS_METHOD_MAP(mTextLayout, newNode)
    CLASS_METHOD_MAP(mTextLayout, newLeaf)
    CLASS_METHOD_MAP(mTextLayout, onTextChanged)
    CLASS_METHOD_MAP(mTextLayout, getTextBoundSize)
    CLASS_METHOD_MAP(mTextLayout, reset)
    CLASS_METHOD_MAP(mTextLayout, textLen2Count)
    CLASS_METHOD_MAP(mTextLayout, textCount2Len)
    CLASS_METHOD_MAP(mTextLayout, foreachLine)
    CLASS_METHOD_MAP(mTextLayout, travelNode)
    CLASS_METHOD_MAP(mTextLayout, getReleatedFont)
    CLASS_METHOD_MAP(mTextLayout, getParaByIndex)
    CLASS_METHOD_MAP(mTextLayout, getLineByIndex)
    CLASS_METHOD_MAP(mTextLayout, getIndexByPara)
    CLASS_METHOD_MAP(mTextLayout, getIndexByLine)
END_MINI_CLASS


/////////////////////////////////////////////////////////
//mTextRender
static int mTextRender_calc(mTextRender* self, HDC hdc, 
        ITextIterator* begin, int str_count, int *ptext_count, 
        const RECT* maxBound, RECT *bound)
{
    int rel_count;
    int visible_str_count;
    SIZE size;
    const char* text;
    if(!begin)
        return 0;
    
    text = (begin)->_vtable->getMore(INTEFACE_ADJUST(begin), &str_count, &visible_str_count, FALSE);
    rel_count = GetTabbedTextExtentPoint(hdc, text, visible_str_count,
            maxBound ? RECTWP(maxBound) : 0,
            ptext_count,
            NULL, NULL,
            &size);
    if(bound)
    {
        bound->left = 0;
        bound->right = size.cx;
        bound->top = 0;
        bound->bottom = size.cy;
    }
    if(rel_count == visible_str_count)
    {
        rel_count = str_count;
        if(ptext_count && visible_str_count < str_count)
            *ptext_count += (str_count - visible_str_count);
    }
    return rel_count;
}

static int mTextRender_draw(mTextRender* self, HDC hdc, 
        int x, int y, ITextIterator* text, int str_count)
{
    int visible_str_count;
    const char* str_text = 
        (text)->_vtable->getMore(INTEFACE_ADJUST(text), &str_count, &visible_str_count, TRUE);
    //usleep(100000); //to make it slowed
    return TabbedTextOutLen(hdc, x, y, str_text, visible_str_count);
}

static int mTextRender_getTextByteLen(mTextRender* self, 
        HDC hdc, ITextIterator* it, int *ptext_count)
{
    int text_count;
    int total_len = -1;
    int len = 0;
    const char* str;
    PLOGFONT plogfont;
    if(!ptext_count || !it || hdc == (HDC)0)
        return 0;

    text_count = *ptext_count;

    str = (it)->_vtable->getMore(INTEFACE_ADJUST(it), &total_len, NULL, FALSE);

    if(!str)
        return 0;
    
    plogfont = GetCurFont(hdc);

    while(text_count > 0)
    {
        len += get_first_char_len(plogfont, str + len, total_len - len);
        text_count --;
    }

    return len;

}

static int mTextRender_getTextCount(mTextRender* self, 
        HDC hdc, ITextIterator* it, int *pstr_len)
{
    int     text_count = 0;
    int     all_len;
    int     first_len = 0, len = 0;
    const char  *str;
    PLOGFONT    plogfont;

    if(!pstr_len || !it || hdc == (HDC)0)
        return 0;

    all_len = *pstr_len;
    str = (it)->_vtable->getMore(INTEFACE_ADJUST(it), &all_len, NULL, FALSE);

    if(!str)
        return 0;

    plogfont = GetCurFont(hdc);
    
    while (len < all_len) {
        first_len = get_first_char_len(plogfont, str + len, 4);

        //if failure or overflow, we should delete current character.
        if (first_len <= 0 || len + first_len > all_len) 
            break;

        text_count ++;
        len += first_len;
    }
    *pstr_len = len;
    return text_count;
}

static int mTextRender_getCaretSize(mTextRender* self, HDC hdc, ITextIterator* it, int *pwidth, int *pheight)
{
    int len = 0;
    int char_type;
    PLOGFONT     plogfont ;
    FONTMETRICS  font_metrics;
    if(!pwidth && !pheight)
        return 1; //have caret size

    char_type = (it)->_vtable->getCharType(INTEFACE_ADJUST(it), 0);
    if(char_type == TI_VISIBILE_CHAR)
    {
        int txt_count = 1;
        len = _c(self)->getTextByteLen(self, hdc, it, &txt_count);
    }

    plogfont = GetCurFont(hdc);
    GetFontMetrics(plogfont, &font_metrics);
    if(pwidth) 
        *pwidth = font_metrics.ave_width;
    if(pheight)
        *pheight = font_metrics.font_height;

    if(len != 0)
    {
        int visible_len;
        const char* text = (it)->_vtable->getMore(INTEFACE_ADJUST(it), &len, &visible_len, FALSE);
        if(text && visible_len > 0)
        {
            SIZE size;
            len = GetTabbedTextExtentPoint(hdc, text, visible_len, 0x7FFFFFFF, NULL, NULL,NULL, &size);
            if(len > 0)
            {
                if(pwidth)
                    *pwidth = size.cx;
                if(pheight)
                {
                    if(*pheight < size.cy)
                        *pheight = size.cy;
                }
            }
        }
        else
            len = 0;
    }


    return 1;
}

BEGIN_MINI_CLASS(mTextRender, mObject)
    IMPLEMENT(mTextRender, ITextRender)
    CLASS_METHOD_MAP(mTextRender, calc)
    CLASS_METHOD_MAP(mTextRender, draw)
    CLASS_METHOD_MAP(mTextRender, getTextByteLen)
    CLASS_METHOD_MAP(mTextRender, getTextCount)
    CLASS_METHOD_MAP(mTextRender, getCaretSize)

    //init the g_textRender
    (void)INIT_OBJ(mTextRender, &g_textRender);
END_MINI_CLASS

//////////////////////////////////////////////////////////////
/*internal flags for textedit*/
enum {
    NCS_TEF_NOCARET = 0x0001,
    NCS_TEF_FOCUS   = 0x0002,
    NCS_TEF_CONTCHANGED = 0x0004,
    NCS_TEF_CURSORPAGEMOVE = 0x8,
};

#define pTextBuffer self->textBuffer
#define pTextLayout self->textLayout

#define TE_VALID_OBJ(self)  (self && pTextLayout && pTextBuffer)
#define TE_IS_FOCUS(self)  (self->flags & NCS_TEF_FOCUS)
#define TE_IS_CURSORE_PAGE_MOVE(self)   (self->flags & NCS_TEF_CURSORPAGEMOVE)
#define TE_SET_CURSORE_PAGE_MOVE(self)  (self->flags |= NCS_TEF_CURSORPAGEMOVE)
#define TE_CLEAR_CUSROR_PAGE_MOVE(self) (self->flags &= ~NCS_TEF_CURSORPAGEMOVE)

//TE_SEL_CHANGING and TE_SEL_CHANGED is used simuteously.
#define TE_SEL_CHANGING(self)\
    int old_txt_selidx, old_txt_cursoridx;\
    old_txt_selidx = _c(pTextLayout)->getCursor(pTextLayout, NULL, NULL, NULL, TRUE);\
    old_txt_cursoridx = _c(pTextLayout)->getCursor(pTextLayout, NULL, NULL, NULL, FALSE);    

#define TE_SEL_CHANGED(self, isSel)\
do{ \
    if (!isSel) {\
        if (old_txt_selidx != old_txt_cursoridx)/*cancel selection*/\
            ncsNotifyParent (self, NCSN_TE_SELCHANGED);\
    }\
    else {\
        int new_txt_selidx = _c(pTextLayout)->getCursor(pTextLayout, NULL, NULL, NULL,TRUE);\
        if (old_txt_selidx != new_txt_selidx)   /*begin or change seleciton*/  \
            ncsNotifyParent (self, NCSN_TE_SELCHANGED);\
    }\
}while(0)

/* update title */
#ifdef _MGHAVE_TEXTEDITTITLE
static void texteditor_update_title_line(mTextEditor* self)
{
    int i;
    mCommBTreeLeafIterator node_it;
    mTextLayoutNode* node;

    self->title_linecount = _c(pTextLayout)->getLineByIndex(pTextLayout, 
            self->title_text_idx, &self->title_last_line_text_index, TRUE);
    
    /* FixMe: this determine depend text.
     * deal with when the title last line just autowrap. */
    if(self->title_last_line_text_index != 0)
    {
        node = textlayout_get_first_node(pTextLayout);
        (void)initObjectArgs((mObject*)((void*)(&node_it)), 
                (mObjectClass*)((void*)(&Class(mCommBTreeLeafIterator))), 2, pTextLayout, node);
        
        for (i = 0; i <= self->title_linecount; i++)
        {
            node = (mTextLayoutNode*)node_it.current;
            _c(&node_it)->next(&node_it);
        }
        
        if(node->text_count == self->title_last_line_text_index) 
        {
            self->title_linecount ++;
            self->title_last_line_text_index = 0;
        }
    }
}

static void texteditor_update_title(mTextEditor* self)
{
    if((GetWindowStyle(self->hwnd)& NCSS_TE_TITLE) && self->title_idx > 0)
    {
        self->title_text_idx = _c(pTextLayout)->getCharPos(pTextLayout, self->title_idx,
            &self->title_x, &self->title_y, &self->title_last_height, FALSE);
        self->title_paracount = _c(pTextLayout)->getParaByIndex(pTextLayout, self->title_text_idx, &self->title_last_para_text_index, TRUE);

        /* when have any text, the title line content can't calc correct.
         * call this function to recalc title line content correct, when you need the title line content. */
        texteditor_update_title_line(self);
    }
    else{
        self->title_text_idx = -1;
        self->title_x = self->title_y = self->title_last_height = -1;
        self->title_paracount = self->title_last_para_text_index = -1;
        self->title_linecount = self->title_last_line_text_index = -1;
    }
}
#else
#define texteditor_update_title(self)
#define texteditor_update_title_line(mTextEditor* self) 
#endif


//margins
static inline void texteditor_get_view_rect(mTextEditor* self, RECT *prc)
{
    if(!prc)
        return ;
    
    GetClientRect(self->hwnd, prc);
    
    prc->left  += self->margins.left;
    prc->right -= (self->margins.right/*- (self->margins.right>0?1:0)*/);
    prc->top   += self->margins.top;
    prc->bottom -= (self->margins.bottom/*-(self->margins.bottom>0?1:0)*/);

}

static inline void texteditor_view_to_client(mTextEditor* self, int *px, int *py)
{
    if(px)
        *px += self->margins.left;
    if(py)
        *py += self->margins.top;
}

static inline void texteditor_client_to_view(mTextEditor *self, int *px, int *py)
{
    if(px)
        *px -= self->margins.left;
    if(py)
        *py -= self->margins.top;
}

static inline void texteditor_rect_view_to_client(mTextEditor* self, RECT *prc)
{
    OffsetRect(prc, self->margins.left, self->margins.top);
}

static inline void texteditor_rect_client_to_view(mTextEditor* self, RECT *prc)
{
    OffsetRect(prc, -self->margins.left, self->margins.top);
}

#ifdef _MGHAVE_TEXTEDITTITLE
static void texteditor_add_title_content(mTextEditor* self, int* idx, int* offset, BOOL bLine)
{
    if (bLine)
    {
        texteditor_update_title_line(self);

        if (self->title_idx > 0)
        {
            if (0 == *idx)
                *offset += self->title_last_line_text_index;
            *idx += self->title_linecount;
        }
    }
    else
    {
        if (self->title_idx > 0)
        {
            if (0 == *idx)
                *offset += self->title_last_para_text_index;
            *idx += self->title_paracount;
        }
    }
}
#else
#define texteditor_add_title_content(mTextEditor* self, int* idx, int* offset, BOOL bLine) 
#endif

static  void _update_scrollbar(mTextEditor *self)
{
    SCROLLINFO si;
    int cx, cy;
    DWORD style;
    if (!TE_VALID_OBJ(self))
        return;

    if (self->visWidth <= 0 || self->visHeight <= 0)
        return ;

    style = GetWindowStyle(self->hwnd);
    if (self->scrollBarMode == NCS_TEF_SCROLLBAR_NEVER) {
        ShowScrollBar(self->hwnd, SB_VERT, FALSE);
        ShowScrollBar(self->hwnd, SB_HORZ, FALSE);
        return;
    }

    _c(pTextLayout)->getTextBoundSize(pTextLayout, &cx, &cy);

    //vertical scrollbar
    if (cy <= self->visHeight) {
        SetScrollPos(self->hwnd, SB_VERT, 0);
        if (self->scrollBarMode == NCS_TEF_SCROLLBAR_AUTO) { /* Auto */
            ShowScrollBar (self->hwnd, SB_VERT, FALSE);
        } else if (self->scrollBarMode == NCS_TEF_SCROLLBAR_ALWAYS) { /* Always */
            EnableScrollBar(self->hwnd, SB_VERT, TRUE);

            si.fMask = SIF_RANGE | SIF_PAGE;
            si.nMax = 1;
            si.nMin = 0;
            si.nPage = 0;
            SetScrollInfo (self->hwnd, SB_VERT, &si, TRUE);
            ShowScrollBar (self->hwnd, SB_VERT, TRUE);
        }
    }
    else {
        IncludeWindowStyle(self->hwnd, WS_VSCROLL);

        si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
        si.nMin = 0;
        si.nMax = cy - 1;
        si.nPage = self->visHeight;
        si.nPos = self->offY;

        SetScrollInfo (self->hwnd, SB_VERT, &si, TRUE);

        EnableScrollBar(self->hwnd, SB_VERT, TRUE);
        ShowScrollBar (self->hwnd, SB_VERT, TRUE);
    }

    //horz scrollbar
    if(style & NCSS_TE_AUTOWRAP)
    {
        SetScrollPos(self->hwnd, SB_HORZ, 0);
        if (self->scrollBarMode == NCS_TEF_SCROLLBAR_AUTO) { /* Auto */
            ShowScrollBar (self->hwnd, SB_HORZ, FALSE);
        } else if (self->scrollBarMode == NCS_TEF_SCROLLBAR_ALWAYS) { /* Always */
            EnableScrollBar(self->hwnd, SB_HORZ, FALSE);

            si.fMask = SIF_RANGE | SIF_PAGE;
            si.nMax = 1;
            si.nMin = 0;
            si.nPage = 0;
            SetScrollInfo (self->hwnd, SB_HORZ, &si, TRUE);
            ShowScrollBar (self->hwnd, SB_HORZ, TRUE);
        }
    }
    else
    {
        if (cx <= self->visWidth) {
            SetScrollPos(self->hwnd, SB_HORZ, 0);
            if (self->scrollBarMode == NCS_TEF_SCROLLBAR_AUTO) { /* Auto */
                ShowScrollBar (self->hwnd, SB_HORZ, FALSE);
            } else if (self->scrollBarMode == NCS_TEF_SCROLLBAR_ALWAYS) { /* Always */
                EnableScrollBar(self->hwnd, SB_HORZ, TRUE);

                si.fMask = SIF_RANGE | SIF_PAGE;
                si.nMax = 1;
                si.nMin = 0;
                si.nPage = 0;
                SetScrollInfo (self->hwnd, SB_HORZ, &si, TRUE);
                ShowScrollBar (self->hwnd, SB_HORZ, TRUE);
            }
        }
        else {
            IncludeWindowStyle(self->hwnd, WS_HSCROLL);

            si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
            si.nMin = 0;
            si.nMax = cx - 1;
            si.nPage = self->visWidth;
            si.nPos = self->offX;

            SetScrollInfo (self->hwnd, SB_HORZ, &si, TRUE);

            EnableScrollBar(self->hwnd, SB_HORZ, TRUE);
            ShowScrollBar (self->hwnd, SB_HORZ, TRUE);
        }
    }
}

static BOOL texteditor_must_update_bkgnd(mTextEditor* self)
{
    return (GetWindowExStyle(self->hwnd) & WS_EX_TRANSPARENT) /*|| use bitmap as bkgound  */
#ifdef __MGNCS_LIB__
        || (GetWindowStyle(self->hwnd) & NCSS_TE_OWNERBKGND)
        || self->bkimg.img.pbmp
        || ncsGetComponentHandler((mComponent*)self, MSG_ERASEBKGND)
#else
        || SendMessage(self->hwnd, EM_MUSTUPDATEBKGND, 0, 0)
#endif
        ;
}

static void _scroll_invalidate_window(mTextEditor* self, int offX, int offY)
{
    RECT rcScrolled;

    //make the the progress goto onPaint
    if(texteditor_must_update_bkgnd(self))
    {
        if(offX != self->offX && offY != self->offY)
        {
            InvalidateRect(self->hwnd, NULL, TRUE);
            return ;
        }
        else 
        {
            RECT rc;
            texteditor_get_view_rect(self, &rc);
            InvalidateRect(self->hwnd, &rc, TRUE);
        }
    }
    else
    {
        //update the area must be update

        texteditor_get_view_rect(self, &rcScrolled);

#if 1
        //printf("---- update scrollbar:self->offX=%d,offX=%d,self->offY=%d,offY=%d\n", self->offX, offX, self->offY, offY);
        ScrollWindow(self->hwnd, self->offX - offX, self->offY - offY, &rcScrolled, NULL);
#else
        rc = rcScrolled;
        if(offX != self->offX)
        {
            if(offX > self->offX)
                rcScrolled.left = rcScrolled.right - offX + self->offX - 1;
            else
                rcScrolled.right = self->offX - offX + rcScrolled.left + 1;
            InvalidateRect(self->hwnd, &rcScrolled, TRUE);

        }
        
        if(offY != self->offY)
        {
            rcScrolled = rc;
            if(offY > self->offY)
                rcScrolled.top = rcScrolled.bottom - offY + self->offY - 1;
            else
                rcScrolled.bottom = self->offY - offY + rcScrolled.top + 1;
            //printf("--- rcScrollend = %d,%d,%d,%d\n", rcScrolled.left, rcScrolled.top, rcScrolled.right, rcScrolled.bottom);
            InvalidateRect(self->hwnd, &rcScrolled, TRUE);
        }
#endif
    }

}

static void _update_scrolled_margins(mTextEditor* self, int offX, int offY)
{
    RECT rcScrolled;
    RECT rc;

    if(texteditor_must_update_bkgnd(self))
        return ;

    GetClientRect(self->hwnd, &rc);
    //make the margins invalidate, because i don't known what and how it will draw
    if(offX != self->offX)
    {
        rcScrolled = rc;
        //update the top && bottom margins too
        if(self->margins.top > 0)
        {
            rcScrolled.top = rc.top;
            rcScrolled.bottom = self->margins.top;
            InvalidateRect(self->hwnd, &rcScrolled, TRUE);
        }
        if(self->margins.bottom > 0)
        {
            rcScrolled.top = rc.bottom - self->margins.bottom;
            rcScrolled.bottom = rc.bottom;
            InvalidateRect(self->hwnd, &rcScrolled, TRUE);
        }
    }
    
    if(offY != self->offY)
    {
        rcScrolled = rc;
       //update the left && right margins
        if(self->margins.left > 0)
        {
            rcScrolled.left = rc.left;
            rcScrolled.right = self->margins.left;
            InvalidateRect(self->hwnd, &rcScrolled, TRUE);
        }
        if(self->margins.right > 0)
        {
            rcScrolled.left = rc.right - self->margins.right;
            rcScrolled.right = rc.right;
            InvalidateRect(self->hwnd, &rcScrolled, TRUE);
        }
    }
}

static void _update_content_view(mTextEditor* self, int width, int height, BOOL bresetContent)
{
    FONTMETRICS fm;

    if (!TE_VALID_OBJ(self))
        return;

    if(width == -1 || height == -1)
    {
        RECT rc;
        GetClientRect(self->hwnd, &rc);
        width  = RECTW(rc);
        height = RECTH(rc);
    }

    GetFontMetrics(GetWindowFont(self->hwnd), &fm);

    width -= (self->margins.left + self->margins.right);
    height -= (self->margins.top + self->margins.bottom);

    if(width <= fm.max_width)
        width = fm.max_width + 1;
    if(height <= fm.font_height)
        height = fm.font_height + 1;

    //printf("--- update content view: width=%d,height=%d, self->visWidth=%d, self->visHeight=%d\n", width, height, self->visWidth, self->visHeight);
    if(!bresetContent && self->visWidth == width && self->visHeight == height)
        return;

    self->visWidth = width;
    self->visHeight = height;

    if ((bresetContent || (GetWindowStyle(self->hwnd)&NCSS_TE_AUTOWRAP) ) && _c(pTextBuffer)->getCount(pTextBuffer) > 0 ) {
        //printf("--- update content view\n");
        _c(pTextLayout)->reset(pTextLayout);

        texteditor_update_title(self);
    }
    else
    {
        _update_scrollbar(self);
    }

    _c(self)->updateCaret(self);

}


static void _update_caret_size(mTextEditor* self);
static void _change_caret_size(mTextEditor* self, BOOL show_caret);

static void _update_height_changed(mTextEditor* self, int offX, int offY)
{
    //ScrollWindow may offset the invalidate area of window, so, we must call it firstly

    if(self->textLayout->old_height != -1 
            && self->textLayout->old_height != self->textLayout->new_height)
    {
        RECT rc;
        if(texteditor_must_update_bkgnd(self)) //text layout's height changed, 
        {
            int top;
            //invalidate All changed part
            GetClientRect(self->hwnd, &rc);
            top = self->textLayout->new_height - offY;
            rc.top = top;
            InvalidateRect(self->hwnd, &rc, TRUE);
            self->textLayout->old_height = -1;
        }
        else 
        {
#if 1
            //scroll the area
            texteditor_get_view_rect(self, &rc);
            if(self->textLayout->old_height < self->textLayout->new_height)
                rc.top = self->textLayout->old_height - offY + self->margins.top;
            else
                rc.top = self->textLayout->new_height - offY + self->margins.top;
            //printf("--- scrollwindow: self->new_height=%d, self->old_height=%d\n", self->textLayout->new_height, self->textLayout->old_height);
            //printf("--- scrollwindow: rc=%d,%d,%d,%d, offx=%d, offy=%d\n",rc.left, rc.top, rc.right, rc.bottom, 0, self->textLayout->new_height - self->textLayout->old_height);
            ScrollWindow(self->hwnd, 0, self->textLayout->new_height - self->textLayout->old_height, &rc, NULL);

#endif
        }
    }

}

static void _update_height_changed_margins(mTextEditor* self)
{
    RECT rc;
    int mintop;
    int right;

    if(texteditor_must_update_bkgnd(self)) //text layout's height changed, 
        return;

    if(!(self->textLayout->old_height != -1 
            && self->textLayout->old_height != self->textLayout->new_height))
        return;

    mintop = self->textLayout->old_height;
    if(self->textLayout->old_height > self->textLayout->new_height)  //only update the area when scroll up
    {
        texteditor_get_view_rect(self, &rc);
        //update the buttom area which is not updated and margin area of same part
        rc.top = rc.bottom - self->textLayout->old_height + self->textLayout->new_height - 1;
        InvalidateRect(self->hwnd, &rc, TRUE);
        mintop = self->textLayout->new_height;
    }
    //update the left and  right margins area
    GetClientRect(self->hwnd, &rc);
    rc.top = mintop;
    right = rc.right;
    if(self->margins.left > 0)
    {
        rc.right = rc.left + self->margins.left;
        InvalidateRect(self->hwnd, &rc, TRUE);
    }
    if(self->margins.right > 0)
    {
        rc.right = right;
        rc.left = right - self->margins.right;
        InvalidateRect(self->hwnd, &rc, TRUE);
    }
}

static void _update_content_dirty_region(mTextEditor* self)
{
    RECT rc;
    PCLIPRGN prcRgn;
    if (!self || !self->textLayout)
        return;


    prcRgn = self->textLayout->dirty_area;
    if(prcRgn && !IsEmptyClipRgn(prcRgn))
    {
        PCLIPRECT prc;
        RECT rcView;
        prc = prcRgn->head;

        texteditor_get_view_rect(self,&rcView);
        while(prc)
        {
            rc = prc->rc;
            OffsetRect(&rc, -self->offX, -self->offY);
            texteditor_rect_view_to_client(self, &rc);
            if(IntersectRect(&rc, &rc, &rcView))
            {
                InvalidateRect(self->hwnd, &rc, TRUE);
            }
            prc = prc->next;
            
        }
        EmptyClipRgn(self->textLayout->dirty_area);
    }


}

static int texteditor_get_text_count(mTextEditor* self, int start_with, BOOL bText, const char* str, int len)
{
    PLOGFONT plogfont;
    int idx = 0;
    int l;
    if(!self || !str || len <= 0)
        return 0;

    plogfont = _c(pTextLayout)->getReleatedFont(pTextLayout, start_with, bText);
    if(!plogfont)
    {
        plogfont = GetWindowFont(self->hwnd);
        if(!plogfont)
            return len;
    }

    if(plogfont->devfonts[1] == NULL)
        return len;

    while(idx < len)
    {
        l = get_first_char_len(plogfont, str + idx, len - idx);
        if(l <= 0)
            break;
        idx += l;
    }
    return idx;
}

static int texteditor_get_text_length(mTextEditor* self,int start_with, BOOL bText, const char* str, int count, int total_len)
{
    PLOGFONT plogfont;
    int idx = 0;
    int l;
    if(!self || !str || total_len <= 0 || count <= 0)
        return 0;

    plogfont = _c(pTextLayout)->getReleatedFont(pTextLayout, start_with, bText);
    if(!plogfont)
    {
        plogfont = GetWindowFont(self->hwnd);
        if(!plogfont)
            return count;
    }

    while(count > 0 && total_len > 0)
    {
        l = get_first_char_len(plogfont, str + idx, total_len);
        if(l == 0)
            break;
        idx += l;
        count --;
        total_len -= l;
    }

    return idx;
}

//get the validate length of text. if the text include half-bytes of mutil-code character, we must exclude them
//return the validate length of text
static int texteditor_get_validate_text_length(mTextEditor* self, int start_with, BOOL start_with_is_text, const char* text, int len)
{
    PLOGFONT plogfont;
    int validate_len = 0;
    if(!text||len <= 1)
        return len < 0 ? 0 : len;

    plogfont = _c(pTextLayout)->getReleatedFont(pTextLayout, start_with, start_with_is_text);
    if(!plogfont)
    {
        plogfont = GetWindowFont(self->hwnd);
        if(!plogfont)
            return len;
    }

    while(validate_len < len)
    {
        int l = get_first_char_len(plogfont, text + validate_len, len - validate_len);
        if(l == 0)
            break;
        validate_len += l;
    }

    return validate_len;
}

static void str_upper_copy(char* str, const char* src, int len) {
    int i;
    if(!src || !str || len <= 0)
        return;
    for(i=0; i < len; i ++) {
        str[i] = (src[i] >= 'a' && src[i] <= 'z') ? (src[i] - 'a' + 'A') : src[i];
    }
}
static void str_lower_copy(char* str, const char* src, int len) {
    int i;
    if(!src || !str || len <= 0)
        return;
    for(i=0; i < len; i ++) {
        str[i] = (src[i] >= 'A' && src[i] <= 'Z') ? (src[i] - 'A' + 'a') : src[i];
    }
}
static char * texteditor_case_copy(mTextEditor* self, const char* text, int len)
{
    DWORD bCase;
    if(!text)
        return NULL;
    if(len <= 0)
        len = strlen(text);
    if(len == 0)
        return NULL;

    bCase = GetWindowStyle(self->hwnd) & (NCSS_TE_LOWERCASE | NCSS_TE_UPPERCASE);

    if(bCase)
    {
        char* str = FixStrAlloc(len);
        if(bCase & NCSS_TE_UPPERCASE)
            str_upper_copy(str, text, len);
        else
            str_lower_copy(str, text, len);
        return str;
    }
    else
        return (char*)text;

}


static void mTextEditor_construct(mTextEditor* self, DWORD addData)
{
    DWORD style;
    Class(mWidget).construct((mWidget*)self, addData);
    self->hardLimit     = -1;
    self->textBuffer    = NEW(mTextBuffer);
    self->scrollHStep   = 18;
    self->scrollVStep   = 18;
    self->textLayout    = NEWV(mTextLayout, 2,
                            INTERFACE_CAST(ITextBuffer, self->textBuffer),
                            INTERFACE_CAST(ITextLayoutContext, self)); 

#ifdef _MGHAVE_TEXTEDITTITLE
    self->title_idx = -1;
    self->title_text_idx = -1;
    self->title_x = self->title_y = self->title_last_height = -1;
#endif
    
    style = GetWindowStyle(self->hwnd);
    if(style & (NCSS_TE_AUTOHSCROLL | NCSS_TE_AUTOVSCROLL))
        self->scrollBarMode = NCS_TEF_SCROLLBAR_AUTO;
    else if(style & (WS_VSCROLL | WS_HSCROLL))
        self->scrollBarMode = NCS_TEF_SCROLLBAR_ALWAYS;
    else
        self->scrollBarMode = NCS_TEF_SCROLLBAR_NEVER;

    TE_CLEAR_CUSROR_PAGE_MOVE(self);

    _c(pTextBuffer)->addObserver(pTextBuffer, 
       (ITextBufferObserver*)INTERFACE_CAST(ITextLayout, pTextLayout));
}

static BOOL mTextEditor_onCreate(mTextEditor* self, LPARAM lParam)
{
    /* create caret */
    CreateCaret(self->hwnd, NULL, 1, GetWindowFont(self->hwnd)->size);
    _c(self)->setText(self, GetWindowCaption(self->hwnd));
    return TRUE;
}

static void mTextEditor_destroy(mTextEditor* self)
{
    if (pTextBuffer)
        DELETE(pTextBuffer);

    if (pTextLayout)
        DELETE(pTextLayout);

    DestroyCaret(self->hwnd);
    Class(mWidget).destroy((mWidget*)self);
}

static int mTextEditor_getMetrics(mTextEditor *self,int type)            
{
    if (self) {
        switch (type) {
            case TEXTMETRICS_MAXWIDTH:
                if(GetWindowStyle(self->hwnd)&NCSS_TE_AUTOWRAP)
                    return self->visWidth;
                else {
                    //max value, to void the signal int overflow, 
                    //we only give the half of max integer
                    return 0x7FFFFFFF/2;
                }

            case TEXTMETRICS_MAXHEIGHT:
                return self->visHeight;

            case TEXTMETRICS_LINESPACE:
                return 2;

            case TEXTMETRICS_DEFLINEHEIGHT:
                return GetWindowFont(self->hwnd)->size + 2;
        }
    }

    return 0;
}

static void mTextEditor_beginSelection(mTextEditor *self, HDC hdc)
{
    if (!self)
        return;

    SetBkMode(hdc, BM_OPAQUE);

    SetTextColor (hdc, 
        GetWindowElementPixel (self->hwnd, 
            GetWindowStyle(self->hwnd)&WS_DISABLED?
                WE_FGC_DISABLED_ITEM : WE_FGC_SELECTED_ITEM));

    if (TE_IS_FOCUS(self)) {
        SetBkColor (hdc, 
            GetWindowElementPixel(self->hwnd, WE_BGC_SELECTED_ITEM));
    }
    else {
        SetBkColor (hdc, 
            GetWindowElementPixel (self->hwnd, WE_BGC_SELECTED_LOSTFOCUS));
    }
}

static void mTextEditor_setupDC(mTextEditor *self, HDC hdc)
{
    if (self) {
        SetBkMode (hdc, BM_TRANSPARENT);
        SetBkColor (hdc, GetWindowBkColor (self->hwnd));

        SetTextColor (hdc, 
            GetWindowElementPixel (self->hwnd, 
                GetWindowStyle(self->hwnd)&WS_DISABLED?
                    WE_FGC_DISABLED_ITEM : WE_FGC_WINDOW));
    }
}

static void mTextEditor_contentPosToDCPos(mTextEditor* self, HDC hdc, int *px, int *py)
{
    texteditor_view_to_client(self, px, py);
}

static void mTextEditor_endSelection(mTextEditor *self, HDC hdc)
{
    if (self) _c(self)->setupDC(self, hdc);
}

static HDC mTextEditor_getDC(mTextEditor *self)                         
{
    return self ? GetSecondaryClientDC(self->hwnd) : HDC_INVALID; 
}

static void mTextEditor_releaseDC(mTextEditor *self, HDC hdc)            
{
    if (self) ReleaseSecondaryDC(self->hwnd, hdc);
}

static void mTextEditor_update(mTextEditor *self, BOOL bUpdateImdiate)
{
    if (!self )
        return;

    _update_scrollbar(self);

    if(bUpdateImdiate)
    {
        _update_content_dirty_region(self);
    }


#if 0
    if (prcUpdate) {
        RECT updateRC = *prcUpdate;
        //printf("++ update:%d,%d,%d,%d\n", prcUpdate->left, prcUpdate->right, prcUpdate->top, prcUpdate->bottom);

        if (self->offX) {
            updateRC.left     -= self->offX;
            updateRC.right    -= self->offX;
        }
        if (self->offY) {
            updateRC.top      -= self->offY;
            updateRC.bottom   -= self->offY;
        }

        //printf("-- update:%d,%d,%d,%d,self->offY=%d\n", prcUpdate->left, prcUpdate->right, prcUpdate->top, prcUpdate->bottom, self->offY);
        texteditor_rect_view_to_client(self, &updateRC);
        InvalidateRect(self->hwnd, &updateRC, TRUE);

    }
    else {
        InvalidateRect(self->hwnd, NULL, TRUE);
    }
#endif
}

static int mTextEditor_getInvalidBkgnd(mTextEditor *self, RECT *prc)
{
    int cx, cy;
    if (!prc || !TE_VALID_OBJ(self)) {
        if (prc) memset(prc, 0, sizeof(RECT));
        return 0;
    }

    _c(pTextLayout)->getTextBoundSize(pTextLayout, &cx, &cy);   
    GetClientRect(self->hwnd, prc);

    if (RECTWP(prc) != self->visWidth)
        prc->right = prc->left + self->visWidth;
    if (RECTHP(prc) != self->visHeight)
        prc->bottom = prc->top + self->visHeight;

    if((cy - self->offY) >= self->visHeight)
    {
        memset(prc, 0, sizeof(RECT));
        return 0;
    }
    
    prc->top = cy - self->offY;
    return 1;
}

#define INIT_SELECT_BRUSH(self, hdc, oldColor) \
    if (TE_IS_FOCUS(self)) {\
        oldColor = SetBrushColor (hdc, GetWindowElementPixel(self->hwnd, \
                    WE_BGC_SELECTED_ITEM));    \
    }\
    else {\
        oldColor = SetBrushColor (hdc, GetWindowElementPixel(self->hwnd, \
                    WE_BGC_SELECTED_LOSTFOCUS));\
    }

#define INIT_NORMAL_BRUSH(self, hdc, oldColor) \
    oldColor = SetBrushColor(hdc, GetWindowBkColor(self->hwnd));

static inline void _fill_bkgnd(mTextEditor *self, 
        HDC hdc, const RECT *prcBk, BOOL isSel)
{
    gal_pixel oldColor; 
    RECT rc;


    if (isSel) {
        INIT_SELECT_BRUSH(self, hdc, oldColor);
    }
    else {
        INIT_NORMAL_BRUSH(self, hdc, oldColor);
    }

    if(!prcBk)
    {
        GetClientRect(self->hwnd, &rc);
        prcBk = &rc;
    }


    FillBox(hdc, prcBk->left, prcBk->top, RECTWP(prcBk), RECTHP(prcBk));

    SetBrushColor(hdc, oldColor);



}

static BOOL 
mTextEditor_onEraseBkgnd(mTextEditor* self, HDC hdc, const PRECT pClip)
{
    //do nothing, we clear the bkground in onPaint
    //return TRUE;
    
#ifdef __MINIGUI_LIB__
    if (!self || (GetWindowExStyle(self->hwnd) && WS_EX_TRANSPARENT))
        return TRUE;

    _fill_bkgnd(self, hdc, pClip, FALSE);
    return TRUE;
#else
    return Class(mWidget).onEraseBkgnd((mWidget*)self, hdc, pClip);
#endif
}

#if 0
static void mTextEditor_drawBkgnd(mTextEditor* self, HDC hdc, 
        const RECT *prcBk, BOOL isSel)
{
/*  RECT rcClient;
    int  width, height;

    if (!self)
        return;

    GetClientRect(self->hwnd, &rcClient);
    width  = RECTW(rcClient);
    height = RECTH(rcClient);

    rcClient = *prcBk;
    OffsetRect(&rcClient, self->offX, 0);
    if(RECTW(rcClient) > width)
        rcClient.right = rcClient.left + width;

    if(RECTH(rcClient) > height)
        rcClient.bottom = rcClient.top + height;

    if (isSel) {
        _fill_bkgnd(self, hdc, &rcClient, TRUE);
    }
    else
        //SendMessage(self->hwnd, MSG_ERASEBKGND, hdc, (LPARAM)&rcClient);          
        _c(self)->onEraseBkgnd(self, hdc, &rcClient);*/
}
#endif

static BOOL _read_only(mTextEditor *self)
{
    if (GetWindowStyle(self->hwnd) & NCSS_TE_READONLY) {
        Ping();
        return TRUE;
    }
    return FALSE;
}

static inline void min_swap(int *pa, int *pb)
{
    if(*pa > *pb) {
        int tmp = *pa;
        *pa = *pb;
        *pb = tmp;  
    }
}

static inline void _remove_one_char(mTextEditor* self, int textPos)
{
    int strPos, strLen = 0;
    if (textPos < 0)
        return;

    strPos = _c(pTextLayout)->textCount2Len(pTextLayout, textPos, 1, &strLen);
    _c(pTextBuffer)->replace(pTextBuffer, strPos, strLen, NULL, -1);
}

static inline void _remove_chars(mTextEditor* self, BOOL bback)
{
    int selBegin, selEnd;

    if(_c(pTextBuffer)->getCount(pTextBuffer) <= 0)
        return ;

    selBegin = pTextLayout->sel_begin;
    selEnd = pTextLayout->cursor;
    
    min_swap(&selBegin, &selEnd);

#ifdef _MGHAVE_TEXTEDITTITLE
    if(self->title_idx > 0)
    {
        if(selBegin < self->title_idx || (bback && selBegin == selEnd && selBegin == self->title_idx))
            return;
    }
#endif


    if(selBegin == selEnd) {
        _remove_one_char(self, bback ? (pTextLayout->text_cursor - 1 ): pTextLayout->text_cursor);
    }
    else
    {
#ifdef _MGHAVE_TEXTEDITTITLE
        _c(pTextBuffer)->replace(pTextBuffer, selBegin, selEnd - selBegin, NULL, -1);
#endif
    }
    ncsNotifyParent(self, NCSN_TE_CHANGE);
}

static inline void _set_cont_changed(mTextEditor *self, BOOL changed)
{
    if (self) {
        if(changed)
            self->flags |= NCS_TEF_CONTCHANGED;
        else 
            self->flags &= ~NCS_TEF_CONTCHANGED;
    }
}

static int texteditor_get_text_char_size(mTextEditor* self, int begin)
{
    int char_size = 1;
    //replace mode
    ITextIterator *txtit = _c(pTextBuffer)->getAt(pTextBuffer, begin);
    if((txtit)->_vtable->getCharType(INTEFACE_ADJUST(txtit), 0) == TI_VISIBILE_CHAR)
    {
        int txt_idx = 1;
        ITextRender *render = (txtit)->_vtable->getTextRender(INTEFACE_ADJUST(txtit));
        HDC hdc = GetClientDC(self->hwnd);
        char_size = (render)->_vtable->getTextByteLen(INTEFACE_ADJUST(render), hdc, txtit, &txt_idx);
        ReleaseDC(hdc);
    }
    (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));
    return char_size;
}

static int mTextEditor_onChar(mTextEditor *self, WPARAM eucCode, DWORD keyFlags)
{
    int selBegin, selEnd, chlen;
    unsigned char ch[4];
    int replace_char_size = 0;

    if (!TE_VALID_OBJ(self) || _read_only(self) || (keyFlags & KS_CTRL))
        return 0;

    if(eucCode == 127 || eucCode == '\b') {
        _remove_chars(self, TRUE);
        return 0;
    }
  
    ch [0] = FIRSTBYTE (eucCode);
    ch [1] = SECONDBYTE (eucCode);
    ch [2] = THIRDBYTE (eucCode);
    ch [3] = FOURTHBYTE (eucCode);
    if (ch[3]) {
        chlen = 4;
    }
    else if (ch[2]) {
        chlen = 3;
    }
    else if (ch[1]) {
        chlen = 2;
    }
    else {
        chlen = 1;
    }

    selBegin = pTextLayout->sel_begin;
    selEnd = pTextLayout->cursor;

    if (self->hardLimit != -1) //limit text
    {
        int newCount = textlayout_get_text_count(pTextLayout) ;
        if(selBegin == selEnd)
        {
            if(!mTextEditor_isReplaceMode(self) || 
                    pTextLayout->text_sel_begin >= newCount)
                newCount ++;
        }

#ifdef _MGHAVE_TEXTEDITTITLE
        if(self->title_text_idx > 0)
            newCount -= self->title_text_idx ;
#endif
        if(newCount > self->hardLimit)
        {
            Ping ();
            ncsNotifyParent (self, NCSN_TE_MAXTEXT);
            return 0;
        }
    }

    min_swap(&selBegin, &selEnd);

    if (chlen == 1) {
        if (GetWindowStyle(self->hwnd) & NCSS_TE_UPPERCASE) {
            eucCode = toupper(eucCode);
        }
        else if (GetWindowStyle(self->hwnd) & NCSS_TE_LOWERCASE) {
            eucCode = tolower(eucCode);
        }
    }

    if(mTextEditor_isReplaceMode(self) && selBegin == selEnd
        && selBegin < _c(pTextBuffer)->getCount(pTextBuffer))
    {
        if(replace_char_size == 0)
            replace_char_size = texteditor_get_text_char_size(self, selBegin);
        selEnd = selBegin + replace_char_size ;
    }


    _c(self->textBuffer)->replace(self->textBuffer,
            selBegin, selEnd - selBegin, (char*)&eucCode, -1);

    ncsNotifyParent (self, NCSN_TE_CHANGE);
    _set_cont_changed(self, TRUE);
    return 0;
}

static void _te_set_cursor_pos(mTextEditor *self, int x, int y, BOOL isSel)
{
    if (TE_VALID_OBJ(self)) {
        int title_x = 0;
        int title_y = 0;
        int title_last_height = 0;
        int cx, cy;
        int total_height;

#ifdef _MGHAVE_TEXTEDITTITLE
        if(self->title_x >= 0) title_x = self->title_x;
        if(self->title_y >= 0) title_y = self->title_y;
        if(self->title_last_height >= 0) title_last_height = self->title_last_height;
#endif

        if(y < 0) y = 0;
        if(x < 0) x = 0;

        total_height = textlayout_get_total_height(pTextLayout);

        // text bound maybe bigger than visible rect, when not auotwrap.
        _c(pTextLayout)->getTextBoundSize(pTextLayout, &cx, &cy);

        //printf("--- x=%d, y=%d, title:(x=%d,y=%d, height=%d, idx=%d, text_idx=%d\n", x, y, self->title_x, self->title_y, self->title_last_height, self->title_idx, self->title_text_idx);

        // click at title rect
        if(y <= title_y 
                || (y >= title_y  && y <= (title_y + title_last_height) && x < title_x))
        {
            x = title_x;
            y = title_y + title_last_height;
        }
        // click at text rect outside
        else if(y > total_height)
        {
            y = total_height;
            if( x < title_x && y == (title_y + title_last_height))
                x = self->title_x;
            else
                x = cx;
        }

        //printf("---- new x=%d, y=%d total_height=%d\n", x, y,total_height);
        { /* add {} for avoid compile error in Visual C. */
            TE_SEL_CHANGING(self);
            _c(pTextLayout)->setCursorPos(pTextLayout, x, y, isSel);
            TE_SEL_CHANGED(self, isSel);
        }
    }
}

/* Note: the pos is in the char not in byte. */
static void _te_set_cursor(mTextEditor *self, int pos, BOOL isSel)
{
    if (TE_VALID_OBJ(self)) {
#ifdef _MGHAVE_TEXTEDITTITLE
        if(pos < self->title_text_idx)
            pos = self->title_text_idx;
        //printf ("_te_set_cursor: pos:%d, text_idx:%d\n", pos, self->title_text_idx);
#endif
        { /* add {} for avoid compile error in Visual C. */
            TE_SEL_CHANGING(self);
            _c(pTextLayout)->setCursor(pTextLayout, pos, isSel);
            TE_SEL_CHANGED(self, isSel);
        }
    }
}

static int 
mTextEditor_onKeyDown(mTextEditor *self, int scancode, DWORD keyFlags)
{
    BOOL bShift = keyFlags & KS_SHIFT;
    BOOL bCtrl  = keyFlags & KS_CTRL;

    if (!TE_VALID_OBJ(self)) return 1;

    switch (scancode) {
        /*case SCANCODE_BACKSPACE:
            if (!_read_only(self))
                _remove_chars(self,TRUE);
            return 0;*/

        case SCANCODE_INSERT:
            if(mTextEditor_isReplaceMode(self))
            {
                self->flags &= ~NCS_TEF_REPLACE_MODE;
            }
            else
            {
                self->flags |= NCS_TEF_REPLACE_MODE;
            }
            _update_caret_size(self);
            _change_caret_size(self, TRUE);
            return 0;

        case SCANCODE_REMOVE:
            if (!_read_only(self))
                _remove_chars(self,FALSE);
            return 0;

        case SCANCODE_CURSORBLOCKLEFT:
            _te_set_cursor(self, 
                    (bShift ? pTextLayout->text_sel_begin 
                            : pTextLayout->text_cursor) - 1, 
                    bShift);
            return 0;

        case SCANCODE_CURSORBLOCKRIGHT:
            _te_set_cursor(self, 
                    (bShift ? pTextLayout->text_sel_begin 
                            : pTextLayout->text_cursor) + 1, 
                    bShift);
            return 0;

        case SCANCODE_HOME:
        case SCANCODE_END:
        {
            int caretX, caretY;
            _c(pTextLayout)->getCursor(pTextLayout, &caretX, &caretY, NULL, bShift);
            if(scancode == SCANCODE_HOME)
                caretX = 0;
            else
                caretX = _c(self)->getMetrics(self, TEXTMETRICS_MAXWIDTH);
            _te_set_cursor_pos(self, caretX, caretY + 1, bShift);
            return 0;
        }

        case SCANCODE_PAGEDOWN:
        case SCANCODE_PAGEUP:
        {
            TE_SET_CURSORE_PAGE_MOVE(self);
            _c(self)->onVScroll(self, 
                scancode == SCANCODE_PAGEUP ?  SB_PAGEUP : SB_PAGEDOWN, 0);
            return 0;
        }

        case SCANCODE_CURSORBLOCKUP:
        case SCANCODE_CURSORBLOCKDOWN:
        {
            int caretX, caretY;
            int height;
            
            _c(pTextLayout)->getCursor(pTextLayout, &caretX, &caretY, &height, bShift);
            if(scancode == SCANCODE_CURSORBLOCKUP && caretY <= 0)
                break;
        
            if(scancode == SCANCODE_CURSORBLOCKDOWN)
                caretY += (height+1);
            else 
                caretY -= (height-1);
            _te_set_cursor_pos(self, caretX, caretY, bShift);

            return 0;
        }
        case SCANCODE_A:
        {
            if (bCtrl) {
                _c(self)->selectAll(self);
            }
            return 0;
        }

        case SCANCODE_X:
        case SCANCODE_C:
        {
            if (bCtrl) {
                int selBegin, selEnd;
                selBegin = pTextLayout->sel_begin;
                selEnd = pTextLayout->cursor;

                if (selBegin == selEnd)
                   return 0; 

                min_swap(&selBegin, &selEnd);

                //clear clipboard
                SetClipBoardData (CBNAME_TEXT, NULL, 0, CBOP_NORMAL);

                SetClipBoardData(CBNAME_TEXT, 
                        pTextBuffer->buffer + selBegin, 
                        selEnd - selBegin, CBOP_APPEND);

                //for SCANCODE_X: need delete selected text
                if (scancode == SCANCODE_X && !_read_only(self)) {
                    _c(pTextBuffer)->replace(pTextBuffer, 
                            selBegin, selEnd - selBegin, NULL, -1);
                    ncsNotifyParent(self, NCSN_TE_CHANGE);
                }

                return selEnd - selBegin;
            }
            return 0;
        }

        case SCANCODE_V:
        {
            if (GetWindowStyle(self->hwnd) & NCSS_TE_READONLY)
                return 0;

            if (bCtrl) {
                int inserting;
                int begin_pos;
                char *txtBuffer;
                DWORD dwStyle = GetWindowStyle(self->hwnd);

                inserting = GetClipBoardDataLen (CBNAME_TEXT);
                if (inserting > 0) {
                    txtBuffer = FixStrAlloc (inserting);
                    GetClipBoardData (CBNAME_TEXT, txtBuffer, inserting);

                    if(pTextLayout->sel_begin > pTextLayout->cursor)
                        begin_pos = pTextLayout->cursor;
                    else 
                        begin_pos = pTextLayout->sel_begin;

                    _c(self)->insertText(self, txtBuffer, inserting);

                    if(dwStyle & NCSS_TE_UPPERCASE)
                        _c(pTextBuffer)->setCase(pTextBuffer, begin_pos, begin_pos+inserting, TRUE);
                    else if(dwStyle & NCSS_TE_LOWERCASE)
                        _c(pTextBuffer)->setCase(pTextBuffer, begin_pos, begin_pos+inserting, FALSE);

                    FreeFixStr(txtBuffer);
                }
                return inserting;
            }
            return 0;
        }
    }
    return 1;
}

static int mTextEditor_getSel(mTextEditor *self, char *buffer, int len)
{
    int selBegin, selEnd;

    if (!TE_VALID_OBJ(self)) {
        return -1;
    }

    selBegin = pTextLayout->sel_begin;
    selEnd = pTextLayout->cursor;


    if (selBegin == selEnd)
        return -1;

    min_swap(&selBegin, &selEnd);

    if(!buffer || len <= 0)
        return selEnd - selBegin; //return the length of sel

    len = MIN(len, selEnd - selBegin);

    buffer[0] = 0;

    //strncpy(buffer, pTextBuffer->buffer + selBegin, len);
    _c(pTextBuffer)->getText(pTextBuffer, selBegin, buffer, len);
    buffer[len] = 0;
    return len;
}

static int mTextEditor_setText(mTextEditor *self, const char* text)
{
    int len = -1;
    int at = 0;
    char * str;

    if (!TE_VALID_OBJ(self))
        return -1;

    if(text)
        len = strlen(text);

#ifdef _MGHAVE_TEXTEDITTITLE
    at = self->title_idx;
    if(at < 0)
        at = 0;
#endif

    if (len > 0 && self->hardLimit != -1) {
        int text_count;
        text_count = texteditor_get_text_count(self, at, FALSE, text, len);
        
        if(text_count > self->hardLimit)
            len = texteditor_get_text_length(self, at, FALSE, text, self->hardLimit, len);
    }
    else {
        len = texteditor_get_validate_text_length(self, 0, FALSE, text, len);
    }

    str = texteditor_case_copy(self, text, len);

    len = _c(pTextBuffer)->replace(pTextBuffer, at, 
                _c(pTextBuffer)->getCount(pTextBuffer) - at, str, len);

    if(str && str != text)
        FreeFixStr(str);

    _te_set_cursor(self, 0, FALSE);

    ncsNotifyParent (self, NCSN_TE_UPDATE);
    _set_cont_changed(self, TRUE);
    return len;
}

static int mTextEditor_insertText(mTextEditor *self, const char* text, int len)
{
    int length = -1;
    int begin_pos;
    int replace_len;
    char * str;

    if (!TE_VALID_OBJ(self))
        return -1;

    if(!text && len < 0)
        len = 0;
    else if(text && len < 0)
        len = strlen(text);

    if(pTextLayout->sel_begin > pTextLayout->cursor)
    {
        begin_pos = pTextLayout->cursor;
        replace_len = pTextLayout->sel_begin - pTextLayout->cursor;
    }
    else {
        begin_pos = pTextLayout->sel_begin;
        replace_len = pTextLayout->cursor - pTextLayout->sel_begin;
    }


    if(len>0 && self->hardLimit != -1) //limit text
    {
        int selBegin, selEnd;
        int newCount;
        int text_len;
        
        text_len = texteditor_get_text_count(self, begin_pos,  FALSE, text, len); 

        selBegin = pTextLayout->text_sel_begin;
        selEnd = pTextLayout->text_cursor;
        min_swap(&selBegin, &selEnd);

       newCount = textlayout_get_text_count(pTextLayout);
     
       if(text_len != (selEnd - selBegin))
       {
           if(mTextEditor_isReplaceMode(self))
           {
               if(text_len > (selEnd - selBegin))
               {
                    int insert = text_len - selEnd + selBegin;
                    if(selEnd  + insert > newCount)
                        newCount = selEnd + insert;
               }
               else
                   newCount += (text_len - selEnd + selBegin);
           }
           else
               newCount += (text_len - (selEnd - selBegin)); 
       }

#ifdef _MGHAVE_TEXTEDITTITLE
       if(self->title_idx > 0)
           newCount -= self->title_text_idx;
#endif
       if(newCount > self->hardLimit)
       {
           text_len += (self->hardLimit - newCount);
           if(text_len <= 0)
           {
                Ping();
                ncsNotifyParent(self, NCSN_TE_MAXTEXT);
                return 0;
           }
           //get the real len
           len = texteditor_get_text_length(self, begin_pos, FALSE, text, text_len, len);
       }
    }
    else
    {
        len = texteditor_get_validate_text_length(self, begin_pos,FALSE, text, len);
    }

    str = texteditor_case_copy(self, text, len);

    length = _c(pTextBuffer)->replace(pTextBuffer, begin_pos, replace_len, str, len);
    
    if(str && str != text)
        FreeFixStr(str);

    ncsNotifyParent (self, NCSN_TE_CHANGE);
    _set_cont_changed(self, TRUE);

    return length;
}

static int mTextEditor_getText(mTextEditor *self, char* text, int len)
{
    if (TE_VALID_OBJ(self)) 
    {
#ifdef _MGHAVE_TEXTEDITTITLE
        int at = self->title_idx;
        if(at < 0) 
            at = 0;
#else
        int at = 0;
#endif
        return _c(pTextBuffer)->getText(pTextBuffer, at, text, len);
    }
    return 0;
}

static int mTextEditor_getTextLen(mTextEditor *self)
{
    if (TE_VALID_OBJ(self)) {
        return _c(pTextBuffer)->getCount(pTextBuffer)
#ifdef _MGHAVE_TEXTEDITTITLE
            - (self->title_idx > 0 ? self->title_idx : 0)
#endif
            ;
    }
    return 0;
}

static void mTextEditor_onSetFocus(mTextEditor *self, HWND oldActiveWnd, int lParam)
{
    int text_len = 0;

    if (!self || self->hwnd == oldActiveWnd)
        return;

    self->flags |= NCS_TEF_FOCUS;

    ncsNotifyParent (self, NCSN_TE_SETFOCUS);
    InvalidateRect (self->hwnd, NULL, TRUE);

#ifdef _MGHAVE_TEXTEDITTITLE
    text_len = self->title_idx <= -1 ? 0: self->title_idx;
#endif

    /*[bug5291]: when get focus don't auto move caret. 
     * only there is empty text, call it to show the caret. */
    if (self->textBuffer->char_len == text_len)
        _c(self)->updateCaret(self);
}

static void mTextEditor_onKillFocus(mTextEditor *self, HWND newActiveWnd, int lParam)
{
    if (!self)
        return;

    self->flags &= ~NCS_TEF_FOCUS;

    //unselect all
    if (!(GetWindowStyle(self->hwnd) & NCSS_TE_NOHIDESEL)) {
        int cursor = _c(pTextLayout)->getCursor(pTextLayout, NULL, NULL, NULL, FALSE);
        _c(pTextLayout)->setCursor(pTextLayout, cursor, FALSE);
    }

    HideCaret(self->hwnd);

    if (self->flags & NCS_TEF_CONTCHANGED)
        ncsNotifyParent (self, NCSN_TE_CONTENTCHANGED);
    ncsNotifyParent (self, NCSN_TE_KILLFOCUS);

    InvalidateRect (self->hwnd, NULL, TRUE);
}

static BOOL mTextEditor_onFontChanged(mTextEditor *self)
{
    if (!TE_VALID_OBJ(self))
        return FALSE;


    DestroyCaret (self->hwnd);
    CreateCaret(self->hwnd, NULL, 1, GetWindowFont(self->hwnd)->size);

    _update_content_view(self, -1, -1, TRUE);

    InvalidateRect(self->hwnd, NULL, TRUE);

    return TRUE;
}

static void mTextEditor_onPaint(mTextEditor *self, HDC hdc, const PCLIPRGN pClip)
{
    self->flags |= NCS_TEF_HAS_PAINTED;

    if (TE_VALID_OBJ(self)) {
        RECT rcView;

        texteditor_get_view_rect(self, &rcView);
        //printf("-- rcView = %d,%d,%d,%d\n", rcView.left, rcView.top, rcView.right, rcView.bottom);

#if 0
        if(!texteditor_must_update_bkgnd(self) && (
                    (self->oldOffX != self->offX || self->oldOffY != self->offY )
                        || (self->textLayout->old_height != -1
                    && self->textLayout->old_height != self->textLayout->new_height)
                    ))
        {
            //we scroll the view area
            //use bitblt to scroll the area 
            int offx = self->oldOffX - self->offX;
            int offy = self->oldOffY - self->offY; 
            int sx, sy, sw, sh, dx, dy;
            RECT rc;
            PCLIPRGN prgn = CreateClipRgn();
        
            //save clipRgn
            GetClipRegion(hdc,prgn);

            //set ClipRgn make the scrolled area enabled
           
            if(self->oldOffX != self->offX || self->oldOffY != self->offY)
            {
                if(offx == 0) {
                    sx = dx = rcView.left;
                    sw = RECTW(rcView);
                }
                else if(offx > 0)
                {
                    sx = rcView.left;
                    dx = rcView.left + offx;
                    sw = RECTW(rcView) - offx;
                }
                else {
                    sx = rcView.left - offx;
                    dx = rcView.left;
                    sw = RECTW(rcView) + offx;
                }

                if(offy == 0){
                    sy = dy = rcView.top;
                    sh = RECTH(rcView);
                }
                else if(offy > 0){
                    sy = rcView.top;
                    dy = rcView.top + offy;
                    sh = RECTH(rcView) - offy;
                }
                else {
                    sy = rcView.top - offy;
                    dy = rcView.top;
                    sh = RECTH(rcView) + offy;
                }
                //sw --;
                //sh --;

                rc.left = dx;
                rc.top  = dy;
                rc.right = dx + sw;
                rc.bottom = dy + sh;

                SelectClipRect(hdc, &rc);

                //printf("--- oldOffX=%d, Y=%d, offX=%d, y=%d\n",self->oldOffX, self->oldOffY, self->offX, self->offY);
                //printf("--- bitblt:sx=%d,sy=%d,sw=%d,sh=%d, dx=%d,dy=%d\n", sx,sy, sw, sh, dx,dy);
                BitBlt(hdc, sx, sy, sw, sh, hdc, dx, dy, 0);
            }


            //bitblt the area the height changed
            if(self->textLayout->old_height != -1
                    && self->textLayout->old_height != self->textLayout->new_height)
            {
                //printf("---- old_height=%d, new_height=%d\n", self->textLayout->old_height, self->textLayout->new_height);
                sx = rcView.left;
                sw = RECTW(rcView);
                sy = self->textLayout->old_height - self->offY + self->margins.top;
                sh = rcView.bottom - sy;
                dy = self->textLayout->new_height - self->offY + self->margins.top;
                if( (dy + sh) > rcView.bottom)
                    sh = rcView.bottom - dy;
                dx = sx;

                rc.left = dx;
                rc.top  = dy;
                rc.right = dx + sw;
                rc.bottom = dy + sh;

                SelectClipRect(hdc, &rc);

                //printf("--- bitblt height:sx=%d,sy=%d,sw=%d,sh=%d, dx=%d,dy=%d\n", sx,sy, sw, sh, dx,dy);
                BitBlt(hdc, sx, sy, sw, sh, hdc, dx, dy, 0);
                self->textLayout->old_height = -1;
            }
            
            SelectClipRegion(hdc, prgn);
            DestroyClipRgn(prgn);
        }

        //clear the scroll info
        self->oldOffX = self->offX;
        self->oldOffY = self->offY;
#endif

/*     if(!(GetWindowExStyle(self->hwnd) & WS_EX_TRANSPARENT))
            _fill_bkgnd(self, hdc, NULL, FALSE);
*/
        SetBkMode(hdc, BM_TRANSPARENT);

        //usleep(50000);

        //TODO draw margins Decorative hear, e.g frameset

        //draw linedectors
        mTextEditor_drawLinesDecorative(self, hdc, self->offX, self->offY, RECTW(rcView), RECTH(rcView));
        //usleep(50000);

        ClipRectIntersect(hdc, &rcView);
        _c(pTextLayout)->draw(pTextLayout, hdc, self->offX, 
                self->offY, self->visWidth, self->visHeight);

        //draw margin
/*        SelectClipRect(hdc, NULL);
        SetPenColor(hdc, COLOR_red);
        RECT myrc;
        GetClientRect(self->hwnd, &myrc);
        Rectangle(hdc, self->margins.left-1, self->margins.top-1,myrc.right - self->margins.right-1, myrc.bottom - self->margins.bottom-1);
        SetPenColor(hdc, COLOR_blue);
        Rectangle(hdc, 0, 0, myrc.right ,self->margins.top);
        Rectangle(hdc, 0, self->margins.top, self->margins.left, myrc.bottom - self->margins.bottom);
        Rectangle(hdc, 0, myrc.bottom - self->margins.bottom, myrc.right, myrc.bottom);
        Rectangle(hdc, myrc.right - self->margins.right, 0, myrc.right, myrc.bottom);*/
    }
}

static int mTextEditor_onLButtonDown(mTextEditor *self, int x, int y, DWORD keyFlags)
{
    if (!GetCapture()) {
        SetCapture(self->hwnd);
    }
    texteditor_client_to_view(self, &x, &y);
    _te_set_cursor_pos(self,  x + self->offX, y + self->offY, FALSE);
    return 0;
}

static int mTextEditor_onLButtonDBClk(mTextEditor *self, int x, int y, DWORD keyFlags)
{
    ncsNotifyParent (self, NCSN_WIDGET_DBCLICKED);
    return 0;
}

static int mTextEditor_onLButtonUp(mTextEditor *self, int x, int y, DWORD keyFlags)
{
    if(GetCapture() == self->hwnd)
        ReleaseCapture();
    ncsNotifyParent (self, NCSN_WIDGET_CLICKED);
    return 0;
}

static int mTextEditor_onMouseMove(mTextEditor *self, int x, int y, DWORD keyFlags)
{
    if(GetCapture() == self->hwnd)
    {
        ScreenToClient(self->hwnd, &x, &y);
        texteditor_client_to_view(self, &x, &y);
        _te_set_cursor_pos(self,  x + self->offX, y + self->offY, TRUE);
    }
    return 0;
}


static void mTextEditor_onCSizeChanged(mTextEditor *self, int visWidth, int visHeight)
{
    //-- WARNING:  don't use visWidth and visHeight, because they would be different with ClientRect
    //MSG_CSIZECHANGED is sent as Notify Message, the visWidth and visHeight is the client width and height
    //before seconds
    _update_content_view(self, -1, -1,FALSE);
    //need update the margin
    InvalidateRect(self->hwnd, NULL, TRUE);
}

/**
 * index:   paragraph index (not contain title context)
 * start:   start position (in byte)
 * len:     copied string length in specified paragraph
 * buffer:  string buffer
 */
static int mTextEditor_getParaText(mTextEditor *self, int index,
                        int start, int len, char* buffer)
{
    int char_start = 0, count = 0;
    int copy_len = 0;
    ITextIterator * txtit = NULL;

    if (!TE_VALID_OBJ(self) || index < -1 || start < 0 || len <= 0)
        return 0;

    // get whole text
    if(index == -1) 
        return _c(self)->getText(self, buffer, len);

    count = _c(pTextBuffer)->getCount(pTextBuffer);

#ifdef _MGHAVE_TEXTEDITTITLE
    if(self->title_idx > 0)
    {
        if(index == 0)
        {
            _c(pTextLayout)->textCount2Len(pTextLayout, 
                    self->title_text_idx - self->title_last_para_text_index,
                    self->title_last_para_text_index, &char_start);
        }
        else
            char_start = 0;
        index += self->title_paracount;
    }
#else
    char_start = 0;
#endif
    
    if(index > 0)
        char_start = _c(pTextLayout)->getIndexByPara(pTextLayout, index, char_start, NULL, FALSE);

    if(char_start >= count)
        goto RETURN_0;

    txtit = _c(pTextBuffer)->getAt(pTextBuffer, char_start);

    (void) (txtit)->_vtable->getMore(INTEFACE_ADJUST(txtit), &len, &copy_len, FALSE);

    if(start >= copy_len)
        goto RETURN_0;

    /* set start
     * don't need to judge the copy_len whether overrange the line. 
     * because mTextIterator_getMore will stop when have '\n'. */
    (txtit)->_vtable->reset(INTEFACE_ADJUST(txtit), char_start + start);
    copy_len -= start;
    strncpy(buffer, (txtit)->_vtable->getMore(INTEFACE_ADJUST(txtit), NULL, NULL, FALSE), copy_len);

    buffer[copy_len] = 0;
    (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));

    return copy_len;
RETURN_0:
    buffer[0] = 0;
    if(txtit)
        (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));
    return 0;
}

/* External interface: the index is not contain the title context. */
static int mTextEditor_getLineText(mTextEditor *self, int index,
                        int start, int len, char* buffer)
{
    int char_start = 0, count = 0, title_linecount = 0;
    int col = 0, copy_len = 0, line_len = 0;
    char* str = NULL;
    ITextIterator* txtit = NULL;

    if (!TE_VALID_OBJ(self) || index < -1 || start < 0 || len <= 0)
        return 0;

    // get whole text
    if(index == -1) 
        return _c(self)->getText(self, buffer, len);

    count = _c(pTextBuffer)->getCount(pTextBuffer);

    // col must be byte 
    // texteditor_add_title_content(self, &index, &col, TRUE);
#ifdef _MGHAVE_TEXTEDITTITLE
    texteditor_update_title_line(self);
    title_linecount = self->title_linecount;
    if (title_linecount < 0)
        title_linecount = 0;

    if (self->title_idx > 0)
    {
        if (0 == index)
        {
            _c(pTextLayout)->textCount2Len(pTextLayout, 
                    self->title_text_idx - self->title_last_line_text_index, 
                    self->title_last_line_text_index, &col);
        }
        index += self->title_linecount;
    }
#endif
    
    if(index > 0)
        char_start = _c(pTextLayout)->getIndexByLine(pTextLayout, index, col, NULL, FALSE);

    if(char_start >= count)
        return 0;

    txtit = _c(pTextBuffer)->getAt(pTextBuffer, char_start);
    str = (char*)(txtit)->_vtable->getMore(INTEFACE_ADJUST(txtit), &len, &copy_len, FALSE);

    if(start >= copy_len)
    {
        (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));
        return 0;
    }

    // set start
    (txtit)->_vtable->reset(INTEFACE_ADJUST(txtit), char_start + start);

    /* note: the index must subtract the title context,
     * because mTextLayout_getLineLength param of index is not contain title context.
     * It's a external interface. All external interface(mTextEditor) param of index not contain title context,
     * and all internal interface(mTextLayout) param of index contain title context. */
    line_len = _c(self)->getLineLength(self, index - title_linecount, NULL, FALSE);

    copy_len -= start;
    line_len -= start;
    copy_len = MIN(copy_len, line_len);
    if(copy_len < 0)
        copy_len = 0;

    if(str)
    {
        strncpy(buffer, str, copy_len);
    }
    else
    {
        (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));
        return 0;
    }

    buffer[copy_len] = 0;
    (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));
    return copy_len;
}

static int mTextEditor_getParaNum(mTextEditor *self)
{
    if (TE_VALID_OBJ(self)) { 
        int count = _c(pTextBuffer)->getCount(pTextBuffer);
#ifdef _MGHAVE_TEXTEDITTITLE
        if(count == self->title_idx)
            return 0;
#endif
        return count ? 
            (_c(pTextLayout)->getParaByIndex(pTextLayout, count, NULL, FALSE) + 1
#ifdef _MGHAVE_TEXTEDITTITLE
             - (self->title_idx > 0 ? self->title_paracount : 0)
#endif
            ) 
            : 0;
    }
        
    return 0;
}

// index: begin 0 
static int mTextEditor_getParaLength(mTextEditor *self, int index, int *startPos)
{
    int start = 0, count;
    ITextIterator * txtit;
    int visible_chars = 0;

    if (index < 0 || !TE_VALID_OBJ(self))
        return -1;

    count = _c(pTextBuffer)->getCount(pTextBuffer);

#ifdef _MGHAVE_TEXTEDITTITLE
    if(self->title_idx > 0)
    {
        if(index == 0)
        {
            _c(pTextLayout)->textCount2Len(pTextLayout, 
                    self->title_text_idx - self->title_last_para_text_index, 
                    self->title_last_para_text_index, &start);
        }
        index += self->title_paracount;
    }
#endif

    start = _c(pTextLayout)->getIndexByPara(pTextLayout, index, start, NULL, FALSE);
    if(start >= count)
    {
        start = count;
        if(startPos)
            *startPos = start;
        return 0;
    }

    txtit = _c(pTextBuffer)->getAt(pTextBuffer, start);

    if(!txtit)
    {
        if(startPos)
            *startPos = start;
        return 0;
    }

    while(1)
    {
        int len;
        (void)(txtit)->_vtable->getMore(INTEFACE_ADJUST(txtit), NULL, &len, TRUE);
        visible_chars += len;
        if(_I(txtit)->index(INTEFACE_ADJUST(txtit)) >= (count -1))
            break;
        if(_I(txtit)->getCharType(INTEFACE_ADJUST(txtit), -1) == TI_LINERETURN)
            break;
    }

    (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));

    if(startPos)
        *startPos = start;

    return visible_chars;
}

static int mTextEditor_getParaLengthInMChar(mTextEditor *self, int paraNo, int *startPos)
{
    int total;
    int len = 0, col = 0;
    int str_idx = 0, para_idx = 0;

    mTextLayoutNode* node;
    ITextIterator * txtit;
    mCommBTreeLeafIterator node_it;

    if (paraNo < 0 || !TE_VALID_OBJ(self)) 
        return -1;

    total = _c(pTextBuffer)->getCount(pTextBuffer);

    texteditor_add_title_content(self, &paraNo, &col, FALSE);

    node = textlayout_get_first_node(pTextLayout);

    txtit = _c(pTextBuffer)->getAt(pTextBuffer, 0);

    (void)initObjectArgs((mObject*)((void*)(&node_it)), 
            (mObjectClass*)((void*)(&Class(mCommBTreeLeafIterator))), 2, pTextLayout, node);

    while((node = (mTextLayoutNode*)node_it.current))
    {
        if (para_idx >= paraNo)
        {
            len += node->text_count;
            if(_I(txtit)->getCharType(INTEFACE_ADJUST(txtit), str_idx + node->str_count - 1) == TI_LINERETURN)
            {
                // ignore the '\n'
                if (len > 0)
                    len -= 1;
                break;
            }
        }

        str_idx += node->str_count;
        if(_I(txtit)->getCharType(INTEFACE_ADJUST(txtit), str_idx - 1) == TI_LINERETURN)
            para_idx ++;
        _c(&node_it)->next(&node_it);
    }

    (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));

    if (str_idx >= total)
    {
        SMART_ASSIGN(startPos, total); 
        return 0;
    }

#ifdef _MGHAVE_TEXTEDITTITLE
    if (paraNo == self->title_paracount)
        len -= self->title_last_para_text_index;
#endif

    SMART_ASSIGN(startPos, str_idx);
    return len;
}

/* External interface: the index is not contain the title context. */
static int mTextEditor_getLineLength(mTextEditor *self, int lineNo, int *startPos, BOOL bMChar)
{
    int total;
    int col = 0, str_idx = 0;
    int str_len = 0, text_len = 0, line_idx = 0;

    mTextLayoutNode* node;
    ITextIterator * txtit;
    mCommBTreeLeafIterator node_it;

    if (lineNo < 0 || !TE_VALID_OBJ(self)) 
        return -1;

    total = _c(pTextBuffer)->getCount(pTextBuffer);

    texteditor_add_title_content(self, &lineNo, &col, TRUE);

    node = textlayout_get_first_node(pTextLayout);

    (void)initObjectArgs((mObject*)((void*)(&node_it)), 
            (mObjectClass*)((void*)(&Class(mCommBTreeLeafIterator))), 2, pTextLayout, node);

    txtit = _c(pTextBuffer)->getAt(pTextBuffer, 0);

    while((node = (mTextLayoutNode*)node_it.current))
    {
        if (line_idx >= lineNo)
        {
            text_len = node->text_count;
            str_len = node->str_count;

            if(_I(txtit)->getCharType(INTEFACE_ADJUST(txtit), str_idx + node->str_count - 1) == TI_LINERETURN)
            {
                // ignore the '\n'
                if (text_len > 0) text_len -= 1;
                if (str_len > 0) str_len -= 1;
            }

            break;
        }

        str_idx += node->str_count;
        line_idx ++;
        _c(&node_it)->next(&node_it);
    }

    (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));

    if (str_idx >= total)
    {
        SMART_ASSIGN(startPos, total); 
        return 0;
    }

#ifdef _MGHAVE_TEXTEDITTITLE
    if (lineNo == self->title_linecount)
    {
        text_len -= self->title_last_line_text_index;

        if (!bMChar)
        {
            _c(pTextLayout)->textCount2Len(pTextLayout, 
                    self->title_text_idx - self->title_last_line_text_index, 
                    self->title_last_line_text_index, &total);
            str_len -= total;
        }
    }
#endif

    SMART_ASSIGN(startPos, str_idx);
    if (bMChar)
        return text_len;
    else
        return str_len;
}

static BOOL mTextEditor_setProperty(mTextEditor *self, int id, DWORD value)
{
    if (!self || id >= NCSP_TE_MAX)
        return FALSE;

    switch (id) {
        case NCSP_TE_SCROLL_HSTEP:
            self->scrollHStep = value;
            return TRUE;

        case NCSP_TE_SCROLL_VSTEP:
            self->scrollVStep = value;
            return TRUE;

        case NCSP_TE_SCROLL_MODE:
        {
            if (value == self->scrollBarMode)
                return FALSE;

            self->scrollBarMode = value;
            switch (value) {
                case NCS_TEF_SCROLLBAR_NEVER:
                {
                    //previous:scrollbar shown
                    ExcludeWindowStyle(self->hwnd, NCSS_TE_AUTOVSCROLL | NCSS_TE_AUTOHSCROLL);
                    break;
                }

                case NCS_TEF_SCROLLBAR_AUTO:
                {
                    IncludeWindowStyle(self->hwnd, NCSS_TE_AUTOVSCROLL | NCSS_TE_AUTOHSCROLL | WS_HSCROLL | WS_VSCROLL);
                    break;
                }

                case NCS_TEF_SCROLLBAR_ALWAYS:
                {
                    IncludeWindowStyle(self->hwnd, WS_HSCROLL | WS_VSCROLL );
                    ExcludeWindowStyle(self->hwnd, NCSS_TE_AUTOHSCROLL | NCSS_TE_AUTOVSCROLL);
                    break;
                }
                default:
                    return FALSE;
            }
            _update_scrollbar(self);

            return TRUE;
        }

        case NCSP_TE_READONLY:
        {
            if (self && self->hwnd) {
                if (value)
                    IncludeWindowStyle (self->hwnd, NCSS_TE_READONLY);
                else
                    ExcludeWindowStyle (self->hwnd, NCSS_TE_READONLY);
                return TRUE;
            }
            break;
        }
        case NCSP_TE_MARGINRECT:
        {
            const RECT * margins = (const RECT*)value;
            if(margins)
            {
                RECT rc;
                GetClientRect(self->hwnd, &rc);

                self->margins = *margins;

                if(self->margins.left >= RECTW(rc)/2)
                    self->margins.left = RECTW(rc) / 2 - 5;
                if(self->margins.left < 0)
                    self->margins.left = 0;

                if(self->margins.top >= RECTH(rc) / 2)
                    self->margins.top = RECTH(rc) / 2 - 5;
                if(self->margins.top < 0)
                    self->margins.top = 0;

                if(self->margins.right >= RECTW(rc)/2)
                    self->margins.right = RECTW(rc) / 2 - 5;
                if(self->margins.right < 0)
                    self->margins.right = 0;
                if(self->margins.bottom >= RECTH(rc)/2)
                    self->margins.bottom = RECTH(rc)/2 - 5;
                if(self->margins.bottom < 0)
                    self->margins.bottom = 0;

                //update client area
                _update_content_view(self, -1, -1, FALSE);
                InvalidateRect(self->hwnd, NULL, TRUE);
                return TRUE;
            }
            return FALSE;
        }
        case NCSP_TE_TEXTLIMIT:
        {
            int newLimit = (int)value;
            if(newLimit >= 0){
                int count = textlayout_get_text_count(pTextLayout);
#ifdef _MGHAVE_TEXTEDITTITLE
                if(self->title_text_idx > 0)
                    count -= self->title_text_idx;
#endif
                if(count > newLimit) {
                    Ping();
                    return FALSE;
                }
                else 
                    self->hardLimit = newLimit;
            }
            else
                self->hardLimit = -1; //cancel limit
            return TRUE;
        }
        
        default:
            break;
    }

    return Class(mWidget).setProperty((mWidget*)self, id, value);
}

static DWORD mTextEditor_getProperty(mTextEditor *self, int id)
{
    if (!self || id >= NCSP_TE_MAX)
        return -1;

    switch (id) {
#if 0
        case NCSP_TE_SCROLL_MODE:
            return self->scrollBarMode;
#endif

        case NCSP_TE_SCROLL_HSTEP:
            return self->scrollHStep;

        case NCSP_TE_SCROLL_VSTEP:
            return self->scrollVStep;

        case NCSP_TE_READONLY:
        {
            if (self && self->hwnd) {
                return GetWindowStyle(self->hwnd) & NCSS_TE_READONLY;
            }
            break;
        }
        case NCSP_TE_MARGINRECT:
        {
            return (DWORD)(&self->margins);
        }
        case NCSP_TE_TEXTLIMIT:
            return (DWORD)(self->hardLimit);
        case NCSP_TE_LINECOUNT:
            return (DWORD)(_c(self)->getLineCount(self));
        case NCSP_TE_PARAGRAPHCOUNT:
            return (DWORD)(_c(self)->getParaNum(self));
        default:
            break;
    }
    return Class(mWidget).getProperty((mWidget*)self,id) ;
}

static BOOL mTextEditor_enableCaret(mTextEditor *self, BOOL enable)
{
    if (!TE_VALID_OBJ(self))
        return FALSE;

    if (enable && (self->flags & NCS_TEF_NOCARET)) {
        self->flags &= ~NCS_TEF_NOCARET;
        _c(self)->updateCaret(self);
        return TRUE;
    }
    else if (!enable && !(self->flags & NCS_TEF_NOCARET)) {
        self->flags |= NCS_TEF_NOCARET;
        HideCaret(self->hwnd);
        return TRUE;
    }
    return FALSE;
}

static int mTextEditor_setCaretPosByPara(mTextEditor *self, int paraIdx, 
        int charPos, BOOL isSel)
{
    int index = 0, cursor = 0; 
    BOOL out_of_range = FALSE;

    if (!TE_VALID_OBJ(self) || charPos < 0 || paraIdx < 0)
        return -1;

    texteditor_add_title_content(self, &paraIdx, &charPos, FALSE);

    index = _c(pTextLayout)->getIndexByPara(pTextLayout, paraIdx, charPos, &out_of_range, TRUE);

    _te_set_cursor(self, index, isSel);

    cursor = isSel ? pTextLayout->text_sel_begin : pTextLayout->text_cursor;

    if (out_of_range)
        return -1;
    else
        return cursor
#ifdef _MGHAVE_TEXTEDITTITLE
                - (self->title_text_idx > 0 ? self->title_text_idx : 0)
#endif
            ;
}

static int mTextEditor_setCaretPosByLine(mTextEditor *self, int lineIdx, 
        int charPos, BOOL isSel)
{
    int index = 0, cursor = 0;
    BOOL out_of_range = FALSE;

    if (!TE_VALID_OBJ(self) || charPos < 0 || lineIdx < 0)
        return -1;

#ifdef _MGHAVE_TEXTEDITTITLE
    {
        int total = _c(self->textBuffer)->getCount(self->textBuffer);
        /* when have any text, the texteditor_update_title_line can't calc correct. 
         * so don't call it in this situation. */
        if (total <= self->title_idx)
        {
            _te_set_cursor(self, index, isSel);
            if (lineIdx > 0 || charPos > 0)
                return -1;
            else
                return 0;
        }
    }
#endif

    texteditor_add_title_content(self, &lineIdx, &charPos, TRUE);

    index = _c(pTextLayout)->getIndexByLine(pTextLayout, lineIdx, charPos, &out_of_range, TRUE);

    _te_set_cursor(self, index, isSel);

    cursor = isSel ? pTextLayout->text_sel_begin : pTextLayout->text_cursor;

    if (out_of_range)
        return -1;
    else
        return cursor
#ifdef _MGHAVE_TEXTEDITTITLE
                - (self->title_text_idx > 0 ? self->title_text_idx : 0)
#endif
            ;
}

static int mTextEditor_setSel(mTextEditor *self, 
        unsigned int startParaIdx, unsigned int startOffChar, 
        unsigned int endParaIdx, unsigned int endOffChar)
{
    if (_c(self)->setCaretPosByPara(self, startParaIdx, startOffChar, FALSE) != -1
        && _c(self)->setCaretPosByPara(self, endParaIdx, endOffChar, TRUE) != -1)
        return 0;

    return -1;
}

static int mTextEditor_getLineCount(mTextEditor *self)
{
    mCommBTreeLeafIterator iter;
    mTextLayoutNode *node;
    int line = 0;
#ifdef _MGHAVE_TEXTEDITTITLE
    int str_count = 0;
    
    /* if last node is ended with '\n', it will add a new empty line.
     * so when the title last is '\n', and there has any real text,
     * it will make line count overrange 1. */
    if (_c(pTextBuffer)->getCount(pTextBuffer) == self->title_idx)
        return 0;
#endif

    if (!TE_VALID_OBJ(self))
        return -1;

    (void)INIT_OBJV(mCommBTreeLeafIterator, &iter, 2, pTextLayout, NULL);
    node = (mTextLayoutNode*)_c(&iter)->getCurrent(&iter);
#ifdef _MGHAVE_TEXTEDITTITLE
    if(self->title_idx > 0)
    {
        while(node && node->str_count > 0){
            str_count += node->str_count;
            if(str_count > self->title_idx)
                break;
            node = (mTextLayoutNode*)_c(&iter)->next(&iter);
        }
    }
#endif

    while (node && node->height > 0){
        //the line is valid.
        line++;

        node = (mTextLayoutNode*)_c(&iter)->next(&iter);
    };

    return line;
}

static int mTextEditor_getCaretPosByPara(mTextEditor *self, 
        int *paraIdx, int *offChar, int *height, BOOL isSel)
{
    int cursor, paraNo;

    if (!TE_VALID_OBJ(self) || !(paraIdx || offChar || height))
        return -1;

    //cursor is the text not charactors
    //cursor = _c(pTextLayout)->getCursor(pTextLayout, NULL, NULL, height, isSel); 
    //FIXEDME: add Get string cursor pos interface
    //cursor = isSel? pTextLayout->sel_begin : pTextLayout->cursor;
    cursor = isSel ? pTextLayout->text_sel_begin : pTextLayout->text_cursor;
    if (height)
        *height = isSel ? pTextLayout->sel_begin_height : pTextLayout->cursor_height;
    paraNo = _c(pTextLayout)->getParaByIndex(pTextLayout, cursor, offChar, TRUE);

#ifdef _MGHAVE_TEXTEDITTITLE
    if (self->title_idx > 0)
    {
        if(offChar && paraNo == self->title_paracount)
            *offChar -= self->title_last_para_text_index;

        paraNo -= self->title_paracount;
    }
#endif

    if (paraIdx)
        *paraIdx = paraNo;

    return cursor
#ifdef _MGHAVE_TEXTEDITTITLE
        - (self->title_text_idx > 0 ? self->title_text_idx : 0);
#endif
        ;
}

static int mTextEditor_getCaretPosByLine(mTextEditor *self, 
        int *lineIdx, int *offChar, int *height, BOOL isSel)
{
    int cursor, lineNo, i;
    int idx = 0;
    int h = 0;

    mCommBTreeLeafIterator node_it;
    mTextLayoutNode* node;

    if (!TE_VALID_OBJ(self) || !(lineIdx || offChar || height))
        return -1;

#ifdef _MGHAVE_TEXTEDITTITLE
    {
        int total = _c(self->textBuffer)->getCount(self->textBuffer);
        if (total <= self->title_idx)
        {
            *lineIdx = 0;
            *offChar = 0;
            return 0;
        }
    }
#endif

    // cursor is the text not charactors
    cursor = isSel ? pTextLayout->text_sel_begin : pTextLayout->text_cursor;
    if (height)
        *height = isSel ? pTextLayout->sel_begin_height : pTextLayout->cursor_height;
    lineNo = _c(pTextLayout)->getLineByIndex(pTextLayout, cursor, offChar, TRUE);

    texteditor_update_title_line(self);

#ifdef _MGHAVE_TEXTEDITTITLE
    if (self->title_idx > 0)
    {
        if(offChar && lineNo == self->title_linecount)
            *offChar -= self->title_last_line_text_index;
    }
#endif

    // deal with when the cursor in the autowrap line tail or line head
    if (*offChar != 0)
    {
        node = textlayout_get_first_node(pTextLayout);
        (void)initObjectArgs((mObject*)((void*)(&node_it)), 
                (mObjectClass*)((void*)(&Class(mCommBTreeLeafIterator))), 2, pTextLayout, node);

        for (i = 0; i <= lineNo; i++)
        {
            node = (mTextLayoutNode*)node_it.current;
            idx += node->text_count;
            h += node->height;
            _c(&node_it)->next(&node_it);
        }

        if(node && (idx == cursor) 
                && (pTextLayout->cursor_y >= h)) 
        {
            lineNo ++;
            *offChar = 0;
        }
    }

#ifdef _MGHAVE_TEXTEDITTITLE
    if (self->title_idx > 0)
        lineNo -= self->title_linecount;
#endif

    if (lineIdx) 
        *lineIdx = lineNo;

    return cursor
#ifdef _MGHAVE_TEXTEDITTITLE
        - (self->title_text_idx > 0 ? self->title_text_idx : 0);
#endif
        ;
}

static void mTextEditor_selectAll(mTextEditor *self)
{
    if (TE_VALID_OBJ(self)) {
        _te_set_cursor(self, 0, FALSE);
        _te_set_cursor(self, _c(pTextBuffer)->getCount(pTextBuffer), TRUE);
    }
}

static inline BOOL _needCaretWidth(mTextEditor* self)
{
    return (mTextEditor_isReplaceMode(self) &&
            pTextLayout->cursor == pTextLayout->sel_begin);
}

static void _change_caret_size(mTextEditor* self, BOOL show_caret)
{
    if(!ChangeCaretSize(self->hwnd, self->caretWidth, self->caretHeight))
    {
        //recerate one
        DestroyCaret(self->hwnd);
        CreateCaret(self->hwnd, NULL, self->caretWidth, self->caretHeight);
    }

    if (show_caret && !(self->flags & NCS_TEF_NOCARET) && (self->flags & NCS_TEF_FOCUS)) {
        ActiveCaret(self->hwnd);
        ShowCaret(self->hwnd);
    }

}   

static void _update_caret_size(mTextEditor* self)
{
    HDC hdc;
    ITextIterator* txtit = _c(pTextBuffer)->getAt(pTextBuffer, pTextLayout->cursor);
    int width, height;
    ITextRender * render = (txtit)->_vtable->getTextRender(INTEFACE_ADJUST(txtit));
    hdc = GetClientDC(self->hwnd);
    if(!(render)->_vtable->getCaretSize(INTEFACE_ADJUST(render), hdc, txtit, &width, &height)) //the text cannot show an relpace size, can only show as default
    {
        width = 1;
        height = self->caretHeight;
    }
    else if(!_needCaretWidth(self)) //cannot show as replace size
    {
        width = 1; //change width as 1
    }
    ReleaseDC(hdc);

    self->caretWidth = width;
    self->caretHeight = height;

    (txtit)->_vtable->releaseIterator(INTEFACE_ADJUST(txtit));

}



static void _make_caret_visible(mTextEditor *self, 
        int caretX, int caretY, int caretW, int caretH)
{
    int cx, cy;
    int offX, offY;
    BOOL refresh = FALSE;

    if (! mTextEditor_hasPainted(self)) {
        pTextLayout->old_height = -1; /* Fix bug #5606 */
        return;
    }

    //make cursor visible
    _c(pTextLayout)->getTextBoundSize(pTextLayout, &cx, &cy);
    offX = self->offX;
    offY = self->offY;

    if(cy >= self->visHeight) {
        if(caretY < offY || caretH >= self->visHeight)
            offY = caretY;
        else if((caretY+caretH) > (offY + self->visHeight))
            offY = caretY + caretH - self->visHeight;

        if (offY > cy - self->visHeight)
            offY = cy - self->visHeight;
    }
    else {
        offY = 0;
    }
    
    if(cx >= self->visWidth) {
        if(caretX < offX)
            offX = caretX;
        else if((caretX + 1) > (offX + self->visWidth))
            offX = caretX + 1- self->visWidth;

        if (offX > (cx - self->visWidth + 1))
            offX = cx - self->visWidth + 1;
    }
    else {
        offX = 0;
    }

    if (offX != self->offX) {
        int maxx = (cx > self->visWidth ? cx - self->visWidth : 0) + 1;
        if (offX > maxx)
            offX = maxx;

        if (offX >= 0 && offX <= maxx && offX != self->offX) {
            refresh = TRUE;
        }
    }

    if (offY != self->offY) {
        int maxy = cy > self->visHeight ? cy - self->visHeight: 0;
        if (offY > maxy)
            offY = maxy;

        if (offY >= 0 && offY <= maxy && offY != self->offY) {
            refresh = TRUE;
        }
    }

    if (refresh) {
        _scroll_invalidate_window(self, offX, offY);
        _update_height_changed(self, offX, offY);
        _update_scrolled_margins(self, offX, offY);

        self->offX = offX;
        self->offY = offY;
        
    }
    else {
        _update_height_changed(self, self->offX, self->offY);
    }

    _update_scrollbar(self);
    _update_height_changed_margins(self);
    pTextLayout->old_height = -1;
    _update_content_dirty_region(self);
}

static void _update_caret(mTextEditor *self, 
        void (*cb)(mTextEditor*, int, int,int, int))
{
    int posx, posy, height, cursor;

    //update caret size
    _update_caret_size(self);

    //make selected position visible
    cursor = _c(pTextLayout)->getCursor(pTextLayout, &posx, &posy, &height, TRUE);

    if (cb)
        cb(self, posx, posy, self->caretWidth, height);

    //get cursor position
    cursor = _c(pTextLayout)->getCursor(pTextLayout, &posx, &posy, &height, FALSE);

    if (cursor < 0)
        cursor = 0;
    {
        RECT rcView;
        posx -= self->offX;
        posy -= self->offY;
        texteditor_view_to_client(self, &posx, &posy);
        texteditor_get_view_rect(self, &rcView);
        if( posx >= rcView.left && (posx + self->caretWidth) <= rcView.right
                && posy >= rcView.top && (posy + self->caretHeight) <= rcView.bottom)
        {
            //if(self->flags & NCS_TEF_FOCUS)
            //    ShowCaret(self->hwnd);
            SetCaretPos(self->hwnd, posx, posy);
        }
        else
        {
            HideCaret(self->hwnd);
            return ;
        }

    }

    // ChangeCaretSize(self->hwnd, 1, height);
    _change_caret_size(self, TRUE);
}


static void mTextEditor_updateCaret(mTextEditor *self)
{
    if (TE_VALID_OBJ(self))
        _update_caret(self, _make_caret_visible);
}

#define EQUAL_SEL_TO_CURSOR(layout)                        \
    do {                                                   \
        layout->sel_begin_x = layout->cursor_x;            \
        layout->sel_begin_y = layout->cursor_y;            \
        layout->sel_begin_height = layout->cursor_height;  \
        layout->sel_begin = layout->cursor;                \
        layout->text_sel_begin = layout->text_cursor;      \
    } while(0)      

#ifdef _MGHAVE_TEXTEDITTITLE
#define OUT_CURSOR_FORM_TITLE(self)                           \
    do {                                                      \
        if (pTextLayout->cursor_y <= self->title_y) {         \
            pTextLayout->cursor_x = self->title_x;            \
            pTextLayout->cursor_y = self->title_y;            \
            pTextLayout->cursor = self->title_idx;            \
            pTextLayout->text_cursor = self->title_text_idx;  \
        }                                                     \
    } while(0) 
#else
#define OUT_CURSOR_FORM_TITLE(self)     
#endif

static void _update_scroll_region(mTextEditor* self, 
        int scrollVal, int newPos, int code, BOOL bHScroll)
{
    int scrollBoundMax, scrollBoundMin, scroll = 0;
    int nOffset, cx, cy;
    BOOL bScroll = FALSE;

    _c(pTextLayout)->getTextBoundSize(pTextLayout, &cx, &cy);
    
    if (bHScroll) {
        scrollBoundMax = cx - self->visWidth;
        nOffset = self->offX;
    }
    else {
        scrollBoundMax = cy - self->visHeight;
        nOffset = self->offY;
    }
    scrollBoundMin = 0;

    if (newPos >= 0) { 
        scrollVal = newPos - nOffset;
    }

    scroll = (scrollVal > 0 ? scrollVal : -scrollVal);

    if (scrollVal > 0 && nOffset < scrollBoundMax) {
        if ((nOffset + scroll) > scrollBoundMax)
            nOffset = scrollBoundMax;
        else
            nOffset += scroll;
        bScroll = TRUE;
    }
    else if ( scrollVal < 0 && nOffset > scrollBoundMin) {
        if ((nOffset - scroll) < scrollBoundMin)
            nOffset = scrollBoundMin;
        else
            nOffset -= scroll;
        bScroll = TRUE;
    }

    if (bScroll) {
        if (bHScroll) {
            int maxContX = cx - self->visWidth;
            int x = nOffset > maxContX ? maxContX: nOffset;
            if (x >= 0 && self->offX != x) {
                SetScrollPos (self->hwnd, SB_HORZ, x);

                _scroll_invalidate_window(self, x, self->offY);
                _update_height_changed(self, x, self->offY);
                _update_scrolled_margins(self, x, self->offY);
           
                self->offX = x;
                _update_caret(self, NULL);
                
                _update_height_changed_margins(self);
                pTextLayout->old_height = -1;
                _update_content_dirty_region(self);
            }
        }
        else {
            int maxContY = cy - self->visHeight;
            int y = nOffset > maxContY ? maxContY: nOffset;
            if( self->offY != y)
            {
                SetScrollPos (self->hwnd, SB_VERT, y);

                _scroll_invalidate_window(self, self->offX, y);
                _update_height_changed(self, self->offX, y);
                
                _update_scrolled_margins(self, self->offX, y);

                self->offY = y;

                // PAGEUP or PAGEDOWN will move the cursor
                if (TE_IS_CURSORE_PAGE_MOVE(self) && (code == SB_PAGEUP || code == SB_PAGEDOWN)) {
                    pTextLayout->cursor_y += scrollVal; 
                    pTextLayout->text_cursor = _c(pTextLayout)->getCharIndex(pTextLayout, &pTextLayout->cursor_x, 
                            &pTextLayout->cursor_y, &pTextLayout->cursor_height, &pTextLayout->cursor);

                    // cursor can't move to title
                    OUT_CURSOR_FORM_TITLE(self);
                    
                    // move cursor, so cancel selection
                    EQUAL_SEL_TO_CURSOR(pTextLayout);
                    _update_caret(self, _make_caret_visible);
                }
                else {
                    _update_caret(self, NULL);
                    _update_height_changed_margins(self);
                    pTextLayout->old_height = -1;
                    _update_content_dirty_region(self);
                }
            }
        }
    }
    else {
        if ((!bHScroll) && (code == SB_PAGEUP || code == SB_PAGEDOWN)) {
                pTextLayout->cursor_y += scrollVal; 
                pTextLayout->text_cursor = _c(pTextLayout)->getCharIndex(pTextLayout, &pTextLayout->cursor_x, 
                        &pTextLayout->cursor_y, &pTextLayout->cursor_height, &pTextLayout->cursor);
                OUT_CURSOR_FORM_TITLE(self);
                EQUAL_SEL_TO_CURSOR(pTextLayout);
                _update_caret(self, _make_caret_visible);
        }
    }

    TE_CLEAR_CUSROR_PAGE_MOVE(self);
}

static void mTextEditor_onHScroll(mTextEditor *self, int code, int mouseX)
{
    int hScroll = 0, newPos = -1;

    if (!TE_VALID_OBJ(self))
        return;

    switch (code) {
        case SB_LINERIGHT:
            hScroll = self->scrollHStep;
            break;
        case SB_LINELEFT:
            hScroll = -self->scrollHStep;
            break;
        case SB_PAGERIGHT:
            hScroll = self->visWidth - self->scrollHStep;
            break;
        case SB_PAGELEFT:
            hScroll = -(self->visWidth - self->scrollHStep);
            break;
        case SB_THUMBTRACK:
        case SB_THUMBPOSITION:
            newPos = mouseX;
            break;
    }
    
    _update_scroll_region(self, hScroll, newPos, code, TRUE);
}

static void mTextEditor_onVScroll(mTextEditor *self, int code, int mouseY)
{
    int hScroll = 0, newPos = -1;

    if (!TE_VALID_OBJ(self))
        return;

    switch (code) {
        case SB_LINEDOWN:
            hScroll = self->scrollVStep;
            break;
        case SB_LINEUP:
            hScroll = -self->scrollVStep;
            break;
        case SB_PAGEDOWN:
            hScroll = self->visHeight - self->scrollVStep;
            break;
        case SB_PAGEUP:
            hScroll = -(self->visHeight - self->scrollVStep);
            break;
        case SB_THUMBTRACK:
        case SB_THUMBPOSITION:
            newPos = mouseY;
            break;
    }
    
    _update_scroll_region(self, hScroll, newPos, code, FALSE);
}

static void mTextEditor_lineDecorative(mTextEditor* self, int x, int y, int w, int h, int lineNo, HDC hdc)
{
    RECT rcview;
#ifdef _MGHAVE_TEXTEDITTITLE
    //TODO FIXEDME lineNo would be not correct if we set the title
    //printf("--linedecorative x=%d, y=%d, title:(x=%d,y=%d, height=%d, idx=%d, text_idx=%d\n", x, y, self->title_x, self->title_y, self->title_last_height, self->title_idx, self->title_text_idx);
    if( y < self->title_y)
        return;
    else if( y >= self->title_y && y < (self->title_y + self->title_last_height))
    {
        x = self->title_x;
        x -= self->offX;
        if(x < 0) x = 0;
    }
    else
        x = 0;
    //printf("--linedecorative x=%d\n", x);
#else
    x = 0;
#endif
    texteditor_get_view_rect(self, &rcview);
    y = y - self->offY + self->margins.top + h - 1;
    //printf("---- print at y=%d<self->offY=%d, self->marigns.top=%d, h=%d, self->visHeight=%d>\n", y, self->offY, self->margins.top, h,self->visHeight);
    if(y > rcview.bottom || y < rcview.top)
        return ;
    //printf("-- lineNo=%d\n", lineNo);
    //TODO hear, draw some information, e.g line No 
#if MARGIN_UPDATE 
    //implement it if need
    if(lineNo >= 0)
    {
        char szbuff[20];
        RECT rcLineNo;
        sprintf(szbuff, "%d", lineNo+1);
        rcLineNo.left = 0;
        rcLineNo.right = self->margins.left - 1;
        rcLineNo.top  = y - h;
        rcLineNo.bottom = y;
        DrawText(hdc, szbuff, -1, &rcLineNo, DT_RIGHT | DT_BOTTOM | DT_SINGLELINE);

    }
#endif

#if 1
    //TODO : use the user defined handler to draw the lineDecorative
    /*
     if ( self->userLineDecorative )
        self->userLineDecorative( hdc, self->margins.left, y, self->visWidth - 1)
    else
     */
    DrawHDotLine(hdc, self->margins.left + x + (self->offX % 2) ,
            y,
            self->visWidth - x);  
#else
    LineEx(hdc, self->margins.left + x, y, self->margins.left + self->visWidth, y);
#endif
}

static BOOL mTextEditor_needLineDecorative(mTextEditor* self)
{
    return GetWindowStyle(self->hwnd) & NCSS_TE_BASELINE;
}

#ifdef __MINIGUI_LIB__
void TextEditorDrawLinesDecorative(mTextEditor * self, HDC hdc, int x, int y, int w, int h)
#else
void mTextEditor_drawLinesDecorative(mTextEditor * self, HDC hdc, int x, int y, int w, int h)
#endif
{
    mTextLayout * txtLayout = self->textLayout;

    if(!_c(self)->needLineDecorative(self))
        return ;

    if(w <= 0 || h <= 0)
    {
        w = self->visWidth;
        h = self->visHeight;
    }

    _c(txtLayout)->foreachLine(txtLayout, 0, y, w, h, (CB_LINE_PROC)_c(self)->lineDecorative, (mObject*)self, (void*)hdc);

}

static int mTextEditor_wndProc(mTextEditor *self, int message, WPARAM wParam, LPARAM lParam)
{
    switch (message) {
        case MSG_GETTEXTLENGTH:
            return _c(self)->getTextLen(self);

        case MSG_GETTEXT:
            //lParam:string; wParam:len
            return _c(self)->getText(self, (char*)lParam, wParam);

        case MSG_SETTEXT:
            //lParam:string; wParam:meaningless
            return _c(self)->setText(self, (const char*)lParam);

        case MSG_SETCURSOR:
            if (GetWindowStyle(self->hwnd) & WS_DISABLED) {
                SetCursor (GetSystemCursor (IDC_ARROW));
                return 0;
            }
            break;

        case MSG_SETFOCUS:
            _c(self)->onSetFocus(self, (HWND)wParam, lParam);
            break;

        case MSG_KILLFOCUS:
            _c(self)->onKillFocus(self, (HWND)wParam, lParam);
            break;

        case MSG_DOESNEEDIME:
            if ((GetWindowStyle(self->hwnd) & NCSS_TE_READONLY) || (GetWindowExStyle(self->hwnd)&NCSS_EX_UNNEEDIME))
                return FALSE;
            return TRUE;

        case MSG_LBUTTONDBLCLK:
            _c(self)->onLButtonDBClk(self, LOSWORD(lParam), HISWORD(lParam), wParam);
            break;

        case MSG_SIZECHANGED:
            {
                RECT* rc = (RECT*)lParam;
                if (rc)
                    _c(self)->onCSizeChanged(self, RECTWP(rc), RECTHP(rc));
            }
            return 0;
#if 0
        case MSG_CSIZECHANGED:
            _c(self)->onCSizeChanged(self, wParam, lParam);
            return 0;
#endif

        case MSG_CHAR:
            return _c(self)->onChar(self, wParam, lParam);

        case MSG_UTF8CHAR:
            _ERR_PRINTF ("CONTROL>EDIT: MSG_UTF8CHAR is not implemented.\n");
            break;

        case MSG_FONTCHANGED:
            _c(self)->onFontChanged(self);
            return 0;

        case MSG_HSCROLL:
            _c(self)->onHScroll(self, wParam, lParam);
            break;

        case MSG_VSCROLL:
            _c(self)->onVScroll(self, wParam, lParam);
            break;

    }


    return Class(mWidget).wndProc((mWidget*)self, message, wParam, lParam);
}

#ifdef _MGHAVE_TEXTEDITTITLE
static void mTextEditor_setTitle(mTextEditor* self, const char* buff, int len)
{
    //repalce
    int slen ;

    if(!(GetWindowStyle(self->hwnd) & NCSS_TE_TITLE))
        return ;

    slen = buff ? strlen(buff) : -1;
    if(len <= 0 || len > slen)
        len = slen;
    if(len <= 0)
        len = -1;

    _c(pTextBuffer)->replace(pTextBuffer, 0, self->title_idx, buff, len);
    if(buff == NULL || len <= 0)
    {
        self->title_idx = -1;

    }
    else{
        self->title_idx = len;
    } 

    texteditor_update_title(self);

}

static int mTextEditor_getTitle(mTextEditor* self, char* buff, int max)
{
    if(GetWindowStyle(self->hwnd) & NCSS_TE_TITLE)
        return _c(pTextBuffer)->getText(pTextBuffer, 0, buff, self->title_idx > max ? max : self->title_idx); 
    else
    {
        if(buff)
            buff[0] = 0;
        return 0;
    }
}
#endif

#if defined(__MGNCS_LIB__) && defined(_MGNCS_GUIBUILDER_SUPPORT)
static BOOL mTextEditor_refresh(mTextEditor* self)
{
    DWORD dwStyle = GetWindowStyle(self->hwnd);
    int   scrollmode;
    if(dwStyle & NCSS_TE_UPPERCASE)
        _c(self->textBuffer)->setCase(self->textBuffer, 0,-1, TRUE);
    else if(dwStyle & NCSS_TE_LOWERCASE)
        _c(self->textBuffer)->setCase(self->textBuffer, 0, -1, FALSE);

    //reset the scrollmode
    scrollmode = self->scrollBarMode;
    self->scrollBarMode = -1;
    _c(self)->setProperty(self, NCSP_TE_SCROLL_MODE, scrollmode);

    if(dwStyle &(NCSS_TE_LOWERCASE|NCSS_TE_LOWERCASE))
        InvalidateRect(self->hwnd, NULL, TRUE);

    return TRUE;
}
#endif

BEGIN_CMPT_CLASS(mTextEditor, mWidget)
    IMPLEMENT(mTextEditor, ITextLayoutContext)
    CLASS_METHOD_MAP(mTextEditor, construct)
    CLASS_METHOD_MAP(mTextEditor, destroy)
    CLASS_METHOD_MAP(mTextEditor, wndProc)
    CLASS_METHOD_MAP(mTextEditor, onCreate)
    CLASS_METHOD_MAP(mTextEditor, setProperty)
    CLASS_METHOD_MAP(mTextEditor, getProperty)
    CLASS_METHOD_MAP(mTextEditor, getMetrics)
    CLASS_METHOD_MAP(mTextEditor, beginSelection)
    CLASS_METHOD_MAP(mTextEditor, endSelection)
    CLASS_METHOD_MAP(mTextEditor, setupDC)
    CLASS_METHOD_MAP(mTextEditor, getDC)
    CLASS_METHOD_MAP(mTextEditor, releaseDC)
    CLASS_METHOD_MAP(mTextEditor, contentPosToDCPos)
    CLASS_METHOD_MAP(mTextEditor, update)
    CLASS_METHOD_MAP(mTextEditor, updateCaret)
    
    //message
    CLASS_METHOD_MAP(mTextEditor, onChar)
    CLASS_METHOD_MAP(mTextEditor, onKeyDown)
    CLASS_METHOD_MAP(mTextEditor, onSetFocus)
    CLASS_METHOD_MAP(mTextEditor, onKillFocus)
    CLASS_METHOD_MAP(mTextEditor, onPaint)
    CLASS_METHOD_MAP(mTextEditor, onEraseBkgnd)
    CLASS_METHOD_MAP(mTextEditor, getInvalidBkgnd)
    CLASS_METHOD_MAP(mTextEditor, onFontChanged)
    CLASS_METHOD_MAP(mTextEditor, onLButtonDown)
    CLASS_METHOD_MAP(mTextEditor, onLButtonUp)
    CLASS_METHOD_MAP(mTextEditor, onLButtonDBClk)
    CLASS_METHOD_MAP(mTextEditor, onMouseMove)
    CLASS_METHOD_MAP(mTextEditor, onCSizeChanged)
    CLASS_METHOD_MAP(mTextEditor, onVScroll)
    CLASS_METHOD_MAP(mTextEditor, onHScroll)
    CLASS_METHOD_MAP(mTextEditor, setCaretPosByLine)
    CLASS_METHOD_MAP(mTextEditor, setCaretPosByPara)
    CLASS_METHOD_MAP(mTextEditor, getCaretPosByPara)
    CLASS_METHOD_MAP(mTextEditor, getCaretPosByLine)
    //CLASS_METHOD_MAP(mTextEditor, drawBkgnd)
    CLASS_METHOD_MAP(mTextEditor, getSel)
    CLASS_METHOD_MAP(mTextEditor, setSel)
    SET_DLGCODE(DLGC_WANTCHARS | DLGC_HASSETSEL | DLGC_WANTARROWS | DLGC_WANTENTER);

    CLASS_METHOD_MAP(mTextEditor, setText)
    CLASS_METHOD_MAP(mTextEditor, getText)
    CLASS_METHOD_MAP(mTextEditor, getTextLen)
    CLASS_METHOD_MAP(mTextEditor, insertText)
    CLASS_METHOD_MAP(mTextEditor, getParaText)
    CLASS_METHOD_MAP(mTextEditor, getLineText)
    CLASS_METHOD_MAP(mTextEditor, getParaLength)
    CLASS_METHOD_MAP(mTextEditor, getParaLengthInMChar)
    CLASS_METHOD_MAP(mTextEditor, getLineLength)
    CLASS_METHOD_MAP(mTextEditor, getParaNum)
    CLASS_METHOD_MAP(mTextEditor, enableCaret)
    CLASS_METHOD_MAP(mTextEditor, getLineCount)
    CLASS_METHOD_MAP(mTextEditor, selectAll)
    CLASS_METHOD_MAP(mTextEditor, needLineDecorative)
    CLASS_METHOD_MAP(mTextEditor, lineDecorative)

#ifdef _MGHAVE_TEXTEDITTITLE
    CLASS_METHOD_MAP(mTextEditor, setTitle)
    CLASS_METHOD_MAP(mTextEditor, getTitle)
#endif

#if defined(__MGNCS_LIB__) && defined(_MGNCS_GUIBUILDER_SUPPORT)
    CLASS_METHOD_MAP(mTextEditor, refresh)
#endif
END_CMPT_CLASS

///////////////////////////////////////////
#ifdef __MINIGUI_LIB__
static void init_textedit(void)
{
    MGNCS_INIT_CLASS(mObject);
    MGNCS_INIT_CLASS(mWidget);
    MGNCS_INIT_CLASS(mCommBTreeNode);
    MGNCS_INIT_CLASS(mCommBTree);
    MGNCS_INIT_CLASS(mCommBTreeLeafIterator);
    MGNCS_INIT_CLASS(mTextBuffer);
    MGNCS_INIT_CLASS(mTextIterator);
    MGNCS_INIT_CLASS(mTextLayout);
    MGNCS_INIT_CLASS(mTextLayoutNode);
    MGNCS_INIT_CLASS(mTextRender);

    MGNCS_INIT_CLASS(mTextEditor);
}

/*======== The following is textedit control implementation. =========*/
static LRESULT mTextEditCtrlProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    mTextEditor *self = NULL;

    if (message != MSG_CREATE)
        self = (mTextEditor*) GetWindowAdditionalData2 (hWnd);

    switch(message)
    {
        case MSG_CREATE:
            if (!(self = NEWEX(mTextEditor, (DWORD)hWnd)))
                return -1;

            SetWindowAdditionalData2 (hWnd, (DWORD)self);
            break;

        case MSG_DESTROY:
            if (self)
                DELETE(self);
            SetWindowAdditionalData2 (hWnd, 0);
            return 0;

        case EM_INSERTTEXT:
            //wParam:len; lParam:string
            return _c(self)->insertText(self, (const char*)lParam, wParam);

        case EM_SETMARGINS:
            {
                return _c(self)->setProperty(self, NCSP_TE_MARGINRECT, (DWORD)lParam);
            }
        case EM_GETMARGINS:
            {
                RECT *prcMargins = (RECT*)lParam;
                if(!prcMargins)
                    return 0;
                *prcMargins = self->margins;
                return 0;
            }

        case TEM_GETBKGNDINVALID:
            return _c(self)->getInvalidBkgnd(self, (RECT*)lParam);


        case TEM_RESETCONTENT:
            return _c(self)->setText(self, "");

        case EM_GETLINECOUNT:
            return _c(self)->getLineCount(self);

        case EM_SETREADONLY:
            return _c(self)->setProperty(self, NCSP_TE_READONLY, wParam);

        case EM_INSERTCBTEXT:
        {
            if (GetWindowStyle(hWnd) & NCSS_TE_READONLY)
                return 0;
            return _c(self)->onKeyDown(self, SCANCODE_V, KS_CTRL);
        }
        case EM_COPYTOCB:
        case EM_CUTTOCB:
            return _c(self)->onKeyDown(self, message == EM_CUTTOCB ? SCANCODE_X : SCANCODE_C, KS_CTRL);

        case EM_GETSEL:
        {
            char *buffer = (char*)lParam;
            int len = (int)wParam;
            return _c(self)->getSel(self, buffer, len);
        }

        case EM_GETSELPOS:
        case EM_GETCARETPOS:
            return _c(self)->getCaretPosByPara(self, (int*)wParam, (int*)lParam, 
                    NULL, message == EM_GETSELPOS);

        case EM_SETCARETPOS:
        case EM_SETSELPOS:
            return _c(self)->setCaretPosByPara(self, wParam, lParam, (EM_SETSELPOS == message));

        case EM_SETCARETPOSBYLINE:
        case EM_SETSELPOSBYLINE:
            return _c(self)->setCaretPosByLine(self, wParam, lParam, (EM_SETSELPOSBYLINE == message));

        case EM_GETCARETPOSBYLINE:
        case EM_GETSELPOSBYLINE:
            return _c(self)->getCaretPosByLine(self, (int*)wParam, (int*)lParam, 
                    NULL, message == EM_GETSELPOSBYLINE);

        case EM_REFRESHCARET:
            SetCaretPos(self->hwnd, pTextLayout->cursor_x, pTextLayout->cursor_y);
            return 0;

        case EM_LIMITTEXT:
            return _c(self)->setProperty(self, NCSP_TE_TEXTLIMIT, wParam) ? 0 : -1;

        case EM_GETLIMITTEXT:
            return self->hardLimit;

        case EM_GETLINEHEIGHT:
            return _c(pTextLayout)->getLineHeight(pTextLayout, 0);

        case EM_GETTITLETEXT:
#ifdef _MGHAVE_TEXTEDITTITLE
            return _c(self)->getTitle(self, (char*)lParam, (int)wParam);
#endif

        case EM_SETTITLETEXT:
#ifdef _MGHAVE_TEXTEDITTITLE
            _c(self)->setTitle(self, (const char*)lParam, (int)wParam);
#endif
            return 0;

        case EM_CHANGECARETSHAPE:
            if(wParam == ED_CARETSHAPE_LINE && mTextEditor_isReplaceMode(self))
            {
                self->flags &= ~NCS_TEF_REPLACE_MODE;
                _update_caret_size(self);
            }
            else if(wParam == ED_CARETSHAPE_BLOCK && !mTextEditor_isReplaceMode(self))
            {
                self->flags |= NCS_TEF_REPLACE_MODE;
                _update_caret_size(self);
            }
            return 0;

        case EM_ENABLECARET:
            _c(self)->enableCaret(self, wParam);
            return 0;

        case EM_GETNUMOFPARAGRAPHS:
            return _c(self)->getParaNum(self); 

        case EM_GETPARAGRAPHTEXT:
        {
            //wParam: TEXTPOSINFO
            TEXTPOSINFO *info = (TEXTPOSINFO*)wParam;
            if (info)
                return _c(self)->getParaText(self, info->paragraph_index,
                        info->start_pos, info->copy_len, info->buff);
            return 0;
        }

        case EM_GETLINETEXT:
        {
            //wParam: TEXTPOSINFO
            TEXTPOSINFO *info = (TEXTPOSINFO*)wParam;
            if (info)
                return _c(self)->getLineText(self, info->line_index,
                        info->start_pos, info->copy_len, info->buff);
            return 0;
        }

        case EM_GETPARAGRAPHLENGTH:
            //wParam:paragraph index
            return _c(self)->getParaLength(self, wParam, NULL);

        case EM_GETPARAGRAPHLENGTHINMCHAR:
            //wParam:paragraph index
            return _c(self)->getParaLengthInMChar(self, wParam, NULL);

        case EM_GETLINELENGTH:
        case EM_GETLINELENGTHINMCHAR:
            //wParam:line index
            return _c(self)->getLineLength(self, wParam, NULL, message == EM_GETLINELENGTHINMCHAR);

        case EM_SELECTALL:
            _c(self)->selectAll(self);
            return 0;

        //TODO: unsupport message
        case EM_SETLINEHEIGHT:
        case EM_SETLFDISPCHAR:
        case EM_SETLINESEP:
        case EM_SETDRAWSELECTFUNC:
        case EM_SETGETCARETWIDTHFUNC:
            break;

        case EM_UNDO:
        case EM_REDO:
            return 0;
    }

    if(self)
        return _c(self)->wndProc(self, message, wParam, lParam);
    else
        return DefaultControlProc (hWnd, message, wParam, lParam);
}

extern int AddNewControlClass (PWNDCLASS pWndClass);
BOOL RegisterTextEditControl (void)
{
    WNDCLASS WndClass;

    WndClass.spClassName = CTRL_TEXTEDIT;
    WndClass.dwStyle     = WS_NONE;
    WndClass.dwExStyle   = WS_EX_NONE;
    WndClass.hCursor     = GetSystemCursor (IDC_IBEAM);
    WndClass.iBkColor    = GetWindowElementPixel (HWND_NULL, WE_BGC_WINDOW);
    WndClass.WinProc     = mTextEditCtrlProc;

    init_textedit();

    if (AddNewControlClass (&WndClass) != ERR_OK)
        return FALSE;

    WndClass.spClassName = CTRL_MLEDIT;

    if (AddNewControlClass (&WndClass) != ERR_OK)
        return FALSE;

    WndClass.spClassName = CTRL_MEDIT;

    return AddNewControlClass (&WndClass) == ERR_OK;
}
#endif /* __MINIGUI_LIB__ */
#endif /* defined(_MGCTRL_TEXTEDIT_USE_NEW_IMPL) || defined(_MGNCS_TEXTEDITOR) */
