wined3d: Make use of ps_compile_args in arb shader.
[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, const 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); /* Calls ME_WrapMarkedParagraphs */
36   item = editor->pBuffer->pFirst->next;
37   c.pt.y -= yoffset;
38   while(item != editor->pBuffer->pLast) {
39     int yTextOffset = 0;
40     int ys, ye;
41     assert(item->type == diParagraph);
42     if (item->member.para.pCell
43         != item->member.para.next_para->member.para.pCell)
44     {
45       ME_Cell *cell = NULL;
46       cell = &ME_FindItemBack(item->member.para.next_para, diCell)->member.cell;
47       ye = cell->pt.y + cell->nHeight - yoffset;
48     } else {
49       ye = c.pt.y + item->member.para.nHeight;
50     }
51     if (!(item->member.para.nFlags & MEPF_ROWEND) &&
52         item->member.para.pCell != item->member.para.prev_para->member.para.pCell)
53     {
54       ME_DisplayItem *cell;
55       if (item->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART)
56         cell = item->member.para.pCell;
57       else
58         cell = item->member.para.prev_para->member.para.pCell;
59       assert(cell);
60       /* the border shifts the text down */
61       yTextOffset = cell->member.cell.yTextOffset;
62       ye += yTextOffset;
63     }
64     if (!bOnlyNew || (item->member.para.nFlags & MEPF_REPAINT))
65     {
66       /* Draw the pargraph if any of the paragraph is in the update region. */
67       BOOL bPaint = (rcUpdate == NULL);
68       ys = c.pt.y;
69       if (rcUpdate)
70         bPaint = ys + c.rcView.top < rcUpdate->bottom &&
71                  ye + c.rcView.top > rcUpdate->top;
72       if (bPaint)
73       {
74         c.pt.y += yTextOffset;
75         ME_DrawParagraph(&c, item);
76         /* Clear the repaint flag if the whole paragraph is in the
77          * update region. */
78         if (!rcUpdate || (rcUpdate->top <= ys + c.rcView.top &&
79                           rcUpdate->bottom >= ye + c.rcView.top))
80           item->member.para.nFlags &= ~MEPF_REPAINT;
81       }
82     }
83     if (item->member.para.pCell)
84     {
85       ME_Cell *cell = &item->member.para.pCell->member.cell;
86       ME_DisplayItem *next_para = item->member.para.next_para;
87       c.pt.x = cell->pt.x + cell->nWidth;
88       if (item->member.para.pCell == next_para->member.para.pCell &&
89           !(next_para->member.para.nFlags & (MEPF_ROWSTART|MEPF_ROWEND)))
90       {
91         c.pt.y = ye;
92       } else {
93         if (next_para->member.para.nFlags & MEPF_ROWSTART)
94         {
95           cell = &ME_FindItemFwd(next_para, diCell)->member.cell;
96         }
97         else if (next_para->member.para.nFlags & MEPF_ROWEND)
98         {
99           cell = &cell->next_cell->member.cell;
100         }
101         else
102         {
103           cell = &next_para->member.para.pCell->member.cell;
104         }
105         c.pt.y = cell->pt.y - yoffset;
106       }
107     } else if (!(item->member.para.nFlags & MEPF_ROWSTART)) {
108       c.pt.y = ye;
109     }
110     item = item->member.para.next_para;
111   }
112   if (c.pt.y < c.rcView.bottom - c.rcView.top)
113   {
114     /* Fill space after the end of the text. */
115     int xs = c.rcView.left,          xe = c.rcView.right;
116     int ys = c.rcView.top + c.pt.y,  ye = c.rcView.bottom;
117
118     if (bOnlyNew)
119     {
120       /* Only erase region drawn from previous call to ME_PaintContent */
121       int y1 = editor->nTotalLength-yoffset, y2 = editor->nLastTotalLength-yoffset;
122       if (y1<y2)
123         ys = y1, ye = y2+1;
124       else
125         ys = ye; /* empty fill area */
126     }
127
128     if (rcUpdate)
129     {
130       /* Clip to update region */
131       xs = max(xs, rcUpdate->left);
132       xe = min(xe, rcUpdate->right);
133       ys = max(ys, rcUpdate->top);
134       ye = min(ye, rcUpdate->bottom);
135     }
136
137     if (xe > xs && ye > ys)
138     {
139       RECT rc;
140       rc.left = xs;
141       rc.top = ys;
142       rc.right = xe;
143       rc.bottom = ye;
144       FillRect(hDC, &rc, c.editor->hbrBackground);
145     }
146   }
147   if (editor->nTotalLength != editor->nLastTotalLength)
148     ME_SendRequestResize(editor, FALSE);
149   editor->nLastTotalLength = editor->nTotalLength;
150   ME_DestroyContext(&c, NULL);
151 }
152
153 void ME_Repaint(ME_TextEditor *editor)
154 {
155   if (ME_WrapMarkedParagraphs(editor))
156   {
157     ME_UpdateScrollBar(editor);
158     FIXME("ME_Repaint had to call ME_WrapMarkedParagraphs\n");
159   }
160   if (!IsWindowVisible(editor->hWnd))
161     return;
162   if (!editor->bEmulateVersion10 || (editor->nEventMask & ENM_UPDATE))
163     ME_SendOldNotify(editor, EN_UPDATE);
164   UpdateWindow(editor->hWnd);
165 }
166
167 void ME_UpdateRepaint(ME_TextEditor *editor)
168 {
169   /* Should be called whenever the contents of the control have changed */
170   ME_Cursor *pCursor;
171   BOOL wrappedParagraphs;
172
173   wrappedParagraphs = ME_WrapMarkedParagraphs(editor);
174   if (wrappedParagraphs)
175     ME_UpdateScrollBar(editor);
176   
177   /* Ensure that the cursor is visible */
178   pCursor = &editor->pCursors[0];
179   ME_EnsureVisible(editor, pCursor->pRun);
180   
181   /* send EN_CHANGE if the event mask asks for it */
182   if(editor->nEventMask & ENM_CHANGE)
183   {
184     editor->nEventMask &= ~ENM_CHANGE;
185     ME_SendOldNotify(editor, EN_CHANGE);
186     editor->nEventMask |= ENM_CHANGE;
187   }
188   ME_Repaint(editor);
189   ME_SendSelChange(editor);
190 }
191
192 void
193 ME_RewrapRepaint(ME_TextEditor *editor)
194
195   /* RewrapRepaint should be called whenever the control has changed in
196    * looks, but not content. Like resizing. */
197   
198   ME_MarkAllForWrapping(editor);
199   ME_WrapMarkedParagraphs(editor);
200   ME_UpdateScrollBar(editor);
201   ME_Repaint(editor);
202 }
203
204 int ME_twips2pointsX(ME_Context *c, int x)
205 {
206   if (c->editor->nZoomNumerator == 0)
207     return x * c->dpi.cx / 1440;
208   else
209     return x * c->dpi.cx * c->editor->nZoomNumerator / 1440 / c->editor->nZoomDenominator;
210 }
211
212 int ME_twips2pointsY(ME_Context *c, int y)
213 {
214   if (c->editor->nZoomNumerator == 0)
215     return y * c->dpi.cy / 1440;
216   else
217     return y * c->dpi.cy * c->editor->nZoomNumerator / 1440 / c->editor->nZoomDenominator;
218 }
219
220 static void ME_HighlightSpace(ME_Context *c, int x, int y, LPCWSTR szText,
221                               int nChars, ME_Style *s, int width,
222                               int nSelFrom, int nSelTo, int ymin, int cy)
223 {
224   HDC hDC = c->hDC;
225   HGDIOBJ hOldFont = NULL;
226   SIZE sz;
227   int selWidth;
228   /* Only highlight if there is a selection in the run and when
229    * EM_HIDESELECTION is not being used to hide the selection. */
230   if (nSelFrom >= nChars || nSelTo < 0 || nSelFrom >= nSelTo
231       || c->editor->bHideSelection)
232     return;
233   hOldFont = ME_SelectStyleFont(c, s);
234   if (width <= 0)
235   {
236     GetTextExtentPoint32W(hDC, szText, nChars, &sz);
237     width = sz.cx;
238   }
239   if (nSelFrom < 0) nSelFrom = 0;
240   if (nSelTo > nChars) nSelTo = nChars;
241   GetTextExtentPoint32W(hDC, szText, nSelFrom, &sz);
242   x += sz.cx;
243   if (nSelTo != nChars)
244   {
245     GetTextExtentPoint32W(hDC, szText+nSelFrom, nSelTo-nSelFrom, &sz);
246     selWidth = sz.cx;
247   } else {
248     selWidth = width - sz.cx;
249   }
250   ME_UnselectStyleFont(c, s, hOldFont);
251
252   if (c->editor->bEmulateVersion10)
253     PatBlt(hDC, x, ymin, selWidth, cy, DSTINVERT);
254   else
255   {
256     RECT rect;
257     HBRUSH hBrush;
258     rect.left = x;
259     rect.top = ymin;
260     rect.right = x + selWidth;
261     rect.bottom = ymin + cy;
262     hBrush = CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT));
263     FillRect(hDC, &rect, hBrush);
264     DeleteObject(hBrush);
265   }
266 }
267
268 static void ME_DrawTextWithStyle(ME_Context *c, int x, int y, LPCWSTR szText,
269                                  int nChars, ME_Style *s, int width,
270                                  int nSelFrom, int nSelTo, int ymin, int cy)
271 {
272   HDC hDC = c->hDC;
273   HGDIOBJ hOldFont;
274   COLORREF rgbOld;
275   int yOffset = 0, yTwipsOffset = 0;
276   SIZE          sz;
277   COLORREF      rgb;
278   HPEN hPen = NULL, hOldPen = NULL;
279   BOOL bHighlightedText = (nSelFrom < nChars && nSelTo >= 0
280                            && nSelFrom < nSelTo && !c->editor->bHideSelection);
281   int xSelStart = x, xSelEnd = x;
282   int *lpDx = NULL;
283   /* lpDx is only needed for tabs to make sure the underline done automatically
284    * by the font extends to the end of the tab. Tabs are always stored as
285    * a single character run, so we can handle this case separately, since
286    * otherwise lpDx would need to specify the lengths of each character. */
287   if (width && nChars == 1)
288       lpDx = &width; /* Make sure underline for tab extends across tab space */
289
290   hOldFont = ME_SelectStyleFont(c, s);
291   if ((s->fmt.dwMask & s->fmt.dwEffects) & CFM_OFFSET) {
292     yTwipsOffset = s->fmt.yOffset;
293   }
294   if ((s->fmt.dwMask & s->fmt.dwEffects) & (CFM_SUPERSCRIPT | CFM_SUBSCRIPT)) {
295     if (s->fmt.dwEffects & CFE_SUPERSCRIPT) yTwipsOffset = s->fmt.yHeight/3;
296     if (s->fmt.dwEffects & CFE_SUBSCRIPT) yTwipsOffset = -s->fmt.yHeight/12;
297   }
298   if (yTwipsOffset)
299     yOffset = ME_twips2pointsY(c, yTwipsOffset);
300
301   if ((s->fmt.dwMask & CFM_LINK) && (s->fmt.dwEffects & CFE_LINK))
302     rgb = RGB(0,0,255);
303   else if ((s->fmt.dwMask & CFM_COLOR) && (s->fmt.dwEffects & CFE_AUTOCOLOR))
304     rgb = GetSysColor(COLOR_WINDOWTEXT);
305   else
306     rgb = s->fmt.crTextColor;
307
308   /* Determine the area that is selected in the run. */
309   GetTextExtentPoint32W(hDC, szText, nChars, &sz);
310   /* Treat width as an optional parameter.  We can get the width from the
311    * text extent of the string if it isn't specified. */
312   if (!width) width = sz.cx;
313   if (bHighlightedText)
314   {
315     if (nSelFrom <= 0)
316     {
317       nSelFrom = 0;
318     }
319     else
320     {
321       GetTextExtentPoint32W(hDC, szText, nSelFrom, &sz);
322       xSelStart = x + sz.cx;
323     }
324     if (nSelTo >= nChars)
325     {
326       nSelTo = nChars;
327       xSelEnd = x + width;
328     }
329     else
330     {
331       GetTextExtentPoint32W(hDC, szText+nSelFrom, nSelTo-nSelFrom, &sz);
332       xSelEnd = xSelStart + sz.cx;
333     }
334   }
335
336   /* Choose the pen type for underlining the text. */
337   if (s->fmt.dwMask & CFM_UNDERLINETYPE)
338   {
339     switch (s->fmt.bUnderlineType)
340     {
341     case CFU_UNDERLINE:
342     case CFU_UNDERLINEWORD: /* native seems to map it to simple underline (MSDN) */
343     case CFU_UNDERLINEDOUBLE: /* native seems to map it to simple underline (MSDN) */
344       hPen = CreatePen(PS_SOLID, 1, rgb);
345       break;
346     case CFU_UNDERLINEDOTTED:
347       hPen = CreatePen(PS_DOT, 1, rgb);
348       break;
349     default:
350       WINE_FIXME("Unknown underline type (%u)\n", s->fmt.bUnderlineType);
351       /* fall through */
352     case CFU_CF1UNDERLINE: /* this type is supported in the font, do nothing */
353     case CFU_UNDERLINENONE:
354       hPen = NULL;
355       break;
356     }
357     if (hPen)
358     {
359       hOldPen = SelectObject(hDC, hPen);
360     }
361   }
362
363   rgbOld = SetTextColor(hDC, rgb);
364   if (bHighlightedText && !c->editor->bEmulateVersion10)
365   {
366     COLORREF rgbBackOld;
367     RECT dim;
368     /* FIXME: should use textmetrics info for Descent info */
369     if (hPen)
370       MoveToEx(hDC, x, y - yOffset + 1, NULL);
371     if (xSelStart > x)
372     {
373       ExtTextOutW(hDC, x, y-yOffset, 0, NULL, szText, nSelFrom, NULL);
374       if (hPen)
375         LineTo(hDC, xSelStart, y - yOffset + 1);
376     }
377     dim.top = ymin;
378     dim.bottom = ymin + cy;
379     dim.left = xSelStart;
380     dim.right = xSelEnd;
381     SetTextColor(hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
382     rgbBackOld = SetBkColor(hDC, GetSysColor(COLOR_HIGHLIGHT));
383     ExtTextOutW(hDC, xSelStart, y-yOffset, ETO_OPAQUE, &dim,
384                 szText+nSelFrom, nSelTo-nSelFrom, lpDx);
385     if (hPen)
386       LineTo(hDC, xSelEnd, y - yOffset + 1);
387     SetBkColor(hDC, rgbBackOld);
388     if (xSelEnd < x + width)
389     {
390       SetTextColor(hDC, rgb);
391       ExtTextOutW(hDC, xSelEnd, y-yOffset, 0, NULL, szText+nSelTo,
392                   nChars-nSelTo, NULL);
393       if (hPen)
394         LineTo(hDC, x + width, y - yOffset + 1);
395     }
396   }
397   else
398   {
399     ExtTextOutW(hDC, x, y-yOffset, 0, NULL, szText, nChars, lpDx);
400
401     /* FIXME: should use textmetrics info for Descent info */
402     if (hPen)
403     {
404       MoveToEx(hDC, x, y - yOffset + 1, NULL);
405       LineTo(hDC, x + width, y - yOffset + 1);
406     }
407
408     if (bHighlightedText) /* v1.0 inverts the selection */
409     {
410       PatBlt(hDC, xSelStart, ymin, xSelEnd-xSelStart, cy, DSTINVERT);
411     }
412   }
413
414   if (hPen)
415   {
416     SelectObject(hDC, hOldPen);
417     DeleteObject(hPen);
418   }
419   SetTextColor(hDC, rgbOld);
420   ME_UnselectStyleFont(c, s, hOldFont);
421 }
422
423 static void ME_DebugWrite(HDC hDC, const POINT *pt, LPCWSTR szText) {
424   int align = SetTextAlign(hDC, TA_LEFT|TA_TOP);
425   HGDIOBJ hFont = SelectObject(hDC, GetStockObject(DEFAULT_GUI_FONT));
426   COLORREF color = SetTextColor(hDC, RGB(128,128,128));
427   TextOutW(hDC, pt->x, pt->y, szText, lstrlenW(szText));
428   SelectObject(hDC, hFont);
429   SetTextAlign(hDC, align);
430   SetTextColor(hDC, color);
431 }
432
433 static void ME_DrawRun(ME_Context *c, int x, int y, ME_DisplayItem *rundi, ME_Paragraph *para) 
434 {
435   ME_Run *run = &rundi->member.run;
436   ME_DisplayItem *start;
437   int runofs = run->nCharOfs+para->nCharOfs;
438   int nSelFrom, nSelTo;
439   const WCHAR wszSpace[] = {' ', 0};
440   
441   if (run->nFlags & MERF_HIDDEN)
442     return;
443
444   start = ME_FindItemBack(rundi, diStartRow);
445   ME_GetSelection(c->editor, &nSelFrom, &nSelTo);
446
447   /* Draw selected end-of-paragraph mark */
448   /* you can always comment it out if you need visible paragraph marks */
449   if (run->nFlags & MERF_ENDPARA)
450   {
451     if (runofs >= nSelFrom && runofs < nSelTo)
452     {
453       ME_HighlightSpace(c, x, y, wszSpace, 1, run->style, 0, 0, 1,
454                         c->rcView.top + c->pt.y + start->member.row.pt.y,
455                         start->member.row.nHeight);
456     }
457     return;
458   }
459
460   if (run->nFlags & (MERF_TAB | MERF_ENDCELL))
461   {
462     /* wszSpace is used instead of the tab character because otherwise
463      * an unwanted symbol can be inserted instead. */
464     ME_DrawTextWithStyle(c, x, y, wszSpace, 1, run->style, run->nWidth,
465                          nSelFrom-runofs, nSelTo-runofs,
466                          c->rcView.top + c->pt.y + start->member.row.pt.y,
467                          start->member.row.nHeight);
468     return;
469   }
470
471   if (run->nFlags & MERF_GRAPHICS)
472     ME_DrawOLE(c, x, y, run, para, (runofs >= nSelFrom) && (runofs < nSelTo));
473   else
474   {
475     if (c->editor->cPasswordMask)
476     {
477       ME_String *szMasked = ME_MakeStringR(c->editor->cPasswordMask,ME_StrVLen(run->strText));
478       ME_DrawTextWithStyle(c, x, y,
479         szMasked->szData, ME_StrVLen(szMasked), run->style, run->nWidth,
480         nSelFrom-runofs,nSelTo-runofs,
481         c->rcView.top + c->pt.y + start->member.row.pt.y,
482         start->member.row.nHeight);
483       ME_DestroyString(szMasked);
484     }
485     else
486       ME_DrawTextWithStyle(c, x, y,
487         run->strText->szData, ME_StrVLen(run->strText), run->style, run->nWidth,
488         nSelFrom-runofs,nSelTo-runofs,
489         c->rcView.top + c->pt.y + start->member.row.pt.y,
490         start->member.row.nHeight);
491   }
492 }
493
494 static const struct {unsigned width_num : 4, width_den : 4, pen_style : 4, dble : 1;} border_details[] = {
495   /* none */            {0, 1, PS_SOLID, FALSE},
496   /* 3/4 */             {3, 4, PS_SOLID, FALSE},
497   /* 1 1/2 */           {3, 2, PS_SOLID, FALSE},
498   /* 2 1/4 */           {9, 4, PS_SOLID, FALSE},
499   /* 3 */               {3, 1, PS_SOLID, FALSE},
500   /* 4 1/2 */           {9, 2, PS_SOLID, FALSE},
501   /* 6 */               {6, 1, PS_SOLID, FALSE},
502   /* 3/4 double */      {3, 4, PS_SOLID, TRUE},
503   /* 1 1/2 double */    {3, 2, PS_SOLID, TRUE},
504   /* 2 1/4 double */    {9, 4, PS_SOLID, TRUE},
505   /* 3/4 gray */        {3, 4, PS_DOT /* FIXME */, FALSE},
506   /* 1 1/2 dashed */    {3, 2, PS_DASH, FALSE},
507 };
508
509 static const COLORREF pen_colors[16] = {
510   /* Black */           RGB(0x00, 0x00, 0x00),  /* Blue */            RGB(0x00, 0x00, 0xFF),
511   /* Cyan */            RGB(0x00, 0xFF, 0xFF),  /* Green */           RGB(0x00, 0xFF, 0x00),
512   /* Magenta */         RGB(0xFF, 0x00, 0xFF),  /* Red */             RGB(0xFF, 0x00, 0x00),
513   /* Yellow */          RGB(0xFF, 0xFF, 0x00),  /* White */           RGB(0xFF, 0xFF, 0xFF),
514   /* Dark blue */       RGB(0x00, 0x00, 0x80),  /* Dark cyan */       RGB(0x00, 0x80, 0x80),
515   /* Dark green */      RGB(0x00, 0x80, 0x80),  /* Dark magenta */    RGB(0x80, 0x00, 0x80),
516   /* Dark red */        RGB(0x80, 0x00, 0x00),  /* Dark yellow */     RGB(0x80, 0x80, 0x00),
517   /* Dark gray */       RGB(0x80, 0x80, 0x80),  /* Light gray */      RGB(0xc0, 0xc0, 0xc0),
518 };
519
520 static int ME_GetBorderPenWidth(ME_TextEditor* editor, int idx)
521 {
522   int width;
523
524   if (editor->nZoomNumerator == 0)
525   {
526       width = border_details[idx].width_num + border_details[idx].width_den / 2;
527       width /= border_details[idx].width_den;
528   }
529   else
530   {
531       width = border_details[idx].width_num * editor->nZoomNumerator;
532       width += border_details[idx].width_den * editor->nZoomNumerator / 2;
533       width /= border_details[idx].width_den * editor->nZoomDenominator;
534   }
535   return width;
536 }
537
538 int  ME_GetParaBorderWidth(ME_TextEditor* editor, int flags)
539 {
540   int idx = (flags >> 8) & 0xF;
541   int width;
542
543   if (idx >= sizeof(border_details) / sizeof(border_details[0]))
544   {
545       FIXME("Unsupported border value %d\n", idx);
546       return 0;
547   }
548   width = ME_GetBorderPenWidth(editor, idx);
549   if (border_details[idx].dble) width = width * 2 + 1;
550   return width;
551 }
552
553 int  ME_GetParaLineSpace(ME_Context* c, ME_Paragraph* para)
554 {
555   int   sp = 0, ls = 0;
556   if (!(para->pFmt->dwMask & PFM_LINESPACING)) return 0;
557
558   /* FIXME: how to compute simply the line space in ls ??? */
559   /* FIXME: does line spacing include the line itself ??? */
560   switch (para->pFmt->bLineSpacingRule)
561   {
562   case 0:       sp = ls; break;
563   case 1:       sp = (3 * ls) / 2; break;
564   case 2:       sp = 2 * ls; break;
565   case 3:       sp = ME_twips2pointsY(c, para->pFmt->dyLineSpacing); if (sp < ls) sp = ls; break;
566   case 4:       sp = ME_twips2pointsY(c, para->pFmt->dyLineSpacing); break;
567   case 5:       sp = para->pFmt->dyLineSpacing / 20; break;
568   default: FIXME("Unsupported spacing rule value %d\n", para->pFmt->bLineSpacingRule);
569   }
570   if (c->editor->nZoomNumerator == 0)
571     return sp;
572   else
573     return sp * c->editor->nZoomNumerator / c->editor->nZoomDenominator;
574 }
575
576 static void ME_DrawParaDecoration(ME_Context* c, ME_Paragraph* para, int y, RECT* bounds)
577 {
578   int           idx, border_width, top_border, bottom_border;
579   RECT          rc;
580   BOOL          hasParaBorder;
581
582   SetRectEmpty(bounds);
583   if (!(para->pFmt->dwMask & (PFM_BORDER | PFM_SPACEBEFORE | PFM_SPACEAFTER))) return;
584
585   border_width = top_border = bottom_border = 0;
586   idx = (para->pFmt->wBorders >> 8) & 0xF;
587   hasParaBorder = (!(c->editor->bEmulateVersion10 &&
588                      para->pFmt->dwMask & PFM_TABLE &&
589                      para->pFmt->wEffects & PFE_TABLE) &&
590                    (para->pFmt->dwMask & PFM_BORDER) &&
591                     idx != 0 &&
592                     (para->pFmt->wBorders & 0xF));
593   if (hasParaBorder)
594   {
595     /* FIXME: wBorders is not stored as MSDN says in v1.0 - 4.1 of richedit
596      * controls. It actually stores the paragraph or row border style. Although
597      * the value isn't used for drawing, it is used for streaming out rich text.
598      *
599      * wBorders stores the border style for each side (top, left, bottom, right)
600      * using nibble (4 bits) to store each border style.  The rich text format
601      * control words, and their associated value are the following:
602      *   \brdrdash       0
603      *   \brdrdashsm     1
604      *   \brdrdb         2
605      *   \brdrdot        3
606      *   \brdrhair       4
607      *   \brdrs          5
608      *   \brdrth         6
609      *   \brdrtriple     7
610      *
611      * The order of the sides stored actually differs from v1.0 to 3.0 and v4.1.
612      * The mask corresponding to each side for the version are the following:
613      *     mask       v1.0-3.0    v4.1
614      *     0x000F     top         left
615      *     0x00F0     left        top
616      *     0x0F00     bottom      right
617      *     0xF000     right       bottom
618      */
619     if (para->pFmt->wBorders & 0x00B0)
620       FIXME("Unsupported border flags %x\n", para->pFmt->wBorders);
621     border_width = ME_GetParaBorderWidth(c->editor, para->pFmt->wBorders);
622     if (para->pFmt->wBorders & 4)       top_border = border_width;
623     if (para->pFmt->wBorders & 8)       bottom_border = border_width;
624   }
625
626   if (para->pFmt->dwMask & PFM_SPACEBEFORE)
627   {
628     rc.left = c->rcView.left;
629     rc.right = c->rcView.right;
630     rc.top = y;
631     bounds->top = ME_twips2pointsY(c, para->pFmt->dySpaceBefore);
632     rc.bottom = y + bounds->top + top_border;
633     FillRect(c->hDC, &rc, c->editor->hbrBackground);
634   }
635
636   if (para->pFmt->dwMask & PFM_SPACEAFTER)
637   {
638     rc.left = c->rcView.left;
639     rc.right = c->rcView.right;
640     rc.bottom = y + para->nHeight;
641     bounds->bottom = ME_twips2pointsY(c, para->pFmt->dySpaceAfter);
642     rc.top = rc.bottom - bounds->bottom - bottom_border;
643     FillRect(c->hDC, &rc, c->editor->hbrBackground);
644   }
645
646   /* Native richedit doesn't support paragraph borders in v1.0 - 4.1,
647    * but might support it in later versions. */
648   if (hasParaBorder) {
649     int         pen_width;
650     COLORREF    pencr;
651     HPEN        pen = NULL, oldpen = NULL;
652     POINT       pt;
653
654     if (para->pFmt->wBorders & 64) /* autocolor */
655       pencr = GetSysColor(COLOR_WINDOWTEXT);
656     else
657       pencr = pen_colors[(para->pFmt->wBorders >> 12) & 0xF];
658
659     pen_width = ME_GetBorderPenWidth(c->editor, idx);
660     pen = CreatePen(border_details[idx].pen_style, pen_width, pencr);
661     oldpen = SelectObject(c->hDC, pen);
662     MoveToEx(c->hDC, 0, 0, &pt);
663
664     /* before & after spaces are not included in border */
665
666     /* helper to draw the double lines in case of corner */
667 #define DD(x)   ((para->pFmt->wBorders & (x)) ? (pen_width + 1) : 0)
668
669     if (para->pFmt->wBorders & 1)
670     {
671       MoveToEx(c->hDC, c->rcView.left, y + bounds->top, NULL);
672       LineTo(c->hDC, c->rcView.left, y + para->nHeight - bounds->bottom);
673       if (border_details[idx].dble) {
674         rc.left = c->rcView.left + 1;
675         rc.right = rc.left + border_width;
676         rc.top = y + bounds->top;
677         rc.bottom = y + para->nHeight - bounds->bottom;
678         FillRect(c->hDC, &rc, c->editor->hbrBackground);
679         MoveToEx(c->hDC, c->rcView.left + pen_width + 1, y + bounds->top + DD(4), NULL);
680         LineTo(c->hDC, c->rcView.left + pen_width + 1, y + para->nHeight - bounds->bottom - DD(8));
681       }
682       bounds->left += border_width;
683     }
684     if (para->pFmt->wBorders & 2)
685     {
686       MoveToEx(c->hDC, c->rcView.right - 1, y + bounds->top, NULL);
687       LineTo(c->hDC, c->rcView.right - 1, y + para->nHeight - bounds->bottom);
688       if (border_details[idx].dble) {
689         rc.left = c->rcView.right - pen_width - 1;
690         rc.right = c->rcView.right - 1;
691         rc.top = y + bounds->top;
692         rc.bottom = y + para->nHeight - bounds->bottom;
693         FillRect(c->hDC, &rc, c->editor->hbrBackground);
694         MoveToEx(c->hDC, c->rcView.right - 1 - pen_width - 1, y + bounds->top + DD(4), NULL);
695         LineTo(c->hDC, c->rcView.right - 1 - pen_width - 1, y + para->nHeight - bounds->bottom - DD(8));
696       }
697       bounds->right += border_width;
698     }
699     if (para->pFmt->wBorders & 4)
700     {
701       MoveToEx(c->hDC, c->rcView.left, y + bounds->top, NULL);
702       LineTo(c->hDC, c->rcView.right, y + bounds->top);
703       if (border_details[idx].dble) {
704         MoveToEx(c->hDC, c->rcView.left + DD(1), y + bounds->top + pen_width + 1, NULL);
705         LineTo(c->hDC, c->rcView.right - DD(2), y + bounds->top + pen_width + 1);
706       }
707       bounds->top += border_width;
708     }
709     if (para->pFmt->wBorders & 8)
710     {
711       MoveToEx(c->hDC, c->rcView.left, y + para->nHeight - bounds->bottom - 1, NULL);
712       LineTo(c->hDC, c->rcView.right, y + para->nHeight - bounds->bottom - 1);
713       if (border_details[idx].dble) {
714         MoveToEx(c->hDC, c->rcView.left + DD(1), y + para->nHeight - bounds->bottom - 1 - pen_width - 1, NULL);
715         LineTo(c->hDC, c->rcView.right - DD(2), y + para->nHeight - bounds->bottom - 1 - pen_width - 1);
716       }
717       bounds->bottom += border_width;
718     }
719 #undef DD
720
721     MoveToEx(c->hDC, pt.x, pt.y, NULL);
722     SelectObject(c->hDC, oldpen);
723     DeleteObject(pen);
724   }
725 }
726
727 static void ME_DrawTableBorders(ME_Context *c, ME_DisplayItem *paragraph)
728 {
729   ME_Paragraph *para = &paragraph->member.para;
730   if (!c->editor->bEmulateVersion10) /* v4.1 */
731   {
732     if (para->pCell)
733     {
734       RECT rc;
735       ME_Cell *cell = &para->pCell->member.cell;
736       ME_DisplayItem *paraAfterRow;
737       HPEN pen, oldPen;
738       LOGBRUSH logBrush;
739       HBRUSH brush;
740       COLORREF color;
741       POINT oldPt;
742       int width;
743       BOOL atTop = (para->pCell != para->prev_para->member.para.pCell);
744       BOOL atBottom = (para->pCell != para->next_para->member.para.pCell);
745       int top = c->rcView.top + (atTop ? cell->pt.y : para->pt.y) - ME_GetYScrollPos(c->editor);
746       int bottom = (atBottom ?
747                     c->rcView.top + cell->pt.y + cell->nHeight - ME_GetYScrollPos(c->editor):
748                     top + para->nHeight + (atTop ? cell->yTextOffset : 0));
749       rc.left = c->rcView.left + cell->pt.x;
750       rc.right = rc.left + cell->nWidth;
751       if (atTop) {
752         /* Erase gap before text if not all borders are the same height. */
753         width = max(ME_twips2pointsY(c, cell->border.top.width), 1);
754         rc.top = top + width;
755         width = cell->yTextOffset - width;
756         rc.bottom = rc.top + width;
757         if (width) {
758           FillRect(c->hDC, &rc, c->editor->hbrBackground);
759         }
760       }
761       /* Draw cell borders.
762        * The borders borders are draw in is left, top, bottom, right in order
763        * to be consistent with native richedit.  This is noticeable from the
764        * overlap of borders of different colours. */
765       if (!(para->nFlags & MEPF_ROWEND)) {
766         rc.top = top;
767         rc.bottom = bottom;
768         if (cell->border.left.width > 0)
769         {
770           color = cell->border.left.colorRef;
771           width = max(ME_twips2pointsX(c, cell->border.left.width), 1);
772         } else {
773           color = RGB(192,192,192);
774           width = 1;
775         }
776         logBrush.lbStyle = BS_SOLID;
777         logBrush.lbColor = color;
778         logBrush.lbHatch = 0;
779         pen = ExtCreatePen(PS_GEOMETRIC|PS_SOLID|PS_ENDCAP_FLAT|PS_JOIN_MITER,
780                            width, &logBrush, 0, NULL);
781         oldPen = SelectObject(c->hDC, pen);
782         MoveToEx(c->hDC, rc.left, rc.top, &oldPt);
783         LineTo(c->hDC, rc.left, rc.bottom);
784         SelectObject(c->hDC, oldPen);
785         DeleteObject(pen);
786         MoveToEx(c->hDC, oldPt.x, oldPt.y, NULL);
787       }
788
789       if (atTop) {
790         if (cell->border.top.width > 0)
791         {
792           brush = CreateSolidBrush(cell->border.top.colorRef);
793           width = max(ME_twips2pointsY(c, cell->border.top.width), 1);
794         } else {
795           brush = GetStockObject(LTGRAY_BRUSH);
796           width = 1;
797         }
798         rc.top = top;
799         rc.bottom = rc.top + width;
800         FillRect(c->hDC, &rc, brush);
801         if (cell->border.top.width > 0)
802           DeleteObject(brush);
803       }
804
805       /* Draw the bottom border if at the last paragraph in the cell, and when
806        * in the last row of the table. */
807       if (atBottom) {
808         int oldLeft = rc.left;
809         width = max(ME_twips2pointsY(c, cell->border.bottom.width), 1);
810         paraAfterRow = ME_GetTableRowEnd(paragraph)->member.para.next_para;
811         if (paraAfterRow->member.para.nFlags & MEPF_ROWSTART) {
812           ME_DisplayItem *nextEndCell;
813           nextEndCell = ME_FindItemBack(ME_GetTableRowEnd(paraAfterRow), diCell);
814           assert(nextEndCell && !nextEndCell->member.cell.next_cell);
815           rc.left = c->rcView.left + nextEndCell->member.cell.pt.x;
816           /* FIXME: Native draws FROM the bottom of the table rather than
817            * TO the bottom of the table in this case, but just doing so here
818            * will cause the next row to erase the border. */
819           /*
820           rc.top = bottom;
821           rc.bottom = rc.top + width;
822            */
823         }
824         if (rc.left < rc.right) {
825           if (cell->border.bottom.width > 0) {
826             brush = CreateSolidBrush(cell->border.bottom.colorRef);
827           } else {
828             brush = GetStockObject(LTGRAY_BRUSH);
829           }
830           rc.bottom = bottom;
831           rc.top = rc.bottom - width;
832           FillRect(c->hDC, &rc, brush);
833           if (cell->border.bottom.width > 0)
834             DeleteObject(brush);
835         }
836         rc.left = oldLeft;
837       }
838
839       /* Right border only drawn if at the end of the table row. */
840       if (!cell->next_cell->member.cell.next_cell &&
841           !(para->nFlags & MEPF_ROWSTART))
842       {
843         rc.top = top;
844         rc.bottom = bottom;
845         if (cell->border.right.width > 0) {
846           color = cell->border.right.colorRef;
847           width = max(ME_twips2pointsX(c, cell->border.right.width), 1);
848         } else {
849           color = RGB(192,192,192);
850           width = 1;
851         }
852         logBrush.lbStyle = BS_SOLID;
853         logBrush.lbColor = color;
854         logBrush.lbHatch = 0;
855         pen = ExtCreatePen(PS_GEOMETRIC|PS_SOLID|PS_ENDCAP_FLAT|PS_JOIN_MITER,
856                            width, &logBrush, 0, NULL);
857         oldPen = SelectObject(c->hDC, pen);
858         MoveToEx(c->hDC, rc.right - 1, rc.top, &oldPt);
859         LineTo(c->hDC, rc.right - 1, rc.bottom);
860         SelectObject(c->hDC, oldPen);
861         DeleteObject(pen);
862         MoveToEx(c->hDC, oldPt.x, oldPt.y, NULL);
863       }
864     }
865   } else { /* v1.0 - 3.0 */
866     /* Draw simple table border */
867     if (para->pFmt->dwMask & PFM_TABLE && para->pFmt->wEffects & PFE_TABLE) {
868       HPEN pen = NULL, oldpen = NULL;
869       int i, firstX, startX, endX, rowY, rowBottom, nHeight;
870       POINT oldPt;
871       PARAFORMAT2 *pNextFmt;
872
873       pen = CreatePen(PS_SOLID, 0, para->border.top.colorRef);
874       oldpen = SelectObject(c->hDC, pen);
875
876       /* Find the start relative to the text */
877       firstX = c->rcView.left + ME_FindItemFwd(paragraph, diRun)->member.run.pt.x;
878       /* Go back by the horizontal gap, which is stored in dxOffset */
879       firstX -= ME_twips2pointsX(c, para->pFmt->dxOffset);
880       /* The left edge, stored in dxStartIndent affected just the first edge */
881       startX = firstX - ME_twips2pointsX(c, para->pFmt->dxStartIndent);
882       rowY = c->rcView.top + c->pt.y;
883       if (para->pFmt->dwMask & PFM_SPACEBEFORE)
884         rowY += ME_twips2pointsY(c, para->pFmt->dySpaceBefore);
885       nHeight = ME_FindItemFwd(paragraph, diStartRow)->member.row.nHeight;
886       rowBottom = rowY + nHeight;
887
888       /* Draw horizontal lines */
889       MoveToEx(c->hDC, firstX, rowY, &oldPt);
890       i = para->pFmt->cTabCount - 1;
891       endX = startX + ME_twips2pointsX(c, para->pFmt->rgxTabs[i] & 0x00ffffff) + 1;
892       LineTo(c->hDC, endX, rowY);
893       pNextFmt = para->next_para->member.para.pFmt;
894       /* The bottom of the row only needs to be drawn if the next row is
895        * not a table. */
896       if (!(pNextFmt && pNextFmt->dwMask & PFM_TABLE && pNextFmt->wEffects &&
897             para->nRows == 1))
898       {
899         /* Decrement rowBottom to draw the bottom line within the row, and
900          * to not draw over this line when drawing the vertical lines. */
901         rowBottom--;
902         MoveToEx(c->hDC, firstX, rowBottom, NULL);
903         LineTo(c->hDC, endX, rowBottom);
904       }
905
906       /* Draw vertical lines */
907       MoveToEx(c->hDC, firstX, rowY, NULL);
908       LineTo(c->hDC, firstX, rowBottom);
909       for (i = 0; i < para->pFmt->cTabCount; i++)
910       {
911         int rightBoundary = para->pFmt->rgxTabs[i] & 0x00ffffff;
912         endX = startX + ME_twips2pointsX(c, rightBoundary);
913         MoveToEx(c->hDC, endX, rowY, NULL);
914         LineTo(c->hDC, endX, rowBottom);
915       }
916
917       MoveToEx(c->hDC, oldPt.x, oldPt.y, NULL);
918       SelectObject(c->hDC, oldpen);
919       DeleteObject(pen);
920     }
921   }
922 }
923
924 void ME_DrawParagraph(ME_Context *c, ME_DisplayItem *paragraph)
925 {
926   int align = SetTextAlign(c->hDC, TA_BASELINE);
927   ME_DisplayItem *p;
928   ME_Run *run;
929   ME_Paragraph *para = NULL;
930   RECT rc, bounds;
931   int y = c->rcView.top + c->pt.y;
932   int height = 0, baseline = 0, no=0;
933   BOOL visible = FALSE;
934
935   c->pt.x = c->rcView.left;
936   rc.left = c->rcView.left;
937   rc.right = c->rcView.right;
938   for (p = paragraph; p!=paragraph->member.para.next_para; p = p->next) {
939     switch(p->type) {
940       case diParagraph:
941         para = &p->member.para;
942         assert(para);
943         if (para->pCell)
944         {
945           ME_Cell *cell = &para->pCell->member.cell;
946           rc.left = c->rcView.left + cell->pt.x;
947           rc.right = rc.left + cell->nWidth;
948         }
949         if (para->nFlags & MEPF_ROWSTART) {
950           ME_Cell *cell = &para->next_para->member.para.pCell->member.cell;
951           rc.right = c->rcView.left + cell->pt.x;
952         } else if (para->nFlags & MEPF_ROWEND) {
953           ME_Cell *cell = &para->prev_para->member.para.pCell->member.cell;
954           rc.left = c->rcView.left + cell->pt.x + cell->nWidth;
955         }
956         ME_DrawParaDecoration(c, para, y, &bounds);
957         y += bounds.top;
958         break;
959       case diStartRow:
960         /* we should have seen a diParagraph before */
961         assert(para);
962         y += height;
963         rc.top = y;
964         if (para->nFlags & MEPF_ROWSTART) {
965           ME_Cell *cell = &para->next_para->member.para.pCell->member.cell;
966           rc.bottom = y + cell->nHeight;
967         } else if (para->nFlags & MEPF_ROWEND) {
968           ME_Cell *cell = &para->prev_para->member.para.pCell->member.cell;
969           rc.bottom = y + cell->nHeight;
970         } else {
971           rc.bottom = y + p->member.row.nHeight;
972         }
973         visible = RectVisible(c->hDC, &rc);
974         if (visible) {
975           FillRect(c->hDC, &rc, c->editor->hbrBackground);
976         }
977         if (me_debug)
978         {
979           const WCHAR wszRowDebug[] = {'r','o','w','[','%','d',']',0};
980           WCHAR buf[128];
981           POINT pt = c->pt;
982           wsprintfW(buf, wszRowDebug, no);
983           pt.y = 12+y;
984           ME_DebugWrite(c->hDC, &pt, buf);
985         }
986
987         height = p->member.row.nHeight;
988         baseline = p->member.row.nBaseline;
989         break;
990       case diRun:
991         assert(para);
992         run = &p->member.run;
993         if (visible && me_debug) {
994           rc.left = c->rcView.left + run->pt.x;
995           rc.right = rc.left + run->nWidth;
996           rc.top = c->rcView.top + c->pt.y + run->pt.y;
997           rc.bottom = rc.bottom + height;
998           TRACE("rc = (%d, %d, %d, %d)\n", rc.left, rc.top, rc.right, rc.bottom);
999           if (run->nFlags & MERF_SKIPPED)
1000             DrawFocusRect(c->hDC, &rc);
1001           else
1002             FrameRect(c->hDC, &rc, GetSysColorBrush(COLOR_GRAYTEXT));
1003         }
1004         if (visible)
1005           ME_DrawRun(c, c->rcView.left + run->pt.x,
1006                      c->rcView.top + c->pt.y + run->pt.y + baseline,
1007                      p, &paragraph->member.para);
1008         if (me_debug)
1009         {
1010           /* I'm using %ls, hope wsprintfW is not going to use wrong (4-byte) WCHAR version */
1011           const WCHAR wszRunDebug[] = {'[','%','d',':','%','x',']',' ','%','l','s',0};
1012           WCHAR buf[2560];
1013           POINT pt;
1014           pt.x = run->pt.x;
1015           pt.y = c->rcView.top + c->pt.y + run->pt.y;
1016           wsprintfW(buf, wszRunDebug, no, p->member.run.nFlags, p->member.run.strText->szData);
1017           ME_DebugWrite(c->hDC, &pt, buf);
1018         }
1019         /* c->pt.x += p->member.run.nWidth; */
1020         break;
1021       case diCell:
1022         /* Clear any space at the bottom of the cell after the text. */
1023         if (para->nFlags & MEPF_ROWSTART)
1024           break;
1025         y += height;
1026         rc.top = y;
1027         rc.bottom = c->rcView.top + p->member.cell.pt.y
1028                     + p->member.cell.nHeight - ME_GetYScrollPos(c->editor);
1029         if (RectVisible(c->hDC, &rc))
1030         {
1031           FillRect(c->hDC, &rc, c->editor->hbrBackground);
1032         }
1033         break;
1034       default:
1035         break;
1036     }
1037     no++;
1038   }
1039
1040   ME_DrawTableBorders(c, paragraph);
1041
1042   SetTextAlign(c->hDC, align);
1043 }
1044
1045 void ME_ScrollAbs(ME_TextEditor *editor, int absY)
1046 {
1047   ME_Scroll(editor, absY, 1);
1048 }
1049
1050 void ME_ScrollUp(ME_TextEditor *editor, int cy)
1051 {
1052   ME_Scroll(editor, cy, 2);
1053 }
1054
1055 void ME_ScrollDown(ME_TextEditor *editor, int cy)
1056
1057   ME_Scroll(editor, cy, 3);
1058 }
1059
1060 void ME_Scroll(ME_TextEditor *editor, int value, int type)
1061 {
1062   SCROLLINFO si;
1063   int nOrigPos, nNewPos, nActualScroll;
1064   HWND hWnd;
1065   LONG winStyle;
1066   BOOL bScrollBarIsVisible, bScrollBarWillBeVisible;
1067
1068   nOrigPos = ME_GetYScrollPos(editor);
1069   
1070   si.cbSize = sizeof(SCROLLINFO);
1071   si.fMask = SIF_POS;
1072   
1073   switch (type)
1074   {
1075     case 1:
1076       /*Scroll absolutely*/
1077       si.nPos = value;
1078       break;
1079     case 2:
1080       /* Scroll up - towards the beginning of the document */
1081       si.nPos = nOrigPos - value;
1082       break;
1083     case 3:
1084       /* Scroll down - towards the end of the document */
1085       si.nPos = nOrigPos + value;
1086       break;
1087     default:
1088       FIXME("ME_Scroll called incorrectly\n");
1089       si.nPos = 0;
1090   }
1091   
1092   nNewPos = SetScrollInfo(editor->hWnd, SB_VERT, &si, TRUE);
1093   editor->vert_si.nPos = nNewPos;
1094   nActualScroll = nOrigPos - nNewPos;
1095   if (abs(nActualScroll) > editor->sizeWindow.cy)
1096     InvalidateRect(editor->hWnd, NULL, TRUE);
1097   else
1098     ScrollWindowEx(editor->hWnd, 0, nActualScroll, NULL, NULL, NULL, NULL, SW_INVALIDATE);
1099   ME_Repaint(editor);
1100   
1101   hWnd = editor->hWnd;
1102   winStyle = GetWindowLongW(hWnd, GWL_STYLE);
1103   bScrollBarIsVisible = (winStyle & WS_VSCROLL) != 0;
1104   bScrollBarWillBeVisible = (editor->nHeight > editor->sizeWindow.cy)
1105                             || (winStyle & ES_DISABLENOSCROLL);
1106   if (bScrollBarIsVisible != bScrollBarWillBeVisible)
1107   {
1108     ShowScrollBar(hWnd, SB_VERT, bScrollBarWillBeVisible);
1109   }
1110   ME_UpdateScrollBar(editor);
1111 }
1112
1113  
1114 void ME_UpdateScrollBar(ME_TextEditor *editor)
1115
1116   /* Note that this is the only function that should ever call SetScrolLInfo
1117    * with SIF_PAGE or SIF_RANGE. SetScrollPos and SetScrollRange should never
1118    * be used at all. */
1119   
1120   HWND hWnd;
1121   SCROLLINFO si;
1122   BOOL bScrollBarWasVisible,bScrollBarWillBeVisible;
1123   
1124   if (ME_WrapMarkedParagraphs(editor))
1125     FIXME("ME_UpdateScrollBar had to call ME_WrapMarkedParagraphs\n");
1126   
1127   hWnd = editor->hWnd;
1128   si.cbSize = sizeof(si);
1129   bScrollBarWasVisible = ME_GetYScrollVisible(editor);
1130   bScrollBarWillBeVisible = editor->nHeight > editor->sizeWindow.cy;
1131   
1132   si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;
1133   if (GetWindowLongW(hWnd, GWL_STYLE) & ES_DISABLENOSCROLL)
1134   {
1135     si.fMask |= SIF_DISABLENOSCROLL;
1136     bScrollBarWillBeVisible = TRUE;
1137   }
1138
1139   if (bScrollBarWasVisible != bScrollBarWillBeVisible)
1140   {
1141     ShowScrollBar(hWnd, SB_VERT, bScrollBarWillBeVisible);
1142     ME_MarkAllForWrapping(editor);
1143     ME_WrapMarkedParagraphs(editor);
1144   }
1145   
1146   si.nMin = 0;  
1147   si.nMax = editor->nTotalLength;
1148   si.nPos = editor->vert_si.nPos;
1149   si.nPage = editor->sizeWindow.cy;
1150      
1151   if (!(si.nMin == editor->vert_si.nMin && si.nMax == editor->vert_si.nMax && si.nPage == editor->vert_si.nPage))
1152   {
1153     TRACE("min=%d max=%d page=%d\n", si.nMin, si.nMax, si.nPage);
1154     editor->vert_si.nMin = si.nMin;
1155     editor->vert_si.nMax = si.nMax;
1156     editor->vert_si.nPage = si.nPage;
1157     if (bScrollBarWillBeVisible)
1158     {
1159       SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
1160     }
1161     else
1162     {
1163       if (bScrollBarWasVisible && !(si.fMask & SIF_DISABLENOSCROLL))
1164       {
1165         SetScrollInfo(hWnd, SB_VERT, &si, TRUE);
1166         ShowScrollBar(hWnd, SB_VERT, FALSE);
1167         ME_ScrollAbs(editor, 0);
1168       }
1169     }
1170   }
1171 }
1172
1173 int ME_GetYScrollPos(ME_TextEditor *editor)
1174 {
1175   return editor->vert_si.nPos;
1176 }
1177
1178 BOOL ME_GetYScrollVisible(ME_TextEditor *editor)
1179 { /* Returns true if the scrollbar is visible */
1180   return (editor->vert_si.nMax - editor->vert_si.nMin >= max(editor->vert_si.nPage - 1, 0));
1181 }
1182
1183 void ME_EnsureVisible(ME_TextEditor *editor, ME_DisplayItem *pRun)
1184 {
1185   ME_DisplayItem *pRow = ME_FindItemBack(pRun, diStartRow);
1186   ME_DisplayItem *pPara = ME_FindItemBack(pRun, diParagraph);
1187   int y, yrel, yheight, yold;
1188   
1189   assert(pRow);
1190   assert(pPara);
1191   
1192   y = pPara->member.para.pt.y+pRow->member.row.pt.y;
1193   yheight = pRow->member.row.nHeight;
1194   yold = ME_GetYScrollPos(editor);
1195   yrel = y - yold;
1196   
1197   if (y < yold)
1198     ME_ScrollAbs(editor,y);
1199   else if (yrel + yheight > editor->sizeWindow.cy) 
1200     ME_ScrollAbs(editor,y+yheight-editor->sizeWindow.cy);
1201 }
1202
1203
1204 void
1205 ME_InvalidateFromOfs(ME_TextEditor *editor, int nCharOfs)
1206 {
1207   RECT rc;
1208   int x, y, height;
1209   ME_Cursor tmp;
1210
1211   ME_RunOfsFromCharOfs(editor, nCharOfs, &tmp.pRun, &tmp.nOffset);
1212   ME_GetCursorCoordinates(editor, &tmp, &x, &y, &height);
1213
1214   rc.left = 0;
1215   rc.top = y;
1216   rc.bottom = y + height;
1217   rc.right = editor->rcFormat.right;
1218   InvalidateRect(editor->hWnd, &rc, FALSE);
1219 }
1220
1221
1222 void
1223 ME_InvalidateSelection(ME_TextEditor *editor)
1224 {
1225   ME_DisplayItem *para1, *para2;
1226   int nStart, nEnd;
1227   int len = ME_GetTextLength(editor);
1228
1229   ME_GetSelection(editor, &nStart, &nEnd);
1230   /* if both old and new selection are 0-char (= caret only), then
1231   there's no (inverted) area to be repainted, neither old nor new */
1232   if (nStart == nEnd && editor->nLastSelStart == editor->nLastSelEnd)
1233     return;
1234   ME_WrapMarkedParagraphs(editor);
1235   ME_GetSelectionParas(editor, &para1, &para2);
1236   assert(para1->type == diParagraph);
1237   assert(para2->type == diParagraph);
1238   /* last selection markers aren't always updated, which means
1239   they can point past the end of the document */ 
1240   if (editor->nLastSelStart > len || editor->nLastSelEnd > len) {
1241     ME_MarkForPainting(editor,
1242         ME_FindItemFwd(editor->pBuffer->pFirst, diParagraph),
1243         ME_FindItemFwd(editor->pBuffer->pFirst, diTextEnd));
1244   } else {
1245     /* if the start part of selection is being expanded or contracted... */
1246     if (nStart < editor->nLastSelStart) {
1247       ME_MarkForPainting(editor, para1, ME_FindItemFwd(editor->pLastSelStartPara, diParagraphOrEnd));
1248     } else
1249     if (nStart > editor->nLastSelStart) {
1250       ME_MarkForPainting(editor, editor->pLastSelStartPara, ME_FindItemFwd(para1, diParagraphOrEnd));
1251     }
1252
1253     /* if the end part of selection is being contracted or expanded... */
1254     if (nEnd < editor->nLastSelEnd) {
1255       ME_MarkForPainting(editor, para2, ME_FindItemFwd(editor->pLastSelEndPara, diParagraphOrEnd));
1256     } else
1257     if (nEnd > editor->nLastSelEnd) {
1258       ME_MarkForPainting(editor, editor->pLastSelEndPara, ME_FindItemFwd(para2, diParagraphOrEnd));
1259     }
1260   }
1261
1262   ME_InvalidateMarkedParagraphs(editor);
1263   /* remember the last invalidated position */
1264   ME_GetSelection(editor, &editor->nLastSelStart, &editor->nLastSelEnd);
1265   ME_GetSelectionParas(editor, &editor->pLastSelStartPara, &editor->pLastSelEndPara);
1266   assert(editor->pLastSelStartPara->type == diParagraph);
1267   assert(editor->pLastSelEndPara->type == diParagraph);
1268 }
1269
1270 void
1271 ME_QueueInvalidateFromCursor(ME_TextEditor *editor, int nCursor)
1272 {
1273   editor->nInvalidOfs = ME_GetCursorOfs(editor, nCursor);
1274 }
1275
1276
1277 BOOL
1278 ME_SetZoom(ME_TextEditor *editor, int numerator, int denominator)
1279 {
1280   /* TODO: Zoom images and objects */
1281
1282   if (numerator != 0)
1283   {
1284     if (denominator == 0)
1285       return FALSE;
1286     if (1.0 / 64.0 > (float)numerator / (float)denominator
1287         || (float)numerator / (float)denominator > 64.0)
1288       return FALSE;
1289   }
1290   
1291   editor->nZoomNumerator = numerator;
1292   editor->nZoomDenominator = denominator;
1293   
1294   ME_RewrapRepaint(editor);
1295   return TRUE;
1296 }