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