controls/edit.c

Ulrich Czekalla ulrich.czekalla at utoronto.ca
Thu Mar 29 11:44:14 CST 2001


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 at codeweavers.com>
>         Update format engine to reduce flicker
>
-------------- next part --------------
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 = &current_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);
 }
 
 


More information about the wine-devel mailing list