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