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