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