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