comctl32: header: Merge the simple delete and complex delete.
[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   ME_SendOldNotify(editor, EN_CHANGE);
121   ME_Repaint(editor);
122   ME_SendSelChange(editor);
123 }
124
125 void
126 ME_RewrapRepaint(ME_TextEditor *editor)
127
128   /* RewrapRepaint should be called whenever the control has changed in
129    * looks, but not content. Like resizing. */
130   
131   ME_MarkAllForWrapping(editor);
132   ME_WrapMarkedParagraphs(editor);
133   ME_UpdateScrollBar(editor);
134   
135   ME_Repaint(editor);
136 }
137
138
139 static void ME_DrawTextWithStyle(ME_Context *c, int x, int y, LPCWSTR szText, int nChars, 
140   ME_Style *s, int *width, int nSelFrom, int nSelTo, int ymin, int cy) {
141   HDC hDC = c->hDC;
142   HGDIOBJ hOldFont;
143   COLORREF rgbOld, rgbBack;
144   int yOffset = 0, yTwipsOffset = 0;
145   hOldFont = ME_SelectStyleFont(c->editor, hDC, s);
146   rgbBack = ME_GetBackColor(c->editor);
147   if ((s->fmt.dwMask & CFM_LINK) && (s->fmt.dwEffects & CFE_LINK))
148     rgbOld = SetTextColor(hDC, RGB(0,0,255));  
149   else if ((s->fmt.dwMask & CFM_COLOR) && (s->fmt.dwEffects & CFE_AUTOCOLOR))
150     rgbOld = SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
151   else
152     rgbOld = SetTextColor(hDC, s->fmt.crTextColor);
153   if ((s->fmt.dwMask & s->fmt.dwEffects) & CFM_OFFSET) {
154     yTwipsOffset = s->fmt.yOffset;
155   }
156   if ((s->fmt.dwMask & s->fmt.dwEffects) & (CFM_SUPERSCRIPT | CFM_SUBSCRIPT)) {
157     if (s->fmt.dwEffects & CFE_SUPERSCRIPT) yTwipsOffset = s->fmt.yHeight/3;
158     if (s->fmt.dwEffects & CFE_SUBSCRIPT) yTwipsOffset = -s->fmt.yHeight/12;
159   }
160   if (yTwipsOffset)
161   {
162     int numerator = 1;
163     int denominator = 1;
164     
165     if (c->editor->nZoomNumerator)
166     {
167       numerator = c->editor->nZoomNumerator;
168       denominator = c->editor->nZoomDenominator;
169     }
170     yOffset = yTwipsOffset * GetDeviceCaps(hDC, LOGPIXELSY) * numerator / denominator / 1440;
171   }
172   ExtTextOutW(hDC, x, y-yOffset, 0, NULL, szText, nChars, NULL);
173   if (width) {
174     SIZE sz;
175     GetTextExtentPoint32W(hDC, szText, nChars, &sz);
176     *width = sz.cx;
177   }
178   if (nSelFrom < nChars && nSelTo >= 0 && nSelFrom<nSelTo)
179   {
180     SIZE sz;
181     if (nSelFrom < 0) nSelFrom = 0;
182     if (nSelTo > nChars) nSelTo = nChars;
183     GetTextExtentPoint32W(hDC, szText, nSelFrom, &sz);
184     x += sz.cx;
185     GetTextExtentPoint32W(hDC, szText+nSelFrom, nSelTo-nSelFrom, &sz);
186     
187     /* Invert selection if not hidden by EM_HIDESELECTION */
188     if (c->editor->bHideSelection == FALSE)
189         PatBlt(hDC, x, ymin, sz.cx, cy, DSTINVERT);
190   }
191   SetTextColor(hDC, rgbOld);
192   ME_UnselectStyleFont(c->editor, hDC, s, hOldFont);
193 }
194
195 static void ME_DebugWrite(HDC hDC, POINT *pt, WCHAR *szText) {
196   int align = SetTextAlign(hDC, TA_LEFT|TA_TOP);
197   HGDIOBJ hFont = SelectObject(hDC, GetStockObject(DEFAULT_GUI_FONT));
198   COLORREF color = SetTextColor(hDC, RGB(128,128,128));
199   TextOutW(hDC, pt->x, pt->y, szText, lstrlenW(szText));
200   SelectObject(hDC, hFont);
201   SetTextAlign(hDC, align);
202   SetTextColor(hDC, color);
203 }
204
205 void ME_DrawGraphics(ME_Context *c, int x, int y, ME_Run *run, 
206                      ME_Paragraph *para, BOOL selected) {
207   SIZE sz;
208   int xs, ys, xe, ye, h, ym, width, eyes;
209   ME_GetGraphicsSize(c->editor, run, &sz);
210   xs = run->pt.x;
211   ys = y-sz.cy;
212   xe = xs+sz.cx;
213   ye = y;
214   h = ye-ys;
215   ym = ys+h/4;
216   width = sz.cx;
217   eyes = width/8;
218   /* draw a smiling face :) */
219   Ellipse(c->hDC, xs, ys, xe, ye);
220   Ellipse(c->hDC, xs+width/8, ym, x+width/8+eyes, ym+eyes);
221   Ellipse(c->hDC, xs+7*width/8-eyes, ym, xs+7*width/8, ym+eyes);
222   MoveToEx(c->hDC, xs+width/8, ys+3*h/4-eyes, NULL);
223   LineTo(c->hDC, xs+width/8, ys+3*h/4);
224   LineTo(c->hDC, xs+7*width/8, ys+3*h/4);
225   LineTo(c->hDC, xs+7*width/8, ys+3*h/4-eyes);
226   if (selected)
227   {
228     /* descent is usually (always?) 0 for graphics */
229     PatBlt(c->hDC, x, y-run->nAscent, sz.cx, run->nAscent+run->nDescent, DSTINVERT);    
230   }
231 }
232
233 static void ME_DrawRun(ME_Context *c, int x, int y, ME_DisplayItem *rundi, ME_Paragraph *para) 
234 {
235   ME_Run *run = &rundi->member.run;
236   ME_DisplayItem *start = ME_FindItemBack(rundi, diStartRow);
237   int runofs = run->nCharOfs+para->nCharOfs;
238   int nSelFrom, nSelTo;
239   const WCHAR wszSpace[] = {' ', 0};
240   
241   if (run->nFlags & MERF_HIDDEN)
242     return;
243
244   ME_GetSelection(c->editor, &nSelFrom, &nSelTo);
245
246   /* Draw selected end-of-paragraph mark */
247   if (run->nFlags & MERF_ENDPARA && runofs >= nSelFrom && runofs < nSelTo)
248     ME_DrawTextWithStyle(c, x, y, wszSpace, 1, run->style, NULL, 0, 1,
249                          c->pt.y + start->member.row.nYPos,
250                          start->member.row.nHeight);
251           
252   /* you can always comment it out if you need visible paragraph marks */
253   if (run->nFlags & (MERF_ENDPARA | MERF_TAB | MERF_CELL)) 
254     return;
255
256   if (run->nFlags & MERF_GRAPHICS)
257     ME_DrawGraphics(c, x, y, run, para, (runofs >= nSelFrom) && (runofs < nSelTo));
258   else
259   {
260     if (c->editor->cPasswordMask)
261     {
262       ME_String *szMasked = ME_MakeStringR(c->editor->cPasswordMask,ME_StrVLen(run->strText));
263       ME_DrawTextWithStyle(c, x, y, 
264         szMasked->szData, ME_StrVLen(szMasked), run->style, NULL, 
265         nSelFrom-runofs,nSelTo-runofs, c->pt.y+start->member.row.nYPos, start->member.row.nHeight);
266       ME_DestroyString(szMasked);
267     }
268     else
269       ME_DrawTextWithStyle(c, x, y, 
270         run->strText->szData, ME_StrVLen(run->strText), run->style, NULL, 
271         nSelFrom-runofs,nSelTo-runofs, c->pt.y+start->member.row.nYPos, start->member.row.nHeight);
272     }
273 }
274
275 COLORREF ME_GetBackColor(ME_TextEditor *editor)
276 {
277 /* Looks like I was seriously confused
278     return GetSysColor((GetWindowLong(editor->hWnd, GWL_STYLE) & ES_READONLY) ? COLOR_3DFACE: COLOR_WINDOW);
279 */
280   if (editor->rgbBackColor == -1)
281     return GetSysColor(COLOR_WINDOW);
282   else
283     return editor->rgbBackColor;
284 }
285
286 void ME_DrawParagraph(ME_Context *c, ME_DisplayItem *paragraph) {
287   int align = SetTextAlign(c->hDC, TA_BASELINE);
288   ME_DisplayItem *p;
289   ME_Run *run;
290   ME_Paragraph *para = NULL;
291   RECT rc, rcPara;
292   int y = c->pt.y;
293   int height = 0, baseline = 0, no=0, pno = 0;
294   int xs, xe;
295   int visible = 0;
296   int nMargWidth = 0;
297   
298   c->pt.x = c->rcView.left;
299   rcPara.left = c->rcView.left;
300   rcPara.right = c->rcView.right;
301   for (p = paragraph; p!=paragraph->member.para.next_para; p = p->next) {
302     switch(p->type) {
303       case diParagraph:
304         para = &p->member.para;
305         break;
306       case diStartRow:
307         assert(para);
308         nMargWidth = (pno==0?para->nFirstMargin:para->nLeftMargin);
309         xs = c->rcView.left+nMargWidth;
310         xe = c->rcView.right-para->nRightMargin;
311         y += height;
312         rcPara.top = y;
313         rcPara.bottom = y+p->member.row.nHeight;
314         visible = RectVisible(c->hDC, &rcPara);
315         if (visible) {
316           HBRUSH hbr;
317           hbr = CreateSolidBrush(ME_GetBackColor(c->editor));
318           /* left margin */
319           rc.left = c->rcView.left;
320           rc.right = c->rcView.left+nMargWidth;
321           rc.top = y;
322           rc.bottom = y+p->member.row.nHeight;
323           FillRect(c->hDC, &rc, hbr/* c->hbrMargin */);
324           /* right margin */
325           rc.left = xe;
326           rc.right = c->rcView.right;
327           FillRect(c->hDC, &rc, hbr/* c->hbrMargin */);
328           rc.left = c->rcView.left+nMargWidth;
329           rc.right = xe;
330           FillRect(c->hDC, &rc, hbr);
331           DeleteObject(hbr);
332         }
333         if (me_debug)
334         {
335           const WCHAR wszRowDebug[] = {'r','o','w','[','%','d',']',0};
336           WCHAR buf[128];
337           POINT pt = c->pt;
338           wsprintfW(buf, wszRowDebug, no);
339           pt.y = 12+y;
340           ME_DebugWrite(c->hDC, &pt, buf);
341         }
342         
343         height = p->member.row.nHeight;
344         baseline = p->member.row.nBaseline;
345         pno++;
346         break;
347       case diRun:
348         assert(para);
349         run = &p->member.run;
350         if (visible && me_debug) {
351           rc.left = c->rcView.left+run->pt.x;
352           rc.right = c->rcView.left+run->pt.x+run->nWidth;
353           rc.top = c->pt.y+run->pt.y;
354           rc.bottom = c->pt.y+run->pt.y+height;
355           TRACE("rc = (%ld, %ld, %ld, %ld)\n", rc.left, rc.top, rc.right, rc.bottom);
356           if (run->nFlags & MERF_SKIPPED)
357             DrawFocusRect(c->hDC, &rc);
358           else
359             FrameRect(c->hDC, &rc, GetSysColorBrush(COLOR_GRAYTEXT));
360         }
361         if (visible)
362           ME_DrawRun(c, run->pt.x, c->pt.y+run->pt.y+baseline, p, &paragraph->member.para);
363         if (me_debug)
364         {
365           /* I'm using %ls, hope wsprintfW is not going to use wrong (4-byte) WCHAR version */
366           const WCHAR wszRunDebug[] = {'[','%','d',':','%','x',']',' ','%','l','s',0};
367           WCHAR buf[2560];
368           POINT pt;
369           pt.x = run->pt.x;
370           pt.y = c->pt.y + run->pt.y;
371           wsprintfW(buf, wszRunDebug, no, p->member.run.nFlags, p->member.run.strText->szData);
372           ME_DebugWrite(c->hDC, &pt, buf);
373         }
374         /* c->pt.x += p->member.run.nWidth; */
375         break;
376       default:
377         break;
378     }
379     no++;
380   }
381   SetTextAlign(c->hDC, align);
382 }
383
384 void ME_ScrollAbs(ME_TextEditor *editor, int absY)
385 {
386   ME_Scroll(editor, absY, 1);
387 }
388
389 void ME_ScrollUp(ME_TextEditor *editor, int cy)
390 {
391   ME_Scroll(editor, cy, 2);
392 }
393
394 void ME_ScrollDown(ME_TextEditor *editor, int cy)
395
396   ME_Scroll(editor, cy, 3);
397 }
398
399 void ME_Scroll(ME_TextEditor *editor, int value, int type)
400 {
401   SCROLLINFO si;
402   int nOrigPos, nNewPos, nActualScroll;
403
404   nOrigPos = ME_GetYScrollPos(editor);
405   
406   si.cbSize = sizeof(SCROLLINFO);
407   si.fMask = SIF_POS;
408   
409   switch (type)
410   {
411     case 1:
412       /*Scroll absolutly*/
413       si.nPos = value;
414       break;
415     case 2:
416       /* Scroll up - towards the beginning of the document */
417       si.nPos = nOrigPos - value;
418       break;
419     case 3:
420       /* Scroll down - towards the end of the document */
421       si.nPos = nOrigPos + value;
422       break;
423     default:
424       FIXME("ME_Scroll called incorrectly\n");
425       si.nPos = 0;
426   }
427   
428   nNewPos = SetScrollInfo(editor->hWnd, SB_VERT, &si, editor->bRedraw);
429   nActualScroll = nOrigPos - nNewPos;
430   if (editor->bRedraw)
431   {
432     if (abs(nActualScroll) > editor->sizeWindow.cy)
433       InvalidateRect(editor->hWnd, NULL, TRUE);
434     else
435       ScrollWindowEx(editor->hWnd, 0, nActualScroll, NULL, NULL, NULL, NULL, SW_INVALIDATE);
436     ME_Repaint(editor);
437   }
438   
439   ME_UpdateScrollBar(editor);
440 }
441
442  
443  void ME_UpdateScrollBar(ME_TextEditor *editor)
444
445   /* Note that this is the only funciton that should ever call SetScrolLInfo 
446    * with SIF_PAGE or SIF_RANGE. SetScrollPos and SetScrollRange should never
447    * be used at all. */
448   
449   HWND hWnd;
450   SCROLLINFO si;
451   BOOL bScrollBarWasVisible,bScrollBarWillBeVisible;
452   
453   if (ME_WrapMarkedParagraphs(editor))
454     FIXME("ME_UpdateScrollBar had to call ME_WrapMarkedParagraphs\n");
455   
456   hWnd = editor->hWnd;
457   si.cbSize = sizeof(si);
458   bScrollBarWasVisible = ME_GetYScrollVisible(editor);
459   bScrollBarWillBeVisible = editor->nTotalLength > editor->sizeWindow.cy;
460   
461   if (bScrollBarWasVisible != bScrollBarWillBeVisible)
462   {
463     ShowScrollBar(hWnd, SB_VERT, bScrollBarWillBeVisible);
464     ME_MarkAllForWrapping(editor);
465     ME_WrapMarkedParagraphs(editor);
466   }
467   
468   si.fMask = SIF_PAGE | SIF_RANGE;
469   if (GetWindowLongW(hWnd, GWL_STYLE) & ES_DISABLENOSCROLL)
470     si.fMask |= SIF_DISABLENOSCROLL;
471   
472   si.nMin = 0;  
473   si.nMax = editor->nTotalLength;
474   
475   si.nPage = editor->sizeWindow.cy;
476      
477   TRACE("min=%d max=%d page=%d\n", si.nMin, si.nMax, si.nPage);
478   SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
479 }
480
481 int ME_GetYScrollPos(ME_TextEditor *editor)
482 {
483   SCROLLINFO si;
484   si.cbSize = sizeof(si);
485   si.fMask = SIF_POS;
486   GetScrollInfo(editor->hWnd, SB_VERT, &si);
487   return si.nPos;
488 }
489
490 BOOL ME_GetYScrollVisible(ME_TextEditor *editor)
491 { /* Returns true if the scrollbar is visible */
492   SCROLLBARINFO sbi;
493   sbi.cbSize = sizeof(sbi);
494   GetScrollBarInfo(editor->hWnd, OBJID_VSCROLL, &sbi);
495   return ((sbi.rgstate[0] & STATE_SYSTEM_INVISIBLE) == 0);
496 }
497
498 void ME_EnsureVisible(ME_TextEditor *editor, ME_DisplayItem *pRun)
499 {
500   ME_DisplayItem *pRow = ME_FindItemBack(pRun, diStartRow);
501   ME_DisplayItem *pPara = ME_FindItemBack(pRun, diParagraph);
502   int y, yrel, yheight, yold;
503   
504   assert(pRow);
505   assert(pPara);
506   
507   y = pPara->member.para.nYPos+pRow->member.row.nYPos;
508   yheight = pRow->member.row.nHeight;
509   yold = ME_GetYScrollPos(editor);
510   yrel = y - yold;
511   
512   if (y < yold)
513     ME_ScrollAbs(editor,y);
514   else if (yrel + yheight > editor->sizeWindow.cy) 
515     ME_ScrollAbs(editor,y+yheight-editor->sizeWindow.cy);
516 }
517
518
519 void
520 ME_InvalidateFromOfs(ME_TextEditor *editor, int nCharOfs)
521 {
522   RECT rc;
523   int x, y, height;
524   ME_Cursor tmp;
525
526   ME_RunOfsFromCharOfs(editor, nCharOfs, &tmp.pRun, &tmp.nOffset);
527   ME_GetCursorCoordinates(editor, &tmp, &x, &y, &height);
528
529   rc.left = 0;
530   rc.top = y;
531   rc.bottom = y + height;
532   rc.right = editor->rcFormat.right;
533   InvalidateRect(editor->hWnd, &rc, FALSE);
534 }
535
536
537 void
538 ME_InvalidateSelection(ME_TextEditor *editor)
539 {
540   ME_DisplayItem *para1, *para2;
541   int nStart, nEnd;
542   int len = ME_GetTextLength(editor);
543
544   ME_GetSelection(editor, &nStart, &nEnd);
545   /* if both old and new selection are 0-char (= caret only), then
546   there's no (inverted) area to be repainted, neither old nor new */
547   if (nStart == nEnd && editor->nLastSelStart == editor->nLastSelEnd)
548     return;
549   ME_WrapMarkedParagraphs(editor);
550   ME_GetSelectionParas(editor, &para1, &para2);
551   assert(para1->type == diParagraph);
552   assert(para2->type == diParagraph);
553   /* last selection markers aren't always updated, which means
554   they can point past the end of the document */ 
555   if (editor->nLastSelStart > len)
556     editor->nLastSelEnd = len; 
557   if (editor->nLastSelEnd > len)
558     editor->nLastSelEnd = len; 
559     
560   /* if the start part of selection is being expanded or contracted... */
561   if (nStart < editor->nLastSelStart) {
562     ME_MarkForPainting(editor, para1, ME_FindItemFwd(editor->pLastSelStartPara, diParagraphOrEnd));
563   } else 
564   if (nStart > editor->nLastSelStart) {
565     ME_MarkForPainting(editor, editor->pLastSelStartPara, ME_FindItemFwd(para1, diParagraphOrEnd));
566   }
567
568   /* if the end part of selection is being contracted or expanded... */
569   if (nEnd < editor->nLastSelEnd) {
570     ME_MarkForPainting(editor, para2, ME_FindItemFwd(editor->pLastSelEndPara, diParagraphOrEnd));
571   } else 
572   if (nEnd > editor->nLastSelEnd) {
573     ME_MarkForPainting(editor, editor->pLastSelEndPara, ME_FindItemFwd(para2, diParagraphOrEnd));
574   }
575
576   ME_InvalidateMarkedParagraphs(editor);
577   /* remember the last invalidated position */
578   ME_GetSelection(editor, &editor->nLastSelStart, &editor->nLastSelEnd);
579   ME_GetSelectionParas(editor, &editor->pLastSelStartPara, &editor->pLastSelEndPara);
580   assert(editor->pLastSelStartPara->type == diParagraph);
581   assert(editor->pLastSelEndPara->type == diParagraph);
582 }
583
584 void
585 ME_QueueInvalidateFromCursor(ME_TextEditor *editor, int nCursor)
586 {
587   editor->nInvalidOfs = ME_GetCursorOfs(editor, nCursor);
588 }
589
590
591 BOOL
592 ME_SetZoom(ME_TextEditor *editor, int numerator, int denominator)
593 {
594   /* TODO: Zoom images and objects */
595
596   if (numerator != 0)
597   {
598     if (denominator == 0)
599       return FALSE;
600     if (1.0 / 64.0 > (float)numerator / (float)denominator
601         || (float)numerator / (float)denominator > 64.0)
602       return FALSE;
603   }
604   
605   editor->nZoomNumerator = numerator;
606   editor->nZoomDenominator = denominator;
607   
608   ME_RewrapRepaint(editor);
609   return TRUE;
610 }