riched20: Declare some functions static.
[wine] / dlls / riched20 / paint.c
1 /*
2  * RichEdit - painting functions
3  *
4  * Copyright 2004 by Krzysztof Foltman
5  * Copyright 2005 by Phil Krylov
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21
22 #include "editor.h"
23
24 WINE_DEFAULT_DEBUG_CHANNEL(richedit);
25
26 void ME_PaintContent(ME_TextEditor *editor, HDC hDC, BOOL bOnlyNew, RECT *rcUpdate) {
27   ME_DisplayItem *item;
28   ME_Context c;
29   int yoffset;
30
31   editor->nSequence++;
32   yoffset = ME_GetYScrollPos(editor);
33   ME_InitContext(&c, editor, hDC);
34   SetBkMode(hDC, TRANSPARENT);
35   ME_MoveCaret(editor);
36   item = editor->pBuffer->pFirst->next;
37   c.pt.y -= yoffset;
38   while(item != editor->pBuffer->pLast) {
39     int ye;
40     assert(item->type == diParagraph);
41     ye = c.pt.y + item->member.para.nHeight;
42     if (!bOnlyNew || (item->member.para.nFlags & MEPF_REPAINT))
43     {
44       BOOL bPaint = (rcUpdate == NULL);
45       if (rcUpdate)
46         bPaint = c.pt.y<rcUpdate->bottom && 
47           c.pt.y+item->member.para.nHeight>rcUpdate->top;
48       if (bPaint)
49       {
50         ME_DrawParagraph(&c, item);
51         if (!rcUpdate || (rcUpdate->top<=c.pt.y && rcUpdate->bottom>=ye))
52           item->member.para.nFlags &= ~MEPF_REPAINT;
53       }
54     }
55     c.pt.y = ye;
56     item = item->member.para.next_para;
57   }
58   if (c.pt.y<c.rcView.bottom) {
59     RECT rc;
60     int xs = c.rcView.left, xe = c.rcView.right;
61     int ys = c.pt.y, ye = c.rcView.bottom;
62     
63     if (bOnlyNew)
64     {
65       int y1 = editor->nTotalLength-yoffset, y2 = editor->nLastTotalLength-yoffset;
66       if (y1<y2)
67         ys = y1, ye = y2+1;
68       else
69         ys = ye;
70     }
71     
72     if (rcUpdate && ys!=ye)
73     {
74       xs = rcUpdate->left, xe = rcUpdate->right;
75       if (rcUpdate->top > ys)
76         ys = rcUpdate->top;
77       if (rcUpdate->bottom < ye)
78         ye = rcUpdate->bottom;
79     }
80
81     if (ye>ys) {
82       rc.left = xs;
83       rc.top = ys;
84       rc.right = xe;
85       rc.bottom = ye;
86       FillRect(hDC, &rc, c.editor->hbrBackground);
87     }
88     if (ys == c.pt.y) /* don't overwrite the top bar */
89       ys++;
90   }
91   if (editor->nTotalLength != editor->nLastTotalLength)
92     ME_SendRequestResize(editor, FALSE);
93   editor->nLastTotalLength = editor->nTotalLength;
94   ME_DestroyContext(&c);
95 }
96
97 void ME_Repaint(ME_TextEditor *editor)
98 {
99   if (ME_WrapMarkedParagraphs(editor))
100   {
101     ME_UpdateScrollBar(editor);
102     FIXME("ME_Repaint had to call ME_WrapMarkedParagraphs\n");
103   }
104   ME_SendOldNotify(editor, EN_UPDATE);
105   UpdateWindow(editor->hWnd);
106 }
107
108 void ME_UpdateRepaint(ME_TextEditor *editor)
109 {
110   /* Should be called whenever the contents of the control have changed */
111   ME_Cursor *pCursor;
112   
113   if (ME_WrapMarkedParagraphs(editor))
114     ME_UpdateScrollBar(editor);
115   
116   /* Ensure that the cursor is visible */
117   pCursor = &editor->pCursors[0];
118   ME_EnsureVisible(editor, pCursor->pRun);
119   
120   /* send EN_CHANGE if the event mask asks for it */
121   if(editor->nEventMask & ENM_CHANGE)
122   {
123     ME_SendOldNotify(editor, EN_CHANGE);
124   }
125   ME_Repaint(editor);
126   ME_SendSelChange(editor);
127 }
128
129 void
130 ME_RewrapRepaint(ME_TextEditor *editor)
131
132   /* RewrapRepaint should be called whenever the control has changed in
133    * looks, but not content. Like resizing. */
134   
135   ME_MarkAllForWrapping(editor);
136   ME_WrapMarkedParagraphs(editor);
137   ME_UpdateScrollBar(editor);
138   
139   ME_Repaint(editor);
140 }
141
142
143 static void ME_DrawTextWithStyle(ME_Context *c, int x, int y, LPCWSTR szText, int nChars, 
144   ME_Style *s, int *width, int nSelFrom, int nSelTo, int ymin, int cy) {
145   HDC hDC = c->hDC;
146   HGDIOBJ hOldFont;
147   COLORREF rgbOld, rgbBack;
148   int yOffset = 0, yTwipsOffset = 0;
149   hOldFont = ME_SelectStyleFont(c->editor, hDC, s);
150   rgbBack = ME_GetBackColor(c->editor);
151   if ((s->fmt.dwMask & CFM_LINK) && (s->fmt.dwEffects & CFE_LINK))
152     rgbOld = SetTextColor(hDC, RGB(0,0,255));  
153   else if ((s->fmt.dwMask & CFM_COLOR) && (s->fmt.dwEffects & CFE_AUTOCOLOR))
154     rgbOld = SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
155   else
156     rgbOld = SetTextColor(hDC, s->fmt.crTextColor);
157   if ((s->fmt.dwMask & s->fmt.dwEffects) & CFM_OFFSET) {
158     yTwipsOffset = s->fmt.yOffset;
159   }
160   if ((s->fmt.dwMask & s->fmt.dwEffects) & (CFM_SUPERSCRIPT | CFM_SUBSCRIPT)) {
161     if (s->fmt.dwEffects & CFE_SUPERSCRIPT) yTwipsOffset = s->fmt.yHeight/3;
162     if (s->fmt.dwEffects & CFE_SUBSCRIPT) yTwipsOffset = -s->fmt.yHeight/12;
163   }
164   if (yTwipsOffset)
165   {
166     int numerator = 1;
167     int denominator = 1;
168     
169     if (c->editor->nZoomNumerator)
170     {
171       numerator = c->editor->nZoomNumerator;
172       denominator = c->editor->nZoomDenominator;
173     }
174     yOffset = yTwipsOffset * GetDeviceCaps(hDC, LOGPIXELSY) * numerator / denominator / 1440;
175   }
176   ExtTextOutW(hDC, x, y-yOffset, 0, NULL, szText, nChars, NULL);
177   if (width) {
178     SIZE sz;
179     GetTextExtentPoint32W(hDC, szText, nChars, &sz);
180     *width = sz.cx;
181   }
182   if (nSelFrom < nChars && nSelTo >= 0 && nSelFrom<nSelTo)
183   {
184     SIZE sz;
185     if (nSelFrom < 0) nSelFrom = 0;
186     if (nSelTo > nChars) nSelTo = nChars;
187     GetTextExtentPoint32W(hDC, szText, nSelFrom, &sz);
188     x += sz.cx;
189     GetTextExtentPoint32W(hDC, szText+nSelFrom, nSelTo-nSelFrom, &sz);
190     
191     /* Invert selection if not hidden by EM_HIDESELECTION */
192     if (c->editor->bHideSelection == FALSE)
193         PatBlt(hDC, x, ymin, sz.cx, cy, DSTINVERT);
194   }
195   SetTextColor(hDC, rgbOld);
196   ME_UnselectStyleFont(c->editor, hDC, s, hOldFont);
197 }
198
199 static void ME_DebugWrite(HDC hDC, POINT *pt, WCHAR *szText) {
200   int align = SetTextAlign(hDC, TA_LEFT|TA_TOP);
201   HGDIOBJ hFont = SelectObject(hDC, GetStockObject(DEFAULT_GUI_FONT));
202   COLORREF color = SetTextColor(hDC, RGB(128,128,128));
203   TextOutW(hDC, pt->x, pt->y, szText, lstrlenW(szText));
204   SelectObject(hDC, hFont);
205   SetTextAlign(hDC, align);
206   SetTextColor(hDC, color);
207 }
208
209 static void ME_DrawGraphics(ME_Context *c, int x, int y, ME_Run *run,
210                             ME_Paragraph *para, BOOL selected) {
211   SIZE sz;
212   int xs, ys, xe, ye, h, ym, width, eyes;
213   ME_GetGraphicsSize(c->editor, run, &sz);
214   xs = run->pt.x;
215   ys = y-sz.cy;
216   xe = xs+sz.cx;
217   ye = y;
218   h = ye-ys;
219   ym = ys+h/4;
220   width = sz.cx;
221   eyes = width/8;
222   /* draw a smiling face :) */
223   Ellipse(c->hDC, xs, ys, xe, ye);
224   Ellipse(c->hDC, xs+width/8, ym, x+width/8+eyes, ym+eyes);
225   Ellipse(c->hDC, xs+7*width/8-eyes, ym, xs+7*width/8, ym+eyes);
226   MoveToEx(c->hDC, xs+width/8, ys+3*h/4-eyes, NULL);
227   LineTo(c->hDC, xs+width/8, ys+3*h/4);
228   LineTo(c->hDC, xs+7*width/8, ys+3*h/4);
229   LineTo(c->hDC, xs+7*width/8, ys+3*h/4-eyes);
230   if (selected)
231   {
232     /* descent is usually (always?) 0 for graphics */
233     PatBlt(c->hDC, x, y-run->nAscent, sz.cx, run->nAscent+run->nDescent, DSTINVERT);    
234   }
235 }
236
237 static void ME_DrawRun(ME_Context *c, int x, int y, ME_DisplayItem *rundi, ME_Paragraph *para) 
238 {
239   ME_Run *run = &rundi->member.run;
240   ME_DisplayItem *start = ME_FindItemBack(rundi, diStartRow);
241   int runofs = run->nCharOfs+para->nCharOfs;
242   int nSelFrom, nSelTo;
243   const WCHAR wszSpace[] = {' ', 0};
244   
245   if (run->nFlags & MERF_HIDDEN)
246     return;
247
248   ME_GetSelection(c->editor, &nSelFrom, &nSelTo);
249
250   /* Draw selected end-of-paragraph mark */
251   if (run->nFlags & MERF_ENDPARA && runofs >= nSelFrom && runofs < nSelTo)
252     ME_DrawTextWithStyle(c, x, y, wszSpace, 1, run->style, NULL, 0, 1,
253                          c->pt.y + start->member.row.nYPos,
254                          start->member.row.nHeight);
255           
256   /* you can always comment it out if you need visible paragraph marks */
257   if (run->nFlags & (MERF_ENDPARA | MERF_TAB | MERF_CELL)) 
258     return;
259
260   if (run->nFlags & MERF_GRAPHICS)
261     ME_DrawGraphics(c, x, y, run, para, (runofs >= nSelFrom) && (runofs < nSelTo));
262   else
263   {
264     if (c->editor->cPasswordMask)
265     {
266       ME_String *szMasked = ME_MakeStringR(c->editor->cPasswordMask,ME_StrVLen(run->strText));
267       ME_DrawTextWithStyle(c, x, y, 
268         szMasked->szData, ME_StrVLen(szMasked), run->style, NULL, 
269         nSelFrom-runofs,nSelTo-runofs, c->pt.y+start->member.row.nYPos, start->member.row.nHeight);
270       ME_DestroyString(szMasked);
271     }
272     else
273       ME_DrawTextWithStyle(c, x, y, 
274         run->strText->szData, ME_StrVLen(run->strText), run->style, NULL, 
275         nSelFrom-runofs,nSelTo-runofs, c->pt.y+start->member.row.nYPos, start->member.row.nHeight);
276     }
277 }
278
279 COLORREF ME_GetBackColor(ME_TextEditor *editor)
280 {
281 /* Looks like I was seriously confused
282     return GetSysColor((GetWindowLong(editor->hWnd, GWL_STYLE) & ES_READONLY) ? COLOR_3DFACE: COLOR_WINDOW);
283 */
284   if (editor->rgbBackColor == -1)
285     return GetSysColor(COLOR_WINDOW);
286   else
287     return editor->rgbBackColor;
288 }
289
290 void ME_DrawParagraph(ME_Context *c, ME_DisplayItem *paragraph) {
291   int align = SetTextAlign(c->hDC, TA_BASELINE);
292   ME_DisplayItem *p;
293   ME_Run *run;
294   ME_Paragraph *para = NULL;
295   RECT rc, rcPara;
296   int y = c->pt.y;
297   int height = 0, baseline = 0, no=0, pno = 0;
298   int xs, xe;
299   int visible = 0;
300   int nMargWidth = 0;
301   
302   c->pt.x = c->rcView.left;
303   rcPara.left = c->rcView.left;
304   rcPara.right = c->rcView.right;
305   for (p = paragraph; p!=paragraph->member.para.next_para; p = p->next) {
306     switch(p->type) {
307       case diParagraph:
308         para = &p->member.para;
309         break;
310       case diStartRow:
311         assert(para);
312         nMargWidth = (pno==0?para->nFirstMargin:para->nLeftMargin);
313         xs = c->rcView.left+nMargWidth;
314         xe = c->rcView.right-para->nRightMargin;
315         y += height;
316         rcPara.top = y;
317         rcPara.bottom = y+p->member.row.nHeight;
318         visible = RectVisible(c->hDC, &rcPara);
319         if (visible) {
320           HBRUSH hbr;
321           hbr = CreateSolidBrush(ME_GetBackColor(c->editor));
322           /* left margin */
323           rc.left = c->rcView.left;
324           rc.right = c->rcView.left+nMargWidth;
325           rc.top = y;
326           rc.bottom = y+p->member.row.nHeight;
327           FillRect(c->hDC, &rc, hbr/* c->hbrMargin */);
328           /* right margin */
329           rc.left = xe;
330           rc.right = c->rcView.right;
331           FillRect(c->hDC, &rc, hbr/* c->hbrMargin */);
332           rc.left = c->rcView.left+nMargWidth;
333           rc.right = xe;
334           FillRect(c->hDC, &rc, hbr);
335           DeleteObject(hbr);
336         }
337         if (me_debug)
338         {
339           const WCHAR wszRowDebug[] = {'r','o','w','[','%','d',']',0};
340           WCHAR buf[128];
341           POINT pt = c->pt;
342           wsprintfW(buf, wszRowDebug, no);
343           pt.y = 12+y;
344           ME_DebugWrite(c->hDC, &pt, buf);
345         }
346         
347         height = p->member.row.nHeight;
348         baseline = p->member.row.nBaseline;
349         pno++;
350         break;
351       case diRun:
352         assert(para);
353         run = &p->member.run;
354         if (visible && me_debug) {
355           rc.left = c->rcView.left+run->pt.x;
356           rc.right = c->rcView.left+run->pt.x+run->nWidth;
357           rc.top = c->pt.y+run->pt.y;
358           rc.bottom = c->pt.y+run->pt.y+height;
359           TRACE("rc = (%d, %d, %d, %d)\n", rc.left, rc.top, rc.right, rc.bottom);
360           if (run->nFlags & MERF_SKIPPED)
361             DrawFocusRect(c->hDC, &rc);
362           else
363             FrameRect(c->hDC, &rc, GetSysColorBrush(COLOR_GRAYTEXT));
364         }
365         if (visible)
366           ME_DrawRun(c, run->pt.x, c->pt.y+run->pt.y+baseline, p, &paragraph->member.para);
367         if (me_debug)
368         {
369           /* I'm using %ls, hope wsprintfW is not going to use wrong (4-byte) WCHAR version */
370           const WCHAR wszRunDebug[] = {'[','%','d',':','%','x',']',' ','%','l','s',0};
371           WCHAR buf[2560];
372           POINT pt;
373           pt.x = run->pt.x;
374           pt.y = c->pt.y + run->pt.y;
375           wsprintfW(buf, wszRunDebug, no, p->member.run.nFlags, p->member.run.strText->szData);
376           ME_DebugWrite(c->hDC, &pt, buf);
377         }
378         /* c->pt.x += p->member.run.nWidth; */
379         break;
380       default:
381         break;
382     }
383     no++;
384   }
385   SetTextAlign(c->hDC, align);
386 }
387
388 void ME_ScrollAbs(ME_TextEditor *editor, int absY)
389 {
390   ME_Scroll(editor, absY, 1);
391 }
392
393 void ME_ScrollUp(ME_TextEditor *editor, int cy)
394 {
395   ME_Scroll(editor, cy, 2);
396 }
397
398 void ME_ScrollDown(ME_TextEditor *editor, int cy)
399
400   ME_Scroll(editor, cy, 3);
401 }
402
403 void ME_Scroll(ME_TextEditor *editor, int value, int type)
404 {
405   SCROLLINFO si;
406   int nOrigPos, nNewPos, nActualScroll;
407
408   nOrigPos = ME_GetYScrollPos(editor);
409   
410   si.cbSize = sizeof(SCROLLINFO);
411   si.fMask = SIF_POS;
412   
413   switch (type)
414   {
415     case 1:
416       /*Scroll absolutly*/
417       si.nPos = value;
418       break;
419     case 2:
420       /* Scroll up - towards the beginning of the document */
421       si.nPos = nOrigPos - value;
422       break;
423     case 3:
424       /* Scroll down - towards the end of the document */
425       si.nPos = nOrigPos + value;
426       break;
427     default:
428       FIXME("ME_Scroll called incorrectly\n");
429       si.nPos = 0;
430   }
431   
432   nNewPos = SetScrollInfo(editor->hWnd, SB_VERT, &si, editor->bRedraw);
433   nActualScroll = nOrigPos - nNewPos;
434   if (editor->bRedraw)
435   {
436     if (abs(nActualScroll) > editor->sizeWindow.cy)
437       InvalidateRect(editor->hWnd, NULL, TRUE);
438     else
439       ScrollWindowEx(editor->hWnd, 0, nActualScroll, NULL, NULL, NULL, NULL, SW_INVALIDATE);
440     ME_Repaint(editor);
441   }
442   
443   ME_UpdateScrollBar(editor);
444 }
445
446  
447  void ME_UpdateScrollBar(ME_TextEditor *editor)
448
449   /* Note that this is the only funciton that should ever call SetScrolLInfo 
450    * with SIF_PAGE or SIF_RANGE. SetScrollPos and SetScrollRange should never
451    * be used at all. */
452   
453   HWND hWnd;
454   SCROLLINFO si;
455   BOOL bScrollBarWasVisible,bScrollBarWillBeVisible;
456   
457   if (ME_WrapMarkedParagraphs(editor))
458     FIXME("ME_UpdateScrollBar had to call ME_WrapMarkedParagraphs\n");
459   
460   hWnd = editor->hWnd;
461   si.cbSize = sizeof(si);
462   bScrollBarWasVisible = ME_GetYScrollVisible(editor);
463   bScrollBarWillBeVisible = editor->nTotalLength > editor->sizeWindow.cy;
464   
465   if (bScrollBarWasVisible != bScrollBarWillBeVisible)
466   {
467     ShowScrollBar(hWnd, SB_VERT, bScrollBarWillBeVisible);
468     ME_MarkAllForWrapping(editor);
469     ME_WrapMarkedParagraphs(editor);
470   }
471   
472   si.fMask = SIF_PAGE | SIF_RANGE;
473   if (GetWindowLongW(hWnd, GWL_STYLE) & ES_DISABLENOSCROLL)
474     si.fMask |= SIF_DISABLENOSCROLL;
475   
476   si.nMin = 0;  
477   si.nMax = editor->nTotalLength;
478   
479   si.nPage = editor->sizeWindow.cy;
480      
481   TRACE("min=%d max=%d page=%d\n", si.nMin, si.nMax, si.nPage);
482   SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
483 }
484
485 int ME_GetYScrollPos(ME_TextEditor *editor)
486 {
487   SCROLLINFO si;
488   si.cbSize = sizeof(si);
489   si.fMask = SIF_POS;
490   GetScrollInfo(editor->hWnd, SB_VERT, &si);
491   return si.nPos;
492 }
493
494 BOOL ME_GetYScrollVisible(ME_TextEditor *editor)
495 { /* Returns true if the scrollbar is visible */
496   SCROLLBARINFO sbi;
497   sbi.cbSize = sizeof(sbi);
498   GetScrollBarInfo(editor->hWnd, OBJID_VSCROLL, &sbi);
499   return ((sbi.rgstate[0] & STATE_SYSTEM_INVISIBLE) == 0);
500 }
501
502 void ME_EnsureVisible(ME_TextEditor *editor, ME_DisplayItem *pRun)
503 {
504   ME_DisplayItem *pRow = ME_FindItemBack(pRun, diStartRow);
505   ME_DisplayItem *pPara = ME_FindItemBack(pRun, diParagraph);
506   int y, yrel, yheight, yold;
507   
508   assert(pRow);
509   assert(pPara);
510   
511   y = pPara->member.para.nYPos+pRow->member.row.nYPos;
512   yheight = pRow->member.row.nHeight;
513   yold = ME_GetYScrollPos(editor);
514   yrel = y - yold;
515   
516   if (y < yold)
517     ME_ScrollAbs(editor,y);
518   else if (yrel + yheight > editor->sizeWindow.cy) 
519     ME_ScrollAbs(editor,y+yheight-editor->sizeWindow.cy);
520 }
521
522
523 void
524 ME_InvalidateFromOfs(ME_TextEditor *editor, int nCharOfs)
525 {
526   RECT rc;
527   int x, y, height;
528   ME_Cursor tmp;
529
530   ME_RunOfsFromCharOfs(editor, nCharOfs, &tmp.pRun, &tmp.nOffset);
531   ME_GetCursorCoordinates(editor, &tmp, &x, &y, &height);
532
533   rc.left = 0;
534   rc.top = y;
535   rc.bottom = y + height;
536   rc.right = editor->rcFormat.right;
537   InvalidateRect(editor->hWnd, &rc, FALSE);
538 }
539
540
541 void
542 ME_InvalidateSelection(ME_TextEditor *editor)
543 {
544   ME_DisplayItem *para1, *para2;
545   int nStart, nEnd;
546   int len = ME_GetTextLength(editor);
547
548   ME_GetSelection(editor, &nStart, &nEnd);
549   /* if both old and new selection are 0-char (= caret only), then
550   there's no (inverted) area to be repainted, neither old nor new */
551   if (nStart == nEnd && editor->nLastSelStart == editor->nLastSelEnd)
552     return;
553   ME_WrapMarkedParagraphs(editor);
554   ME_GetSelectionParas(editor, &para1, &para2);
555   assert(para1->type == diParagraph);
556   assert(para2->type == diParagraph);
557   /* last selection markers aren't always updated, which means
558   they can point past the end of the document */ 
559   if (editor->nLastSelStart > len)
560     editor->nLastSelEnd = len; 
561   if (editor->nLastSelEnd > len)
562     editor->nLastSelEnd = len; 
563     
564   /* if the start part of selection is being expanded or contracted... */
565   if (nStart < editor->nLastSelStart) {
566     ME_MarkForPainting(editor, para1, ME_FindItemFwd(editor->pLastSelStartPara, diParagraphOrEnd));
567   } else 
568   if (nStart > editor->nLastSelStart) {
569     ME_MarkForPainting(editor, editor->pLastSelStartPara, ME_FindItemFwd(para1, diParagraphOrEnd));
570   }
571
572   /* if the end part of selection is being contracted or expanded... */
573   if (nEnd < editor->nLastSelEnd) {
574     ME_MarkForPainting(editor, para2, ME_FindItemFwd(editor->pLastSelEndPara, diParagraphOrEnd));
575   } else 
576   if (nEnd > editor->nLastSelEnd) {
577     ME_MarkForPainting(editor, editor->pLastSelEndPara, ME_FindItemFwd(para2, diParagraphOrEnd));
578   }
579
580   ME_InvalidateMarkedParagraphs(editor);
581   /* remember the last invalidated position */
582   ME_GetSelection(editor, &editor->nLastSelStart, &editor->nLastSelEnd);
583   ME_GetSelectionParas(editor, &editor->pLastSelStartPara, &editor->pLastSelEndPara);
584   assert(editor->pLastSelStartPara->type == diParagraph);
585   assert(editor->pLastSelEndPara->type == diParagraph);
586 }
587
588 void
589 ME_QueueInvalidateFromCursor(ME_TextEditor *editor, int nCursor)
590 {
591   editor->nInvalidOfs = ME_GetCursorOfs(editor, nCursor);
592 }
593
594
595 BOOL
596 ME_SetZoom(ME_TextEditor *editor, int numerator, int denominator)
597 {
598   /* TODO: Zoom images and objects */
599
600   if (numerator != 0)
601   {
602     if (denominator == 0)
603       return FALSE;
604     if (1.0 / 64.0 > (float)numerator / (float)denominator
605         || (float)numerator / (float)denominator > 64.0)
606       return FALSE;
607   }
608   
609   editor->nZoomNumerator = numerator;
610   editor->nZoomDenominator = denominator;
611   
612   ME_RewrapRepaint(editor);
613   return TRUE;
614 }