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