I forgot to plug a region leak before I sent the patch in. I have attached an updated patch.
/Ulrich
Ulrich Czekalla wrote:
This patch modifies the edit control to keep the line format data and simply update it as changes occur. It also uses the line data to calculate a proper update region. The result is a significant reduction in flicker. Since this is a big change to the format engine of the edit control it should be tested for a while before committing.
Changelog: Ulrich Czekalla uczekalla@codeweavers.com Update format engine to reduce flicker
Index: controls/edit.c =================================================================== RCS file: /home/wine/wine/controls/edit.c,v retrieving revision 1.75 diff -u -w -r1.75 edit.c --- controls/edit.c 2001/03/13 23:31:08 1.75 +++ controls/edit.c 2001/03/29 17:31:36 @@ -63,6 +63,7 @@ INT net_length; /* netto length of a line in visible characters */ LINE_END ending; INT width; /* width of the line in pixels */ + INT index; /* line index into the buffer */ struct tagLINEDEF *next; } LINEDEF;
@@ -172,7 +173,7 @@ /* * Helper functions only valid for one type of control */ -static void EDIT_BuildLineDefs_ML(WND *wnd, EDITSTATE *es); +static void EDIT_BuildLineDefs_ML(WND *wnd, EDITSTATE *es, INT iStart, INT iEnd, INT delta, HRGN hrgn); static void EDIT_CalcLineWidth_SL(WND *wnd, EDITSTATE *es); static LPWSTR EDIT_GetPasswordPointer_SL(EDITSTATE *es); static void EDIT_MoveDown_ML(WND *wnd, EDITSTATE *es, BOOL extend); @@ -263,6 +264,7 @@ static void EDIT_WM_Timer(WND *wnd, EDITSTATE *es); static LRESULT EDIT_WM_VScroll(WND *wnd, EDITSTATE *es, INT action, INT pos); static void EDIT_UpdateText(WND *wnd, LPRECT rc, BOOL bErase); +static void EDIT_UpdateTextRegion(WND *wnd, HRGN hrgn, BOOL bErase);
LRESULT WINAPI EditWndProcA(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); LRESULT WINAPI EditWndProcW(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); @@ -1132,100 +1134,250 @@ * a soft return '\r\r\n' or a hard return '\r\n' * */ -static void EDIT_BuildLineDefs_ML(WND *wnd, EDITSTATE *es) +static void EDIT_BuildLineDefs_ML(WND *wnd, EDITSTATE *es, INT istart, INT iend, INT delta, HRGN hrgn) { HDC dc; HFONT old_font = 0; - LPWSTR start, cp; + LPWSTR current_position, cp; INT fw; - LINEDEF *current_def; - LINEDEF **previous_next; + LINEDEF *current_line; + LINEDEF *previous_line; + LINEDEF *start_line; + INT line_index = 0, nstart_line = 0, nstart_index = 0; + INT line_count = es->line_count; + INT orig_net_length; + RECT rc;
- current_def = es->first_line_def; - do { - LINEDEF *next_def = current_def->next; - HeapFree(GetProcessHeap(), 0, current_def); - current_def = next_def; - } while (current_def); - es->line_count = 0; - es->text_width = 0; + if (istart == iend && delta == 0) + return;
dc = GetDC(wnd->hwndSelf); if (es->font) old_font = SelectObject(dc, es->font);
+ previous_line = NULL; + current_line = es->first_line_def; + + /* Find starting line. istart must lie inside an existing line or + * at the end of buffer */ + do { + if (istart < current_line->index + current_line->length || + current_line->ending == END_0) + break; + + previous_line = current_line; + current_line = current_line->next; + line_index++; + } while (current_line); + + if (!current_line) /* Error occurred start is not inside previous buffer */ + { + FIXME(" modification occurred outside buffer\n"); + return; + } + + /* Remember start of modifications in order to calculate update region */ + nstart_line = line_index; + nstart_index = current_line->index; + + /* We must start to reformat from the previous line since the modifications + * may have caused the line to wrap upwards. */ + if (!(es->style & ES_AUTOHSCROLL) && line_index > 0) + { + line_index--; + current_line = previous_line; + } + start_line = current_line; + fw = es->format_rect.right - es->format_rect.left; - start = es->text; - previous_next = &es->first_line_def; + current_position = es->text + current_line->index; do { - current_def = HeapAlloc(GetProcessHeap(), 0, sizeof(LINEDEF)); - current_def->next = NULL; - cp = start; + if (current_line != start_line) + { + if (!current_line || current_line->index + delta > current_position - es->text) + { + /* The buffer has been expanded, create a new line and + insert it into the link list */ + LINEDEF *new_line = HeapAlloc(GetProcessHeap(), 0, sizeof(LINEDEF)); + new_line->next = previous_line->next; + previous_line->next = new_line; + current_line = new_line; + es->line_count++; + } + else if (current_line->index + delta < current_position - es->text) + { + /* The previous line merged with this line so we delete this extra entry */ + previous_line->next = current_line->next; + HeapFree(GetProcessHeap(), 0, current_line); + current_line = previous_line->next; + es->line_count--; + continue; + } + else /* current_line->index + delta == current_position */ + { + if (current_position - es->text > iend) + break; /* We reached end of line modifications */ + /* else recalulate this line */ + } + } + + current_line->index = current_position - es->text; + orig_net_length = current_line->net_length; + + /* Find end of line */ + cp = current_position; while (*cp) { if ((*cp == '\r') && (*(cp + 1) == '\n')) break; cp++; } + + /* Mark type of line termination */ if (!(*cp)) { - current_def->ending = END_0; - current_def->net_length = strlenW(start); - } else if ((cp > start) && (*(cp - 1) == '\r')) { - current_def->ending = END_SOFT; - current_def->net_length = cp - start - 1; + current_line->ending = END_0; + current_line->net_length = strlenW(current_position); + } else if ((cp > current_position) && (*(cp - 1) == '\r')) { + current_line->ending = END_SOFT; + current_line->net_length = cp - current_position - 1; } else { - current_def->ending = END_HARD; - current_def->net_length = cp - start; + current_line->ending = END_HARD; + current_line->net_length = cp - current_position; } - current_def->width = (INT)LOWORD(GetTabbedTextExtentW(dc, - start, current_def->net_length, + + /* Calculate line width */ + current_line->width = (INT)LOWORD(GetTabbedTextExtentW(dc, + current_position, current_line->net_length, es->tabs_count, es->tabs)); + /* FIXME: check here for lines that are too wide even in AUTOHSCROLL (> 32767 ???) */ - if ((!(es->style & ES_AUTOHSCROLL)) && (current_def->width > fw)) { + if ((!(es->style & ES_AUTOHSCROLL)) && (current_line->width > fw)) { INT next = 0; INT prev; do { prev = next; - next = EDIT_CallWordBreakProc(es, start - es->text, - prev + 1, current_def->net_length, WB_RIGHT); - current_def->width = (INT)LOWORD(GetTabbedTextExtentW(dc, - start, next, es->tabs_count, es->tabs)); - } while (current_def->width <= fw); - if (!prev) { + next = EDIT_CallWordBreakProc(es, current_position - es->text, + prev + 1, current_line->net_length, WB_RIGHT); + current_line->width = (INT)LOWORD(GetTabbedTextExtentW(dc, + current_position, next, es->tabs_count, es->tabs)); + } while (current_line->width <= fw); + if (!prev) { /* Didn't find a line break so force a break */ next = 0; do { prev = next; next++; - current_def->width = (INT)LOWORD(GetTabbedTextExtentW(dc, - start, next, es->tabs_count, es->tabs)); - } while (current_def->width <= fw); + current_line->width = (INT)LOWORD(GetTabbedTextExtentW(dc, + current_position, next, es->tabs_count, es->tabs)); + } while (current_line->width <= fw); if (!prev) prev = 1; } - current_def->net_length = prev; - current_def->ending = END_WRAP; - current_def->width = (INT)LOWORD(GetTabbedTextExtentW(dc, start, - current_def->net_length, es->tabs_count, es->tabs)); + + /* If the first line we are calculating, wrapped before istart, we must + * adjust istart in order for this to be reflected in the update region. */ + if (current_line->index == nstart_index && istart > current_line->index + prev) + istart = current_line->index + prev; + /* else if we are updating the previous line before the first line we + * are re-caulculating and it expanded */ + else if (current_line == start_line && + current_line->index != nstart_index && orig_net_length < prev) + { + /* Line expanded due to an upwards line wrap so we must partially include + * previous line in update region */ + nstart_line = line_index; + nstart_index = current_line->index; + istart = current_line->index + orig_net_length; + } + + current_line->net_length = prev; + current_line->ending = END_WRAP; + current_line->width = (INT)LOWORD(GetTabbedTextExtentW(dc, current_position, + current_line->net_length, es->tabs_count, es->tabs)); } - switch (current_def->ending) { + + + /* Adjust length to include line termination */ + switch (current_line->ending) { case END_SOFT: - current_def->length = current_def->net_length + 3; + current_line->length = current_line->net_length + 3; break; case END_HARD: - current_def->length = current_def->net_length + 2; + current_line->length = current_line->net_length + 2; break; case END_WRAP: case END_0: - current_def->length = current_def->net_length; + current_line->length = current_line->net_length; break; } - es->text_width = max(es->text_width, current_def->width); - start += current_def->length; - *previous_next = current_def; - previous_next = ¤t_def->next; - es->line_count++; - } while (current_def->ending != END_0); + es->text_width = max(es->text_width, current_line->width); + current_position += current_line->length; + previous_line = current_line; + current_line = current_line->next; + line_index++; + } while (previous_line->ending != END_0); + + /* Finish adjusting line index's by delta or remove hanging lines */ + if (previous_line->ending == END_0) + { + LINEDEF *pnext = NULL; + + previous_line->next = NULL; + while (current_line) + { + pnext = current_line->next; + HeapFree(GetProcessHeap(), 0, current_line); + current_line = pnext; + es->line_count--; + } + } + else + { + while (current_line) + { + current_line->index += delta; + current_line = current_line->next; + } + } + + /* Calculate rest of modification rectangle */ + if (hrgn) + { + HRGN tmphrgn; + /* + * We calculate two rectangles. One for the first line which may have + * an indent with respect to the format rect. The other is a format-width + * rectangle that spans the rest of the lines that changed or moved. + */ + rc.top = es->format_rect.top + nstart_line * es->line_height - + (es->y_offset * es->line_height); /* Adjust for vertical scrollbar */ + rc.bottom = rc.top + es->line_height; + rc.left = es->format_rect.left + (INT)LOWORD(GetTabbedTextExtentW(dc, + es->text + nstart_index, istart - nstart_index, + es->tabs_count, es->tabs)) - es->x_offset; /* Adjust for horz scroll */ + rc.right = es->format_rect.right; + SetRectRgn(hrgn, rc.left, rc.top, rc.right, rc.bottom); + + rc.top = rc.bottom; + rc.left = es->format_rect.left; + rc.right = es->format_rect.right; + /* + * If lines were added or removed we must re-paint the remainder of the + * lines since the remaining lines were either shifted up or down. + */ + if (line_count < es->line_count) /* We added lines */ + rc.bottom = es->line_count * es->line_height; + else if (line_count > es->line_count) /* We removed lines */ + rc.bottom = line_count * es->line_height; + else + rc.bottom = line_index * es->line_height; + rc.bottom -= (es->y_offset * es->line_height); /* Adjust for vertical scrollbar */ + tmphrgn = CreateRectRgn(rc.left, rc.top, rc.right, rc.bottom); + CombineRgn(hrgn, hrgn, tmphrgn, RGN_OR); + DeleteObject(tmphrgn); + } + if (es->font) SelectObject(dc, old_font); + ReleaseDC(wnd->hwndSelf, dc); }
@@ -2140,7 +2292,7 @@ es->format_rect.bottom = es->format_rect.top + es->line_height;
if ((es->style & ES_MULTILINE) && !(es->style & ES_AUTOHSCROLL)) - EDIT_BuildLineDefs_ML(wnd, es); + EDIT_BuildLineDefs_ML(wnd, es, 0, strlenW(es->text), 0, (HRGN)0); }
@@ -2836,6 +2988,7 @@ UINT e; UINT i; LPWSTR p; + HRGN hrgn = 0;
TRACE("%s, can_undo %d, send_update %d\n", debugstr_w(lpsz_replace), can_undo, send_update); @@ -2916,9 +3069,14 @@ CharLowerBuffW(p, strl); s += strl; } - /* FIXME: really inefficient */ if (es->style & ES_MULTILINE) - EDIT_BuildLineDefs_ML(wnd, es); + { + INT s = min(es->selection_start, es->selection_end); + + hrgn = CreateRectRgn(0, 0, 0, 0); + EDIT_BuildLineDefs_ML(wnd, es, s, s + strl, + strl - (es->selection_end - es->selection_start), hrgn); + } else EDIT_CalcLineWidth_SL(wnd, es);
@@ -2930,7 +3088,12 @@ /* force scroll info update */ EDIT_UpdateScrollInfo(wnd, es);
- /* FIXME: really inefficient */ + if (hrgn) + { + EDIT_UpdateTextRegion(wnd, hrgn, TRUE); + DeleteObject(hrgn); + } + else EDIT_UpdateText(wnd, NULL, TRUE);
if(es->flags & EF_UPDATE) @@ -3134,7 +3297,7 @@ EDIT_EM_EmptyUndoBuffer(es); es->flags &= ~EF_MODIFIED; es->flags &= ~EF_UPDATE; - EDIT_BuildLineDefs_ML(wnd, es); + EDIT_BuildLineDefs_ML(wnd, es, 0, strlenW(es->text), 0, (HRGN)0); EDIT_UpdateText(wnd, NULL, TRUE); EDIT_EM_ScrollCaret(wnd, es); /* force scroll info update */ @@ -3200,7 +3363,7 @@ EDIT_EM_EmptyUndoBuffer(es); es->flags &= ~EF_MODIFIED; es->flags &= ~EF_UPDATE; - EDIT_BuildLineDefs_ML(wnd, es); + EDIT_BuildLineDefs_ML(wnd, es, 0, strlenW(es->text), 0, (HRGN)0); EDIT_UpdateText(wnd, NULL, TRUE); EDIT_EM_ScrollCaret(wnd, es); /* force scroll info update */ @@ -3403,7 +3566,7 @@ es->word_break_proc16 = NULL;
if ((es->style & ES_MULTILINE) && !(es->style & ES_AUTOHSCROLL)) { - EDIT_BuildLineDefs_ML(wnd, es); + EDIT_BuildLineDefs_ML(wnd, es, 0, strlenW(es->text), 0, (HRGN)0); EDIT_UpdateText(wnd, NULL, TRUE); } } @@ -3422,7 +3585,7 @@ es->word_break_proc = NULL; es->word_break_proc16 = wbp; if ((es->style & ES_MULTILINE) && !(es->style & ES_AUTOHSCROLL)) { - EDIT_BuildLineDefs_ML(wnd, es); + EDIT_BuildLineDefs_ML(wnd, es, 0, strlenW(es->text), 0, (HRGN)0); EDIT_UpdateText(wnd, NULL, TRUE); } } @@ -3683,6 +3846,8 @@ */ static void EDIT_WM_Destroy(WND *wnd, EDITSTATE *es) { + LINEDEF *pc, *pp; + if (es->hloc32W) { while (LocalUnlock(es->hloc32W)) ; LocalFree(es->hloc32W); @@ -3695,6 +3860,15 @@ while (LOCAL_Unlock(wnd->hInstance, es->hloc16)) ; LOCAL_Free(wnd->hInstance, es->hloc16); } + + pc = es->first_line_def; + while (pc) + { + pp = pc->next; + HeapFree(GetProcessHeap(), 0, pc); + pc = pp; + } + HeapFree(GetProcessHeap(), 0, es); *(EDITSTATE **)wnd->wExtra = NULL; } @@ -4447,7 +4621,7 @@ EDIT_SetRectNP(wnd, es, &r);
if (es->style & ES_MULTILINE) - EDIT_BuildLineDefs_ML(wnd, es); + EDIT_BuildLineDefs_ML(wnd, es, 0, strlenW(es->text), 0, (HRGN)0); else EDIT_CalcLineWidth_SL(wnd, es);
@@ -4678,6 +4852,21 @@ if (dy) EDIT_EM_LineScroll(wnd, es, 0, dy); return 0; +} + +/********************************************************************* + * + * EDIT_UpdateText + * + */ +static void EDIT_UpdateTextRegion(WND *wnd, HRGN hrgn, BOOL bErase) +{ + EDITSTATE *es = *(EDITSTATE **)((wnd)->wExtra); + + if (es->flags & EF_UPDATE) + EDIT_NOTIFY_PARENT(es, EN_UPDATE, "EN_UPDATE"); + + InvalidateRgn(wnd->hwndSelf, hrgn, bErase); }