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