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