/* * 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 . * * 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 * . */ /* ** textedit.c: the new textedit module. */ #include #include #include #include #include #ifdef __MINIGUI_LIB__ #include "common.h" #include "minigui.h" #include "gdi.h" #include "window.h" #include "object.h" #elif defined (__MGNCS_LIB__) #include #include #include #include #include #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, ©_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, ©_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, ¶No, &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, ¶Idx, &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=%doffY=%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) */