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