1 /* 2 * RichEdit - Caret and selection 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 23 #include "editor.h" 24 25 WINE_DEFAULT_DEBUG_CHANNEL(richedit); 26 27 void ME_SetCursorToStart(ME_TextEditor *editor, ME_Cursor *cursor) 28 { 29 cursor->pPara = editor->pBuffer->pFirst->member.para.next_para; 30 cursor->pRun = ME_FindItemFwd(cursor->pPara, diRun); 31 cursor->nOffset = 0; 32 } 33 34 static void ME_SetCursorToEnd(ME_TextEditor *editor, ME_Cursor *cursor, BOOL final_eop) 35 { 36 cursor->pPara = editor->pBuffer->pLast->member.para.prev_para; 37 cursor->pRun = ME_FindItemBack(editor->pBuffer->pLast, diRun); 38 cursor->nOffset = final_eop ? cursor->pRun->member.run.len : 0; 39 } 40 41 42 int ME_GetSelectionOfs(ME_TextEditor *editor, int *from, int *to) 43 { 44 *from = ME_GetCursorOfs(&editor->pCursors[0]); 45 *to = ME_GetCursorOfs(&editor->pCursors[1]); 46 47 if (*from > *to) 48 { 49 int tmp = *from; 50 *from = *to; 51 *to = tmp; 52 return 1; 53 } 54 return 0; 55 } 56 57 int ME_GetSelection(ME_TextEditor *editor, ME_Cursor **from, ME_Cursor **to) 58 { 59 int from_ofs = ME_GetCursorOfs( &editor->pCursors[0] ); 60 int to_ofs = ME_GetCursorOfs( &editor->pCursors[1] ); 61 BOOL swap = (from_ofs > to_ofs); 62 63 if (from_ofs == to_ofs) 64 { 65 /* If cursor[0] is at the beginning of a run and cursor[1] at the end 66 of the prev run then we need to swap. */ 67 if (editor->pCursors[0].nOffset < editor->pCursors[1].nOffset) 68 swap = TRUE; 69 } 70 71 if (!swap) 72 { 73 *from = &editor->pCursors[0]; 74 *to = &editor->pCursors[1]; 75 return 0; 76 } else { 77 *from = &editor->pCursors[1]; 78 *to = &editor->pCursors[0]; 79 return 1; 80 } 81 } 82 83 int ME_GetTextLength(ME_TextEditor *editor) 84 { 85 ME_Cursor cursor; 86 ME_SetCursorToEnd(editor, &cursor, FALSE); 87 return ME_GetCursorOfs(&cursor); 88 } 89 90 91 int ME_GetTextLengthEx(ME_TextEditor *editor, const GETTEXTLENGTHEX *how) 92 { 93 int length; 94 95 if (how->flags & GTL_PRECISE && how->flags & GTL_CLOSE) 96 return E_INVALIDARG; 97 if (how->flags & GTL_NUMCHARS && how->flags & GTL_NUMBYTES) 98 return E_INVALIDARG; 99 100 length = ME_GetTextLength(editor); 101 102 if ((editor->styleFlags & ES_MULTILINE) 103 && (how->flags & GTL_USECRLF) 104 && !editor->bEmulateVersion10) /* Ignore GTL_USECRLF flag in 1.0 emulation */ 105 length += editor->nParagraphs - 1; 106 107 if (how->flags & GTL_NUMBYTES || 108 (how->flags & GTL_PRECISE && /* GTL_PRECISE seems to imply GTL_NUMBYTES */ 109 !(how->flags & GTL_NUMCHARS))) /* unless GTL_NUMCHARS is given */ 110 { 111 CPINFO cpinfo; 112 113 if (how->codepage == 1200) 114 return length * 2; 115 if (how->flags & GTL_PRECISE) 116 FIXME("GTL_PRECISE flag unsupported. Using GTL_CLOSE\n"); 117 if (GetCPInfo(how->codepage, &cpinfo)) 118 return length * cpinfo.MaxCharSize; 119 ERR("Invalid codepage %u\n", how->codepage); 120 return E_INVALIDARG; 121 } 122 return length; 123 } 124 125 /****************************************************************** 126 * set_selection_cursors 127 * 128 * Updates the selection cursors. 129 * 130 * Note that this does not invalidate either the old or the new selections. 131 */ 132 int set_selection_cursors(ME_TextEditor *editor, int from, int to) 133 { 134 int selectionEnd = 0; 135 const int len = ME_GetTextLength(editor); 136 137 /* all negative values are effectively the same */ 138 if (from < 0) 139 from = -1; 140 if (to < 0) 141 to = -1; 142 143 /* select all */ 144 if (from == 0 && to == -1) 145 { 146 ME_SetCursorToStart(editor, &editor->pCursors[1]); 147 ME_SetCursorToEnd(editor, &editor->pCursors[0], TRUE); 148 return len + 1; 149 } 150 151 /* if both values are equal and also out of bound, that means to */ 152 /* put the selection at the end of the text */ 153 if ((from == to) && (to < 0 || to > len)) 154 { 155 selectionEnd = 1; 156 } 157 else 158 { 159 /* if from is negative and to is positive then selection is */ 160 /* deselected and caret moved to end of the current selection */ 161 if (from < 0) 162 { 163 int start, end; 164 ME_GetSelectionOfs(editor, &start, &end); 165 if (start != end) 166 { 167 if (end > len) 168 { 169 editor->pCursors[0].nOffset = 0; 170 end --; 171 } 172 editor->pCursors[1] = editor->pCursors[0]; 173 } 174 return end; 175 } 176 177 /* adjust to if it's a negative value */ 178 if (to < 0) 179 to = len + 1; 180 181 /* flip from and to if they are reversed */ 182 if (from>to) 183 { 184 int tmp = from; 185 from = to; 186 to = tmp; 187 } 188 189 /* after fiddling with the values, we find from > len && to > len */ 190 if (from > len) 191 selectionEnd = 1; 192 /* special case with to too big */ 193 else if (to > len) 194 to = len + 1; 195 } 196 197 if (selectionEnd) 198 { 199 ME_SetCursorToEnd(editor, &editor->pCursors[0], FALSE); 200 editor->pCursors[1] = editor->pCursors[0]; 201 return len; 202 } 203 204 ME_CursorFromCharOfs(editor, from, &editor->pCursors[1]); 205 editor->pCursors[0] = editor->pCursors[1]; 206 ME_MoveCursorChars(editor, &editor->pCursors[0], to - from, FALSE); 207 /* Selection is not allowed in the middle of an end paragraph run. */ 208 if (editor->pCursors[1].pRun->member.run.nFlags & MERF_ENDPARA) 209 editor->pCursors[1].nOffset = 0; 210 if (editor->pCursors[0].pRun->member.run.nFlags & MERF_ENDPARA) 211 { 212 if (to > len) 213 editor->pCursors[0].nOffset = editor->pCursors[0].pRun->member.run.len; 214 else 215 editor->pCursors[0].nOffset = 0; 216 } 217 return to; 218 } 219 220 221 void ME_GetCursorCoordinates(ME_TextEditor *editor, ME_Cursor *pCursor, 222 int *x, int *y, int *height) 223 { 224 ME_DisplayItem *row; 225 ME_DisplayItem *run = pCursor->pRun; 226 ME_DisplayItem *para = pCursor->pPara; 227 ME_DisplayItem *pSizeRun = run; 228 ME_Context c; 229 int run_x; 230 231 assert(height && x && y); 232 assert(~para->member.para.nFlags & MEPF_REWRAP); 233 assert(run && run->type == diRun); 234 assert(para && para->type == diParagraph); 235 236 row = ME_FindItemBack(run, diStartRowOrParagraph); 237 assert(row && row->type == diStartRow); 238 239 ME_InitContext(&c, editor, ITextHost_TxGetDC(editor->texthost)); 240 241 if (!pCursor->nOffset) 242 { 243 ME_DisplayItem *prev = ME_FindItemBack(run, diRunOrParagraph); 244 assert(prev); 245 if (prev->type == diRun) 246 pSizeRun = prev; 247 } 248 if (editor->bCaretAtEnd && !pCursor->nOffset && 249 run == ME_FindItemFwd(row, diRun)) 250 { 251 ME_DisplayItem *tmp = ME_FindItemBack(row, diRunOrParagraph); 252 assert(tmp); 253 if (tmp->type == diRun) 254 { 255 row = ME_FindItemBack(tmp, diStartRow); 256 pSizeRun = run = tmp; 257 assert(run); 258 assert(run->type == diRun); 259 } 260 } 261 run_x = ME_PointFromCharContext( &c, &run->member.run, pCursor->nOffset, TRUE ); 262 263 *height = pSizeRun->member.run.nAscent + pSizeRun->member.run.nDescent; 264 *x = c.rcView.left + run->member.run.pt.x + run_x - editor->horz_si.nPos; 265 *y = c.rcView.top + para->member.para.pt.y + row->member.row.nBaseline 266 + run->member.run.pt.y - pSizeRun->member.run.nAscent 267 - editor->vert_si.nPos; 268 ME_DestroyContext(&c); 269 return; 270 } 271 272 void create_caret(ME_TextEditor *editor) 273 { 274 int x, y, height; 275 276 ME_GetCursorCoordinates(editor, &editor->pCursors[0], &x, &y, &height); 277 ITextHost_TxCreateCaret(editor->texthost, NULL, 0, height); 278 editor->caret_height = height; 279 editor->caret_hidden = TRUE; 280 } 281 282 void show_caret(ME_TextEditor *editor) 283 { 284 ITextHost_TxShowCaret(editor->texthost, TRUE); 285 editor->caret_hidden = FALSE; 286 } 287 288 void hide_caret(ME_TextEditor *editor) 289 { 290 /* calls to HideCaret are cumulative; do so only once */ 291 if (!editor->caret_hidden) 292 { 293 ITextHost_TxShowCaret(editor->texthost, FALSE); 294 editor->caret_hidden = TRUE; 295 } 296 } 297 298 void update_caret(ME_TextEditor *editor) 299 { 300 int x, y, height; 301 302 if (!editor->bHaveFocus) return; 303 if (!ME_IsSelection(editor)) 304 { 305 ME_GetCursorCoordinates(editor, &editor->pCursors[0], &x, &y, &height); 306 if (height != editor->caret_height) create_caret(editor); 307 x = min(x, editor->rcFormat.right-1); 308 ITextHost_TxSetCaretPos(editor->texthost, x, y); 309 show_caret(editor); 310 } 311 else 312 hide_caret(editor); 313 #ifdef __REACTOS__ 314 if (ImmIsIME(GetKeyboardLayout(0))) 315 { 316 HIMC hIMC = ImmGetContext(editor->hWnd); 317 if (hIMC) 318 { 319 CHARFORMAT2W fmt; 320 LOGFONTW lf; 321 COMPOSITIONFORM CompForm; 322 POINT pt = { x, y }; 323 324 CompForm.ptCurrentPos = pt; 325 if (editor->styleFlags & ES_MULTILINE) 326 { 327 CompForm.dwStyle = CFS_RECT; 328 CompForm.rcArea = editor->rcFormat; 329 } 330 else 331 { 332 CompForm.dwStyle = CFS_POINT; 333 SetRectEmpty(&CompForm.rcArea); 334 } 335 ImmSetCompositionWindow(hIMC, &CompForm); 336 337 fmt.cbSize = sizeof(fmt); 338 ME_GetSelectionCharFormat(editor, &fmt); 339 340 ZeroMemory(&lf, sizeof(lf)); 341 lf.lfCharSet = DEFAULT_CHARSET; 342 if (fmt.dwMask & CFM_SIZE) 343 { 344 HDC hdc = CreateCompatibleDC(NULL); 345 lf.lfHeight = -MulDiv(fmt.yHeight, GetDeviceCaps(hdc, LOGPIXELSY), 1440); 346 DeleteDC(hdc); 347 } 348 if (fmt.dwMask & CFM_CHARSET) 349 lf.lfCharSet = fmt.bCharSet; 350 if (fmt.dwMask & CFM_FACE) 351 lstrcpynW(lf.lfFaceName, fmt.szFaceName, ARRAY_SIZE(lf.lfFaceName)); 352 ImmSetCompositionFontW(hIMC, &lf); 353 354 ImmReleaseContext(editor->hWnd, hIMC); 355 } 356 } 357 #endif 358 } 359 360 BOOL ME_InternalDeleteText(ME_TextEditor *editor, ME_Cursor *start, 361 int nChars, BOOL bForce) 362 { 363 ME_Cursor c = *start; 364 int nOfs = ME_GetCursorOfs(start), text_len = ME_GetTextLength( editor ); 365 int shift = 0; 366 int totalChars = nChars; 367 ME_DisplayItem *start_para; 368 BOOL delete_all = FALSE; 369 370 /* Prevent deletion past last end of paragraph run. */ 371 nChars = min(nChars, text_len - nOfs); 372 if (nChars == text_len) delete_all = TRUE; 373 start_para = c.pPara; 374 375 if (!bForce) 376 { 377 ME_ProtectPartialTableDeletion(editor, &c, &nChars); 378 if (nChars == 0) 379 return FALSE; 380 } 381 382 while(nChars > 0) 383 { 384 ME_Run *run; 385 ME_CursorFromCharOfs(editor, nOfs+nChars, &c); 386 if (!c.nOffset && 387 nOfs+nChars == (c.pRun->member.run.nCharOfs 388 + c.pPara->member.para.nCharOfs)) 389 { 390 /* We aren't deleting anything in this run, so we will go back to the 391 * last run we are deleting text in. */ 392 ME_PrevRun(&c.pPara, &c.pRun, TRUE); 393 c.nOffset = c.pRun->member.run.len; 394 } 395 run = &c.pRun->member.run; 396 if (run->nFlags & MERF_ENDPARA) { 397 int eollen = c.pRun->member.run.len; 398 BOOL keepFirstParaFormat; 399 400 if (!ME_FindItemFwd(c.pRun, diParagraph)) 401 { 402 return TRUE; 403 } 404 keepFirstParaFormat = (totalChars == nChars && nChars <= eollen && 405 run->nCharOfs); 406 if (!editor->bEmulateVersion10) /* v4.1 */ 407 { 408 ME_DisplayItem *next_para = ME_FindItemFwd(c.pRun, diParagraphOrEnd); 409 ME_DisplayItem *this_para = next_para->member.para.prev_para; 410 411 /* The end of paragraph before a table row is only deleted if there 412 * is nothing else on the line before it. */ 413 if (this_para == start_para && 414 next_para->member.para.nFlags & MEPF_ROWSTART) 415 { 416 /* If the paragraph will be empty, then it should be deleted, however 417 * it still might have text right now which would inherit the 418 * MEPF_STARTROW property if we joined it right now. 419 * Instead we will delete it after the preceding text is deleted. */ 420 if (nOfs > this_para->member.para.nCharOfs) { 421 /* Skip this end of line. */ 422 nChars -= (eollen < nChars) ? eollen : nChars; 423 continue; 424 } 425 keepFirstParaFormat = TRUE; 426 } 427 } 428 ME_JoinParagraphs(editor, c.pPara, keepFirstParaFormat); 429 /* ME_SkipAndPropagateCharOffset(p->pRun, shift); */ 430 ME_CheckCharOffsets(editor); 431 nChars -= (eollen < nChars) ? eollen : nChars; 432 continue; 433 } 434 else 435 { 436 ME_Cursor cursor; 437 int nCharsToDelete = min(nChars, c.nOffset); 438 int i; 439 440 c.nOffset -= nCharsToDelete; 441 442 mark_para_rewrap(editor, ME_FindItemBack(c.pRun, diParagraph)); 443 444 cursor = c; 445 /* nChars is the number of characters that should be deleted from the 446 PRECEDING runs (these BEFORE cursor.pRun) 447 nCharsToDelete is a number of chars to delete from THIS run */ 448 nChars -= nCharsToDelete; 449 shift -= nCharsToDelete; 450 TRACE("Deleting %d (remaining %d) chars at %d in %s (%d)\n", 451 nCharsToDelete, nChars, c.nOffset, 452 debugstr_run( run ), run->len); 453 454 /* nOfs is a character offset (from the start of the document 455 to the current (deleted) run */ 456 add_undo_insert_run( editor, nOfs + nChars, get_text( run, c.nOffset ), nCharsToDelete, run->nFlags, run->style ); 457 458 ME_StrDeleteV(run->para->text, run->nCharOfs + c.nOffset, nCharsToDelete); 459 run->len -= nCharsToDelete; 460 TRACE("Post deletion string: %s (%d)\n", debugstr_run( run ), run->len); 461 TRACE("Shift value: %d\n", shift); 462 463 /* update cursors (including c) */ 464 for (i=-1; i<editor->nCursors; i++) { 465 ME_Cursor *pThisCur = editor->pCursors + i; 466 if (i == -1) pThisCur = &c; 467 if (pThisCur->pRun == cursor.pRun) { 468 if (pThisCur->nOffset > cursor.nOffset) { 469 if (pThisCur->nOffset-cursor.nOffset < nCharsToDelete) 470 pThisCur->nOffset = cursor.nOffset; 471 else 472 pThisCur->nOffset -= nCharsToDelete; 473 assert(pThisCur->nOffset >= 0); 474 assert(pThisCur->nOffset <= run->len); 475 } 476 if (pThisCur->nOffset == run->len) 477 { 478 pThisCur->pRun = ME_FindItemFwd(pThisCur->pRun, diRunOrParagraphOrEnd); 479 assert(pThisCur->pRun->type == diRun); 480 pThisCur->nOffset = 0; 481 } 482 } 483 } 484 485 /* c = updated data now */ 486 487 if (c.pRun == cursor.pRun) 488 ME_SkipAndPropagateCharOffset(c.pRun, shift); 489 else 490 ME_PropagateCharOffset(c.pRun, shift); 491 492 if (!cursor.pRun->member.run.len) 493 { 494 TRACE("Removing empty run\n"); 495 ME_Remove(cursor.pRun); 496 ME_DestroyDisplayItem(cursor.pRun); 497 } 498 499 shift = 0; 500 /* 501 ME_CheckCharOffsets(editor); 502 */ 503 continue; 504 } 505 } 506 if (delete_all) ME_SetDefaultParaFormat( editor, &start_para->member.para.fmt ); 507 return TRUE; 508 } 509 510 BOOL ME_DeleteTextAtCursor(ME_TextEditor *editor, int nCursor, int nChars) 511 { 512 assert(nCursor>=0 && nCursor<editor->nCursors); 513 /* text operations set modified state */ 514 editor->nModifyStep = 1; 515 return ME_InternalDeleteText(editor, &editor->pCursors[nCursor], 516 nChars, FALSE); 517 } 518 519 static ME_DisplayItem * 520 ME_InternalInsertTextFromCursor(ME_TextEditor *editor, int nCursor, 521 const WCHAR *str, int len, ME_Style *style, 522 int flags) 523 { 524 ME_Cursor *p = &editor->pCursors[nCursor]; 525 526 editor->bCaretAtEnd = FALSE; 527 528 assert(p->pRun->type == diRun); 529 530 return ME_InsertRunAtCursor(editor, p, style, str, len, flags); 531 } 532 533 static struct re_object* create_re_object(const REOBJECT *reo) 534 { 535 struct re_object *reobj = heap_alloc(sizeof(*reobj)); 536 537 if (!reobj) 538 { 539 WARN("Fail to allocate re_object.\n"); 540 return NULL; 541 } 542 ME_CopyReObject(&reobj->obj, reo, REO_GETOBJ_ALL_INTERFACES); 543 return reobj; 544 } 545 546 void ME_InsertOLEFromCursor(ME_TextEditor *editor, const REOBJECT* reo, int nCursor) 547 { 548 ME_Style *pStyle = ME_GetInsertStyle(editor, nCursor); 549 ME_DisplayItem *di; 550 WCHAR space = ' '; 551 ME_DisplayItem *di_prev = NULL; 552 struct re_object *reobj_prev = NULL; 553 554 /* FIXME no no no */ 555 if (ME_IsSelection(editor)) 556 ME_DeleteSelection(editor); 557 558 di = ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, pStyle, 559 MERF_GRAPHICS); 560 di->member.run.reobj = create_re_object(reo); 561 562 di_prev = di; 563 while (ME_PrevRun(NULL, &di_prev, TRUE)) 564 { 565 if (di_prev->member.run.reobj) 566 { 567 reobj_prev = di_prev->member.run.reobj; 568 break; 569 } 570 } 571 if (reobj_prev) 572 list_add_after(&reobj_prev->entry, &di->member.run.reobj->entry); 573 else 574 list_add_head(&editor->reobj_list, &di->member.run.reobj->entry); 575 576 ME_ReleaseStyle(pStyle); 577 } 578 579 580 void ME_InsertEndRowFromCursor(ME_TextEditor *editor, int nCursor) 581 { 582 ME_Style *pStyle = ME_GetInsertStyle(editor, nCursor); 583 WCHAR space = ' '; 584 585 /* FIXME no no no */ 586 if (ME_IsSelection(editor)) 587 ME_DeleteSelection(editor); 588 589 ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, pStyle, 590 MERF_ENDROW); 591 ME_ReleaseStyle(pStyle); 592 } 593 594 595 void ME_InsertTextFromCursor(ME_TextEditor *editor, int nCursor, 596 const WCHAR *str, int len, ME_Style *style) 597 { 598 const WCHAR *pos; 599 ME_Cursor *p = NULL; 600 int oldLen; 601 602 /* FIXME really HERE ? */ 603 if (ME_IsSelection(editor)) 604 ME_DeleteSelection(editor); 605 606 /* FIXME: is this too slow? */ 607 /* Didn't affect performance for WM_SETTEXT (around 50sec/30K) */ 608 oldLen = ME_GetTextLength(editor); 609 610 /* text operations set modified state */ 611 editor->nModifyStep = 1; 612 613 assert(style); 614 615 assert(nCursor>=0 && nCursor<editor->nCursors); 616 if (len == -1) 617 len = lstrlenW(str); 618 619 /* grow the text limit to fit our text */ 620 if(editor->nTextLimit < oldLen +len) 621 editor->nTextLimit = oldLen + len; 622 623 pos = str; 624 625 while (len) 626 { 627 /* FIXME this sucks - no respect for unicode (what else can be a line separator in unicode?) */ 628 while(pos - str < len && *pos != '\r' && *pos != '\n' && *pos != '\t') 629 pos++; 630 631 if (pos != str) { /* handle text */ 632 ME_InternalInsertTextFromCursor(editor, nCursor, str, pos-str, style, 0); 633 } else if (*pos == '\t') { /* handle tabs */ 634 WCHAR tab = '\t'; 635 ME_InternalInsertTextFromCursor(editor, nCursor, &tab, 1, style, MERF_TAB); 636 pos++; 637 } else { /* handle EOLs */ 638 ME_DisplayItem *tp, *end_run, *run, *prev; 639 int eol_len = 0; 640 641 /* Check if new line is allowed for this control */ 642 if (!(editor->styleFlags & ES_MULTILINE)) 643 break; 644 645 /* Find number of CR and LF in end of paragraph run */ 646 if (*pos =='\r') 647 { 648 if (len > 1 && pos[1] == '\n') 649 eol_len = 2; 650 else if (len > 2 && pos[1] == '\r' && pos[2] == '\n') 651 eol_len = 3; 652 else 653 eol_len = 1; 654 } else { 655 assert(*pos == '\n'); 656 eol_len = 1; 657 } 658 pos += eol_len; 659 660 if (!editor->bEmulateVersion10 && eol_len == 3) 661 { 662 /* handle special \r\r\n sequence (richedit 2.x and higher only) */ 663 WCHAR space = ' '; 664 ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, style, 0); 665 } else { 666 const WCHAR cr = '\r', *eol_str = str; 667 668 if (!editor->bEmulateVersion10) 669 { 670 eol_str = &cr; 671 eol_len = 1; 672 } 673 674 p = &editor->pCursors[nCursor]; 675 676 if (p->nOffset == p->pRun->member.run.len) 677 { 678 run = ME_FindItemFwd( p->pRun, diRun ); 679 if (!run) run = p->pRun; 680 } 681 else 682 { 683 if (p->nOffset) ME_SplitRunSimple(editor, p); 684 run = p->pRun; 685 } 686 687 tp = ME_SplitParagraph(editor, run, style, eol_str, eol_len, 0); 688 689 end_run = ME_FindItemBack(tp, diRun); 690 691 /* Move any cursors that were at the end of the previous run to the beginning of the new para */ 692 prev = ME_FindItemBack( end_run, diRun ); 693 if (prev) 694 { 695 int i; 696 for (i = 0; i < editor->nCursors; i++) 697 { 698 if (editor->pCursors[i].pRun == prev && 699 editor->pCursors[i].nOffset == prev->member.run.len) 700 { 701 editor->pCursors[i].pPara = tp; 702 editor->pCursors[i].pRun = run; 703 editor->pCursors[i].nOffset = 0; 704 } 705 } 706 } 707 708 } 709 } 710 len -= pos - str; 711 str = pos; 712 } 713 } 714 715 /* Move the cursor nRelOfs characters (either forwards or backwards) 716 * If final_eop is TRUE, allow moving the cursor to the end of the final eop. 717 * 718 * returns the actual number of characters moved. 719 **/ 720 int ME_MoveCursorChars(ME_TextEditor *editor, ME_Cursor *cursor, int nRelOfs, BOOL final_eop) 721 { 722 cursor->nOffset += nRelOfs; 723 if (cursor->nOffset < 0) 724 { 725 cursor->nOffset += cursor->pRun->member.run.nCharOfs; 726 if (cursor->nOffset >= 0) 727 { 728 /* new offset in the same paragraph */ 729 do { 730 cursor->pRun = ME_FindItemBack(cursor->pRun, diRun); 731 } while (cursor->nOffset < cursor->pRun->member.run.nCharOfs); 732 cursor->nOffset -= cursor->pRun->member.run.nCharOfs; 733 return nRelOfs; 734 } 735 736 cursor->nOffset += cursor->pPara->member.para.nCharOfs; 737 if (cursor->nOffset <= 0) 738 { 739 /* moved to the start of the text */ 740 nRelOfs -= cursor->nOffset; 741 ME_SetCursorToStart(editor, cursor); 742 return nRelOfs; 743 } 744 745 /* new offset in a previous paragraph */ 746 do { 747 cursor->pPara = cursor->pPara->member.para.prev_para; 748 } while (cursor->nOffset < cursor->pPara->member.para.nCharOfs); 749 cursor->nOffset -= cursor->pPara->member.para.nCharOfs; 750 751 cursor->pRun = ME_FindItemBack(cursor->pPara->member.para.next_para, diRun); 752 while (cursor->nOffset < cursor->pRun->member.run.nCharOfs) { 753 cursor->pRun = ME_FindItemBack(cursor->pRun, diRun); 754 } 755 cursor->nOffset -= cursor->pRun->member.run.nCharOfs; 756 } else if (cursor->nOffset >= cursor->pRun->member.run.len) { 757 ME_DisplayItem *next_para; 758 int new_offset; 759 760 new_offset = ME_GetCursorOfs(cursor); 761 next_para = cursor->pPara->member.para.next_para; 762 if (new_offset < next_para->member.para.nCharOfs) 763 { 764 /* new offset in the same paragraph */ 765 do { 766 cursor->nOffset -= cursor->pRun->member.run.len; 767 cursor->pRun = ME_FindItemFwd(cursor->pRun, diRun); 768 } while (cursor->nOffset >= cursor->pRun->member.run.len); 769 return nRelOfs; 770 } 771 772 if (new_offset >= ME_GetTextLength(editor) + (final_eop ? 1 : 0)) 773 { 774 /* new offset at the end of the text */ 775 ME_SetCursorToEnd(editor, cursor, final_eop); 776 nRelOfs -= new_offset - (ME_GetTextLength(editor) + (final_eop ? 1 : 0)); 777 return nRelOfs; 778 } 779 780 /* new offset in a following paragraph */ 781 do { 782 cursor->pPara = next_para; 783 next_para = next_para->member.para.next_para; 784 } while (new_offset >= next_para->member.para.nCharOfs); 785 786 cursor->nOffset = new_offset - cursor->pPara->member.para.nCharOfs; 787 cursor->pRun = ME_FindItemFwd(cursor->pPara, diRun); 788 while (cursor->nOffset >= cursor->pRun->member.run.len) 789 { 790 cursor->nOffset -= cursor->pRun->member.run.len; 791 cursor->pRun = ME_FindItemFwd(cursor->pRun, diRun); 792 } 793 } /* else new offset is in the same run */ 794 return nRelOfs; 795 } 796 797 798 BOOL 799 ME_MoveCursorWords(ME_TextEditor *editor, ME_Cursor *cursor, int nRelOfs) 800 { 801 ME_DisplayItem *pRun = cursor->pRun, *pOtherRun; 802 ME_DisplayItem *pPara = cursor->pPara; 803 int nOffset = cursor->nOffset; 804 805 if (nRelOfs == -1) 806 { 807 /* Backward movement */ 808 while (TRUE) 809 { 810 nOffset = ME_CallWordBreakProc(editor, get_text( &pRun->member.run, 0 ), 811 pRun->member.run.len, nOffset, WB_MOVEWORDLEFT); 812 if (nOffset) 813 break; 814 pOtherRun = ME_FindItemBack(pRun, diRunOrParagraph); 815 if (pOtherRun->type == diRun) 816 { 817 if (ME_CallWordBreakProc(editor, get_text( &pOtherRun->member.run, 0 ), 818 pOtherRun->member.run.len, 819 pOtherRun->member.run.len - 1, 820 WB_ISDELIMITER) 821 && !(pRun->member.run.nFlags & MERF_ENDPARA) 822 && !(cursor->pRun == pRun && cursor->nOffset == 0) 823 && !ME_CallWordBreakProc(editor, get_text( &pRun->member.run, 0 ), 824 pRun->member.run.len, 0, 825 WB_ISDELIMITER)) 826 break; 827 pRun = pOtherRun; 828 nOffset = pOtherRun->member.run.len; 829 } 830 else if (pOtherRun->type == diParagraph) 831 { 832 if (cursor->pRun == pRun && cursor->nOffset == 0) 833 { 834 pPara = pOtherRun; 835 /* Skip empty start of table row paragraph */ 836 if (pPara->member.para.prev_para->member.para.nFlags & MEPF_ROWSTART) 837 pPara = pPara->member.para.prev_para; 838 /* Paragraph breaks are treated as separate words */ 839 if (pPara->member.para.prev_para->type == diTextStart) 840 return FALSE; 841 842 pRun = ME_FindItemBack(pPara, diRun); 843 pPara = pPara->member.para.prev_para; 844 } 845 break; 846 } 847 } 848 } 849 else 850 { 851 /* Forward movement */ 852 BOOL last_delim = FALSE; 853 854 while (TRUE) 855 { 856 if (last_delim && !ME_CallWordBreakProc(editor, get_text( &pRun->member.run, 0 ), 857 pRun->member.run.len, nOffset, WB_ISDELIMITER)) 858 break; 859 nOffset = ME_CallWordBreakProc(editor, get_text( &pRun->member.run, 0 ), 860 pRun->member.run.len, nOffset, WB_MOVEWORDRIGHT); 861 if (nOffset < pRun->member.run.len) 862 break; 863 pOtherRun = ME_FindItemFwd(pRun, diRunOrParagraphOrEnd); 864 if (pOtherRun->type == diRun) 865 { 866 last_delim = ME_CallWordBreakProc(editor, get_text( &pRun->member.run, 0 ), 867 pRun->member.run.len, nOffset - 1, WB_ISDELIMITER); 868 pRun = pOtherRun; 869 nOffset = 0; 870 } 871 else if (pOtherRun->type == diParagraph) 872 { 873 if (pOtherRun->member.para.nFlags & MEPF_ROWSTART) 874 pOtherRun = pOtherRun->member.para.next_para; 875 if (cursor->pRun == pRun) { 876 pPara = pOtherRun; 877 pRun = ME_FindItemFwd(pPara, diRun); 878 } 879 nOffset = 0; 880 break; 881 } 882 else /* diTextEnd */ 883 { 884 if (cursor->pRun == pRun) 885 return FALSE; 886 nOffset = 0; 887 break; 888 } 889 } 890 } 891 cursor->pPara = pPara; 892 cursor->pRun = pRun; 893 cursor->nOffset = nOffset; 894 return TRUE; 895 } 896 897 898 static void 899 ME_SelectByType(ME_TextEditor *editor, ME_SelectionType selectionType) 900 { 901 /* pCursor[0] is the end of the selection 902 * pCursor[1] is the start of the selection (or the position selection anchor) 903 * pCursor[2] and [3] are the selection anchors that are backed up 904 * so they are kept when the selection changes for drag selection. 905 */ 906 907 editor->nSelectionType = selectionType; 908 switch(selectionType) 909 { 910 case stPosition: 911 break; 912 case stWord: 913 ME_MoveCursorWords(editor, &editor->pCursors[0], +1); 914 editor->pCursors[1] = editor->pCursors[0]; 915 ME_MoveCursorWords(editor, &editor->pCursors[1], -1); 916 break; 917 case stLine: 918 case stParagraph: 919 { 920 ME_DisplayItem *pItem; 921 ME_DIType fwdSearchType, backSearchType; 922 if (selectionType == stParagraph) { 923 backSearchType = diParagraph; 924 fwdSearchType = diParagraphOrEnd; 925 } else { 926 backSearchType = diStartRow; 927 fwdSearchType = diStartRowOrParagraphOrEnd; 928 } 929 pItem = ME_FindItemFwd(editor->pCursors[0].pRun, fwdSearchType); 930 assert(pItem); 931 if (pItem->type == diTextEnd) 932 editor->pCursors[0].pRun = ME_FindItemBack(pItem, diRun); 933 else 934 editor->pCursors[0].pRun = ME_FindItemFwd(pItem, diRun); 935 editor->pCursors[0].pPara = ME_GetParagraph(editor->pCursors[0].pRun); 936 editor->pCursors[0].nOffset = 0; 937 938 pItem = ME_FindItemBack(pItem, backSearchType); 939 editor->pCursors[1].pRun = ME_FindItemFwd(pItem, diRun); 940 editor->pCursors[1].pPara = ME_GetParagraph(editor->pCursors[1].pRun); 941 editor->pCursors[1].nOffset = 0; 942 break; 943 } 944 case stDocument: 945 /* Select everything with cursor anchored from the start of the text */ 946 editor->nSelectionType = stDocument; 947 ME_SetCursorToStart(editor, &editor->pCursors[1]); 948 ME_SetCursorToEnd(editor, &editor->pCursors[0], FALSE); 949 break; 950 default: assert(0); 951 } 952 /* Store the anchor positions for extending the selection. */ 953 editor->pCursors[2] = editor->pCursors[0]; 954 editor->pCursors[3] = editor->pCursors[1]; 955 } 956 957 int ME_GetCursorOfs(const ME_Cursor *cursor) 958 { 959 return cursor->pPara->member.para.nCharOfs 960 + cursor->pRun->member.run.nCharOfs + cursor->nOffset; 961 } 962 963 /* Helper function for ME_FindPixelPos to find paragraph within tables */ 964 static ME_DisplayItem* ME_FindPixelPosInTableRow(int x, int y, 965 ME_DisplayItem *para) 966 { 967 ME_DisplayItem *cell, *next_cell; 968 assert(para->member.para.nFlags & MEPF_ROWSTART); 969 cell = para->member.para.next_para->member.para.pCell; 970 assert(cell); 971 972 /* find the cell we are in */ 973 while ((next_cell = cell->member.cell.next_cell) != NULL) { 974 if (x < next_cell->member.cell.pt.x) 975 { 976 para = ME_FindItemFwd(cell, diParagraph); 977 /* Found the cell, but there might be multiple paragraphs in 978 * the cell, so need to search down the cell for the paragraph. */ 979 while (cell == para->member.para.pCell) { 980 if (y < para->member.para.pt.y + para->member.para.nHeight) 981 { 982 if (para->member.para.nFlags & MEPF_ROWSTART) 983 return ME_FindPixelPosInTableRow(x, y, para); 984 else 985 return para; 986 } 987 para = para->member.para.next_para; 988 } 989 /* Past the end of the cell, so go back to the last cell paragraph */ 990 return para->member.para.prev_para; 991 } 992 cell = next_cell; 993 } 994 /* Return table row delimiter */ 995 para = ME_FindItemFwd(cell, diParagraph); 996 assert(para->member.para.nFlags & MEPF_ROWEND); 997 assert(para->member.para.fmt.dwMask & PFM_TABLEROWDELIMITER); 998 assert(para->member.para.fmt.wEffects & PFE_TABLEROWDELIMITER); 999 return para; 1000 } 1001 1002 static BOOL ME_FindRunInRow(ME_TextEditor *editor, ME_DisplayItem *pRow, 1003 int x, ME_Cursor *cursor, BOOL *pbCaretAtEnd) 1004 { 1005 ME_DisplayItem *pNext, *pLastRun; 1006 ME_Row *row = &pRow->member.row; 1007 BOOL exact = TRUE; 1008 1009 if (x < row->pt.x) 1010 { 1011 x = row->pt.x; 1012 exact = FALSE; 1013 } 1014 pNext = ME_FindItemFwd(pRow, diRunOrStartRow); 1015 assert(pNext->type == diRun); 1016 if (pbCaretAtEnd) *pbCaretAtEnd = FALSE; 1017 cursor->nOffset = 0; 1018 do { 1019 int run_x = pNext->member.run.pt.x; 1020 int width = pNext->member.run.nWidth; 1021 1022 if (x >= run_x && x < run_x+width) 1023 { 1024 cursor->nOffset = ME_CharFromPoint(editor, x-run_x, &pNext->member.run, TRUE, TRUE); 1025 cursor->pRun = pNext; 1026 cursor->pPara = ME_GetParagraph( cursor->pRun ); 1027 return exact; 1028 } 1029 pLastRun = pNext; 1030 pNext = ME_FindItemFwd(pNext, diRunOrStartRow); 1031 } while(pNext && pNext->type == diRun); 1032 1033 if ((pLastRun->member.run.nFlags & MERF_ENDPARA) == 0) 1034 { 1035 cursor->pRun = ME_FindItemFwd(pNext, diRun); 1036 if (pbCaretAtEnd) *pbCaretAtEnd = TRUE; 1037 } 1038 else 1039 cursor->pRun = pLastRun; 1040 1041 cursor->pPara = ME_GetParagraph( cursor->pRun ); 1042 return FALSE; 1043 } 1044 1045 /* Finds the run and offset from the pixel position. 1046 * 1047 * x & y are pixel positions in virtual coordinates into the rich edit control, 1048 * so client coordinates must first be adjusted by the scroll position. 1049 * 1050 * If final_eop is TRUE consider the final end-of-paragraph. 1051 * 1052 * returns TRUE if the result was exactly under the cursor, otherwise returns 1053 * FALSE, and result is set to the closest position to the coordinates. 1054 */ 1055 static BOOL ME_FindPixelPos(ME_TextEditor *editor, int x, int y, 1056 ME_Cursor *result, BOOL *is_eol, BOOL final_eop) 1057 { 1058 ME_DisplayItem *p = editor->pBuffer->pFirst->member.para.next_para; 1059 BOOL isExact = TRUE; 1060 1061 x -= editor->rcFormat.left; 1062 y -= editor->rcFormat.top; 1063 1064 if (is_eol) 1065 *is_eol = FALSE; 1066 1067 /* find paragraph */ 1068 for (; p != editor->pBuffer->pLast; p = p->member.para.next_para) 1069 { 1070 assert(p->type == diParagraph); 1071 if (y < p->member.para.pt.y + p->member.para.nHeight) 1072 { 1073 if (p->member.para.nFlags & MEPF_ROWSTART) 1074 p = ME_FindPixelPosInTableRow(x, y, p); 1075 y -= p->member.para.pt.y; 1076 p = ME_FindItemFwd(p, diStartRow); 1077 break; 1078 } else if (p->member.para.nFlags & MEPF_ROWSTART) { 1079 p = ME_GetTableRowEnd(p); 1080 } 1081 } 1082 /* find row */ 1083 for (; p != editor->pBuffer->pLast; ) 1084 { 1085 ME_DisplayItem *pp; 1086 assert(p->type == diStartRow); 1087 if (y < p->member.row.pt.y + p->member.row.nHeight) break; 1088 pp = ME_FindItemFwd(p, diStartRow); 1089 if (!pp) break; 1090 p = pp; 1091 } 1092 if (p == editor->pBuffer->pLast && !final_eop) 1093 { 1094 /* The position is below the last paragraph, so the last row will be used 1095 * rather than the end of the text, so the x position will be used to 1096 * determine the offset closest to the pixel position. */ 1097 isExact = FALSE; 1098 p = ME_FindItemBack(p, diStartRow); 1099 if (!p) p = editor->pBuffer->pLast; 1100 } 1101 1102 assert( p->type == diStartRow || p == editor->pBuffer->pLast ); 1103 1104 if( p->type == diStartRow ) 1105 return ME_FindRunInRow( editor, p, x, result, is_eol ) && isExact; 1106 1107 ME_SetCursorToEnd(editor, result, TRUE); 1108 return FALSE; 1109 } 1110 1111 1112 /* Sets the cursor to the position closest to the pixel position 1113 * 1114 * x & y are pixel positions in client coordinates. 1115 * 1116 * isExact will be set to TRUE if the run is directly under the pixel 1117 * position, FALSE if it not, unless isExact is set to NULL. 1118 * 1119 * return FALSE if outside client area and the cursor is not set, 1120 * otherwise TRUE is returned. 1121 */ 1122 BOOL ME_CharFromPos(ME_TextEditor *editor, int x, int y, 1123 ME_Cursor *cursor, BOOL *isExact) 1124 { 1125 RECT rc; 1126 BOOL bResult; 1127 1128 ITextHost_TxGetClientRect(editor->texthost, &rc); 1129 if (x < 0 || y < 0 || x >= rc.right || y >= rc.bottom) { 1130 if (isExact) *isExact = FALSE; 1131 return FALSE; 1132 } 1133 x += editor->horz_si.nPos; 1134 y += editor->vert_si.nPos; 1135 bResult = ME_FindPixelPos(editor, x, y, cursor, NULL, FALSE); 1136 if (isExact) *isExact = bResult; 1137 return TRUE; 1138 } 1139 1140 1141 1142 /* Extends the selection with a word, line, or paragraph selection type. 1143 * 1144 * The selection is anchored by editor->pCursors[2-3] such that the text 1145 * between the anchors will remain selected, and one end will be extended. 1146 * 1147 * editor->pCursors[0] should have the position to extend the selection to 1148 * before this function is called. 1149 * 1150 * Nothing will be done if editor->nSelectionType equals stPosition. 1151 */ 1152 static void ME_ExtendAnchorSelection(ME_TextEditor *editor) 1153 { 1154 ME_Cursor tmp_cursor; 1155 int curOfs, anchorStartOfs, anchorEndOfs; 1156 if (editor->nSelectionType == stPosition || editor->nSelectionType == stDocument) 1157 return; 1158 curOfs = ME_GetCursorOfs(&editor->pCursors[0]); 1159 anchorStartOfs = ME_GetCursorOfs(&editor->pCursors[3]); 1160 anchorEndOfs = ME_GetCursorOfs(&editor->pCursors[2]); 1161 1162 tmp_cursor = editor->pCursors[0]; 1163 editor->pCursors[0] = editor->pCursors[2]; 1164 editor->pCursors[1] = editor->pCursors[3]; 1165 if (curOfs < anchorStartOfs) 1166 { 1167 /* Extend the left side of selection */ 1168 editor->pCursors[1] = tmp_cursor; 1169 if (editor->nSelectionType == stWord) 1170 ME_MoveCursorWords(editor, &editor->pCursors[1], -1); 1171 else 1172 { 1173 ME_DisplayItem *pItem; 1174 ME_DIType searchType = ((editor->nSelectionType == stLine) ? 1175 diStartRowOrParagraph:diParagraph); 1176 pItem = ME_FindItemBack(editor->pCursors[1].pRun, searchType); 1177 editor->pCursors[1].pRun = ME_FindItemFwd(pItem, diRun); 1178 editor->pCursors[1].pPara = ME_GetParagraph(editor->pCursors[1].pRun); 1179 editor->pCursors[1].nOffset = 0; 1180 } 1181 } 1182 else if (curOfs >= anchorEndOfs) 1183 { 1184 /* Extend the right side of selection */ 1185 editor->pCursors[0] = tmp_cursor; 1186 if (editor->nSelectionType == stWord) 1187 ME_MoveCursorWords(editor, &editor->pCursors[0], +1); 1188 else 1189 { 1190 ME_DisplayItem *pItem; 1191 ME_DIType searchType = ((editor->nSelectionType == stLine) ? 1192 diStartRowOrParagraphOrEnd:diParagraphOrEnd); 1193 pItem = ME_FindItemFwd(editor->pCursors[0].pRun, searchType); 1194 if (pItem->type == diTextEnd) 1195 editor->pCursors[0].pRun = ME_FindItemBack(pItem, diRun); 1196 else 1197 editor->pCursors[0].pRun = ME_FindItemFwd(pItem, diRun); 1198 editor->pCursors[0].pPara = ME_GetParagraph(editor->pCursors[0].pRun); 1199 editor->pCursors[0].nOffset = 0; 1200 } 1201 } 1202 } 1203 1204 void ME_LButtonDown(ME_TextEditor *editor, int x, int y, int clickNum) 1205 { 1206 ME_Cursor tmp_cursor; 1207 BOOL is_selection = FALSE, is_shift; 1208 1209 editor->nUDArrowX = -1; 1210 1211 x += editor->horz_si.nPos; 1212 y += editor->vert_si.nPos; 1213 1214 tmp_cursor = editor->pCursors[0]; 1215 is_selection = ME_IsSelection(editor); 1216 is_shift = GetKeyState(VK_SHIFT) < 0; 1217 1218 ME_FindPixelPos(editor, x, y, &editor->pCursors[0], &editor->bCaretAtEnd, FALSE); 1219 1220 if (x >= editor->rcFormat.left || is_shift) 1221 { 1222 if (clickNum > 1) 1223 { 1224 editor->pCursors[1] = editor->pCursors[0]; 1225 if (is_shift) { 1226 if (x >= editor->rcFormat.left) 1227 ME_SelectByType(editor, stWord); 1228 else 1229 ME_SelectByType(editor, stParagraph); 1230 } else if (clickNum % 2 == 0) { 1231 ME_SelectByType(editor, stWord); 1232 } else { 1233 ME_SelectByType(editor, stParagraph); 1234 } 1235 } 1236 else if (!is_shift) 1237 { 1238 editor->nSelectionType = stPosition; 1239 editor->pCursors[1] = editor->pCursors[0]; 1240 } 1241 else if (!is_selection) 1242 { 1243 editor->nSelectionType = stPosition; 1244 editor->pCursors[1] = tmp_cursor; 1245 } 1246 else if (editor->nSelectionType != stPosition) 1247 { 1248 ME_ExtendAnchorSelection(editor); 1249 } 1250 } 1251 else 1252 { 1253 if (clickNum < 2) { 1254 ME_SelectByType(editor, stLine); 1255 } else if (clickNum % 2 == 0 || is_shift) { 1256 ME_SelectByType(editor, stParagraph); 1257 } else { 1258 ME_SelectByType(editor, stDocument); 1259 } 1260 } 1261 ME_InvalidateSelection(editor); 1262 update_caret(editor); 1263 ME_SendSelChange(editor); 1264 } 1265 1266 void ME_MouseMove(ME_TextEditor *editor, int x, int y) 1267 { 1268 ME_Cursor tmp_cursor; 1269 1270 if (editor->nSelectionType == stDocument) 1271 return; 1272 x += editor->horz_si.nPos; 1273 y += editor->vert_si.nPos; 1274 1275 tmp_cursor = editor->pCursors[0]; 1276 /* FIXME: do something with the return value of ME_FindPixelPos */ 1277 ME_FindPixelPos(editor, x, y, &tmp_cursor, &editor->bCaretAtEnd, TRUE); 1278 1279 ME_InvalidateSelection(editor); 1280 editor->pCursors[0] = tmp_cursor; 1281 ME_ExtendAnchorSelection(editor); 1282 1283 if (editor->nSelectionType != stPosition && 1284 memcmp(&editor->pCursors[1], &editor->pCursors[3], sizeof(ME_Cursor))) 1285 { 1286 /* The scroll the cursor towards the other end, since it was the one 1287 * extended by ME_ExtendAnchorSelection */ 1288 ME_EnsureVisible(editor, &editor->pCursors[1]); 1289 } else { 1290 ME_EnsureVisible(editor, &editor->pCursors[0]); 1291 } 1292 1293 ME_InvalidateSelection(editor); 1294 update_caret(editor); 1295 ME_SendSelChange(editor); 1296 } 1297 1298 static int ME_GetXForArrow(ME_TextEditor *editor, ME_Cursor *pCursor) 1299 { 1300 ME_DisplayItem *pRun = pCursor->pRun; 1301 int x; 1302 1303 if (editor->nUDArrowX != -1) 1304 x = editor->nUDArrowX; 1305 else { 1306 if (editor->bCaretAtEnd) 1307 { 1308 pRun = ME_FindItemBack(pRun, diRun); 1309 assert(pRun); 1310 x = pRun->member.run.pt.x + pRun->member.run.nWidth; 1311 } 1312 else { 1313 x = pRun->member.run.pt.x; 1314 x += ME_PointFromChar(editor, &pRun->member.run, pCursor->nOffset, TRUE); 1315 } 1316 editor->nUDArrowX = x; 1317 } 1318 return x; 1319 } 1320 1321 1322 static void 1323 ME_MoveCursorLines(ME_TextEditor *editor, ME_Cursor *pCursor, int nRelOfs, BOOL extend) 1324 { 1325 ME_DisplayItem *pRun = pCursor->pRun; 1326 ME_DisplayItem *pOldPara = pCursor->pPara; 1327 ME_DisplayItem *pItem, *pNewPara; 1328 int x = ME_GetXForArrow(editor, pCursor); 1329 1330 if (editor->bCaretAtEnd && !pCursor->nOffset) 1331 if (!ME_PrevRun(&pOldPara, &pRun, TRUE)) 1332 return; 1333 1334 if (nRelOfs == -1) 1335 { 1336 /* start of this row */ 1337 pItem = ME_FindItemBack(pRun, diStartRow); 1338 assert(pItem); 1339 /* start of the previous row */ 1340 pItem = ME_FindItemBack(pItem, diStartRow); 1341 if (!pItem) /* row not found */ 1342 { 1343 if (extend) 1344 ME_SetCursorToStart(editor, pCursor); 1345 return; 1346 } 1347 pNewPara = ME_GetParagraph(pItem); 1348 if (pOldPara->member.para.nFlags & MEPF_ROWEND || 1349 (pOldPara->member.para.pCell && 1350 pOldPara->member.para.pCell != pNewPara->member.para.pCell)) 1351 { 1352 /* Brought out of a cell */ 1353 pNewPara = ME_GetTableRowStart(pOldPara)->member.para.prev_para; 1354 if (pNewPara->type == diTextStart) 1355 return; /* At the top, so don't go anywhere. */ 1356 pItem = ME_FindItemFwd(pNewPara, diStartRow); 1357 } 1358 if (pNewPara->member.para.nFlags & MEPF_ROWEND) 1359 { 1360 /* Brought into a table row */ 1361 ME_Cell *cell = &ME_FindItemBack(pNewPara, diCell)->member.cell; 1362 while (x < cell->pt.x && cell->prev_cell) 1363 cell = &cell->prev_cell->member.cell; 1364 if (cell->next_cell) /* else - we are still at the end of the row */ 1365 pItem = ME_FindItemBack(cell->next_cell, diStartRow); 1366 } 1367 } 1368 else 1369 { 1370 /* start of the next row */ 1371 pItem = ME_FindItemFwd(pRun, diStartRow); 1372 if (!pItem) /* row not found */ 1373 { 1374 if (extend) 1375 ME_SetCursorToEnd(editor, pCursor, TRUE); 1376 return; 1377 } 1378 pNewPara = ME_GetParagraph(pItem); 1379 if (pOldPara->member.para.nFlags & MEPF_ROWSTART || 1380 (pOldPara->member.para.pCell && 1381 pOldPara->member.para.pCell != pNewPara->member.para.pCell)) 1382 { 1383 /* Brought out of a cell */ 1384 pNewPara = ME_GetTableRowEnd(pOldPara)->member.para.next_para; 1385 if (pNewPara->type == diTextEnd) 1386 return; /* At the bottom, so don't go anywhere. */ 1387 pItem = ME_FindItemFwd(pNewPara, diStartRow); 1388 } 1389 if (pNewPara->member.para.nFlags & MEPF_ROWSTART) 1390 { 1391 /* Brought into a table row */ 1392 ME_DisplayItem *cell = ME_FindItemFwd(pNewPara, diCell); 1393 while (cell->member.cell.next_cell && 1394 x >= cell->member.cell.next_cell->member.cell.pt.x) 1395 cell = cell->member.cell.next_cell; 1396 pItem = ME_FindItemFwd(cell, diStartRow); 1397 } 1398 } 1399 if (!pItem) 1400 { 1401 /* row not found - ignore */ 1402 return; 1403 } 1404 ME_FindRunInRow(editor, pItem, x, pCursor, &editor->bCaretAtEnd); 1405 assert(pCursor->pRun); 1406 assert(pCursor->pRun->type == diRun); 1407 } 1408 1409 static void ME_ArrowPageUp(ME_TextEditor *editor, ME_Cursor *pCursor) 1410 { 1411 ME_DisplayItem *p = ME_FindItemFwd(editor->pBuffer->pFirst, diStartRow); 1412 1413 if (editor->vert_si.nPos < p->member.row.nHeight) 1414 { 1415 ME_SetCursorToStart(editor, pCursor); 1416 editor->bCaretAtEnd = FALSE; 1417 /* Native clears seems to clear this x value on page up at the top 1418 * of the text, but not on page down at the end of the text. 1419 * Doesn't make sense, but we try to be bug for bug compatible. */ 1420 editor->nUDArrowX = -1; 1421 } else { 1422 ME_DisplayItem *pRun = pCursor->pRun; 1423 ME_DisplayItem *pLast; 1424 int x, y, yd, yp; 1425 int yOldScrollPos = editor->vert_si.nPos; 1426 1427 x = ME_GetXForArrow(editor, pCursor); 1428 if (!pCursor->nOffset && editor->bCaretAtEnd) 1429 pRun = ME_FindItemBack(pRun, diRun); 1430 1431 p = ME_FindItemBack(pRun, diStartRowOrParagraph); 1432 assert(p->type == diStartRow); 1433 yp = ME_FindItemBack(p, diParagraph)->member.para.pt.y; 1434 y = yp + p->member.row.pt.y; 1435 1436 ME_ScrollUp(editor, editor->sizeWindow.cy); 1437 /* Only move the cursor by the amount scrolled. */ 1438 yd = y + editor->vert_si.nPos - yOldScrollPos; 1439 pLast = p; 1440 1441 do { 1442 p = ME_FindItemBack(p, diStartRowOrParagraph); 1443 if (!p) 1444 break; 1445 if (p->type == diParagraph) { /* crossing paragraphs */ 1446 if (p->member.para.prev_para == NULL) 1447 break; 1448 yp = p->member.para.prev_para->member.para.pt.y; 1449 continue; 1450 } 1451 y = yp + p->member.row.pt.y; 1452 if (y < yd) 1453 break; 1454 pLast = p; 1455 } while(1); 1456 1457 ME_FindRunInRow(editor, pLast, x, pCursor, &editor->bCaretAtEnd); 1458 } 1459 assert(pCursor->pRun); 1460 assert(pCursor->pRun->type == diRun); 1461 } 1462 1463 static void ME_ArrowPageDown(ME_TextEditor *editor, ME_Cursor *pCursor) 1464 { 1465 ME_DisplayItem *pLast; 1466 int x, y; 1467 1468 /* Find y position of the last row */ 1469 pLast = editor->pBuffer->pLast; 1470 y = pLast->member.para.prev_para->member.para.pt.y 1471 + ME_FindItemBack(pLast, diStartRow)->member.row.pt.y; 1472 1473 x = ME_GetXForArrow(editor, pCursor); 1474 1475 if (editor->vert_si.nPos >= y - editor->sizeWindow.cy) 1476 { 1477 ME_SetCursorToEnd(editor, pCursor, FALSE); 1478 editor->bCaretAtEnd = FALSE; 1479 } else { 1480 ME_DisplayItem *pRun = pCursor->pRun; 1481 ME_DisplayItem *p; 1482 int yd, yp; 1483 int yOldScrollPos = editor->vert_si.nPos; 1484 1485 if (!pCursor->nOffset && editor->bCaretAtEnd) 1486 pRun = ME_FindItemBack(pRun, diRun); 1487 1488 p = ME_FindItemBack(pRun, diStartRowOrParagraph); 1489 assert(p->type == diStartRow); 1490 yp = ME_FindItemBack(p, diParagraph)->member.para.pt.y; 1491 y = yp + p->member.row.pt.y; 1492 1493 /* For native richedit controls: 1494 * v1.0 - v3.1 can only scroll down as far as the scrollbar lets us 1495 * v4.1 can scroll past this position here. */ 1496 ME_ScrollDown(editor, editor->sizeWindow.cy); 1497 /* Only move the cursor by the amount scrolled. */ 1498 yd = y + editor->vert_si.nPos - yOldScrollPos; 1499 pLast = p; 1500 1501 do { 1502 p = ME_FindItemFwd(p, diStartRowOrParagraph); 1503 if (!p) 1504 break; 1505 if (p->type == diParagraph) { 1506 yp = p->member.para.pt.y; 1507 continue; 1508 } 1509 y = yp + p->member.row.pt.y; 1510 if (y >= yd) 1511 break; 1512 pLast = p; 1513 } while(1); 1514 1515 ME_FindRunInRow(editor, pLast, x, pCursor, &editor->bCaretAtEnd); 1516 } 1517 assert(pCursor->pRun); 1518 assert(pCursor->pRun->type == diRun); 1519 } 1520 1521 static void ME_ArrowHome(ME_TextEditor *editor, ME_Cursor *pCursor) 1522 { 1523 ME_DisplayItem *pRow = ME_FindItemBack(pCursor->pRun, diStartRow); 1524 if (pRow) { 1525 ME_DisplayItem *pRun; 1526 if (editor->bCaretAtEnd && !pCursor->nOffset) { 1527 pRow = ME_FindItemBack(pRow, diStartRow); 1528 if (!pRow) 1529 return; 1530 } 1531 pRun = ME_FindItemFwd(pRow, diRun); 1532 if (pRun) { 1533 pCursor->pRun = pRun; 1534 assert(pCursor->pPara == ME_GetParagraph(pRun)); 1535 pCursor->nOffset = 0; 1536 } 1537 } 1538 editor->bCaretAtEnd = FALSE; 1539 } 1540 1541 static void ME_ArrowCtrlHome(ME_TextEditor *editor, ME_Cursor *pCursor) 1542 { 1543 ME_SetCursorToStart(editor, pCursor); 1544 editor->bCaretAtEnd = FALSE; 1545 } 1546 1547 static void ME_ArrowEnd(ME_TextEditor *editor, ME_Cursor *pCursor) 1548 { 1549 ME_DisplayItem *pRow; 1550 1551 if (editor->bCaretAtEnd && !pCursor->nOffset) 1552 return; 1553 1554 pRow = ME_FindItemFwd(pCursor->pRun, diStartRowOrParagraphOrEnd); 1555 assert(pRow); 1556 if (pRow->type == diStartRow) { 1557 ME_DisplayItem *pRun = ME_FindItemFwd(pRow, diRun); 1558 assert(pRun); 1559 pCursor->pRun = pRun; 1560 assert(pCursor->pPara == ME_GetParagraph(pCursor->pRun)); 1561 pCursor->nOffset = 0; 1562 editor->bCaretAtEnd = TRUE; 1563 return; 1564 } 1565 pCursor->pRun = ME_FindItemBack(pRow, diRun); 1566 assert(pCursor->pRun && pCursor->pRun->member.run.nFlags & MERF_ENDPARA); 1567 assert(pCursor->pPara == ME_GetParagraph(pCursor->pRun)); 1568 pCursor->nOffset = 0; 1569 editor->bCaretAtEnd = FALSE; 1570 } 1571 1572 static void ME_ArrowCtrlEnd(ME_TextEditor *editor, ME_Cursor *pCursor) 1573 { 1574 ME_SetCursorToEnd(editor, pCursor, FALSE); 1575 editor->bCaretAtEnd = FALSE; 1576 } 1577 1578 BOOL ME_IsSelection(ME_TextEditor *editor) 1579 { 1580 return editor->pCursors[0].pRun != editor->pCursors[1].pRun || 1581 editor->pCursors[0].nOffset != editor->pCursors[1].nOffset; 1582 } 1583 1584 void ME_DeleteSelection(ME_TextEditor *editor) 1585 { 1586 int from, to; 1587 int nStartCursor = ME_GetSelectionOfs(editor, &from, &to); 1588 int nEndCursor = nStartCursor ^ 1; 1589 ME_DeleteTextAtCursor(editor, nStartCursor, to - from); 1590 editor->pCursors[nEndCursor] = editor->pCursors[nStartCursor]; 1591 } 1592 1593 ME_Style *ME_GetSelectionInsertStyle(ME_TextEditor *editor) 1594 { 1595 return ME_GetInsertStyle(editor, 0); 1596 } 1597 1598 void ME_SendSelChange(ME_TextEditor *editor) 1599 { 1600 SELCHANGE sc; 1601 1602 sc.nmhdr.hwndFrom = NULL; 1603 sc.nmhdr.idFrom = 0; 1604 sc.nmhdr.code = EN_SELCHANGE; 1605 ME_GetSelectionOfs(editor, &sc.chrg.cpMin, &sc.chrg.cpMax); 1606 sc.seltyp = SEL_EMPTY; 1607 if (sc.chrg.cpMin != sc.chrg.cpMax) 1608 sc.seltyp |= SEL_TEXT; 1609 if (sc.chrg.cpMin < sc.chrg.cpMax+1) /* what were RICHEDIT authors thinking ? */ 1610 sc.seltyp |= SEL_MULTICHAR; 1611 1612 if (sc.chrg.cpMin != editor->notified_cr.cpMin || sc.chrg.cpMax != editor->notified_cr.cpMax) 1613 { 1614 ME_ClearTempStyle(editor); 1615 1616 editor->notified_cr = sc.chrg; 1617 1618 if (editor->nEventMask & ENM_SELCHANGE) 1619 { 1620 TRACE("cpMin=%d cpMax=%d seltyp=%d (%s %s)\n", 1621 sc.chrg.cpMin, sc.chrg.cpMax, sc.seltyp, 1622 (sc.seltyp & SEL_TEXT) ? "SEL_TEXT" : "", 1623 (sc.seltyp & SEL_MULTICHAR) ? "SEL_MULTICHAR" : ""); 1624 ITextHost_TxNotify(editor->texthost, sc.nmhdr.code, &sc); 1625 } 1626 } 1627 } 1628 1629 BOOL 1630 ME_ArrowKey(ME_TextEditor *editor, int nVKey, BOOL extend, BOOL ctrl) 1631 { 1632 int nCursor = 0; 1633 ME_Cursor *p = &editor->pCursors[nCursor]; 1634 ME_Cursor tmp_curs = *p; 1635 BOOL success = FALSE; 1636 1637 ME_CheckCharOffsets(editor); 1638 switch(nVKey) { 1639 case VK_LEFT: 1640 editor->bCaretAtEnd = FALSE; 1641 if (ctrl) 1642 success = ME_MoveCursorWords(editor, &tmp_curs, -1); 1643 else 1644 success = ME_MoveCursorChars(editor, &tmp_curs, -1, extend); 1645 break; 1646 case VK_RIGHT: 1647 editor->bCaretAtEnd = FALSE; 1648 if (ctrl) 1649 success = ME_MoveCursorWords(editor, &tmp_curs, +1); 1650 else 1651 success = ME_MoveCursorChars(editor, &tmp_curs, +1, extend); 1652 break; 1653 case VK_UP: 1654 ME_MoveCursorLines(editor, &tmp_curs, -1, extend); 1655 break; 1656 case VK_DOWN: 1657 ME_MoveCursorLines(editor, &tmp_curs, +1, extend); 1658 break; 1659 case VK_PRIOR: 1660 ME_ArrowPageUp(editor, &tmp_curs); 1661 break; 1662 case VK_NEXT: 1663 ME_ArrowPageDown(editor, &tmp_curs); 1664 break; 1665 case VK_HOME: { 1666 if (ctrl) 1667 ME_ArrowCtrlHome(editor, &tmp_curs); 1668 else 1669 ME_ArrowHome(editor, &tmp_curs); 1670 editor->bCaretAtEnd = FALSE; 1671 break; 1672 } 1673 case VK_END: 1674 if (ctrl) 1675 ME_ArrowCtrlEnd(editor, &tmp_curs); 1676 else 1677 ME_ArrowEnd(editor, &tmp_curs); 1678 break; 1679 } 1680 1681 if (!extend) 1682 editor->pCursors[1] = tmp_curs; 1683 *p = tmp_curs; 1684 1685 ME_InvalidateSelection(editor); 1686 ME_Repaint(editor); 1687 hide_caret(editor); 1688 ME_EnsureVisible(editor, &tmp_curs); 1689 update_caret(editor); 1690 ME_SendSelChange(editor); 1691 return success; 1692 } 1693