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.nYPos + 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 } else {
246 DestroyCaret();
247 }
248 }
249
250
251 void ME_ShowCaret(ME_TextEditor *ed)
252 {
253 ME_MoveCaret(ed);
254 if(ed->bHaveFocus && !ME_IsSelection(ed))
255 ShowCaret(ed->hWnd);
256 }
257
258 void ME_HideCaret(ME_TextEditor *ed)
259 {
260 if(!ed->bHaveFocus || ME_IsSelection(ed))
261 {
262 HideCaret(ed->hWnd);
263 DestroyCaret();
264 }
265 }
266
267 void ME_InternalDeleteText(ME_TextEditor *editor, int nOfs,
268 int nChars)
269 {
270 ME_Cursor c;
271 int shift = 0;
272
273 while(nChars > 0)
274 {
275 ME_Run *run;
276 ME_CursorFromCharOfs(editor, nOfs, &c);
277 run = &c.pRun->member.run;
278 if (run->nFlags & MERF_ENDPARA) {
279 int eollen = run->nCR + run->nLF;
280
281 if (!ME_FindItemFwd(c.pRun, diParagraph))
282 {
283 return;
284 }
285 ME_JoinParagraphs(editor, ME_GetParagraph(c.pRun));
286 /* ME_SkipAndPropagateCharOffset(p->pRun, shift); */
287 ME_CheckCharOffsets(editor);
288 nChars -= (eollen < nChars) ? eollen : nChars;
289 continue;
290 }
291 else
292 {
293 ME_Cursor cursor;
294 int nIntendedChars = nChars;
295 int nCharsToDelete = nChars;
296 int i;
297 int loc = c.nOffset;
298
299 ME_FindItemBack(c.pRun, diParagraph)->member.para.nFlags |= MEPF_REWRAP;
300
301 cursor = c;
302 ME_StrRelPos(run->strText, loc, &nChars);
303 /* nChars is the number of characters that should be deleted from the
304 FOLLOWING runs (these AFTER cursor.pRun)
305 nCharsToDelete is a number of chars to delete from THIS run */
306 nCharsToDelete -= nChars;
307 shift -= nCharsToDelete;
308 TRACE("Deleting %d (intended %d-remaning %d) chars at %d in '%s' (%d)\n",
309 nCharsToDelete, nIntendedChars, nChars, c.nOffset,
310 debugstr_w(run->strText->szData), run->strText->nLen);
311
312 if (!c.nOffset && ME_StrVLen(run->strText) == nCharsToDelete)
313 {
314 /* undo = reinsert whole run */
315 /* nOfs is a character offset (from the start of the document
316 to the current (deleted) run */
317 ME_UndoItem *pUndo = ME_AddUndoItem(editor, diUndoInsertRun, c.pRun);
318 if (pUndo)
319 pUndo->di.member.run.nCharOfs = nOfs;
320 }
321 else
322 {
323 /* undo = reinsert partial run */
324 ME_UndoItem *pUndo = ME_AddUndoItem(editor, diUndoInsertRun, c.pRun);
325 if (pUndo) {
326 ME_DestroyString(pUndo->di.member.run.strText);
327 pUndo->di.member.run.nCharOfs = nOfs;
328 pUndo->di.member.run.strText = ME_MakeStringN(run->strText->szData+c.nOffset, nCharsToDelete);
329 }
330 }
331 TRACE("Post deletion string: %s (%d)\n", debugstr_w(run->strText->szData), run->strText->nLen);
332 TRACE("Shift value: %d\n", shift);
333 ME_StrDeleteV(run->strText, c.nOffset, nCharsToDelete);
334
335 /* update cursors (including c) */
336 for (i=-1; i<editor->nCursors; i++) {
337 ME_Cursor *pThisCur = editor->pCursors + i;
338 if (i == -1) pThisCur = &c;
339 if (pThisCur->pRun == cursor.pRun) {
340 if (pThisCur->nOffset > cursor.nOffset) {
341 if (pThisCur->nOffset-cursor.nOffset < nCharsToDelete)
342 pThisCur->nOffset = cursor.nOffset;
343 else
344 pThisCur->nOffset -= nCharsToDelete;
345 assert(pThisCur->nOffset >= 0);
346 assert(pThisCur->nOffset <= ME_StrVLen(run->strText));
347 }
348 if (pThisCur->nOffset == ME_StrVLen(run->strText))
349 {
350 pThisCur->pRun = ME_FindItemFwd(pThisCur->pRun, diRunOrParagraphOrEnd);
351 assert(pThisCur->pRun->type == diRun);
352 pThisCur->nOffset = 0;
353 }
354 }
355 }
356
357 /* c = updated data now */
358
359 if (c.pRun == cursor.pRun)
360 ME_SkipAndPropagateCharOffset(c.pRun, shift);
361 else
362 ME_PropagateCharOffset(c.pRun, shift);
363
364 if (!ME_StrVLen(cursor.pRun->member.run.strText))
365 {
366 TRACE("Removing useless run\n");
367 ME_Remove(cursor.pRun);
368 ME_DestroyDisplayItem(cursor.pRun);
369 }
370
371 shift = 0;
372 /*
373 ME_CheckCharOffsets(editor);
374 */
375 continue;
376 }
377 }
378 }
379
380 void ME_DeleteTextAtCursor(ME_TextEditor *editor, int nCursor,
381 int nChars)
382 {
383 assert(nCursor>=0 && nCursor<editor->nCursors);
384 /* text operations set modified state */
385 editor->nModifyStep = 1;
386 ME_InternalDeleteText(editor, ME_GetCursorOfs(editor, nCursor), nChars);
387 }
388
389 static ME_DisplayItem *
390 ME_InternalInsertTextFromCursor(ME_TextEditor *editor, int nCursor,
391 const WCHAR *str, int len, ME_Style *style,
392 int flags)
393 {
394 ME_Cursor *p = &editor->pCursors[nCursor];
395
396 editor->bCaretAtEnd = FALSE;
397
398 assert(p->pRun->type == diRun);
399
400 return ME_InsertRunAtCursor(editor, p, style, str, len, flags);
401 }
402
403
404 void ME_InsertOLEFromCursor(ME_TextEditor *editor, const REOBJECT* reo, int nCursor)
405 {
406 ME_Style *pStyle = ME_GetInsertStyle(editor, nCursor);
407 ME_DisplayItem *di;
408 WCHAR space = ' ';
409
410 /* FIXME no no no */
411 if (ME_IsSelection(editor))
412 ME_DeleteSelection(editor);
413
414 di = ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, pStyle,
415 MERF_GRAPHICS);
416 di->member.run.ole_obj = ALLOC_OBJ(*reo);
417 ME_CopyReObject(di->member.run.ole_obj, reo);
418 ME_SendSelChange(editor);
419 }
420
421
422 void ME_InsertEndRowFromCursor(ME_TextEditor *editor, int nCursor)
423 {
424 ME_Style *pStyle = ME_GetInsertStyle(editor, nCursor);
425 ME_DisplayItem *di;
426 WCHAR space = ' ';
427
428 /* FIXME no no no */
429 if (ME_IsSelection(editor))
430 ME_DeleteSelection(editor);
431
432 di = ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, pStyle,
433 MERF_ENDROW);
434 ME_SendSelChange(editor);
435 }
436
437 void
438 ME_InsertTableCellFromCursor(ME_TextEditor *editor, int nCursor)
439 {
440 WCHAR tab = '\t';
441 ME_DisplayItem *p, *run;
442 ME_Style *pStyle = ME_GetInsertStyle(editor, nCursor);
443
444 p = ME_InternalInsertTextFromCursor(editor, nCursor, &tab, 1, pStyle,
445 MERF_CELL);
446 run = p;
447 while ((run = ME_FindItemBack(run, diRunOrParagraph))->type == diRun)
448 {
449 if (run->member.run.nFlags & MERF_CELL)
450 {
451 assert(run->member.run.pCell->next);
452 p->member.run.pCell = run->member.run.pCell->next;
453 return;
454 }
455 }
456 assert(run->type == diParagraph);
457 assert(run->member.para.pFmt);
458 assert(run->member.para.pFmt->dwMask & PFM_TABLE);
459 assert(run->member.para.pFmt->wEffects & PFE_TABLE);
460 assert(run->member.para.pCells);
461 p->member.run.pCell = run->member.para.pCells;
462 }
463
464
465 void ME_InsertTextFromCursor(ME_TextEditor *editor, int nCursor,
466 const WCHAR *str, int len, ME_Style *style)
467 {
468 const WCHAR *pos;
469 ME_Cursor *p = NULL;
470 int oldLen;
471
472 /* FIXME really HERE ? */
473 if (ME_IsSelection(editor))
474 ME_DeleteSelection(editor);
475
476 /* FIXME: is this too slow? */
477 /* Didn't affect performance for WM_SETTEXT (around 50sec/30K) */
478 oldLen = ME_GetTextLength(editor);
479
480 /* text operations set modified state */
481 editor->nModifyStep = 1;
482
483 assert(style);
484
485 assert(nCursor>=0 && nCursor<editor->nCursors);
486 if (len == -1)
487 len = lstrlenW(str);
488
489 /* grow the text limit to fit our text */
490 if(editor->nTextLimit < oldLen +len)
491 editor->nTextLimit = oldLen + len;
492
493 while (len)
494 {
495 pos = str;
496 /* FIXME this sucks - no respect for unicode (what else can be a line separator in unicode?) */
497 while(pos-str < len && *pos != '\r' && *pos != '\n' && *pos != '\t')
498 pos++;
499 if (pos-str < len && *pos == '\t') { /* handle tabs */
500 WCHAR tab = '\t';
501
502 if (pos!=str)
503 ME_InternalInsertTextFromCursor(editor, nCursor, str, pos-str, style, 0);
504
505 ME_InternalInsertTextFromCursor(editor, nCursor, &tab, 1, style, MERF_TAB);
506
507 pos++;
508 if(pos-str <= len) {
509 len -= pos - str;
510 str = pos;
511 continue;
512 }
513 }
514 /* handle special \r\r\n sequence (richedit 2.x and higher only) */
515 if (!editor->bEmulateVersion10 && pos-str < len-2 && pos[0] == '\r' && pos[1] == '\r' && pos[2] == '\n') {
516 WCHAR space = ' ';
517
518 if (pos!=str)
519 ME_InternalInsertTextFromCursor(editor, nCursor, str, pos-str, style, 0);
520
521 ME_InternalInsertTextFromCursor(editor, nCursor, &space, 1, style, 0);
522
523 pos+=3;
524 if(pos-str <= len) {
525 len -= pos - str;
526 str = pos;
527 continue;
528 }
529 }
530 if (pos-str < len) { /* handle EOLs */
531 ME_DisplayItem *tp, *end_run;
532 ME_Style *tmp_style;
533 int numCR, numLF;
534
535 if (pos!=str)
536 ME_InternalInsertTextFromCursor(editor, nCursor, str, pos-str, style, 0);
537 p = &editor->pCursors[nCursor];
538 if (p->nOffset) {
539 ME_SplitRunSimple(editor, p->pRun, p->nOffset);
540 p = &editor->pCursors[nCursor];
541 }
542 tmp_style = ME_GetInsertStyle(editor, nCursor);
543 /* ME_SplitParagraph increases style refcount */
544
545 /* Encode and fill number of CR and LF according to emulation mode */
546 if (editor->bEmulateVersion10) {
547 const WCHAR * tpos;
548
549 /* We have to find out how many consecutive \r are there, and if there
550 is a \n terminating the run of \r's. */
551 numCR = 0; numLF = 0;
552 tpos = pos;
553 while (tpos-str < len && *tpos == '\r') {
554 tpos++;
555 numCR++;
556 }
557 if (tpos-str >= len) {
558 /* Reached end of text without finding anything but '\r' */
559 if (tpos != pos) {
560 pos++;
561 }
562 numCR = 1; numLF = 0;
563 } else if (*tpos == '\n') {
564 /* The entire run of \r's plus the one \n is one single line break */
565 pos = tpos + 1;
566 numLF = 1;
567 } else {
568 /* Found some other content past the run of \r's */
569 pos++;
570 numCR = 1; numLF = 0;
571 }
572 } else {
573 if(pos-str < len && *pos =='\r')
574 pos++;
575 if(pos-str < len && *pos =='\n')
576 pos++;
577 numCR = 1; numLF = 0;
578 }
579 tp = ME_SplitParagraph(editor, p->pRun, p->pRun->member.run.style, numCR, numLF);
580 p->pRun = ME_FindItemFwd(tp, diRun);
581 end_run = ME_FindItemBack(tp, diRun);
582 ME_ReleaseStyle(end_run->member.run.style);
583 end_run->member.run.style = tmp_style;
584 p->nOffset = 0;
585
586 if(pos-str <= len) {
587 len -= pos - str;
588 str = pos;
589 continue;
590 }
591 }
592 ME_InternalInsertTextFromCursor(editor, nCursor, str, len, style, 0);
593 len = 0;
594 }
595 }
596
597
598 static BOOL
599 ME_MoveCursorChars(ME_TextEditor *editor, ME_Cursor *pCursor, int nRelOfs)
600 {
601 ME_DisplayItem *pRun = pCursor->pRun;
602
603 if (nRelOfs == -1)
604 {
605 if (!pCursor->nOffset)
606 {
607 do {
608 pRun = ME_FindItemBack(pRun, diRunOrParagraph);
609 assert(pRun);
610 switch (pRun->type)
611 {
612 case diRun:
613 break;
614 case diParagraph:
615 if (pRun->member.para.prev_para->type == diTextStart)
616 return FALSE;
617 pRun = ME_FindItemBack(pRun, diRunOrParagraph);
618 /* every paragraph ought to have at least one run */
619 assert(pRun && pRun->type == diRun);
620 assert(pRun->member.run.nFlags & MERF_ENDPARA);
621 break;
622 default:
623 assert(pRun->type != diRun && pRun->type != diParagraph);
624 return FALSE;
625 }
626 } while (RUN_IS_HIDDEN(&pRun->member.run));
627 pCursor->pRun = pRun;
628 if (pRun->member.run.nFlags & MERF_ENDPARA)
629 pCursor->nOffset = 0;
630 else
631 pCursor->nOffset = pRun->member.run.strText->nLen;
632 }
633
634 if (pCursor->nOffset)
635 pCursor->nOffset = ME_StrRelPos2(pCursor->pRun->member.run.strText, pCursor->nOffset, nRelOfs);
636 return TRUE;
637 }
638 else
639 {
640 if (!(pRun->member.run.nFlags & MERF_ENDPARA))
641 {
642 int new_ofs = ME_StrRelPos2(pRun->member.run.strText, pCursor->nOffset, nRelOfs);
643
644 if (new_ofs < pRun->member.run.strText->nLen)
645 {
646 pCursor->nOffset = new_ofs;
647 return TRUE;
648 }
649 }
650 do {
651 pRun = ME_FindItemFwd(pRun, diRun);
652 } while (pRun && RUN_IS_HIDDEN(&pRun->member.run));
653 if (pRun)
654 {
655 pCursor->pRun = pRun;
656 pCursor->nOffset = 0;
657 return TRUE;
658 }
659 }
660 return FALSE;
661 }
662
663
664 static BOOL
665 ME_MoveCursorWords(ME_TextEditor *editor, ME_Cursor *cursor, int nRelOfs)
666 {
667 ME_DisplayItem *pRun = cursor->pRun, *pOtherRun;
668 int nOffset = cursor->nOffset;
669
670 if (nRelOfs == -1)
671 {
672 /* Backward movement */
673 while (TRUE)
674 {
675 nOffset = ME_CallWordBreakProc(editor, pRun->member.run.strText,
676 nOffset, WB_MOVEWORDLEFT);
677 if (nOffset)
678 break;
679 pOtherRun = ME_FindItemBack(pRun, diRunOrParagraph);
680 if (pOtherRun->type == diRun)
681 {
682 if (ME_CallWordBreakProc(editor, pOtherRun->member.run.strText,
683 pOtherRun->member.run.strText->nLen - 1,
684 WB_ISDELIMITER)
685 && !(pRun->member.run.nFlags & MERF_ENDPARA)
686 && !(cursor->pRun == pRun && cursor->nOffset == 0)
687 && !ME_CallWordBreakProc(editor, pRun->member.run.strText, 0,
688 WB_ISDELIMITER))
689 break;
690 pRun = pOtherRun;
691 nOffset = pOtherRun->member.run.strText->nLen;
692 }
693 else if (pOtherRun->type == diParagraph)
694 {
695 if (cursor->pRun == pRun && cursor->nOffset == 0)
696 {
697 /* Paragraph breaks are treated as separate words */
698 if (pOtherRun->member.para.prev_para->type == diTextStart)
699 return FALSE;
700 pRun = ME_FindItemBack(pOtherRun, diRunOrParagraph);
701 }
702 break;
703 }
704 }
705 }
706 else
707 {
708 /* Forward movement */
709 BOOL last_delim = FALSE;
710
711 while (TRUE)
712 {
713 if (last_delim && !ME_CallWordBreakProc(editor, pRun->member.run.strText,
714 nOffset, WB_ISDELIMITER))
715 break;
716 nOffset = ME_CallWordBreakProc(editor, pRun->member.run.strText,
717 nOffset, WB_MOVEWORDRIGHT);
718 if (nOffset < pRun->member.run.strText->nLen)
719 break;
720 pOtherRun = ME_FindItemFwd(pRun, diRunOrParagraphOrEnd);
721 if (pOtherRun->type == diRun)
722 {
723 last_delim = ME_CallWordBreakProc(editor, pRun->member.run.strText,
724 nOffset - 1, WB_ISDELIMITER);
725 pRun = pOtherRun;
726 nOffset = 0;
727 }
728 else if (pOtherRun->type == diParagraph)
729 {
730 if (cursor->pRun == pRun)
731 pRun = ME_FindItemFwd(pOtherRun, diRun);
732 nOffset = 0;
733 break;
734 }
735 else /* diTextEnd */
736 {
737 if (cursor->pRun == pRun)
738 return FALSE;
739 nOffset = 0;
740 break;
741 }
742 }
743 }
744 cursor->pRun = pRun;
745 cursor->nOffset = nOffset;
746 return TRUE;
747 }
748
749
750 void
751 ME_SelectByType(ME_TextEditor *editor, ME_SelectionType selectionType)
752 {
753 /* pCursor[0] will be the start of the selection
754 * pCursor[1] is the other end of the selection range
755 * pCursor[2] and [3] are the selection anchors that are backed up
756 * so they are kept when the selection changes for drag selection.
757 */
758
759 editor->nSelectionType = selectionType;
760 switch(selectionType)
761 {
762 case stPosition:
763 break;
764 case stWord:
765 ME_MoveCursorWords(editor, &editor->pCursors[1], +1);
766 editor->pCursors[0] = editor->pCursors[1];
767 ME_MoveCursorWords(editor, &editor->pCursors[0], -1);
768 break;
769 case stLine:
770 case stParagraph:
771 {
772 ME_DisplayItem *pItem;
773 ME_DIType fwdSearchType, backSearchType;
774 if (selectionType == stParagraph) {
775 backSearchType = diParagraph;
776 fwdSearchType = diParagraphOrEnd;
777 } else {
778 backSearchType = diStartRow;
779 fwdSearchType = diStartRowOrParagraphOrEnd;
780 }
781 pItem = ME_FindItemBack(editor->pCursors[0].pRun, backSearchType);
782 editor->pCursors[0].pRun = ME_FindItemFwd(pItem, diRun);
783 editor->pCursors[0].nOffset = 0;
784
785 pItem = ME_FindItemFwd(editor->pCursors[0].pRun, fwdSearchType);
786 assert(pItem);
787 if (pItem->type == diTextEnd)
788 editor->pCursors[1].pRun = ME_FindItemBack(pItem, diRun);
789 else
790 editor->pCursors[1].pRun = ME_FindItemFwd(pItem, diRun);
791 editor->pCursors[1].nOffset = 0;
792 break;
793 }
794 case stDocument:
795 /* Select everything with cursor anchored from the start of the text */
796 editor->nSelectionType = stDocument;
797 editor->pCursors[1].pRun = ME_FindItemFwd(editor->pBuffer->pFirst, diRun);
798 editor->pCursors[1].nOffset = 0;
799 editor->pCursors[0].pRun = ME_FindItemBack(editor->pBuffer->pLast, diRun);
800 editor->pCursors[0].nOffset = 0;
801 break;
802 default: assert(0);
803 }
804 /* Store the anchor positions for extending the selection. */
805 editor->pCursors[2] = editor->pCursors[0];
806 editor->pCursors[3] = editor->pCursors[1];
807 }
808
809
810 int ME_GetCursorOfs(ME_TextEditor *editor, int nCursor)
811 {
812 ME_Cursor *pCursor = &editor->pCursors[nCursor];
813 return ME_GetParagraph(pCursor->pRun)->member.para.nCharOfs
814 + pCursor->pRun->member.run.nCharOfs + pCursor->nOffset;
815 }
816
817 /* Finds the run and offset from the pixel position.
818 *
819 * x & y are pixel positions in virtual coordinates into the rich edit control,
820 * so client coordinates must first be adjusted by the scroll position.
821 *
822 * returns TRUE if the result was exactly under the cursor, otherwise returns
823 * FALSE, and result is set to the closest position to the coordinates.
824 */
825 static BOOL ME_FindPixelPos(ME_TextEditor *editor, int x, int y,
826 ME_Cursor *result, BOOL *is_eol)
827 {
828 ME_DisplayItem *p = editor->pBuffer->pFirst->member.para.next_para;
829 ME_DisplayItem *last = NULL;
830 int rx = 0;
831 BOOL isExact = TRUE;
832
833 if (is_eol)
834 *is_eol = 0;
835
836 /* find paragraph */
837 for (; p != editor->pBuffer->pLast; p = p->member.para.next_para)
838 {
839 assert(p->type == diParagraph);
840 if (y < p->member.para.nYPos + p->member.para.nHeight)
841 {
842 y -= p->member.para.nYPos;
843 p = ME_FindItemFwd(p, diStartRow);
844 break;
845 }
846 }
847 /* find row */
848 for (; p != editor->pBuffer->pLast; )
849 {
850 ME_DisplayItem *pp;
851 assert(p->type == diStartRow);
852 if (y < p->member.row.nYPos + p->member.row.nHeight)
853 {
854 p = ME_FindItemFwd(p, diRun);
855 break;
856 }
857 pp = ME_FindItemFwd(p, diStartRowOrParagraphOrEnd);
858 if (pp->type != diStartRow)
859 {
860 p = ME_FindItemFwd(p, diRun);
861 break;
862 }
863 p = pp;
864 }
865 if (p == editor->pBuffer->pLast)
866 {
867 /* The position is below the last paragraph, so the last row will be used
868 * rather than the end of the text, so the x position will be used to
869 * determine the offset closest to the pixel position. */
870 isExact = FALSE;
871 p = ME_FindItemBack(p, diStartRow);
872 if (p != NULL){
873 p = ME_FindItemFwd(p, diRun);
874 }
875 else
876 {
877 p = editor->pBuffer->pLast;
878 }
879 }
880 for (; p != editor->pBuffer->pLast; p = p->next)
881 {
882 switch (p->type)
883 {
884 case diRun:
885 rx = x - p->member.run.pt.x;
886 if (rx < p->member.run.nWidth)
887 {
888 found_here:
889 assert(p->type == diRun);
890 if ((p->member.run.nFlags & MERF_ENDPARA) || rx < 0)
891 rx = 0;
892 result->pRun = p;
893 result->nOffset = ME_CharFromPointCursor(editor, rx, &p->member.run);
894 if (editor->pCursors[0].nOffset == p->member.run.strText->nLen && rx)
895 {
896 result->pRun = ME_FindItemFwd(editor->pCursors[0].pRun, diRun);
897 result->nOffset = 0;
898 }
899 return isExact;
900 }
901 break;
902 case diStartRow:
903 isExact = FALSE;
904 p = ME_FindItemFwd(p, diRun);
905 if (is_eol) *is_eol = 1;
906 rx = 0; /* FIXME not sure */
907 goto found_here;
908 case diParagraph:
909 case diTextEnd:
910 isExact = FALSE;
911 rx = 0; /* FIXME not sure */
912 p = last;
913 goto found_here;
914 default: assert(0);
915 }
916 last = p;
917 }
918 result->pRun = ME_FindItemBack(p, diRun);
919 result->nOffset = 0;
920 assert(result->pRun->member.run.nFlags & MERF_ENDPARA);
921 return FALSE;
922 }
923
924
925 /* Returns the character offset closest to the pixel position
926 *
927 * x & y are pixel positions in client coordinates.
928 *
929 * isExact will be set to TRUE if the run is directly under the pixel
930 * position, FALSE if it not, unless isExact is set to NULL.
931 */
932 int ME_CharFromPos(ME_TextEditor *editor, int x, int y, BOOL *isExact)
933 {
934 ME_Cursor cursor;
935 RECT rc;
936 BOOL bResult;
937
938 GetClientRect(editor->hWnd, &rc);
939 if (x < 0 || y < 0 || x >= rc.right || y >= rc.bottom) {
940 if (isExact) *isExact = FALSE;
941 return -1;
942 }
943 y += ME_GetYScrollPos(editor);
944 bResult = ME_FindPixelPos(editor, x, y, &cursor, NULL);
945 if (isExact) *isExact = bResult;
946 return (ME_GetParagraph(cursor.pRun)->member.para.nCharOfs
947 + cursor.pRun->member.run.nCharOfs + cursor.nOffset);
948 }
949
950
951
952 /* Extends the selection with a word, line, or paragraph selection type.
953 *
954 * The selection is anchored by editor->pCursors[2-3] such that the text
955 * between the anchors will remain selected, and one end will be extended.
956 *
957 * editor->pCursors[0] should have the position to extend the selection to
958 * before this function is called.
959 *
960 * Nothing will be done if editor->nSelectionType equals stPosition.
961 */
962 static void ME_ExtendAnchorSelection(ME_TextEditor *editor)
963 {
964 ME_Cursor tmp_cursor;
965 int curOfs, anchorStartOfs, anchorEndOfs;
966 if (editor->nSelectionType == stPosition || editor->nSelectionType == stDocument)
967 return;
968 curOfs = ME_GetCursorOfs(editor, 0);
969 anchorStartOfs = ME_GetCursorOfs(editor, 2);
970 anchorEndOfs = ME_GetCursorOfs(editor, 3);
971
972 tmp_cursor = editor->pCursors[0];
973 editor->pCursors[0] = editor->pCursors[2];
974 editor->pCursors[1] = editor->pCursors[3];
975 if (curOfs < anchorStartOfs)
976 {
977 /* Extend the left side of selection */
978 editor->pCursors[0] = tmp_cursor;
979 if (editor->nSelectionType == stWord)
980 ME_MoveCursorWords(editor, &editor->pCursors[0], -1);
981 else
982 {
983 ME_DisplayItem *pItem;
984 ME_DIType searchType = ((editor->nSelectionType == stLine) ?
985 diStartRowOrParagraph:diParagraph);
986 pItem = ME_FindItemBack(editor->pCursors[0].pRun, searchType);
987 editor->pCursors[0].pRun = ME_FindItemFwd(pItem, diRun);
988 editor->pCursors[0].nOffset = 0;
989 }
990 }
991 else if (curOfs >= anchorEndOfs)
992 {
993 /* Extend the right side of selection */
994 editor->pCursors[1] = tmp_cursor;
995 if (editor->nSelectionType == stWord)
996 ME_MoveCursorWords(editor, &editor->pCursors[1], +1);
997 else
998 {
999 ME_DisplayItem *pItem;
1000 ME_DIType searchType = ((editor->nSelectionType == stLine) ?
1001 diStartRowOrParagraphOrEnd:diParagraphOrEnd);
1002 pItem = ME_FindItemFwd(editor->pCursors[1].pRun, searchType);
1003 if (pItem->type == diTextEnd)
1004 editor->pCursors[1].pRun = ME_FindItemBack(pItem, diRun);
1005 else
1006 editor->pCursors[1].pRun = ME_FindItemFwd(pItem, diRun);
1007 editor->pCursors[1].nOffset = 0;
1008 }
1009 }
1010 }
1011
1012 void ME_LButtonDown(ME_TextEditor *editor, int x, int y, int clickNum)
1013 {
1014 ME_Cursor tmp_cursor;
1015 int is_selection = 0;
1016 BOOL is_shift;
1017
1018 editor->nUDArrowX = -1;
1019
1020 y += ME_GetYScrollPos(editor);
1021
1022 tmp_cursor = editor->pCursors[0];
1023 is_selection = ME_IsSelection(editor);
1024 is_shift = GetKeyState(VK_SHIFT) < 0;
1025
1026 ME_FindPixelPos(editor, x, y, &editor->pCursors[0], &editor->bCaretAtEnd);
1027
1028 if (x >= editor->selofs || is_shift)
1029 {
1030 if (clickNum > 1)
1031 {
1032 editor->pCursors[1] = editor->pCursors[0];
1033 if (is_shift) {
1034 if (x >= editor->selofs)
1035 ME_SelectByType(editor, stWord);
1036 else
1037 ME_SelectByType(editor, stParagraph);
1038 } else if (clickNum % 2 == 0) {
1039 ME_SelectByType(editor, stWord);
1040 } else {
1041 ME_SelectByType(editor, stParagraph);
1042 }
1043 }
1044 else if (!is_shift)
1045 {
1046 editor->nSelectionType = stPosition;
1047 editor->pCursors[1] = editor->pCursors[0];
1048 }
1049 else if (!is_selection)
1050 {
1051 editor->nSelectionType = stPosition;
1052 editor->pCursors[1] = tmp_cursor;
1053 }
1054 else if (editor->nSelectionType != stPosition)
1055 {
1056 ME_ExtendAnchorSelection(editor);
1057 }
1058 }
1059 else
1060 {
1061 if (clickNum < 2) {
1062 ME_SelectByType(editor, stLine);
1063 } else if (clickNum % 2 == 0 || is_shift) {
1064 ME_SelectByType(editor, stParagraph);
1065 } else {
1066 ME_SelectByType(editor, stDocument);
1067 }
1068 }
1069 ME_InvalidateSelection(editor);
1070 HideCaret(editor->hWnd);
1071 ME_ShowCaret(editor);
1072 ME_ClearTempStyle(editor);
1073 ME_SendSelChange(editor);
1074 }
1075
1076 void ME_MouseMove(ME_TextEditor *editor, int x, int y)
1077 {
1078 ME_Cursor tmp_cursor;
1079
1080 if (editor->nSelectionType == stDocument)
1081 return;
1082 y += ME_GetYScrollPos(editor);
1083
1084 tmp_cursor = editor->pCursors[0];
1085 /* FIXME: do something with the return value of ME_FindPixelPos */
1086 ME_FindPixelPos(editor, x, y, &tmp_cursor, &editor->bCaretAtEnd);
1087
1088 ME_InvalidateSelection(editor);
1089 editor->pCursors[0] = tmp_cursor;
1090 ME_ExtendAnchorSelection(editor);
1091
1092 if (editor->nSelectionType != stPosition &&
1093 memcmp(&editor->pCursors[1], &editor->pCursors[3], sizeof(ME_Cursor)))
1094 {
1095 /* The scroll the cursor towards the other end, since it was the one
1096 * extended by ME_ExtendAnchorSelection
1097 */
1098 ME_Cursor tmpCursor = editor->pCursors[0];
1099 editor->pCursors[0] = editor->pCursors[1];
1100 editor->pCursors[1] = tmpCursor;
1101 SendMessageW(editor->hWnd, EM_SCROLLCARET, 0, 0);
1102 editor->pCursors[1] = editor->pCursors[0];
1103 editor->pCursors[0] = tmpCursor;
1104 } else {
1105 SendMessageW(editor->hWnd, EM_SCROLLCARET, 0, 0);
1106 }
1107
1108 ME_InvalidateSelection(editor);
1109 HideCaret(editor->hWnd);
1110 ME_ShowCaret(editor);
1111 ME_SendSelChange(editor);
1112 }
1113
1114 static ME_DisplayItem *ME_FindRunInRow(ME_TextEditor *editor, ME_DisplayItem *pRow,
1115 int x, int *pOffset, int *pbCaretAtEnd)
1116 {
1117 ME_DisplayItem *pNext, *pLastRun;
1118 pNext = ME_FindItemFwd(pRow, diRunOrStartRow);
1119 assert(pNext->type == diRun);
1120 pLastRun = pNext;
1121 if (pbCaretAtEnd) *pbCaretAtEnd = FALSE;
1122 if (pOffset) *pOffset = 0;
1123 do {
1124 int run_x = pNext->member.run.pt.x;
1125 int width = pNext->member.run.nWidth;
1126 if (x < run_x)
1127 {
1128 return pNext;
1129 }
1130 if (x >= run_x && x < run_x+width)
1131 {
1132 int ch = ME_CharFromPointCursor(editor, x-run_x, &pNext->member.run);
1133 ME_String *s = pNext->member.run.strText;
1134 if (ch < s->nLen) {
1135 if (pOffset)
1136 *pOffset = ch;
1137 return pNext;
1138 }
1139 }
1140 pLastRun = pNext;
1141 pNext = ME_FindItemFwd(pNext, diRunOrStartRow);
1142 } while(pNext && pNext->type == diRun);
1143
1144 if ((pLastRun->member.run.nFlags & MERF_ENDPARA) == 0)
1145 {
1146 pNext = ME_FindItemFwd(pNext, diRun);
1147 if (pbCaretAtEnd) *pbCaretAtEnd = TRUE;
1148 return pNext;
1149 } else {
1150 return pLastRun;
1151 }
1152 }
1153
1154 static int ME_GetXForArrow(ME_TextEditor *editor, ME_Cursor *pCursor)
1155 {
1156 ME_DisplayItem *pRun = pCursor->pRun;
1157 int x;
1158
1159 if (editor->nUDArrowX != -1)
1160 x = editor->nUDArrowX;
1161 else {
1162 if (editor->bCaretAtEnd)
1163 {
1164 pRun = ME_FindItemBack(pRun, diRun);
1165 assert(pRun);
1166 x = pRun->member.run.pt.x + pRun->member.run.nWidth;
1167 }
1168 else {
1169 x = pRun->member.run.pt.x;
1170 x += ME_PointFromChar(editor, &pRun->member.run, pCursor->nOffset);
1171 }
1172 editor->nUDArrowX = x;
1173 }
1174 return x;
1175 }
1176
1177
1178 static void
1179 ME_MoveCursorLines(ME_TextEditor *editor, ME_Cursor *pCursor, int nRelOfs)
1180 {
1181 ME_DisplayItem *pRun = pCursor->pRun;
1182 ME_DisplayItem *pItem;
1183 int x = ME_GetXForArrow(editor, pCursor);
1184
1185 if (editor->bCaretAtEnd && !pCursor->nOffset)
1186 pRun = ME_FindItemBack(pRun, diRun);
1187 if (!pRun)
1188 return;
1189 if (nRelOfs == -1)
1190 {
1191 /* start of this row */
1192 pItem = ME_FindItemBack(pRun, diStartRow);
1193 assert(pItem);
1194 /* start of the previous row */
1195 pItem = ME_FindItemBack(pItem, diStartRow);
1196 }
1197 else
1198 {
1199 /* start of the next row */
1200 pItem = ME_FindItemFwd(pRun, diStartRow);
1201 /* FIXME If diParagraph is before diStartRow, wrap the next paragraph?
1202 */
1203 }
1204 if (!pItem)
1205 {
1206 /* row not found - ignore */
1207 return;
1208 }
1209 pCursor->pRun = ME_FindRunInRow(editor, pItem, x, &pCursor->nOffset, &editor->bCaretAtEnd);
1210 assert(pCursor->pRun);
1211 assert(pCursor->pRun->type == diRun);
1212 }
1213
1214
1215 static void ME_ArrowPageUp(ME_TextEditor *editor, ME_Cursor *pCursor)
1216 {
1217 ME_DisplayItem *pRun = pCursor->pRun;
1218 ME_DisplayItem *pLast, *p;
1219 int x, y, ys, yd, yp, yprev;
1220 ME_Cursor tmp_curs = *pCursor;
1221
1222 x = ME_GetXForArrow(editor, pCursor);
1223 if (!pCursor->nOffset && editor->bCaretAtEnd)
1224 pRun = ME_FindItemBack(pRun, diRun);
1225
1226 p = ME_FindItemBack(pRun, diStartRowOrParagraph);
1227 assert(p->type == diStartRow);
1228 yp = ME_FindItemBack(p, diParagraph)->member.para.nYPos;
1229 yprev = ys = y = yp + p->member.row.nYPos;
1230 yd = y - editor->sizeWindow.cy;
1231 pLast = p;
1232
1233 do {
1234 p = ME_FindItemBack(p, diStartRowOrParagraph);
1235 if (!p)
1236 break;
1237 if (p->type == diParagraph) { /* crossing paragraphs */
1238 if (p->member.para.prev_para == NULL)
1239 break;
1240 yp = p->member.para.prev_para->member.para.nYPos;
1241 continue;
1242 }
1243 y = yp + p->member.row.nYPos;
1244 if (y < yd)
1245 break;
1246 pLast = p;
1247 yprev = y;
1248 } while(1);
1249
1250 pCursor->pRun = ME_FindRunInRow(editor, pLast, x, &pCursor->nOffset, &editor->bCaretAtEnd);
1251 ME_UpdateSelection(editor, &tmp_curs);
1252 if (yprev < editor->sizeWindow.cy)
1253 {
1254 ME_EnsureVisible(editor, ME_FindItemFwd(editor->pBuffer->pFirst, diRun));
1255 ME_Repaint(editor);
1256 }
1257 else
1258 {
1259 ME_ScrollUp(editor, ys-yprev);
1260 }
1261 assert(pCursor->pRun);
1262 assert(pCursor->pRun->type == diRun);
1263 }
1264
1265 /* FIXME: in the original RICHEDIT, PageDown always scrolls by the same amount
1266 of pixels, even if it makes the scroll bar position exceed its normal maximum.
1267 In such a situation, clicking the scrollbar restores its position back to the
1268 normal range (ie. sets it to (doclength-screenheight)). */
1269
1270 static void ME_ArrowPageDown(ME_TextEditor *editor, ME_Cursor *pCursor)
1271 {
1272 ME_DisplayItem *pRun = pCursor->pRun;
1273 ME_DisplayItem *pLast, *p;
1274 int x, y, ys, yd, yp, yprev;
1275 ME_Cursor tmp_curs = *pCursor;
1276
1277 x = ME_GetXForArrow(editor, pCursor);
1278 if (!pCursor->nOffset && editor->bCaretAtEnd)
1279 pRun = ME_FindItemBack(pRun, diRun);
1280
1281 p = ME_FindItemBack(pRun, diStartRowOrParagraph);
1282 assert(p->type == diStartRow);
1283 yp = ME_FindItemBack(p, diParagraph)->member.para.nYPos;
1284 yprev = ys = y = yp + p->member.row.nYPos;
1285 yd = y + editor->sizeWindow.cy;
1286 pLast = p;
1287
1288 do {
1289 p = ME_FindItemFwd(p, diStartRowOrParagraph);
1290 if (!p)
1291 break;
1292 if (p->type == diParagraph) {
1293 yp = p->member.para.nYPos;
1294 continue;
1295 }
1296 y = yp + p->member.row.nYPos;
1297 if (y >= yd)
1298 break;
1299 pLast = p;
1300 yprev = y;
1301 } while(1);
1302
1303 pCursor->pRun = ME_FindRunInRow(editor, pLast, x, &pCursor->nOffset, &editor->bCaretAtEnd);
1304 ME_UpdateSelection(editor, &tmp_curs);
1305 if (yprev >= editor->nTotalLength-editor->sizeWindow.cy)
1306 {
1307 ME_EnsureVisible(editor, ME_FindItemBack(editor->pBuffer->pLast, diRun));
1308 ME_Repaint(editor);
1309 }
1310 else
1311 {
1312 ME_ScrollUp(editor,ys-yprev);
1313 }
1314 assert(pCursor->pRun);
1315 assert(pCursor->pRun->type == diRun);
1316 }
1317
1318 static void ME_ArrowHome(ME_TextEditor *editor, ME_Cursor *pCursor)
1319 {
1320 ME_DisplayItem *pRow = ME_FindItemBack(pCursor->pRun, diStartRow);
1321 ME_WrapMarkedParagraphs(editor);
1322 if (pRow) {
1323 ME_DisplayItem *pRun;
1324 if (editor->bCaretAtEnd && !pCursor->nOffset) {
1325 pRow = ME_FindItemBack(pRow, diStartRow);
1326 if (!pRow)
1327 return;
1328 }
1329 pRun = ME_FindItemFwd(pRow, diRun);
1330 if (pRun) {
1331 pCursor->pRun = pRun;
1332 pCursor->nOffset = 0;
1333 }
1334 }
1335 editor->bCaretAtEnd = FALSE;
1336 }
1337
1338 static void ME_ArrowCtrlHome(ME_TextEditor *editor, ME_Cursor *pCursor)
1339 {
1340 ME_DisplayItem *pRow = ME_FindItemBack(pCursor->pRun, diTextStart);
1341 if (pRow) {
1342 ME_DisplayItem *pRun = ME_FindItemFwd(pRow, diRun);
1343 if (pRun) {
1344 pCursor->pRun = pRun;
1345 pCursor->nOffset = 0;
1346 }
1347 }
1348 }
1349
1350 static void ME_ArrowEnd(ME_TextEditor *editor, ME_Cursor *pCursor)
1351 {
1352 ME_DisplayItem *pRow;
1353
1354 if (editor->bCaretAtEnd && !pCursor->nOffset)
1355 return;
1356
1357 pRow = ME_FindItemFwd(pCursor->pRun, diStartRowOrParagraphOrEnd);
1358 assert(pRow);
1359 if (pRow->type == diStartRow) {
1360 /* FIXME WTF was I thinking about here ? */
1361 ME_DisplayItem *pRun = ME_FindItemFwd(pRow, diRun);
1362 assert(pRun);
1363 pCursor->pRun = pRun;
1364 pCursor->nOffset = 0;
1365 editor->bCaretAtEnd = 1;
1366 return;
1367 }
1368 pCursor->pRun = ME_FindItemBack(pRow, diRun);
1369 assert(pCursor->pRun && pCursor->pRun->member.run.nFlags & MERF_ENDPARA);
1370 pCursor->nOffset = 0;
1371 editor->bCaretAtEnd = FALSE;
1372 }
1373
1374 static void ME_ArrowCtrlEnd(ME_TextEditor *editor, ME_Cursor *pCursor)
1375 {
1376 ME_DisplayItem *p = ME_FindItemFwd(pCursor->pRun, diTextEnd);
1377 assert(p);
1378 p = ME_FindItemBack(p, diRun);
1379 assert(p);
1380 assert(p->member.run.nFlags & MERF_ENDPARA);
1381 pCursor->pRun = p;
1382 pCursor->nOffset = 0;
1383 editor->bCaretAtEnd = FALSE;
1384 }
1385
1386 BOOL ME_IsSelection(ME_TextEditor *editor)
1387 {
1388 return memcmp(&editor->pCursors[0], &editor->pCursors[1], sizeof(ME_Cursor))!=0;
1389 }
1390
1391 static int ME_GetSelCursor(ME_TextEditor *editor, int dir)
1392 {
1393 int cdir = ME_GetCursorOfs(editor, 0) - ME_GetCursorOfs(editor, 1);
1394
1395 if (cdir*dir>0)
1396 return 0;
1397 else
1398 return 1;
1399 }
1400
1401 BOOL ME_UpdateSelection(ME_TextEditor *editor, const ME_Cursor *pTempCursor)
1402 {
1403 ME_Cursor old_anchor = editor->pCursors[1];
1404
1405 if (GetKeyState(VK_SHIFT)>=0) /* cancelling selection */
1406 {
1407 /* any selection was present ? if so, it's no more, repaint ! */
1408 editor->pCursors[1] = editor->pCursors[0];
1409 if (memcmp(pTempCursor, &old_anchor, sizeof(ME_Cursor))) {
1410 return TRUE;
1411 }
1412 return FALSE;
1413 }
1414 else
1415 {
1416 if (!memcmp(pTempCursor, &editor->pCursors[1], sizeof(ME_Cursor))) /* starting selection */
1417 {
1418 editor->pCursors[1] = *pTempCursor;
1419 return TRUE;
1420 }
1421 }
1422
1423 ME_Repaint(editor);
1424 return TRUE;
1425 }
1426
1427 void ME_DeleteSelection(ME_TextEditor *editor)
1428 {
1429 int from, to;
1430 ME_GetSelection(editor, &from, &to);
1431 ME_DeleteTextAtCursor(editor, ME_GetSelCursor(editor,-1), to-from);
1432 }
1433
1434 ME_Style *ME_GetSelectionInsertStyle(ME_TextEditor *editor)
1435 {
1436 return ME_GetInsertStyle(editor, 0);
1437 }
1438
1439 void ME_SendSelChange(ME_TextEditor *editor)
1440 {
1441 SELCHANGE sc;
1442
1443 if (!(editor->nEventMask & ENM_SELCHANGE))
1444 return;
1445
1446 sc.nmhdr.hwndFrom = editor->hWnd;
1447 sc.nmhdr.idFrom = GetWindowLongW(editor->hWnd, GWLP_ID);
1448 sc.nmhdr.code = EN_SELCHANGE;
1449 SendMessageW(editor->hWnd, EM_EXGETSEL, 0, (LPARAM)&sc.chrg);
1450 sc.seltyp = SEL_EMPTY;
1451 if (sc.chrg.cpMin != sc.chrg.cpMax)
1452 sc.seltyp |= SEL_TEXT;
1453 if (sc.chrg.cpMin < sc.chrg.cpMax+1) /* wth were RICHEDIT authors thinking ? */
1454 sc.seltyp |= SEL_MULTICHAR;
1455 TRACE("cpMin=%d cpMax=%d seltyp=%d (%s %s)\n",
1456 sc.chrg.cpMin, sc.chrg.cpMax, sc.seltyp,
1457 (sc.seltyp & SEL_TEXT) ? "SEL_TEXT" : "",
1458 (sc.seltyp & SEL_MULTICHAR) ? "SEL_MULTICHAR" : "");
1459 if (sc.chrg.cpMin != editor->notified_cr.cpMin || sc.chrg.cpMax != editor->notified_cr.cpMax)
1460 {
1461 ME_ClearTempStyle(editor);
1462
1463 editor->notified_cr = sc.chrg;
1464 SendMessageW(GetParent(editor->hWnd), WM_NOTIFY, sc.nmhdr.idFrom, (LPARAM)&sc);
1465 }
1466 }
1467
1468 BOOL
1469 ME_ArrowKey(ME_TextEditor *editor, int nVKey, BOOL extend, BOOL ctrl)
1470 {
1471 int nCursor = 0;
1472 ME_Cursor *p = &editor->pCursors[nCursor];
1473 ME_Cursor tmp_curs = *p;
1474 BOOL success = FALSE;
1475
1476 ME_CheckCharOffsets(editor);
1477 switch(nVKey) {
1478 case VK_LEFT:
1479 editor->bCaretAtEnd = 0;
1480 if (ctrl)
1481 success = ME_MoveCursorWords(editor, &tmp_curs, -1);
1482 else
1483 success = ME_MoveCursorChars(editor, &tmp_curs, -1);
1484 break;
1485 case VK_RIGHT:
1486 editor->bCaretAtEnd = 0;
1487 if (ctrl)
1488 success = ME_MoveCursorWords(editor, &tmp_curs, +1);
1489 else
1490 success = ME_MoveCursorChars(editor, &tmp_curs, +1);
1491 break;
1492 case VK_UP:
1493 ME_MoveCursorLines(editor, &tmp_curs, -1);
1494 break;
1495 case VK_DOWN:
1496 ME_MoveCursorLines(editor, &tmp_curs, +1);
1497 break;
1498 case VK_PRIOR:
1499 ME_ArrowPageUp(editor, &tmp_curs);
1500 break;
1501 case VK_NEXT:
1502 ME_ArrowPageDown(editor, &tmp_curs);
1503 break;
1504 case VK_HOME: {
1505 if (ctrl)
1506 ME_ArrowCtrlHome(editor, &tmp_curs);
1507 else
1508 ME_ArrowHome(editor, &tmp_curs);
1509 editor->bCaretAtEnd = 0;
1510 break;
1511 }
1512 case VK_END:
1513 if (ctrl)
1514 ME_ArrowCtrlEnd(editor, &tmp_curs);
1515 else
1516 ME_ArrowEnd(editor, &tmp_curs);
1517 break;
1518 }
1519
1520 if (!extend)
1521 editor->pCursors[1] = tmp_curs;
1522 *p = tmp_curs;
1523
1524 ME_InvalidateSelection(editor);
1525 ME_Repaint(editor);
1526 HideCaret(editor->hWnd);
1527 ME_EnsureVisible(editor, tmp_curs.pRun);
1528 ME_ShowCaret(editor);
1529 ME_SendSelChange(editor);
1530 return success;
1531 }
1532
This page was automatically generated by the
LXR engine.
Visit the LXR main site for more
information.