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